diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2909f37..6e57364 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,7 +29,7 @@ jobs: - name: Vet & Lint run: | go vet ./... - golint ./... + golint -set_exit_status ./... - uses: codecov/codecov-action@v1 with: diff --git a/codecov.yml b/codecov.yml index 1f0045b..bdf0bac 100644 --- a/codecov.yml +++ b/codecov.yml @@ -2,8 +2,8 @@ coverage: status: project: default: - target: 85% + target: 90% threshold: 3% patch: default: - target: 75% + target: 80% diff --git a/internal/code/extractor.go b/internal/code/extractor.go index cf52c74..cbf62bb 100644 --- a/internal/code/extractor.go +++ b/internal/code/extractor.go @@ -165,7 +165,7 @@ func getType(expr ast.Expr) Type { case *ast.ArrayType: containedType := getType(expr.Elt) - return ArrayType{containedType} + return ArrayType{ContainedType: containedType} case *ast.InterfaceType: var methods []Method diff --git a/internal/generator/generator_test.go b/internal/generator/generator_test.go index a69a0c7..2aa7992 100644 --- a/internal/generator/generator_test.go +++ b/internal/generator/generator_test.go @@ -9,30 +9,36 @@ import ( "github.com/sunboyy/repogen/internal/spec" ) +var ( + idField = code.StructField{ + Name: "ID", + Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}, + Tags: map[string][]string{"bson": {"_id", "omitempty"}}, + } + genderField = code.StructField{ + Name: "Gender", + Type: code.SimpleType("Gender"), + Tags: map[string][]string{"bson": {"gender"}}, + } + ageField = code.StructField{ + Name: "Age", + Type: code.SimpleType("int"), + Tags: map[string][]string{"bson": {"age"}}, + } +) + func TestGenerateMongoRepository(t *testing.T) { userModel := code.Struct{ Name: "UserModel", Fields: code.StructFields{ - { - Name: "ID", - Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}, - Tags: map[string][]string{"bson": {"_id", "omitempty"}}, - }, + idField, { Name: "Username", Type: code.SimpleType("string"), Tags: map[string][]string{"bson": {"username"}}, }, - { - Name: "Gender", - Type: code.SimpleType("Gender"), - Tags: map[string][]string{"bson": {"gender"}}, - }, - { - Name: "Age", - Type: code.SimpleType("int"), - Tags: map[string][]string{"bson": {"age"}}, - }, + genderField, + ageField, }, } methods := []spec.MethodSpec{ @@ -48,7 +54,7 @@ func TestGenerateMongoRepository(t *testing.T) { Mode: spec.QueryModeOne, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Field: "ID", Comparator: spec.ComparatorEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, }, }, }, @@ -70,8 +76,8 @@ func TestGenerateMongoRepository(t *testing.T) { Query: spec.QuerySpec{ Operator: spec.OperatorAnd, Predicates: []spec.Predicate{ - {Field: "Gender", Comparator: spec.ComparatorNot, ParamIndex: 1}, - {Field: "Age", Comparator: spec.ComparatorLessThan, ParamIndex: 2}, + {FieldReference: spec.FieldReference{genderField}, Comparator: spec.ComparatorNot, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorLessThan, ParamIndex: 2}, }, }, }, @@ -90,11 +96,11 @@ func TestGenerateMongoRepository(t *testing.T) { Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Field: "Age", Comparator: spec.ComparatorLessThanEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorLessThanEqual, ParamIndex: 1}, }, }, Sorts: []spec.Sort{ - {FieldName: "Age", Ordering: spec.OrderingAscending}, + {FieldReference: spec.FieldReference{ageField}, Ordering: spec.OrderingAscending}, }, }, }, @@ -112,11 +118,11 @@ func TestGenerateMongoRepository(t *testing.T) { Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Field: "Age", Comparator: spec.ComparatorGreaterThan, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorGreaterThan, ParamIndex: 1}, }, }, Sorts: []spec.Sort{ - {FieldName: "Age", Ordering: spec.OrderingAscending}, + {FieldReference: spec.FieldReference{ageField}, Ordering: spec.OrderingAscending}, }, }, }, @@ -134,11 +140,11 @@ func TestGenerateMongoRepository(t *testing.T) { Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Field: "Age", Comparator: spec.ComparatorGreaterThanEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorGreaterThanEqual, ParamIndex: 1}, }, }, Sorts: []spec.Sort{ - {FieldName: "Age", Ordering: spec.OrderingDescending}, + {FieldReference: spec.FieldReference{ageField}, Ordering: spec.OrderingDescending}, }, }, }, @@ -157,7 +163,7 @@ func TestGenerateMongoRepository(t *testing.T) { Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Field: "Age", Comparator: spec.ComparatorBetween, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorBetween, ParamIndex: 1}, }, }, }, @@ -178,8 +184,8 @@ func TestGenerateMongoRepository(t *testing.T) { Query: spec.QuerySpec{ Operator: spec.OperatorOr, Predicates: []spec.Predicate{ - {Field: "Gender", Comparator: spec.ComparatorEqual, ParamIndex: 1}, - {Field: "Age", Comparator: spec.ComparatorEqual, ParamIndex: 2}, + {FieldReference: spec.FieldReference{genderField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorEqual, ParamIndex: 2}, }, }, }, diff --git a/internal/mongo/generator.go b/internal/mongo/generator.go index 13343e2..64c3d05 100644 --- a/internal/mongo/generator.go +++ b/internal/mongo/generator.go @@ -2,8 +2,8 @@ package mongo import ( "bytes" - "fmt" "io" + "strings" "text/template" "github.com/sunboyy/repogen/internal/code" @@ -126,13 +126,13 @@ func (g RepositoryGenerator) mongoSorts(sortSpec []spec.Sort) ([]sort, error) { var sorts []sort for _, s := range sortSpec { - bsonTag, err := g.bsonTagFromFieldName(s.FieldName) + bsonFieldReference, err := g.bsonFieldReference(s.FieldReference) if err != nil { return nil, err } sorts = append(sorts, sort{ - BsonTag: bsonTag, + BsonTag: bsonFieldReference, Ordering: s.Ordering, }) } @@ -169,11 +169,11 @@ func (g RepositoryGenerator) getMongoUpdate(updateSpec spec.Update) (update, err case spec.UpdateFields: var update updateFields for _, field := range updateSpec { - bsonTag, err := g.bsonTagFromFieldName(field.Name) + bsonFieldReference, err := g.bsonFieldReference(field.FieldReference) if err != nil { - return nil, err + return querySpec{}, err } - update.Fields = append(update.Fields, updateField{BsonTag: bsonTag, ParamIndex: field.ParamIndex}) + update.Fields = append(update.Fields, updateField{BsonTag: bsonFieldReference, ParamIndex: field.ParamIndex}) } return update, nil default: @@ -214,13 +214,13 @@ func (g RepositoryGenerator) mongoQuerySpec(query spec.QuerySpec) (querySpec, er var predicates []predicate for _, predicateSpec := range query.Predicates { - bsonTag, err := g.bsonTagFromFieldName(predicateSpec.Field) + bsonFieldReference, err := g.bsonFieldReference(predicateSpec.FieldReference) if err != nil { return querySpec{}, err } predicates = append(predicates, predicate{ - Field: bsonTag, + Field: bsonFieldReference, Comparator: predicateSpec.Comparator, ParamIndex: predicateSpec.ParamIndex, }) @@ -232,15 +232,22 @@ func (g RepositoryGenerator) mongoQuerySpec(query spec.QuerySpec) (querySpec, er }, nil } -func (g RepositoryGenerator) bsonTagFromFieldName(fieldName string) (string, error) { - structField, ok := g.StructModel.Fields.ByName(fieldName) - if !ok { - return "", fmt.Errorf("struct field %s not found", fieldName) +func (g RepositoryGenerator) bsonFieldReference(fieldReference spec.FieldReference) (string, error) { + var bsonTags []string + for _, field := range fieldReference { + tag, err := g.bsonTagFromField(field) + if err != nil { + return "", err + } + bsonTags = append(bsonTags, tag) } + return strings.Join(bsonTags, "."), nil +} - bsonTag, ok := structField.Tags["bson"] +func (g RepositoryGenerator) bsonTagFromField(field code.StructField) (string, error) { + bsonTag, ok := field.Tags["bson"] if !ok { - return "", NewBsonTagNotFoundError(fieldName) + return "", NewBsonTagNotFoundError(field.Name) } return bsonTag[0], nil diff --git a/internal/mongo/generator_test.go b/internal/mongo/generator_test.go index 4c8f35b..a675ff0 100644 --- a/internal/mongo/generator_test.go +++ b/internal/mongo/generator_test.go @@ -10,38 +10,58 @@ import ( "github.com/sunboyy/repogen/internal/testutils" ) +var ( + idField = code.StructField{ + Name: "ID", + Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}, + Tags: map[string][]string{"bson": {"_id", "omitempty"}}, + } + genderField = code.StructField{ + Name: "Gender", + Type: code.SimpleType("Gender"), + Tags: map[string][]string{"bson": {"gender"}}, + } + ageField = code.StructField{ + Name: "Age", + Type: code.SimpleType("int"), + Tags: map[string][]string{"bson": {"age"}}, + } + nameField = code.StructField{ + Name: "Name", + Type: code.SimpleType("NameModel"), + Tags: map[string][]string{"bson": {"name"}}, + } + enabledField = code.StructField{ + Name: "Enabled", + Type: code.SimpleType("bool"), + Tags: map[string][]string{"bson": {"enabled"}}, + } + accessTokenField = code.StructField{ + Name: "AccessToken", + Type: code.SimpleType("string"), + } + + firstNameField = code.StructField{ + Name: "First", + Type: code.SimpleType("string"), + Tags: map[string][]string{"bson": {"first"}}, + } +) + var userModel = code.Struct{ Name: "UserModel", Fields: code.StructFields{ - { - Name: "ID", - Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}, - Tags: map[string][]string{"bson": {"_id", "omitempty"}}, - }, + idField, { Name: "Username", Type: code.SimpleType("string"), Tags: map[string][]string{"bson": {"username"}}, }, - { - Name: "Gender", - Type: code.SimpleType("Gender"), - Tags: map[string][]string{"bson": {"gender"}}, - }, - { - Name: "Age", - Type: code.SimpleType("int"), - Tags: map[string][]string{"bson": {"age"}}, - }, - { - Name: "Enabled", - Type: code.SimpleType("bool"), - Tags: map[string][]string{"bson": {"enabled"}}, - }, - { - Name: "AccessToken", - Type: code.SimpleType("string"), - }, + genderField, + ageField, + nameField, + enabledField, + accessTokenField, }, } @@ -183,7 +203,7 @@ func TestGenerateMethod_Find(t *testing.T) { Mode: spec.QueryModeOne, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorEqual, Field: "ID", ParamIndex: 1}, + {Comparator: spec.ComparatorEqual, FieldReference: spec.FieldReference{idField}, ParamIndex: 1}, }, }, }, @@ -217,7 +237,7 @@ func (r *UserRepositoryMongo) FindByID(arg0 context.Context, arg1 primitive.Obje Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorEqual, Field: "Gender", ParamIndex: 1}, + {Comparator: spec.ComparatorEqual, FieldReference: spec.FieldReference{genderField}, ParamIndex: 1}, }, }, }, @@ -237,6 +257,44 @@ func (r *UserRepositoryMongo) FindByGender(arg0 context.Context, arg1 Gender) ([ } return entities, nil } +`, + }, + { + Name: "find with deep field reference", + MethodSpec: spec.MethodSpec{ + Name: "FindByNameFirst", + Params: []code.Param{ + {Name: "ctx", Type: code.ExternalType{PackageAlias: "context", Name: "Context"}}, + {Name: "firstName", Type: code.SimpleType("string")}, + }, + Returns: []code.Type{ + code.PointerType{ContainedType: code.SimpleType("UserModel")}, + code.SimpleType("error"), + }, + Operation: spec.FindOperation{ + Mode: spec.QueryModeOne, + Query: spec.QuerySpec{ + Predicates: []spec.Predicate{ + { + Comparator: spec.ComparatorEqual, + FieldReference: spec.FieldReference{nameField, firstNameField}, + ParamIndex: 1, + }, + }, + }, + }, + }, + ExpectedCode: ` +func (r *UserRepositoryMongo) FindByNameFirst(arg0 context.Context, arg1 string) (*UserModel, error) { + var entity UserModel + if err := r.collection.FindOne(arg0, bson.M{ + "name.first": arg1, + }, options.FindOne().SetSort(bson.M{ + })).Decode(&entity); err != nil { + return nil, err + } + return &entity, nil +} `, }, { @@ -257,8 +315,8 @@ func (r *UserRepositoryMongo) FindByGender(arg0 context.Context, arg1 Gender) ([ Query: spec.QuerySpec{ Operator: spec.OperatorAnd, Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorEqual, Field: "Gender", ParamIndex: 1}, - {Comparator: spec.ComparatorEqual, Field: "Age", ParamIndex: 2}, + {Comparator: spec.ComparatorEqual, FieldReference: spec.FieldReference{genderField}, ParamIndex: 1}, + {Comparator: spec.ComparatorEqual, FieldReference: spec.FieldReference{ageField}, ParamIndex: 2}, }, }, }, @@ -301,8 +359,8 @@ func (r *UserRepositoryMongo) FindByGenderAndAge(arg0 context.Context, arg1 Gend Query: spec.QuerySpec{ Operator: spec.OperatorOr, Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorEqual, Field: "Gender", ParamIndex: 1}, - {Comparator: spec.ComparatorEqual, Field: "Age", ParamIndex: 2}, + {Comparator: spec.ComparatorEqual, FieldReference: spec.FieldReference{genderField}, ParamIndex: 1}, + {Comparator: spec.ComparatorEqual, FieldReference: spec.FieldReference{ageField}, ParamIndex: 2}, }, }, }, @@ -343,7 +401,7 @@ func (r *UserRepositoryMongo) FindByGenderOrAge(arg0 context.Context, arg1 Gende Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorNot, Field: "Gender", ParamIndex: 1}, + {Comparator: spec.ComparatorNot, FieldReference: spec.FieldReference{genderField}, ParamIndex: 1}, }, }, }, @@ -381,7 +439,7 @@ func (r *UserRepositoryMongo) FindByGenderNot(arg0 context.Context, arg1 Gender) Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorLessThan, Field: "Age", ParamIndex: 1}, + {Comparator: spec.ComparatorLessThan, FieldReference: spec.FieldReference{ageField}, ParamIndex: 1}, }, }, }, @@ -419,7 +477,7 @@ func (r *UserRepositoryMongo) FindByAgeLessThan(arg0 context.Context, arg1 int) Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorLessThanEqual, Field: "Age", ParamIndex: 1}, + {Comparator: spec.ComparatorLessThanEqual, FieldReference: spec.FieldReference{ageField}, ParamIndex: 1}, }, }, }, @@ -457,7 +515,7 @@ func (r *UserRepositoryMongo) FindByAgeLessThanEqual(arg0 context.Context, arg1 Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorGreaterThan, Field: "Age", ParamIndex: 1}, + {Comparator: spec.ComparatorGreaterThan, FieldReference: spec.FieldReference{ageField}, ParamIndex: 1}, }, }, }, @@ -495,7 +553,7 @@ func (r *UserRepositoryMongo) FindByAgeGreaterThan(arg0 context.Context, arg1 in Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorGreaterThanEqual, Field: "Age", ParamIndex: 1}, + {Comparator: spec.ComparatorGreaterThanEqual, FieldReference: spec.FieldReference{ageField}, ParamIndex: 1}, }, }, }, @@ -534,7 +592,7 @@ func (r *UserRepositoryMongo) FindByAgeGreaterThanEqual(arg0 context.Context, ar Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorBetween, Field: "Age", ParamIndex: 1}, + {Comparator: spec.ComparatorBetween, FieldReference: spec.FieldReference{ageField}, ParamIndex: 1}, }, }, }, @@ -572,7 +630,7 @@ func (r *UserRepositoryMongo) FindByAgeBetween(arg0 context.Context, arg1 int, a Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorIn, Field: "Gender", ParamIndex: 1}, + {Comparator: spec.ComparatorIn, FieldReference: spec.FieldReference{genderField}, ParamIndex: 1}, }, }, }, @@ -610,7 +668,7 @@ func (r *UserRepositoryMongo) FindByGenderIn(arg0 context.Context, arg1 []Gender Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorNotIn, Field: "Gender", ParamIndex: 1}, + {Comparator: spec.ComparatorNotIn, FieldReference: spec.FieldReference{genderField}, ParamIndex: 1}, }, }, }, @@ -647,7 +705,7 @@ func (r *UserRepositoryMongo) FindByGenderNotIn(arg0 context.Context, arg1 []Gen Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorTrue, Field: "Enabled", ParamIndex: 1}, + {Comparator: spec.ComparatorTrue, FieldReference: spec.FieldReference{enabledField}, ParamIndex: 1}, }, }, }, @@ -684,7 +742,7 @@ func (r *UserRepositoryMongo) FindByEnabledTrue(arg0 context.Context) ([]*UserMo Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorFalse, Field: "Enabled", ParamIndex: 1}, + {Comparator: spec.ComparatorFalse, FieldReference: spec.FieldReference{enabledField}, ParamIndex: 1}, }, }, }, @@ -720,7 +778,7 @@ func (r *UserRepositoryMongo) FindByEnabledFalse(arg0 context.Context) ([]*UserM Operation: spec.FindOperation{ Mode: spec.QueryModeMany, Sorts: []spec.Sort{ - {FieldName: "Age", Ordering: spec.OrderingAscending}, + {FieldReference: spec.FieldReference{ageField}, Ordering: spec.OrderingAscending}, }, }, }, @@ -745,7 +803,7 @@ func (r *UserRepositoryMongo) FindAllOrderByAge(arg0 context.Context) ([]*UserMo { Name: "find with sort descending", MethodSpec: spec.MethodSpec{ - Name: "FindAllOrderByAge", + Name: "FindAllOrderByAgeDesc", Params: []code.Param{ {Name: "ctx", Type: code.ExternalType{PackageAlias: "context", Name: "Context"}}, }, @@ -756,12 +814,12 @@ func (r *UserRepositoryMongo) FindAllOrderByAge(arg0 context.Context) ([]*UserMo Operation: spec.FindOperation{ Mode: spec.QueryModeMany, Sorts: []spec.Sort{ - {FieldName: "Age", Ordering: spec.OrderingDescending}, + {FieldReference: spec.FieldReference{ageField}, Ordering: spec.OrderingDescending}, }, }, }, ExpectedCode: ` -func (r *UserRepositoryMongo) FindAllOrderByAge(arg0 context.Context) ([]*UserModel, error) { +func (r *UserRepositoryMongo) FindAllOrderByAgeDesc(arg0 context.Context) ([]*UserModel, error) { cursor, err := r.collection.Find(arg0, bson.M{ }, options.Find().SetSort(bson.M{ @@ -776,6 +834,42 @@ func (r *UserRepositoryMongo) FindAllOrderByAge(arg0 context.Context) ([]*UserMo } return entities, nil } +`, + }, + { + Name: "find with deep sort ascending", + MethodSpec: spec.MethodSpec{ + Name: "FindAllOrderByNameFirst", + Params: []code.Param{ + {Name: "ctx", Type: code.ExternalType{PackageAlias: "context", Name: "Context"}}, + }, + Returns: []code.Type{ + code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}}, + code.SimpleType("error"), + }, + Operation: spec.FindOperation{ + Mode: spec.QueryModeMany, + Sorts: []spec.Sort{ + {FieldReference: spec.FieldReference{nameField, firstNameField}, Ordering: spec.OrderingAscending}, + }, + }, + }, + ExpectedCode: ` +func (r *UserRepositoryMongo) FindAllOrderByNameFirst(arg0 context.Context) ([]*UserModel, error) { + cursor, err := r.collection.Find(arg0, bson.M{ + + }, options.Find().SetSort(bson.M{ + "name.first": 1, + })) + if err != nil { + return nil, err + } + var entities []*UserModel + if err := cursor.All(arg0, &entities); err != nil { + return nil, err + } + return entities, nil +} `, }, { @@ -792,8 +886,8 @@ func (r *UserRepositoryMongo) FindAllOrderByAge(arg0 context.Context) ([]*UserMo Operation: spec.FindOperation{ Mode: spec.QueryModeMany, Sorts: []spec.Sort{ - {FieldName: "Gender", Ordering: spec.OrderingAscending}, - {FieldName: "Age", Ordering: spec.OrderingDescending}, + {FieldReference: spec.FieldReference{genderField}, Ordering: spec.OrderingAscending}, + {FieldReference: spec.FieldReference{ageField}, Ordering: spec.OrderingDescending}, }, }, }, @@ -855,7 +949,7 @@ func TestGenerateMethod_Update(t *testing.T) { Mode: spec.QueryModeOne, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Field: "ID", Comparator: spec.ComparatorEqual, ParamIndex: 2}, + {FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 2}, }, }, }, @@ -889,12 +983,12 @@ func (r *UserRepositoryMongo) UpdateByID(arg0 context.Context, arg1 *UserModel, }, Operation: spec.UpdateOperation{ Update: spec.UpdateFields{ - {Name: "Age", ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, ParamIndex: 1}, }, Mode: spec.QueryModeOne, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Field: "ID", Comparator: spec.ComparatorEqual, ParamIndex: 2}, + {FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 2}, }, }, }, @@ -930,12 +1024,12 @@ func (r *UserRepositoryMongo) UpdateAgeByID(arg0 context.Context, arg1 int, arg2 }, Operation: spec.UpdateOperation{ Update: spec.UpdateFields{ - {Name: "Age", ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, ParamIndex: 1}, }, Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Field: "Gender", Comparator: spec.ComparatorEqual, ParamIndex: 2}, + {FieldReference: spec.FieldReference{genderField}, Comparator: spec.ComparatorEqual, ParamIndex: 2}, }, }, }, @@ -954,6 +1048,47 @@ func (r *UserRepositoryMongo) UpdateAgeByGender(arg0 context.Context, arg1 int, } return int(result.MatchedCount), err } +`, + }, + { + Name: "update with deeply referenced field", + MethodSpec: spec.MethodSpec{ + Name: "UpdateNameFirstByID", + Params: []code.Param{ + {Name: "ctx", Type: code.ExternalType{PackageAlias: "context", Name: "Context"}}, + {Name: "firstName", Type: code.SimpleType("string")}, + {Name: "id", Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}}, + }, + Returns: []code.Type{ + code.SimpleType("bool"), + code.SimpleType("error"), + }, + Operation: spec.UpdateOperation{ + Update: spec.UpdateFields{ + {FieldReference: spec.FieldReference{nameField, firstNameField}, ParamIndex: 1}, + }, + Mode: spec.QueryModeOne, + Query: spec.QuerySpec{ + Predicates: []spec.Predicate{ + {FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 2}, + }, + }, + }, + }, + ExpectedCode: ` +func (r *UserRepositoryMongo) UpdateNameFirstByID(arg0 context.Context, arg1 string, arg2 primitive.ObjectID) (bool, error) { + result, err := r.collection.UpdateOne(arg0, bson.M{ + "_id": arg2, + }, bson.M{ + "$set": bson.M{ + "name.first": arg1, + }, + }) + if err != nil { + return false, err + } + return result.MatchedCount > 0, err +} `, }, } @@ -990,7 +1125,7 @@ func TestGenerateMethod_Delete(t *testing.T) { Mode: spec.QueryModeOne, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorEqual, Field: "ID", ParamIndex: 1}, + {Comparator: spec.ComparatorEqual, FieldReference: spec.FieldReference{idField}, ParamIndex: 1}, }, }, }, @@ -1023,7 +1158,7 @@ func (r *UserRepositoryMongo) DeleteByID(arg0 context.Context, arg1 primitive.Ob Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorEqual, Field: "Gender", ParamIndex: 1}, + {Comparator: spec.ComparatorEqual, FieldReference: spec.FieldReference{genderField}, ParamIndex: 1}, }, }, }, @@ -1058,8 +1193,8 @@ func (r *UserRepositoryMongo) DeleteByGender(arg0 context.Context, arg1 Gender) Query: spec.QuerySpec{ Operator: spec.OperatorAnd, Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorEqual, Field: "Gender", ParamIndex: 1}, - {Comparator: spec.ComparatorEqual, Field: "Age", ParamIndex: 2}, + {Comparator: spec.ComparatorEqual, FieldReference: spec.FieldReference{genderField}, ParamIndex: 1}, + {Comparator: spec.ComparatorEqual, FieldReference: spec.FieldReference{ageField}, ParamIndex: 2}, }, }, }, @@ -1097,8 +1232,8 @@ func (r *UserRepositoryMongo) DeleteByGenderAndAge(arg0 context.Context, arg1 Ge Query: spec.QuerySpec{ Operator: spec.OperatorOr, Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorEqual, Field: "Gender", ParamIndex: 1}, - {Comparator: spec.ComparatorEqual, Field: "Age", ParamIndex: 2}, + {Comparator: spec.ComparatorEqual, FieldReference: spec.FieldReference{genderField}, ParamIndex: 1}, + {Comparator: spec.ComparatorEqual, FieldReference: spec.FieldReference{ageField}, ParamIndex: 2}, }, }, }, @@ -1134,7 +1269,7 @@ func (r *UserRepositoryMongo) DeleteByGenderOrAge(arg0 context.Context, arg1 Gen Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorNot, Field: "Gender", ParamIndex: 1}, + {Comparator: spec.ComparatorNot, FieldReference: spec.FieldReference{genderField}, ParamIndex: 1}, }, }, }, @@ -1167,7 +1302,7 @@ func (r *UserRepositoryMongo) DeleteByGenderNot(arg0 context.Context, arg1 Gende Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorLessThan, Field: "Age", ParamIndex: 1}, + {Comparator: spec.ComparatorLessThan, FieldReference: spec.FieldReference{ageField}, ParamIndex: 1}, }, }, }, @@ -1200,7 +1335,7 @@ func (r *UserRepositoryMongo) DeleteByAgeLessThan(arg0 context.Context, arg1 int Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorLessThanEqual, Field: "Age", ParamIndex: 1}, + {Comparator: spec.ComparatorLessThanEqual, FieldReference: spec.FieldReference{ageField}, ParamIndex: 1}, }, }, }, @@ -1233,7 +1368,7 @@ func (r *UserRepositoryMongo) DeleteByAgeLessThanEqual(arg0 context.Context, arg Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorGreaterThan, Field: "Age", ParamIndex: 1}, + {Comparator: spec.ComparatorGreaterThan, FieldReference: spec.FieldReference{ageField}, ParamIndex: 1}, }, }, }, @@ -1266,7 +1401,7 @@ func (r *UserRepositoryMongo) DeleteByAgeGreaterThan(arg0 context.Context, arg1 Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorGreaterThanEqual, Field: "Age", ParamIndex: 1}, + {Comparator: spec.ComparatorGreaterThanEqual, FieldReference: spec.FieldReference{ageField}, ParamIndex: 1}, }, }, }, @@ -1300,7 +1435,7 @@ func (r *UserRepositoryMongo) DeleteByAgeGreaterThanEqual(arg0 context.Context, Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorBetween, Field: "Age", ParamIndex: 1}, + {Comparator: spec.ComparatorBetween, FieldReference: spec.FieldReference{ageField}, ParamIndex: 1}, }, }, }, @@ -1333,7 +1468,7 @@ func (r *UserRepositoryMongo) DeleteByAgeBetween(arg0 context.Context, arg1 int, Mode: spec.QueryModeMany, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Comparator: spec.ComparatorIn, Field: "Gender", ParamIndex: 1}, + {Comparator: spec.ComparatorIn, FieldReference: spec.FieldReference{genderField}, ParamIndex: 1}, }, }, }, @@ -1386,7 +1521,7 @@ func TestGenerateMethod_Count(t *testing.T) { Operation: spec.CountOperation{ Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Field: "Gender", Comparator: spec.ComparatorEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{genderField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, }, }, }, @@ -1420,8 +1555,8 @@ func (r *UserRepositoryMongo) CountByGender(arg0 context.Context, arg1 Gender) ( Query: spec.QuerySpec{ Operator: spec.OperatorAnd, Predicates: []spec.Predicate{ - {Field: "Gender", Comparator: spec.ComparatorEqual, ParamIndex: 1}, - {Field: "Age", Comparator: spec.ComparatorEqual, ParamIndex: 2}, + {FieldReference: spec.FieldReference{genderField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorEqual, ParamIndex: 2}, }, }, }, @@ -1458,8 +1593,8 @@ func (r *UserRepositoryMongo) CountByGenderAndCity(arg0 context.Context, arg1 Ge Query: spec.QuerySpec{ Operator: spec.OperatorOr, Predicates: []spec.Predicate{ - {Field: "Gender", Comparator: spec.ComparatorEqual, ParamIndex: 1}, - {Field: "Age", Comparator: spec.ComparatorEqual, ParamIndex: 2}, + {FieldReference: spec.FieldReference{genderField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorEqual, ParamIndex: 2}, }, }, }, @@ -1494,7 +1629,7 @@ func (r *UserRepositoryMongo) CountByGenderOrCity(arg0 context.Context, arg1 Gen Operation: spec.CountOperation{ Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Field: "Gender", Comparator: spec.ComparatorNot, ParamIndex: 1}, + {FieldReference: spec.FieldReference{genderField}, Comparator: spec.ComparatorNot, ParamIndex: 1}, }, }, }, @@ -1526,7 +1661,7 @@ func (r *UserRepositoryMongo) CountByGenderNot(arg0 context.Context, arg1 Gender Operation: spec.CountOperation{ Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Field: "Age", Comparator: spec.ComparatorLessThan, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorLessThan, ParamIndex: 1}, }, }, }, @@ -1558,7 +1693,7 @@ func (r *UserRepositoryMongo) CountByAgeLessThan(arg0 context.Context, arg1 int) Operation: spec.CountOperation{ Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Field: "Age", Comparator: spec.ComparatorLessThanEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorLessThanEqual, ParamIndex: 1}, }, }, }, @@ -1590,7 +1725,7 @@ func (r *UserRepositoryMongo) CountByAgeLessThanEqual(arg0 context.Context, arg1 Operation: spec.CountOperation{ Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Field: "Age", Comparator: spec.ComparatorGreaterThan, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorGreaterThan, ParamIndex: 1}, }, }, }, @@ -1622,7 +1757,7 @@ func (r *UserRepositoryMongo) CountByAgeGreaterThan(arg0 context.Context, arg1 i Operation: spec.CountOperation{ Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Field: "Age", Comparator: spec.ComparatorGreaterThanEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorGreaterThanEqual, ParamIndex: 1}, }, }, }, @@ -1655,7 +1790,7 @@ func (r *UserRepositoryMongo) CountByAgeGreaterThanEqual(arg0 context.Context, a Operation: spec.CountOperation{ Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Field: "Age", Comparator: spec.ComparatorBetween, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorBetween, ParamIndex: 1}, }, }, }, @@ -1687,7 +1822,7 @@ func (r *UserRepositoryMongo) CountByAgeBetween(arg0 context.Context, arg1 int, Operation: spec.CountOperation{ Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Field: "Age", Comparator: spec.ComparatorIn, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorIn, ParamIndex: 1}, }, }, }, @@ -1770,7 +1905,7 @@ func TestGenerateMethod_Invalid(t *testing.T) { Mode: spec.QueryModeOne, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Field: "AccessToken", Comparator: spec.ComparatorEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{accessTokenField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, }, }, }, @@ -1791,7 +1926,7 @@ func TestGenerateMethod_Invalid(t *testing.T) { Operation: spec.FindOperation{ Mode: spec.QueryModeOne, Sorts: []spec.Sort{ - {FieldName: "AccessToken", Ordering: spec.OrderingAscending}, + {FieldReference: spec.FieldReference{accessTokenField}, Ordering: spec.OrderingAscending}, }, }, }, @@ -1812,12 +1947,12 @@ func TestGenerateMethod_Invalid(t *testing.T) { }, Operation: spec.UpdateOperation{ Update: spec.UpdateFields{ - {Name: "AccessToken", ParamIndex: 1}, + {FieldReference: spec.FieldReference{accessTokenField}, ParamIndex: 1}, }, Mode: spec.QueryModeOne, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Field: "ID", Comparator: spec.ComparatorEqual, ParamIndex: 2}, + {FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 2}, }, }, }, @@ -1842,7 +1977,7 @@ func TestGenerateMethod_Invalid(t *testing.T) { Mode: spec.QueryModeOne, Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Field: "ID", Comparator: spec.ComparatorEqual, ParamIndex: 2}, + {FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 2}, }, }, }, diff --git a/internal/spec/errors.go b/internal/spec/errors.go index c7100f0..996bdc3 100644 --- a/internal/spec/errors.go +++ b/internal/spec/errors.go @@ -75,8 +75,8 @@ func (err unknownOperationError) Error() string { } // NewStructFieldNotFoundError creates structFieldNotFoundError -func NewStructFieldNotFoundError(fieldName string) error { - return structFieldNotFoundError{FieldName: fieldName} +func NewStructFieldNotFoundError(tokens []string) error { + return structFieldNotFoundError{FieldName: strings.Join(tokens, "")} } type structFieldNotFoundError struct { diff --git a/internal/spec/errors_test.go b/internal/spec/errors_test.go index 8b9fdca..89884bd 100644 --- a/internal/spec/errors_test.go +++ b/internal/spec/errors_test.go @@ -22,13 +22,13 @@ func TestError(t *testing.T) { }, { Name: "StructFieldNotFoundError", - Error: spec.NewStructFieldNotFoundError("Country"), - ExpectedString: "struct field 'Country' not found", + Error: spec.NewStructFieldNotFoundError([]string{"Phone", "Number"}), + ExpectedString: "struct field 'PhoneNumber' not found", }, { Name: "InvalidQueryError", - Error: spec.NewInvalidQueryError([]string{"By", "And"}), - ExpectedString: "invalid query 'ByAnd'", + Error: spec.NewInvalidQueryError([]string{"And"}), + ExpectedString: "invalid query 'And'", }, { Name: "IncompatibleComparatorError", diff --git a/internal/spec/field.go b/internal/spec/field.go new file mode 100644 index 0000000..4689371 --- /dev/null +++ b/internal/spec/field.go @@ -0,0 +1,64 @@ +package spec + +import ( + "strings" + + "github.com/sunboyy/repogen/internal/code" +) + +// FieldReference is a reference path to access to the field +type FieldReference []code.StructField + +// ReferencedField returns the last struct field +func (r FieldReference) ReferencedField() code.StructField { + return r[len(r)-1] +} + +type fieldResolver struct { + Structs code.Structs +} + +func (r fieldResolver) ResolveStructField(structModel code.Struct, tokens []string) (FieldReference, bool) { + fieldName := strings.Join(tokens, "") + field, ok := structModel.Fields.ByName(fieldName) + if ok { + return FieldReference{field}, true + } + + for i := len(tokens) - 1; i > 0; i-- { + fieldName := strings.Join(tokens[:i], "") + field, ok := structModel.Fields.ByName(fieldName) + if !ok { + continue + } + + fieldSimpleType, ok := getSimpleType(field.Type) + if !ok { + continue + } + + childStruct, ok := r.Structs.ByName(fieldSimpleType.Code()) + if !ok { + continue + } + + fields, ok := r.ResolveStructField(childStruct, tokens[i:]) + if !ok { + continue + } + return append(FieldReference{field}, fields...), true + } + + return nil, false +} + +func getSimpleType(t code.Type) (code.SimpleType, bool) { + switch t := t.(type) { + case code.SimpleType: + return t, true + case code.PointerType: + return getSimpleType(t.ContainedType) + default: + return "", false + } +} diff --git a/internal/spec/models.go b/internal/spec/models.go index cac7e4c..19f3a67 100644 --- a/internal/spec/models.go +++ b/internal/spec/models.go @@ -50,8 +50,8 @@ func (o FindOperation) Name() string { // Sort is a detail of sorting find result type Sort struct { - FieldName string - Ordering Ordering + FieldReference FieldReference + Ordering Ordering } // Ordering is a sort order @@ -110,8 +110,8 @@ func (o UpdateOperation) Name() string { // UpdateField stores mapping between field name in the model and the parameter index type UpdateField struct { - Name string - ParamIndex int + FieldReference FieldReference + ParamIndex int } // DeleteOperation is a method specification for delete operations diff --git a/internal/spec/parser.go b/internal/spec/parser.go index 105b26f..dd7f481 100644 --- a/internal/spec/parser.go +++ b/internal/spec/parser.go @@ -1,15 +1,16 @@ package spec import ( - "strings" - "github.com/fatih/camelcase" "github.com/sunboyy/repogen/internal/code" ) // ParseInterfaceMethod returns repository method spec from declared interface method -func ParseInterfaceMethod(structModel code.Struct, method code.Method) (MethodSpec, error) { +func ParseInterfaceMethod(structs code.Structs, structModel code.Struct, method code.Method) (MethodSpec, error) { parser := interfaceMethodParser{ + fieldResolver: fieldResolver{ + Structs: structs, + }, StructModel: structModel, Method: method, } @@ -18,8 +19,9 @@ func ParseInterfaceMethod(structModel code.Struct, method code.Method) (MethodSp } type interfaceMethodParser struct { - StructModel code.Struct - Method code.Method + fieldResolver fieldResolver + StructModel code.Struct + Method code.Method } func (p interfaceMethodParser) Parse() (MethodSpec, error) { @@ -115,7 +117,7 @@ func (p interfaceMethodParser) parseFindOperation(tokens []string) (Operation, e queryTokens, sortTokens := p.splitQueryAndSortTokens(tokens) - querySpec, err := parseQuery(queryTokens, 1) + querySpec, err := p.parseQuery(queryTokens, 1) if err != nil { return nil, err } @@ -145,38 +147,43 @@ func (p interfaceMethodParser) parseSort(rawTokens []string) ([]Sort, error) { return nil, nil } - sortTokens := rawTokens[2:] - - var sorts []Sort - var aggregatedToken sortToken - for _, token := range sortTokens { - if token != "And" { - aggregatedToken = append(aggregatedToken, token) - } else if len(aggregatedToken) == 0 { - return nil, NewInvalidSortError(rawTokens) - } else { - sorts = append(sorts, aggregatedToken.ToSort()) - aggregatedToken = sortToken{} - } - } - if len(aggregatedToken) == 0 { + sortTokens, ok := splitByAnd(rawTokens[2:]) + if !ok { return nil, NewInvalidSortError(rawTokens) } - sorts = append(sorts, aggregatedToken.ToSort()) + + var sorts []Sort + for _, token := range sortTokens { + sort, err := p.parseSortToken(token) + if err != nil { + return nil, err + } + sorts = append(sorts, sort) + } return sorts, nil } -type sortToken []string - -func (t sortToken) ToSort() Sort { +func (p interfaceMethodParser) parseSortToken(t []string) (Sort, error) { if len(t) > 1 && t[len(t)-1] == "Asc" { - return Sort{FieldName: strings.Join(t[:len(t)-1], ""), Ordering: OrderingAscending} + return p.createSort(t[:len(t)-1], OrderingAscending) } if len(t) > 1 && t[len(t)-1] == "Desc" { - return Sort{FieldName: strings.Join(t[:len(t)-1], ""), Ordering: OrderingDescending} + return p.createSort(t[:len(t)-1], OrderingDescending) } - return Sort{FieldName: strings.Join(t, ""), Ordering: OrderingAscending} + return p.createSort(t, OrderingAscending) +} + +func (p interfaceMethodParser) createSort(t []string, ordering Ordering) (Sort, error) { + fields, ok := p.fieldResolver.ResolveStructField(p.StructModel, t) + if !ok { + return Sort{}, NewStructFieldNotFoundError(t) + } + + return Sort{ + FieldReference: fields, + Ordering: ordering, + }, nil } func (p interfaceMethodParser) splitQueryAndSortTokens(tokens []string) ([]string, []string) { @@ -245,7 +252,7 @@ func (p interfaceMethodParser) parseUpdateOperation(tokens []string) (Operation, return nil, err } - querySpec, err := parseQuery(queryTokens, 1+update.NumberOfArguments()) + querySpec, err := p.parseQuery(queryTokens, 1+update.NumberOfArguments()) if err != nil { return nil, err } @@ -270,37 +277,57 @@ func (p interfaceMethodParser) parseUpdate(tokens []string) (Update, error) { return UpdateModel{}, nil } + updateFieldTokens, ok := splitByAnd(tokens) + if !ok { + return nil, InvalidUpdateFieldsError + } + + var updateFields UpdateFields + paramIndex := 1 - var update UpdateFields - var aggregatedToken string + for _, updateFieldToken := range updateFieldTokens { + updateFieldReference, ok := p.fieldResolver.ResolveStructField(p.StructModel, updateFieldToken) + if !ok { + return nil, NewStructFieldNotFoundError(updateFieldToken) + } + + updateFields = append(updateFields, UpdateField{ + FieldReference: updateFieldReference, + ParamIndex: paramIndex, + }) + paramIndex++ + } + + for _, field := range updateFields { + if len(p.Method.Params) <= field.ParamIndex || + field.FieldReference.ReferencedField().Type != p.Method.Params[field.ParamIndex].Type { + return nil, InvalidUpdateFieldsError + } + } + + return updateFields, nil +} + +func splitByAnd(tokens []string) ([][]string, bool) { + var updateFieldTokens [][]string + var aggregatedToken []string + for _, token := range tokens { if token != "And" { - aggregatedToken += token + aggregatedToken = append(aggregatedToken, token) } else if len(aggregatedToken) == 0 { - return nil, InvalidUpdateFieldsError + return nil, false } else { - update = append(update, UpdateField{Name: aggregatedToken, ParamIndex: paramIndex}) - paramIndex++ - aggregatedToken = "" + updateFieldTokens = append(updateFieldTokens, aggregatedToken) + aggregatedToken = nil } } if len(aggregatedToken) == 0 { - return nil, InvalidUpdateFieldsError + return nil, false } - update = append(update, UpdateField{Name: aggregatedToken, ParamIndex: paramIndex}) + updateFieldTokens = append(updateFieldTokens, aggregatedToken) - for _, field := range update { - structField, ok := p.StructModel.Fields.ByName(field.Name) - if !ok { - return nil, NewStructFieldNotFoundError(field.Name) - } - - if len(p.Method.Params) <= field.ParamIndex || structField.Type != p.Method.Params[field.ParamIndex].Type { - return nil, InvalidUpdateFieldsError - } - } - - return update, nil + return updateFieldTokens, true } func (p interfaceMethodParser) splitUpdateAndQueryTokens(tokens []string) ([]string, []string) { @@ -325,7 +352,7 @@ func (p interfaceMethodParser) parseDeleteOperation(tokens []string) (Operation, return nil, err } - querySpec, err := parseQuery(tokens, 1) + querySpec, err := p.parseQuery(tokens, 1) if err != nil { return nil, err } @@ -349,7 +376,7 @@ func (p interfaceMethodParser) parseCountOperation(tokens []string) (Operation, return nil, err } - querySpec, err := parseQuery(tokens, 1) + querySpec, err := p.parseQuery(tokens, 1) if err != nil { return nil, err } @@ -420,19 +447,15 @@ func (p interfaceMethodParser) validateQueryFromParams(params []code.Param, quer var currentParamIndex int for _, predicate := range querySpec.Predicates { - structField, ok := p.StructModel.Fields.ByName(predicate.Field) - if !ok { - return NewStructFieldNotFoundError(predicate.Field) - } - if (predicate.Comparator == ComparatorTrue || predicate.Comparator == ComparatorFalse) && - structField.Type != code.SimpleType("bool") { - return NewIncompatibleComparatorError(predicate.Comparator, structField) + predicate.FieldReference.ReferencedField().Type != code.SimpleType("bool") { + return NewIncompatibleComparatorError(predicate.Comparator, + predicate.FieldReference.ReferencedField()) } for i := 0; i < predicate.Comparator.NumberOfArguments(); i++ { if params[currentParamIndex].Type != predicate.Comparator.ArgumentTypeFromFieldType( - structField.Type) { + predicate.FieldReference.ReferencedField().Type) { return InvalidParamError } currentParamIndex++ @@ -441,3 +464,11 @@ func (p interfaceMethodParser) validateQueryFromParams(params []code.Param, quer return nil } + +func (p interfaceMethodParser) parseQuery(queryTokens []string, paramIndex int) (QuerySpec, error) { + queryParser := queryParser{ + fieldResolver: p.fieldResolver, + StructModel: p.StructModel, + } + return queryParser.parseQuery(queryTokens, paramIndex) +} diff --git a/internal/spec/parser_test.go b/internal/spec/parser_test.go index 7a840bb..77a6e4b 100644 --- a/internal/spec/parser_test.go +++ b/internal/spec/parser_test.go @@ -8,34 +8,87 @@ import ( "github.com/sunboyy/repogen/internal/spec" ) -var structModel = code.Struct{ - Name: "UserModel", - Fields: code.StructFields{ - { - Name: "ID", - Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}, +var ( + idField = code.StructField{ + Name: "ID", + Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}, + } + phoneNumberField = code.StructField{ + Name: "PhoneNumber", + Type: code.SimpleType("string"), + } + genderField = code.StructField{ + Name: "Gender", + Type: code.SimpleType("Gender"), + } + cityField = code.StructField{ + Name: "City", + Type: code.SimpleType("string"), + } + ageField = code.StructField{ + Name: "Age", + Type: code.SimpleType("int"), + } + nameField = code.StructField{ + Name: "Name", + Type: code.SimpleType("NameModel"), + } + contactField = code.StructField{ + Name: "Contact", + Type: code.SimpleType("ContactModel"), + } + referrerField = code.StructField{ + Name: "Referrer", + Type: code.PointerType{ContainedType: code.SimpleType("UserModel")}, + } + defaultPaymentField = code.StructField{ + Name: "DefaultPayment", + Type: code.ExternalType{PackageAlias: "payment", Name: "Payment"}, + } + enabledField = code.StructField{ + Name: "Enabled", + Type: code.SimpleType("bool"), + } + + firstNameField = code.StructField{ + Name: "First", + Type: code.SimpleType("string"), + } + lastNameField = code.StructField{ + Name: "Last", + Type: code.SimpleType("string"), + } +) + +var ( + nameStruct = code.Struct{ + Name: "NameModel", + Fields: code.StructFields{ + firstNameField, + lastNameField, }, - { - Name: "PhoneNumber", - Type: code.SimpleType("string"), + } + + structModel = code.Struct{ + Name: "UserModel", + Fields: code.StructFields{ + idField, + phoneNumberField, + genderField, + cityField, + ageField, + nameField, + contactField, + referrerField, + defaultPaymentField, + enabledField, }, - { - Name: "Gender", - Type: code.SimpleType("Gender"), - }, - { - Name: "City", - Type: code.SimpleType("string"), - }, - { - Name: "Age", - Type: code.SimpleType("int"), - }, - { - Name: "Enabled", - Type: code.SimpleType("bool"), - }, - }, + } +) + +var structs = code.Structs{ + nameStruct, + structModel, } type ParseInterfaceMethodTestCase struct { @@ -84,7 +137,7 @@ func TestParseInterfaceMethod_Insert(t *testing.T) { for _, testCase := range testTable { t.Run(testCase.Name, func(t *testing.T) { - actualSpec, err := spec.ParseInterfaceMethod(structModel, testCase.Method) + actualSpec, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method) if err != nil { t.Errorf("Error = %s", err) @@ -120,7 +173,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) { ExpectedOperation: spec.FindOperation{ Mode: spec.QueryModeOne, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "ID", Comparator: spec.ComparatorEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, }}, }, }, @@ -140,7 +193,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) { ExpectedOperation: spec.FindOperation{ Mode: spec.QueryModeOne, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "PhoneNumber", Comparator: spec.ComparatorEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{phoneNumberField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, }}, }, }, @@ -160,7 +213,47 @@ func TestParseInterfaceMethod_Find(t *testing.T) { ExpectedOperation: spec.FindOperation{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "City", Comparator: spec.ComparatorEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, + }}, + }, + }, + { + Name: "FindByDeepArg method", + Method: code.Method{ + Name: "FindByNameFirst", + Params: []code.Param{ + {Type: code.ExternalType{PackageAlias: "context", Name: "Context"}}, + {Type: code.SimpleType("string")}, + }, + Returns: []code.Type{ + code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}}, + code.SimpleType("error"), + }, + }, + ExpectedOperation: spec.FindOperation{ + Mode: spec.QueryModeMany, + Query: spec.QuerySpec{Predicates: []spec.Predicate{ + {FieldReference: spec.FieldReference{nameField, firstNameField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, + }}, + }, + }, + { + Name: "FindByDeepPointerArg method", + Method: code.Method{ + Name: "FindByReferrerID", + Params: []code.Param{ + {Type: code.ExternalType{PackageAlias: "context", Name: "Context"}}, + {Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}}, + }, + Returns: []code.Type{ + code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}}, + code.SimpleType("error"), + }, + }, + ExpectedOperation: spec.FindOperation{ + Mode: spec.QueryModeMany, + Query: spec.QuerySpec{Predicates: []spec.Predicate{ + {FieldReference: spec.FieldReference{referrerField, idField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, }}, }, }, @@ -199,8 +292,8 @@ func TestParseInterfaceMethod_Find(t *testing.T) { Query: spec.QuerySpec{ Operator: spec.OperatorAnd, Predicates: []spec.Predicate{ - {Field: "City", Comparator: spec.ComparatorEqual, ParamIndex: 1}, - {Field: "Gender", Comparator: spec.ComparatorEqual, ParamIndex: 2}, + {FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{genderField}, Comparator: spec.ComparatorEqual, ParamIndex: 2}, }, }, }, @@ -224,8 +317,8 @@ func TestParseInterfaceMethod_Find(t *testing.T) { Query: spec.QuerySpec{ Operator: spec.OperatorOr, Predicates: []spec.Predicate{ - {Field: "City", Comparator: spec.ComparatorEqual, ParamIndex: 1}, - {Field: "Gender", Comparator: spec.ComparatorEqual, ParamIndex: 2}, + {FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{genderField}, Comparator: spec.ComparatorEqual, ParamIndex: 2}, }, }, }, @@ -246,7 +339,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) { ExpectedOperation: spec.FindOperation{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "City", Comparator: spec.ComparatorNot, ParamIndex: 1}, + {FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorNot, ParamIndex: 1}, }}, }, }, @@ -266,7 +359,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) { ExpectedOperation: spec.FindOperation{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "Age", Comparator: spec.ComparatorLessThan, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorLessThan, ParamIndex: 1}, }}, }, }, @@ -286,7 +379,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) { ExpectedOperation: spec.FindOperation{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "Age", Comparator: spec.ComparatorLessThanEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorLessThanEqual, ParamIndex: 1}, }}, }, }, @@ -306,7 +399,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) { ExpectedOperation: spec.FindOperation{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "Age", Comparator: spec.ComparatorGreaterThan, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorGreaterThan, ParamIndex: 1}, }}, }, }, @@ -326,7 +419,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) { ExpectedOperation: spec.FindOperation{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "Age", Comparator: spec.ComparatorGreaterThanEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorGreaterThanEqual, ParamIndex: 1}, }}, }, }, @@ -347,7 +440,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) { ExpectedOperation: spec.FindOperation{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "Age", Comparator: spec.ComparatorBetween, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorBetween, ParamIndex: 1}, }}, }, }, @@ -367,7 +460,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) { ExpectedOperation: spec.FindOperation{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "City", Comparator: spec.ComparatorIn, ParamIndex: 1}, + {FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorIn, ParamIndex: 1}, }}, }, }, @@ -387,7 +480,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) { ExpectedOperation: spec.FindOperation{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "City", Comparator: spec.ComparatorNotIn, ParamIndex: 1}, + {FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorNotIn, ParamIndex: 1}, }}, }, }, @@ -406,7 +499,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) { ExpectedOperation: spec.FindOperation{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "Enabled", Comparator: spec.ComparatorTrue, ParamIndex: 1}, + {FieldReference: spec.FieldReference{enabledField}, Comparator: spec.ComparatorTrue, ParamIndex: 1}, }}, }, }, @@ -425,7 +518,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) { ExpectedOperation: spec.FindOperation{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "Enabled", Comparator: spec.ComparatorFalse, ParamIndex: 1}, + {FieldReference: spec.FieldReference{enabledField}, Comparator: spec.ComparatorFalse, ParamIndex: 1}, }}, }, }, @@ -445,10 +538,10 @@ func TestParseInterfaceMethod_Find(t *testing.T) { ExpectedOperation: spec.FindOperation{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "City", Comparator: spec.ComparatorEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, }}, Sorts: []spec.Sort{ - {FieldName: "Age", Ordering: spec.OrderingAscending}, + {FieldReference: spec.FieldReference{ageField}, Ordering: spec.OrderingAscending}, }, }, }, @@ -468,10 +561,10 @@ func TestParseInterfaceMethod_Find(t *testing.T) { ExpectedOperation: spec.FindOperation{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "City", Comparator: spec.ComparatorEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, }}, Sorts: []spec.Sort{ - {FieldName: "Age", Ordering: spec.OrderingAscending}, + {FieldReference: spec.FieldReference{ageField}, Ordering: spec.OrderingAscending}, }, }, }, @@ -491,10 +584,33 @@ func TestParseInterfaceMethod_Find(t *testing.T) { ExpectedOperation: spec.FindOperation{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "City", Comparator: spec.ComparatorEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, }}, Sorts: []spec.Sort{ - {FieldName: "Age", Ordering: spec.OrderingDescending}, + {FieldReference: spec.FieldReference{ageField}, Ordering: spec.OrderingDescending}, + }, + }, + }, + { + Name: "FindByArgOrderByDeepArg method", + Method: code.Method{ + Name: "FindByCityOrderByNameFirst", + Params: []code.Param{ + {Type: code.ExternalType{PackageAlias: "context", Name: "Context"}}, + {Type: code.SimpleType("string")}, + }, + Returns: []code.Type{ + code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}}, + code.SimpleType("error"), + }, + }, + ExpectedOperation: spec.FindOperation{ + Mode: spec.QueryModeMany, + Query: spec.QuerySpec{Predicates: []spec.Predicate{ + {FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, + }}, + Sorts: []spec.Sort{ + {FieldReference: spec.FieldReference{nameField, firstNameField}, Ordering: spec.OrderingAscending}, }, }, }, @@ -514,11 +630,11 @@ func TestParseInterfaceMethod_Find(t *testing.T) { ExpectedOperation: spec.FindOperation{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "City", Comparator: spec.ComparatorEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, }}, Sorts: []spec.Sort{ - {FieldName: "City", Ordering: spec.OrderingAscending}, - {FieldName: "Age", Ordering: spec.OrderingDescending}, + {FieldReference: spec.FieldReference{cityField}, Ordering: spec.OrderingAscending}, + {FieldReference: spec.FieldReference{ageField}, Ordering: spec.OrderingDescending}, }, }, }, @@ -526,7 +642,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) { for _, testCase := range testTable { t.Run(testCase.Name, func(t *testing.T) { - actualSpec, err := spec.ParseInterfaceMethod(structModel, testCase.Method) + actualSpec, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method) if err != nil { t.Errorf("Error = %s", err) @@ -564,7 +680,7 @@ func TestParseInterfaceMethod_Update(t *testing.T) { Update: spec.UpdateModel{}, Mode: spec.QueryModeOne, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "ID", Comparator: spec.ComparatorEqual, ParamIndex: 2}, + {FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 2}, }}, }, }, @@ -584,11 +700,11 @@ func TestParseInterfaceMethod_Update(t *testing.T) { }, ExpectedOperation: spec.UpdateOperation{ Update: spec.UpdateFields{ - {Name: "Gender", ParamIndex: 1}, + {FieldReference: spec.FieldReference{genderField}, ParamIndex: 1}, }, Mode: spec.QueryModeOne, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "ID", Comparator: spec.ComparatorEqual, ParamIndex: 2}, + {FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 2}, }}, }, }, @@ -608,11 +724,35 @@ func TestParseInterfaceMethod_Update(t *testing.T) { }, ExpectedOperation: spec.UpdateOperation{ Update: spec.UpdateFields{ - {Name: "Gender", ParamIndex: 1}, + {FieldReference: spec.FieldReference{genderField}, ParamIndex: 1}, }, Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "ID", Comparator: spec.ComparatorEqual, ParamIndex: 2}, + {FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 2}, + }}, + }, + }, + { + Name: "UpdateArgByArg one with deeply referenced update field method", + Method: code.Method{ + Name: "UpdateNameFirstByID", + Params: []code.Param{ + {Type: code.ExternalType{PackageAlias: "context", Name: "Context"}}, + {Type: code.SimpleType("string")}, + {Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}}, + }, + Returns: []code.Type{ + code.SimpleType("bool"), + code.SimpleType("error"), + }, + }, + ExpectedOperation: spec.UpdateOperation{ + Update: spec.UpdateFields{ + {FieldReference: spec.FieldReference{nameField, firstNameField}, ParamIndex: 1}, + }, + Mode: spec.QueryModeOne, + Query: spec.QuerySpec{Predicates: []spec.Predicate{ + {FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 2}, }}, }, }, @@ -633,12 +773,12 @@ func TestParseInterfaceMethod_Update(t *testing.T) { }, ExpectedOperation: spec.UpdateOperation{ Update: spec.UpdateFields{ - {Name: "Gender", ParamIndex: 1}, - {Name: "City", ParamIndex: 2}, + {FieldReference: spec.FieldReference{genderField}, ParamIndex: 1}, + {FieldReference: spec.FieldReference{cityField}, ParamIndex: 2}, }, Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "ID", Comparator: spec.ComparatorEqual, ParamIndex: 3}, + {FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 3}, }}, }, }, @@ -646,7 +786,7 @@ func TestParseInterfaceMethod_Update(t *testing.T) { for _, testCase := range testTable { t.Run(testCase.Name, func(t *testing.T) { - actualSpec, err := spec.ParseInterfaceMethod(structModel, testCase.Method) + actualSpec, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method) if err != nil { t.Errorf("Error = %s", err) @@ -682,7 +822,7 @@ func TestParseInterfaceMethod_Delete(t *testing.T) { ExpectedOperation: spec.DeleteOperation{ Mode: spec.QueryModeOne, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "ID", Comparator: spec.ComparatorEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, }}, }, }, @@ -702,7 +842,7 @@ func TestParseInterfaceMethod_Delete(t *testing.T) { ExpectedOperation: spec.DeleteOperation{ Mode: spec.QueryModeOne, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "PhoneNumber", Comparator: spec.ComparatorEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{phoneNumberField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, }}, }, }, @@ -722,7 +862,7 @@ func TestParseInterfaceMethod_Delete(t *testing.T) { ExpectedOperation: spec.DeleteOperation{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "City", Comparator: spec.ComparatorEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, }}, }, }, @@ -761,8 +901,8 @@ func TestParseInterfaceMethod_Delete(t *testing.T) { Query: spec.QuerySpec{ Operator: spec.OperatorAnd, Predicates: []spec.Predicate{ - {Field: "City", Comparator: spec.ComparatorEqual, ParamIndex: 1}, - {Field: "Gender", Comparator: spec.ComparatorEqual, ParamIndex: 2}, + {FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{genderField}, Comparator: spec.ComparatorEqual, ParamIndex: 2}, }, }, }, @@ -786,8 +926,8 @@ func TestParseInterfaceMethod_Delete(t *testing.T) { Query: spec.QuerySpec{ Operator: spec.OperatorOr, Predicates: []spec.Predicate{ - {Field: "City", Comparator: spec.ComparatorEqual, ParamIndex: 1}, - {Field: "Gender", Comparator: spec.ComparatorEqual, ParamIndex: 2}, + {FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{genderField}, Comparator: spec.ComparatorEqual, ParamIndex: 2}, }, }, }, @@ -808,7 +948,7 @@ func TestParseInterfaceMethod_Delete(t *testing.T) { ExpectedOperation: spec.DeleteOperation{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "City", Comparator: spec.ComparatorNot, ParamIndex: 1}, + {FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorNot, ParamIndex: 1}, }}, }, }, @@ -828,7 +968,7 @@ func TestParseInterfaceMethod_Delete(t *testing.T) { ExpectedOperation: spec.DeleteOperation{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "Age", Comparator: spec.ComparatorLessThan, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorLessThan, ParamIndex: 1}, }}, }, }, @@ -848,7 +988,7 @@ func TestParseInterfaceMethod_Delete(t *testing.T) { ExpectedOperation: spec.DeleteOperation{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "Age", Comparator: spec.ComparatorLessThanEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorLessThanEqual, ParamIndex: 1}, }}, }, }, @@ -868,7 +1008,7 @@ func TestParseInterfaceMethod_Delete(t *testing.T) { ExpectedOperation: spec.DeleteOperation{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "Age", Comparator: spec.ComparatorGreaterThan, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorGreaterThan, ParamIndex: 1}, }}, }, }, @@ -888,7 +1028,7 @@ func TestParseInterfaceMethod_Delete(t *testing.T) { ExpectedOperation: spec.DeleteOperation{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "Age", Comparator: spec.ComparatorGreaterThanEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorGreaterThanEqual, ParamIndex: 1}, }}, }, }, @@ -909,7 +1049,7 @@ func TestParseInterfaceMethod_Delete(t *testing.T) { ExpectedOperation: spec.DeleteOperation{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "Age", Comparator: spec.ComparatorBetween, ParamIndex: 1}, + {FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorBetween, ParamIndex: 1}, }}, }, }, @@ -929,7 +1069,7 @@ func TestParseInterfaceMethod_Delete(t *testing.T) { ExpectedOperation: spec.DeleteOperation{ Mode: spec.QueryModeMany, Query: spec.QuerySpec{Predicates: []spec.Predicate{ - {Field: "City", Comparator: spec.ComparatorIn, ParamIndex: 1}, + {FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorIn, ParamIndex: 1}, }}, }, }, @@ -937,7 +1077,7 @@ func TestParseInterfaceMethod_Delete(t *testing.T) { for _, testCase := range testTable { t.Run(testCase.Name, func(t *testing.T) { - actualSpec, err := spec.ParseInterfaceMethod(structModel, testCase.Method) + actualSpec, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method) if err != nil { t.Errorf("Error = %s", err) @@ -989,7 +1129,7 @@ func TestParseInterfaceMethod_Count(t *testing.T) { ExpectedOperation: spec.CountOperation{ Query: spec.QuerySpec{ Predicates: []spec.Predicate{ - {Field: "Gender", Comparator: spec.ComparatorEqual, ParamIndex: 1}, + {FieldReference: spec.FieldReference{genderField}, Comparator: spec.ComparatorEqual, ParamIndex: 1}, }, }, }, @@ -998,7 +1138,7 @@ func TestParseInterfaceMethod_Count(t *testing.T) { for _, testCase := range testTable { t.Run(testCase.Name, func(t *testing.T) { - actualSpec, err := spec.ParseInterfaceMethod(structModel, testCase.Method) + actualSpec, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method) if err != nil { t.Errorf("Error = %s", err) @@ -1023,7 +1163,7 @@ type ParseInterfaceMethodInvalidTestCase struct { } func TestParseInterfaceMethod_Invalid(t *testing.T) { - _, err := spec.ParseInterfaceMethod(structModel, code.Method{ + _, err := spec.ParseInterfaceMethod(structs, structModel, code.Method{ Name: "SearchByID", }) @@ -1132,7 +1272,7 @@ func TestParseInterfaceMethod_Insert_Invalid(t *testing.T) { for _, testCase := range testTable { t.Run(testCase.Name, func(t *testing.T) { - _, err := spec.ParseInterfaceMethod(structModel, testCase.Method) + _, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method) if err != testCase.ExpectedError { t.Errorf("\nExpected = %v\nReceived = %v", testCase.ExpectedError, err) @@ -1197,7 +1337,7 @@ func TestParseInterfaceMethod_Find_Invalid(t *testing.T) { code.SimpleType("error"), }, }, - ExpectedError: spec.NewInvalidQueryError([]string{"By", "And", "Gender"}), + ExpectedError: spec.NewInvalidQueryError([]string{"And", "Gender"}), }, { Name: "misplaced operator token (rightmost)", @@ -1208,7 +1348,7 @@ func TestParseInterfaceMethod_Find_Invalid(t *testing.T) { code.SimpleType("error"), }, }, - ExpectedError: spec.NewInvalidQueryError([]string{"By", "Gender", "And"}), + ExpectedError: spec.NewInvalidQueryError([]string{"Gender", "And"}), }, { Name: "misplaced operator token (double operator)", @@ -1219,7 +1359,7 @@ func TestParseInterfaceMethod_Find_Invalid(t *testing.T) { code.SimpleType("error"), }, }, - ExpectedError: spec.NewInvalidQueryError([]string{"By", "Gender", "And", "And", "City"}), + ExpectedError: spec.NewInvalidQueryError([]string{"Gender", "And", "And", "City"}), }, { Name: "ambiguous query", @@ -1230,7 +1370,7 @@ func TestParseInterfaceMethod_Find_Invalid(t *testing.T) { code.SimpleType("error"), }, }, - ExpectedError: spec.NewInvalidQueryError([]string{"By", "Gender", "And", "City", "Or", "Age"}), + ExpectedError: spec.NewInvalidQueryError([]string{"Gender", "And", "City", "Or", "Age"}), }, { Name: "no context parameter", @@ -1249,7 +1389,7 @@ func TestParseInterfaceMethod_Find_Invalid(t *testing.T) { { Name: "mismatched number of parameters", Method: code.Method{ - Name: "FindByCountry", + Name: "FindByCity", Params: []code.Param{ {Type: code.ExternalType{PackageAlias: "context", Name: "Context"}}, {Type: code.SimpleType("string")}, @@ -1275,7 +1415,52 @@ func TestParseInterfaceMethod_Find_Invalid(t *testing.T) { code.SimpleType("error"), }, }, - ExpectedError: spec.NewStructFieldNotFoundError("Country"), + ExpectedError: spec.NewStructFieldNotFoundError([]string{"Country"}), + }, + { + Name: "deeply referenced struct field not found", + Method: code.Method{ + Name: "FindByNameMiddle", + Params: []code.Param{ + {Type: code.ExternalType{PackageAlias: "context", Name: "Context"}}, + {Type: code.SimpleType("string")}, + }, + Returns: []code.Type{ + code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}}, + code.SimpleType("error"), + }, + }, + ExpectedError: spec.NewStructFieldNotFoundError([]string{"Name", "Middle"}), + }, + { + Name: "deeply referenced struct not found", + Method: code.Method{ + Name: "FindByContactPhone", + Params: []code.Param{ + {Type: code.ExternalType{PackageAlias: "context", Name: "Context"}}, + {Type: code.SimpleType("string")}, + }, + Returns: []code.Type{ + code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}}, + code.SimpleType("error"), + }, + }, + ExpectedError: spec.NewStructFieldNotFoundError([]string{"Contact", "Phone"}), + }, + { + Name: "deeply referenced external struct field", + Method: code.Method{ + Name: "FindByDefaultPaymentMethod", + Params: []code.Param{ + {Type: code.ExternalType{PackageAlias: "context", Name: "Context"}}, + {Type: code.SimpleType("string")}, + }, + Returns: []code.Type{ + code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}}, + code.SimpleType("error"), + }, + }, + ExpectedError: spec.NewStructFieldNotFoundError([]string{"Default", "Payment", "Method"}), }, { Name: "incompatible struct field for True comparator", @@ -1374,11 +1559,22 @@ func TestParseInterfaceMethod_Find_Invalid(t *testing.T) { }, ExpectedError: spec.NewInvalidSortError([]string{"Order", "By", "Age", "And", "And", "Gender"}), }, + { + Name: "sort field not found", + Method: code.Method{ + Name: "FindAllOrderByCountry", + Returns: []code.Type{ + code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}}, + code.SimpleType("error"), + }, + }, + ExpectedError: spec.NewStructFieldNotFoundError([]string{"Country"}), + }, } for _, testCase := range testTable { t.Run(testCase.Name, func(t *testing.T) { - _, err := spec.ParseInterfaceMethod(structModel, testCase.Method) + _, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method) if err.Error() != testCase.ExpectedError.Error() { t.Errorf("\nExpected = %v\nReceived = %v", testCase.ExpectedError.Error(), err.Error()) @@ -1479,7 +1675,7 @@ func TestParseInterfaceMethod_Update_Invalid(t *testing.T) { code.SimpleType("error"), }, }, - ExpectedError: spec.NewInvalidQueryError([]string{"By", "ID", "And", "Username", "Or", "Gender"}), + ExpectedError: spec.NewInvalidQueryError([]string{"ID", "And", "Username", "Or", "Gender"}), }, { Name: "update model with invalid parameter", @@ -1525,7 +1721,7 @@ func TestParseInterfaceMethod_Update_Invalid(t *testing.T) { code.SimpleType("error"), }, }, - ExpectedError: spec.NewStructFieldNotFoundError("Country"), + ExpectedError: spec.NewStructFieldNotFoundError([]string{"Country"}), }, { Name: "struct field does not match parameter in update fields", @@ -1563,7 +1759,7 @@ func TestParseInterfaceMethod_Update_Invalid(t *testing.T) { for _, testCase := range testTable { t.Run(testCase.Name, func(t *testing.T) { - _, err := spec.ParseInterfaceMethod(structModel, testCase.Method) + _, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method) if err != testCase.ExpectedError { t.Errorf("\nExpected = %v\nReceived = %v", testCase.ExpectedError, err) @@ -1628,7 +1824,7 @@ func TestParseInterfaceMethod_Delete_Invalid(t *testing.T) { code.SimpleType("error"), }, }, - ExpectedError: spec.NewInvalidQueryError([]string{"By", "And", "Gender"}), + ExpectedError: spec.NewInvalidQueryError([]string{"And", "Gender"}), }, { Name: "misplaced operator token (rightmost)", @@ -1639,7 +1835,7 @@ func TestParseInterfaceMethod_Delete_Invalid(t *testing.T) { code.SimpleType("error"), }, }, - ExpectedError: spec.NewInvalidQueryError([]string{"By", "Gender", "And"}), + ExpectedError: spec.NewInvalidQueryError([]string{"Gender", "And"}), }, { Name: "misplaced operator token (double operator)", @@ -1650,7 +1846,7 @@ func TestParseInterfaceMethod_Delete_Invalid(t *testing.T) { code.SimpleType("error"), }, }, - ExpectedError: spec.NewInvalidQueryError([]string{"By", "Gender", "And", "And", "City"}), + ExpectedError: spec.NewInvalidQueryError([]string{"Gender", "And", "And", "City"}), }, { Name: "ambiguous query", @@ -1661,7 +1857,7 @@ func TestParseInterfaceMethod_Delete_Invalid(t *testing.T) { code.SimpleType("error"), }, }, - ExpectedError: spec.NewInvalidQueryError([]string{"By", "Gender", "And", "City", "Or", "Age"}), + ExpectedError: spec.NewInvalidQueryError([]string{"Gender", "And", "City", "Or", "Age"}), }, { Name: "no context parameter", @@ -1680,7 +1876,7 @@ func TestParseInterfaceMethod_Delete_Invalid(t *testing.T) { { Name: "mismatched number of parameters", Method: code.Method{ - Name: "DeleteByCountry", + Name: "DeleteByCity", Params: []code.Param{ {Type: code.ExternalType{PackageAlias: "context", Name: "Context"}}, {Type: code.SimpleType("string")}, @@ -1706,7 +1902,7 @@ func TestParseInterfaceMethod_Delete_Invalid(t *testing.T) { code.SimpleType("error"), }, }, - ExpectedError: spec.NewStructFieldNotFoundError("Country"), + ExpectedError: spec.NewStructFieldNotFoundError([]string{"Country"}), }, { Name: "mismatched method parameter type", @@ -1742,7 +1938,7 @@ func TestParseInterfaceMethod_Delete_Invalid(t *testing.T) { for _, testCase := range testTable { t.Run(testCase.Name, func(t *testing.T) { - _, err := spec.ParseInterfaceMethod(structModel, testCase.Method) + _, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method) if err != testCase.ExpectedError { t.Errorf("\nExpected = %v\nReceived = %v", testCase.ExpectedError, err) @@ -1867,13 +2063,13 @@ func TestParseInterfaceMethod_Count_Invalid(t *testing.T) { code.SimpleType("error"), }, }, - ExpectedError: spec.NewStructFieldNotFoundError("Country"), + ExpectedError: spec.NewStructFieldNotFoundError([]string{"Country"}), }, } for _, testCase := range testTable { t.Run(testCase.Name, func(t *testing.T) { - _, err := spec.ParseInterfaceMethod(structModel, testCase.Method) + _, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method) if err != testCase.ExpectedError { t.Errorf("\nExpected = %v\nReceived = %v", testCase.ExpectedError, err) diff --git a/internal/spec/query.go b/internal/spec/query.go index 6a740a5..83ad6ed 100644 --- a/internal/spec/query.go +++ b/internal/spec/query.go @@ -1,8 +1,6 @@ package spec import ( - "strings" - "github.com/sunboyy/repogen/internal/code" ) @@ -72,48 +70,17 @@ func (c Comparator) NumberOfArguments() int { // Predicate is a criteria for querying a field type Predicate struct { - Field string - Comparator Comparator - ParamIndex int + FieldReference FieldReference + Comparator Comparator + ParamIndex int } -type predicateToken []string - -func (t predicateToken) ToPredicate(paramIndex int) Predicate { - if len(t) > 1 && t[len(t)-1] == "Not" { - return Predicate{Field: strings.Join(t[:len(t)-1], ""), Comparator: ComparatorNot, ParamIndex: paramIndex} - } - if len(t) > 2 && t[len(t)-2] == "Less" && t[len(t)-1] == "Than" { - return Predicate{Field: strings.Join(t[:len(t)-2], ""), Comparator: ComparatorLessThan, ParamIndex: paramIndex} - } - if len(t) > 3 && t[len(t)-3] == "Less" && t[len(t)-2] == "Than" && t[len(t)-1] == "Equal" { - return Predicate{Field: strings.Join(t[:len(t)-3], ""), Comparator: ComparatorLessThanEqual, ParamIndex: paramIndex} - } - if len(t) > 2 && t[len(t)-2] == "Greater" && t[len(t)-1] == "Than" { - return Predicate{Field: strings.Join(t[:len(t)-2], ""), Comparator: ComparatorGreaterThan, ParamIndex: paramIndex} - } - if len(t) > 3 && t[len(t)-3] == "Greater" && t[len(t)-2] == "Than" && t[len(t)-1] == "Equal" { - return Predicate{Field: strings.Join(t[:len(t)-3], ""), Comparator: ComparatorGreaterThanEqual, ParamIndex: paramIndex} - } - if len(t) > 2 && t[len(t)-2] == "Not" && t[len(t)-1] == "In" { - return Predicate{Field: strings.Join(t[:len(t)-2], ""), Comparator: ComparatorNotIn, ParamIndex: paramIndex} - } - if len(t) > 1 && t[len(t)-1] == "In" { - return Predicate{Field: strings.Join(t[:len(t)-1], ""), Comparator: ComparatorIn, ParamIndex: paramIndex} - } - if len(t) > 1 && t[len(t)-1] == "Between" { - return Predicate{Field: strings.Join(t[:len(t)-1], ""), Comparator: ComparatorBetween, ParamIndex: paramIndex} - } - if len(t) > 1 && t[len(t)-1] == "True" { - return Predicate{Field: strings.Join(t[:len(t)-1], ""), Comparator: ComparatorTrue, ParamIndex: paramIndex} - } - if len(t) > 1 && t[len(t)-1] == "False" { - return Predicate{Field: strings.Join(t[:len(t)-1], ""), Comparator: ComparatorFalse, ParamIndex: paramIndex} - } - return Predicate{Field: strings.Join(t, ""), Comparator: ComparatorEqual, ParamIndex: paramIndex} +type queryParser struct { + fieldResolver fieldResolver + StructModel code.Struct } -func parseQuery(rawTokens []string, paramIndex int) (QuerySpec, error) { +func (p queryParser) parseQuery(rawTokens []string, paramIndex int) (QuerySpec, error) { if len(rawTokens) == 0 { return QuerySpec{}, QueryRequiredError } @@ -130,38 +97,104 @@ func parseQuery(rawTokens []string, paramIndex int) (QuerySpec, error) { tokens = tokens[1:] } - if len(tokens) == 0 || tokens[0] == "And" || tokens[0] == "Or" { + if len(tokens) == 0 { return QuerySpec{}, NewInvalidQueryError(rawTokens) } + operator, predicateTokens, err := p.splitPredicateTokens(tokens) + if err != nil { + return QuerySpec{}, err + } + + querySpec := QuerySpec{ + Operator: operator, + } + + for _, predicateToken := range predicateTokens { + predicate, err := p.parsePredicate(predicateToken, paramIndex) + if err != nil { + return QuerySpec{}, err + } + querySpec.Predicates = append(querySpec.Predicates, predicate) + paramIndex += predicate.Comparator.NumberOfArguments() + } + + return querySpec, nil +} + +func (p queryParser) splitPredicateTokens(tokens []string) (Operator, [][]string, error) { var operator Operator - var predicates []Predicate - var aggregatedToken predicateToken + var predicateTokens [][]string + var aggregatedToken []string + for _, token := range tokens { if token != "And" && token != "Or" { aggregatedToken = append(aggregatedToken, token) } else if len(aggregatedToken) == 0 { - return QuerySpec{}, NewInvalidQueryError(rawTokens) + return "", nil, NewInvalidQueryError(tokens) } else if token == "And" && operator != OperatorOr { operator = OperatorAnd - predicate := aggregatedToken.ToPredicate(paramIndex) - predicates = append(predicates, predicate) - paramIndex += predicate.Comparator.NumberOfArguments() - aggregatedToken = predicateToken{} + predicateTokens = append(predicateTokens, aggregatedToken) + aggregatedToken = nil } else if token == "Or" && operator != OperatorAnd { operator = OperatorOr - predicate := aggregatedToken.ToPredicate(paramIndex) - predicates = append(predicates, predicate) - paramIndex += predicate.Comparator.NumberOfArguments() - aggregatedToken = predicateToken{} + predicateTokens = append(predicateTokens, aggregatedToken) + aggregatedToken = nil } else { - return QuerySpec{}, NewInvalidQueryError(rawTokens) + return "", nil, NewInvalidQueryError(tokens) } } if len(aggregatedToken) == 0 { - return QuerySpec{}, NewInvalidQueryError(rawTokens) + return "", nil, NewInvalidQueryError(tokens) } - predicates = append(predicates, aggregatedToken.ToPredicate(paramIndex)) + predicateTokens = append(predicateTokens, aggregatedToken) - return QuerySpec{Operator: operator, Predicates: predicates}, nil + return operator, predicateTokens, nil +} + +func (p queryParser) parsePredicate(t []string, paramIndex int) (Predicate, error) { + if len(t) > 1 && t[len(t)-1] == "Not" { + return p.createPredicate(t[:len(t)-1], ComparatorNot, paramIndex) + } + if len(t) > 2 && t[len(t)-2] == "Less" && t[len(t)-1] == "Than" { + return p.createPredicate(t[:len(t)-2], ComparatorLessThan, paramIndex) + } + if len(t) > 3 && t[len(t)-3] == "Less" && t[len(t)-2] == "Than" && t[len(t)-1] == "Equal" { + return p.createPredicate(t[:len(t)-3], ComparatorLessThanEqual, paramIndex) + } + if len(t) > 2 && t[len(t)-2] == "Greater" && t[len(t)-1] == "Than" { + return p.createPredicate(t[:len(t)-2], ComparatorGreaterThan, paramIndex) + } + if len(t) > 3 && t[len(t)-3] == "Greater" && t[len(t)-2] == "Than" && t[len(t)-1] == "Equal" { + return p.createPredicate(t[:len(t)-3], ComparatorGreaterThanEqual, paramIndex) + } + if len(t) > 2 && t[len(t)-2] == "Not" && t[len(t)-1] == "In" { + return p.createPredicate(t[:len(t)-2], ComparatorNotIn, paramIndex) + } + if len(t) > 1 && t[len(t)-1] == "In" { + return p.createPredicate(t[:len(t)-1], ComparatorIn, paramIndex) + } + if len(t) > 1 && t[len(t)-1] == "Between" { + return p.createPredicate(t[:len(t)-1], ComparatorBetween, paramIndex) + } + if len(t) > 1 && t[len(t)-1] == "True" { + return p.createPredicate(t[:len(t)-1], ComparatorTrue, paramIndex) + } + if len(t) > 1 && t[len(t)-1] == "False" { + return p.createPredicate(t[:len(t)-1], ComparatorFalse, paramIndex) + } + return p.createPredicate(t, ComparatorEqual, paramIndex) +} + +func (p queryParser) createPredicate(t []string, comparator Comparator, paramIndex int) (Predicate, error) { + fields, ok := p.fieldResolver.ResolveStructField(p.StructModel, t) + if !ok { + return Predicate{}, NewStructFieldNotFoundError(t) + } + + return Predicate{ + FieldReference: fields, + Comparator: comparator, + ParamIndex: paramIndex, + }, nil } diff --git a/main.go b/main.go index 8228d3e..f653fc5 100644 --- a/main.go +++ b/main.go @@ -75,7 +75,7 @@ func generateFromRequest(fileName, structModelName, repositoryInterfaceName stri var methodSpecs []spec.MethodSpec for _, method := range intf.Methods { - methodSpec, err := spec.ParseInterfaceMethod(structModel, method) + methodSpec, err := spec.ParseInterfaceMethod(file.Structs, structModel, method) if err != nil { return "", err }