Skip to content

Commit

Permalink
feat: add documentation for mgod module + remove unused code
Browse files Browse the repository at this point in the history
* doc: add documentation for mgod module

* fix: documentation links and format

* doc: add missing documentation for package fields and functions

* doc: add package level comments
  • Loading branch information
harsh-2711 authored Dec 6, 2023
1 parent 23ae9e8 commit 5145427
Show file tree
Hide file tree
Showing 29 changed files with 438 additions and 319 deletions.
1 change: 1 addition & 0 deletions bsondoc/bson_doc_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"go.mongodb.org/mongo-driver/bson"
)

// GetFieldValueFromRootDoc returns the value of the provided field (nil if not found) from the root of a bson doc.
func GetFieldValueFromRootDoc(doc *bson.D, field string) interface{} {
if doc == nil {
return nil
Expand Down
19 changes: 11 additions & 8 deletions bsondoc/build_bson_doc.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package bsondoc builds on an existing bson doc according to the provided entity model schema.
package bsondoc

import (
Expand All @@ -12,13 +13,15 @@ import (
"go.mongodb.org/mongo-driver/bson/primitive"
)

// TranslateToEnum is the enum for the type of translation to be done.
type TranslateToEnum string

const (
TranslateToEnumMongo TranslateToEnum = "mongo"
TranslateToEnumEntityModel TranslateToEnum = "entity_model"
TranslateToEnumMongo TranslateToEnum = "mongo" // translate to mongo doc
TranslateToEnumEntityModel TranslateToEnum = "entity_model" // translate to entity model
)

// Build builds on the given bson doc based on the provided [schema.EntityModelSchema].
func Build(
ctx context.Context,
bsonDoc *bson.D,
Expand Down Expand Up @@ -122,7 +125,7 @@ func build(
(*bsonElem)[arrIdx] = convertedValue
}

// default case handles all primitive types i.e. all leaf nodes of schema tree or all bson doc
// Default case handles all primitive types i.e. all leaf nodes of schema tree or all bson doc
// elements which are not of type bson.D or bson.A.
default:
// Transformations related logic starts here
Expand All @@ -138,8 +141,8 @@ func build(

var elemVal interface{}
if _, ok := bsonDocRef.(*interface{}); !ok {
// this case handles only elements of array which are passed as reference from the above *bson.A case.
// hence, reject any other type.
// This case handles only elements of array which are passed as reference from the above *bson.A case.
// Hence, reject any other type.
return nil
} else {
elemVal = *(bsonDocRef.(*interface{}))
Expand Down Expand Up @@ -178,14 +181,14 @@ func getConvertedValueForNode(
var modifiedVal interface{}
var err error

// if nodeVal is nil, then there is no need to do any conversion.
// If nodeVal is nil, then there is no need to do any conversion.
if nodeVal == nil {
//nolint:nilnil // this is a valid case
return nil, nil
}

// this switch case provides type support for bson.D and bson.A type of elements.
// without this, *interface{} type of bsonDoc would be passed in the recursive call,
// This switch case provides type support for bson.D and bson.A type of elements.
// Without this, *interface{} type of bsonDoc would be passed in the recursive call,
// which will then go to the default case and will not be able to handle any nested type.
switch typedValue := nodeVal.(type) {
case bson.D:
Expand Down
4 changes: 4 additions & 0 deletions dateformatter/date_formatter.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package dateformatter provides utilities to get date time in different formats.
package dateformatter

import (
Expand All @@ -7,6 +8,7 @@ import (
)

// DateFormatter provides utilities to get date time in different formats.
//
// NOTE: DateFormatter processes the provided date time string in UTC format.
type DateFormatter struct {
t time.Time
Expand All @@ -16,6 +18,7 @@ func New(t time.Time) *DateFormatter {
return &DateFormatter{t: t.UTC()}
}

// GetISOString returns the date time in ISO 8601 format.
func (d *DateFormatter) GetISOString() (string, error) {
// ISO 8601 format: YYYY-MM-DDTHH:mm:ss.sssZ

Expand Down Expand Up @@ -47,6 +50,7 @@ func (d *DateFormatter) GetISOString() (string, error) {
return "", errors.New("invalid time format")
}

// formatMilliSecondsString adds zeros to milliseconds if not present.
func formatMilliSecondsString(dateTimeStr string) string {
parts := strings.Split(dateTimeStr, ".")
partsAfterMillisSplit := 2
Expand Down
182 changes: 33 additions & 149 deletions entity_mongo_model.go
Original file line number Diff line number Diff line change
@@ -1,33 +1,63 @@
// Package mgod implements ODM logic for MongoDB in Go.
package mgod

import (
"context"
"fmt"

"github.com/Lyearn/mgod/bsondoc"
"github.com/Lyearn/mgod/errors"
"github.com/Lyearn/mgod/schema"
"github.com/Lyearn/mgod/schema/metafield"
"github.com/samber/lo"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)

// EntityMongoModel is a generic interface of available wrapper functions on MongoDB collection.
type EntityMongoModel[T any] interface {
// GetDocToInsert returns the bson.D doc to be inserted in the collection for the provided struct object.
// This function is mainly used while creating a doc to be inserted for Union Type models because the underlying type of a union
// type model is interface{}, so it's not possible to identify the underlying concrete type to validate and insert the doc.
GetDocToInsert(ctx context.Context, model T) (bson.D, error)

// InsertOne inserts a single document in the collection.
// Model is kept as interface{} to support Union Type models i.e. accept both bson.D (generated using GetDocToInsert()) and struct object.
InsertOne(ctx context.Context, model interface{}, opts ...*options.InsertOneOptions) (T, error)

// InsertMany inserts multiple documents in the collection.
// Docs is kept as interface{} to support Union Type models i.e. accept both []bson.D (generated using GetDocToInsert()) and []struct objects.
InsertMany(ctx context.Context, docs interface{}, opts ...*options.InsertManyOptions) ([]T, error)

// UpdateMany updates multiple filtered documents in the collection based on the provided update query.
UpdateMany(ctx context.Context, filter, update interface{}, opts ...*options.UpdateOptions) (*mongo.UpdateResult, error)

// BulkWrite performs multiple write operations on the collection at once.
// Currently, only InsertOne, UpdateOne, and UpdateMany operations are supported.
BulkWrite(ctx context.Context, bulkWrites []mongo.WriteModel, opts ...*options.BulkWriteOptions) (*mongo.BulkWriteResult, error)

// Find returns all documents in the collection matching the provided filter.
Find(ctx context.Context, filter interface{}, opts ...*options.FindOptions) ([]T, error)

// FindOne returns a single document from the collection matching the provided filter.
FindOne(ctx context.Context, filter interface{}, opts ...*options.FindOneOptions) (*T, error)

// FindOneAndUpdate returns a single document from the collection based on the provided filter and updates it.
FindOneAndUpdate(ctx context.Context, filter, update interface{}, opts ...*options.FindOneAndUpdateOptions) (T, error)

// DeleteOne deletes a single document in the collection based on the provided filter.
DeleteOne(ctx context.Context, filter interface{}, opts ...*options.DeleteOptions) (*mongo.DeleteResult, error)

// DeleteMany deletes multiple documents in the collection based on the provided filter.
DeleteMany(ctx context.Context, filter interface{}, opts ...*options.DeleteOptions) (*mongo.DeleteResult, error)

// CountDocuments returns the number of documents in the collection for the provided filter.
CountDocuments(ctx context.Context, filter interface{}, opts ...*options.CountOptions) (int64, error)

// Distinct returns the distinct values for the provided field name in the collection for the provided filter.
Distinct(ctx context.Context, fieldName string, filter interface{}, opts ...*options.DistinctOptions) ([]interface{}, error)

// Aggregate performs an aggregation operation on the collection and returns the results.
Aggregate(ctx context.Context, pipeline interface{}, opts ...*options.AggregateOptions) ([]bson.D, error)
}

Expand All @@ -42,6 +72,7 @@ type entityMongoModel[T any] struct {
discriminatorKey string
}

// NewEntityMongoModel returns a new instance of EntityMongoModel for the provided model type and options.
func NewEntityMongoModel[T any](modelType T, opts EntityMongoOptions) (EntityMongoModel[T], error) {
dbConnection := opts.dbConnection
if dbConnection == nil {
Expand Down Expand Up @@ -83,153 +114,6 @@ func NewEntityMongoModel[T any](modelType T, opts EntityMongoOptions) (EntityMon
}, nil
}

func (m entityMongoModel[T]) getEntityModel() T {
return m.modelType
}

func (m entityMongoModel[T]) getMongoDocFromEntityModel(ctx context.Context, model T) (bson.D, error) {
marshalledDoc, err := bson.Marshal(model)
if err != nil {
return nil, err
}

var bsonDoc bson.D
err = bson.Unmarshal(marshalledDoc, &bsonDoc)
if err != nil {
return nil, err
}

if bsonDoc == nil {
// empty bson doc
return bsonDoc, nil
}

if err = metafield.AddMetaFields(&bsonDoc, m.opts.schemaOptions); err != nil {
return nil, err
}

err = bsondoc.Build(ctx, &bsonDoc, m.schema, bsondoc.TranslateToEnumMongo)
if err != nil {
return nil, err
}

if m.isUnionType {
discriminatorVal := bsondoc.GetFieldValueFromRootDoc(&bsonDoc, m.discriminatorKey)
if discriminatorVal == nil {
discriminatorVal = schema.GetSchemaNameForModel(m.modelType)
bsonDoc = append(bsonDoc, primitive.E{
Key: m.discriminatorKey,
Value: discriminatorVal,
})
}

cacheKey := GetSchemaCacheKey(m.coll.Name(), discriminatorVal.(string))
if _, err := schema.EntityModelSchemaCacheInstance.GetSchema(cacheKey); err != nil {
schema.EntityModelSchemaCacheInstance.SetSchema(cacheKey, m.schema)
}
}

return bsonDoc, nil
}

func (m entityMongoModel[T]) getEntityModelFromMongoDoc(ctx context.Context, bsonDoc bson.D) (T, error) {
model := m.getEntityModel()

if bsonDoc == nil {
// empty bson doc
return model, nil
}

entityModelSchema := m.schema

if m.isUnionType {
discriminatorVal := bsondoc.GetFieldValueFromRootDoc(&bsonDoc, m.discriminatorKey)
if discriminatorVal != nil {
cacheKey := GetSchemaCacheKey(m.coll.Name(), discriminatorVal.(string))
if unionElemSchema, err := schema.EntityModelSchemaCacheInstance.GetSchema(cacheKey); err == nil {
entityModelSchema = unionElemSchema
}
}
}

err := bsondoc.Build(ctx, &bsonDoc, entityModelSchema, bsondoc.TranslateToEnumEntityModel)
if err != nil {
return model, err
}

marshalledDoc, err := bson.Marshal(bsonDoc)
if err != nil {
return model, err
}

err = bson.Unmarshal(marshalledDoc, &model)
if err != nil {
return model, err
}

return model, nil
}

func (m entityMongoModel[T]) handleTimestampsForUpdateQuery(update interface{}, funcName string) (interface{}, error) {
updateQuery, ok := update.(bson.D)
if !ok {
return nil, errors.NewBadRequestError(errors.BadRequestError{
Underlying: "update query",
Got: fmt.Sprintf("%T", update),
Expected: "bson.D",
})
}

if m.opts.schemaOptions.Timestamps {
updatedAtCommand := bson.E{
Key: "$currentDate",
Value: bson.D{{
Key: "updatedAt",
Value: true,
}},
}

updateQuery = append(updateQuery, updatedAtCommand)
}

return updateQuery, nil
}

// Converts bulkWrite entity models to mongo models.
func (m entityMongoModel[T]) transformToBulkWriteBSONDocs(ctx context.Context, bulkWrites []mongo.WriteModel) error {
for _, bulkWrite := range bulkWrites {
switch bulkWriteType := bulkWrite.(type) {
case *mongo.InsertOneModel:
doc := bulkWriteType.Document
if doc == nil {
continue
}

bsonDoc, err := m.getMongoDocFromEntityModel(ctx, doc.(T))
if err != nil {
return err
}

bulkWriteType.Document = bsonDoc
case *mongo.UpdateOneModel:
updateQuery, err := m.handleTimestampsForUpdateQuery(bulkWriteType.Update, "BulkWrite")
if err != nil {
return err
}

bulkWriteType.Update = updateQuery
case *mongo.UpdateManyModel:
updateQuery, err := m.handleTimestampsForUpdateQuery(bulkWriteType.Update, "BulkWrite")
if err != nil {
return err
}

bulkWriteType.Update = updateQuery
}
}
return nil
}

func (m entityMongoModel[T]) GetDocToInsert(ctx context.Context, doc T) (bson.D, error) {
bsonDoc, err := m.getMongoDocFromEntityModel(ctx, doc)
if err != nil {
Expand Down
12 changes: 4 additions & 8 deletions entity_mongo_model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,7 @@ func (s *EntityMongoModelSuite) TestFind() {

mt.AddMockResponses(first, second, killCursors)

opts := mgod.NewEntityMongoOptions(mt.DB).
SetSchemaOptions(schemaopt.SchemaOptions{Collection: s.collName})
opts := mgod.NewEntityMongoOptions(mt.DB, schemaopt.SchemaOptions{Collection: s.collName})

entityMongoModel, err := mgod.NewEntityMongoModel(TestEntity{}, *opts)
s.Nil(err)
Expand Down Expand Up @@ -134,8 +133,7 @@ func (s *EntityMongoModelSuite) TestFindOne() {
{Key: "joinedon", Value: primitive.NewDateTimeFromTime(currentTime)},
}))

opts := mgod.NewEntityMongoOptions(mt.DB).
SetSchemaOptions(schemaopt.SchemaOptions{Collection: s.collName})
opts := mgod.NewEntityMongoOptions(mt.DB, schemaopt.SchemaOptions{Collection: s.collName})

entityMongoModel, err := mgod.NewEntityMongoModel(TestEntity{}, *opts)
s.Nil(err)
Expand Down Expand Up @@ -170,8 +168,7 @@ func (s *EntityMongoModelSuite) TestInsertOne() {
{Key: "age", Value: 18},
}))

opts := mgod.NewEntityMongoOptions(mt.DB).
SetSchemaOptions(schemaopt.SchemaOptions{Collection: s.collName})
opts := mgod.NewEntityMongoOptions(mt.DB, schemaopt.SchemaOptions{Collection: s.collName})

entityMongoModel, err := mgod.NewEntityMongoModel(TestEntity{}, *opts)
s.Nil(err)
Expand All @@ -196,8 +193,7 @@ func (s *EntityMongoModelSuite) TestInsertOne() {
Message: "duplicate key error",
}))

opts := mgod.NewEntityMongoOptions(mt.DB).
SetSchemaOptions(schemaopt.SchemaOptions{Collection: s.collName})
opts := mgod.NewEntityMongoOptions(mt.DB, schemaopt.SchemaOptions{Collection: s.collName})

entityMongoModel, err := mgod.NewEntityMongoModel(TestEntity{}, *opts)
s.Nil(err)
Expand Down
Loading

0 comments on commit 5145427

Please sign in to comment.