Merge pull request #6 from sunboyy/method-signature-validation

Validate parameter types in method signature
This commit is contained in:
sunboyy 2021-01-21 18:58:05 +07:00 committed by GitHub
commit 847d5add53
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 587 additions and 488 deletions

View file

@ -2,7 +2,7 @@ coverage:
status:
project:
default:
target: 65%
target: 70%
threshold: 5%
patch:
default:

View file

@ -13,15 +13,20 @@ import (
// GenerateMongoRepository generates mongodb repository
func GenerateMongoRepository(packageName string, structModel code.Struct, intf code.Interface) (string, error) {
repositorySpec, err := spec.ParseRepositoryInterface(structModel, intf)
if err != nil {
return "", err
var methodSpecs []spec.MethodSpec
for _, method := range intf.Methods {
methodSpec, err := spec.ParseInterfaceMethod(structModel, method)
if err != nil {
return "", err
}
methodSpecs = append(methodSpecs, methodSpec)
}
generator := mongoRepositoryGenerator{
PackageName: packageName,
StructModel: structModel,
RepositorySpec: repositorySpec,
PackageName: packageName,
StructModel: structModel,
InterfaceName: intf.Name,
MethodSpecs: methodSpecs,
}
output, err := generator.Generate()
@ -33,9 +38,10 @@ func GenerateMongoRepository(packageName string, structModel code.Struct, intf c
}
type mongoRepositoryGenerator struct {
PackageName string
StructModel code.Struct
RepositorySpec spec.RepositorySpec
PackageName string
StructModel code.Struct
InterfaceName string
MethodSpecs []spec.MethodSpec
}
func (g mongoRepositoryGenerator) Generate() (string, error) {
@ -44,7 +50,7 @@ func (g mongoRepositoryGenerator) Generate() (string, error) {
return "", err
}
for _, method := range g.RepositorySpec.Methods {
for _, method := range g.MethodSpecs {
if err := g.generateMethod(buffer, method); err != nil {
return "", err
}
@ -66,7 +72,7 @@ func (g mongoRepositoryGenerator) generateBaseContent(buffer *bytes.Buffer) erro
tmplData := mongoBaseTemplateData{
PackageName: g.PackageName,
InterfaceName: g.RepositorySpec.InterfaceName,
InterfaceName: g.InterfaceName,
StructName: g.structName(),
}
@ -167,5 +173,5 @@ func (g mongoRepositoryGenerator) generateFindImplementation(operation spec.Find
}
func (g mongoRepositoryGenerator) structName() string {
return g.RepositorySpec.InterfaceName + "Mongo"
return g.InterfaceName + "Mongo"
}

View file

@ -83,7 +83,7 @@ func TestGenerateMongoRepository(t *testing.T) {
Name: "FindByGenderNot",
Params: []code.Param{
{Name: "ctx", Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Name: "gender", Type: code.SimpleType("int")},
{Name: "gender", Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
@ -220,7 +220,7 @@ func (r *UserRepositoryMongo) FindByIDAndUsername(ctx context.Context, arg0 prim
return &entity, nil
}
func (r *UserRepositoryMongo) FindByGenderNot(ctx context.Context, arg0 int) ([]*UserModel, error) {
func (r *UserRepositoryMongo) FindByGenderNot(ctx context.Context, arg0 Gender) ([]*UserModel, error) {
cursor, err := r.collection.Find(ctx, bson.M{
"gender": bson.M{"$ne": arg0},
})

35
internal/spec/errors.go Normal file
View file

@ -0,0 +1,35 @@
package spec
// ParsingError is an error from parsing interface methods
type ParsingError string
func (err ParsingError) Error() string {
switch err {
case UnknownOperationError:
return "unknown operation"
case UnsupportedNameError:
return "method name is not supported"
case InvalidQueryError:
return "invalid query"
case InvalidParamError:
return "parameters do not match the query"
case UnsupportedReturnError:
return "this type of return is not supported"
case ContextParamRequiredError:
return "context parameter is required"
case StructFieldNotFoundError:
return "struct field not found"
}
return string(err)
}
// parsing error constants
const (
UnknownOperationError ParsingError = "ERROR_UNKNOWN_OPERATION"
UnsupportedNameError ParsingError = "ERROR_UNSUPPORTED"
InvalidQueryError ParsingError = "ERROR_INVALID_QUERY"
InvalidParamError ParsingError = "ERROR_INVALID_PARAM"
UnsupportedReturnError ParsingError = "ERROR_INVALID_RETURN"
ContextParamRequiredError ParsingError = "ERROR_CONTEXT_PARAM_REQUIRED"
StructFieldNotFoundError ParsingError = "ERROR_STRUCT_FIELD_NOT_FOUND"
)

View file

@ -15,12 +15,6 @@ const (
QueryModeMany QueryMode = "MANY"
)
// RepositorySpec is a specification generated from the repository interface
type RepositorySpec struct {
InterfaceName string
Methods []MethodSpec
}
// MethodSpec is a method specification inside repository specification
type MethodSpec struct {
Name string

View file

@ -1,59 +1,40 @@
package spec
import (
"errors"
"fmt"
"github.com/fatih/camelcase"
"github.com/sunboyy/repogen/internal/code"
)
// ParseRepositoryInterface returns repository spec from declared repository interface
func ParseRepositoryInterface(structModel code.Struct, intf code.Interface) (RepositorySpec, error) {
parser := repositoryInterfaceParser{
// ParseInterfaceMethod returns repository method spec from declared interface method
func ParseInterfaceMethod(structModel code.Struct, method code.Method) (MethodSpec, error) {
parser := interfaceMethodParser{
StructModel: structModel,
Interface: intf,
Method: method,
}
return parser.Parse()
}
type repositoryInterfaceParser struct {
type interfaceMethodParser struct {
StructModel code.Struct
Interface code.Interface
Method code.Method
}
func (p repositoryInterfaceParser) Parse() (RepositorySpec, error) {
repositorySpec := RepositorySpec{
InterfaceName: p.Interface.Name,
}
for _, method := range p.Interface.Methods {
methodSpec, err := p.parseMethod(method)
if err != nil {
return RepositorySpec{}, err
}
repositorySpec.Methods = append(repositorySpec.Methods, methodSpec)
}
return repositorySpec, nil
}
func (p repositoryInterfaceParser) parseMethod(method code.Method) (MethodSpec, error) {
methodNameTokens := camelcase.Split(method.Name)
func (p interfaceMethodParser) Parse() (MethodSpec, error) {
methodNameTokens := camelcase.Split(p.Method.Name)
switch methodNameTokens[0] {
case "Find":
return p.parseFindMethod(method, methodNameTokens[1:])
return p.parseFindMethod(methodNameTokens[1:])
}
return MethodSpec{}, errors.New("method name not supported")
return MethodSpec{}, UnknownOperationError
}
func (p repositoryInterfaceParser) parseFindMethod(method code.Method, tokens []string) (MethodSpec, error) {
func (p interfaceMethodParser) parseFindMethod(tokens []string) (MethodSpec, error) {
if len(tokens) == 0 {
return MethodSpec{}, errors.New("method name not supported")
return MethodSpec{}, UnsupportedNameError
}
mode, err := p.extractFindReturns(method.Returns)
mode, err := p.extractFindReturns(p.Method.Returns)
if err != nil {
return MethodSpec{}, err
}
@ -63,14 +44,14 @@ func (p repositoryInterfaceParser) parseFindMethod(method code.Method, tokens []
return MethodSpec{}, err
}
if querySpec.NumberOfArguments()+1 != len(method.Params) {
return MethodSpec{}, errors.New("method parameter not supported")
if err := p.validateMethodSignature(querySpec); err != nil {
return MethodSpec{}, err
}
return MethodSpec{
Name: method.Name,
Params: method.Params,
Returns: method.Returns,
Name: p.Method.Name,
Params: p.Method.Params,
Returns: p.Method.Returns,
Operation: FindOperation{
Mode: mode,
Query: querySpec,
@ -78,13 +59,13 @@ func (p repositoryInterfaceParser) parseFindMethod(method code.Method, tokens []
}, nil
}
func (p repositoryInterfaceParser) extractFindReturns(returns []code.Type) (QueryMode, error) {
func (p interfaceMethodParser) extractFindReturns(returns []code.Type) (QueryMode, error) {
if len(returns) != 2 {
return "", errors.New("method return not supported")
return "", UnsupportedReturnError
}
if returns[1] != code.SimpleType("error") {
return "", errors.New("method return not supported")
return "", UnsupportedReturnError
}
pointerType, ok := returns[0].(code.PointerType)
@ -93,7 +74,7 @@ func (p repositoryInterfaceParser) extractFindReturns(returns []code.Type) (Quer
if simpleType == code.SimpleType(p.StructModel.Name) {
return QueryModeOne, nil
}
return "", fmt.Errorf("invalid return type %s", pointerType.Code())
return "", UnsupportedReturnError
}
arrayType, ok := returns[0].(code.ArrayType)
@ -104,16 +85,16 @@ func (p repositoryInterfaceParser) extractFindReturns(returns []code.Type) (Quer
if simpleType == code.SimpleType(p.StructModel.Name) {
return QueryModeMany, nil
}
return "", fmt.Errorf("invalid return type %s", pointerType.Code())
return "", UnsupportedReturnError
}
}
return "", errors.New("method return not supported")
return "", UnsupportedReturnError
}
func (p repositoryInterfaceParser) parseQuery(tokens []string) (QuerySpec, error) {
func (p interfaceMethodParser) parseQuery(tokens []string) (QuerySpec, error) {
if len(tokens) == 0 {
return QuerySpec{}, errors.New("method name not supported")
return QuerySpec{}, InvalidQueryError
}
if len(tokens) == 1 && tokens[0] == "All" {
@ -128,7 +109,7 @@ func (p repositoryInterfaceParser) parseQuery(tokens []string) (QuerySpec, error
}
if tokens[0] == "And" || tokens[0] == "Or" {
return QuerySpec{}, errors.New("method name not supported")
return QuerySpec{}, InvalidQueryError
}
var operator Operator
@ -137,6 +118,8 @@ func (p repositoryInterfaceParser) parseQuery(tokens []string) (QuerySpec, error
for _, token := range tokens {
if token != "And" && token != "Or" {
aggregatedToken = append(aggregatedToken, token)
} else if len(aggregatedToken) == 0 {
return QuerySpec{}, InvalidQueryError
} else if token == "And" && operator != OperatorOr {
operator = OperatorAnd
predicates = append(predicates, aggregatedToken.ToPredicate())
@ -146,13 +129,40 @@ func (p repositoryInterfaceParser) parseQuery(tokens []string) (QuerySpec, error
predicates = append(predicates, aggregatedToken.ToPredicate())
aggregatedToken = predicateToken{}
} else {
return QuerySpec{}, errors.New("method name contains ambiguous query")
return QuerySpec{}, InvalidQueryError
}
}
if len(aggregatedToken) == 0 {
return QuerySpec{}, errors.New("method name not supported")
return QuerySpec{}, InvalidQueryError
}
predicates = append(predicates, aggregatedToken.ToPredicate())
return QuerySpec{Operator: operator, Predicates: predicates}, nil
}
func (p interfaceMethodParser) validateMethodSignature(querySpec QuerySpec) error {
contextType := code.ExternalType{PackageAlias: "context", Name: "Context"}
if len(p.Method.Params) == 0 || p.Method.Params[0].Type != contextType {
return ContextParamRequiredError
}
if querySpec.NumberOfArguments()+1 != len(p.Method.Params) {
return InvalidParamError
}
currentParamIndex := 1
for _, predicate := range querySpec.Predicates {
structField, ok := p.StructModel.Fields.ByName(predicate.Field)
if !ok {
return StructFieldNotFoundError
}
if structField.Type != p.Method.Params[currentParamIndex].Type {
return InvalidParamError
}
currentParamIndex++
}
return nil
}

View file

@ -8,224 +8,191 @@ import (
"github.com/sunboyy/repogen/internal/spec"
)
type TestCase struct {
Name string
Interface code.Interface
ExpectedOutput spec.RepositorySpec
}
func TestParseRepositoryInterface(t *testing.T) {
testTable := []TestCase{
var structModel = code.Struct{
Name: "UserModel",
Fields: code.StructFields{
{
Name: "interface spec",
Interface: code.Interface{
Name: "UserRepository",
},
ExpectedOutput: spec.RepositorySpec{
InterfaceName: "UserRepository",
},
Name: "ID",
Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"},
},
{
Name: "PhoneNumber",
Type: code.SimpleType("string"),
},
{
Name: "Gender",
Type: code.SimpleType("Gender"),
},
{
Name: "City",
Type: code.SimpleType("string"),
},
{
Name: "Age",
Type: code.SimpleType("int"),
},
},
}
type ParseInterfaceMethodTestCase struct {
Name string
Method code.Method
ExpectedOutput spec.MethodSpec
}
func TestParseInterfaceMethod(t *testing.T) {
testTable := []ParseInterfaceMethodTestCase{
{
Name: "FindOneByArg method",
Interface: code.Interface{
Name: "UserRepository",
Methods: []code.Method{
{
Name: "FindOneByID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
},
Returns: []code.Type{
code.PointerType{ContainedType: code.SimpleType("UserModel")},
code.SimpleType("error"),
},
},
Method: code.Method{
Name: "FindOneByID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
},
Returns: []code.Type{
code.PointerType{ContainedType: code.SimpleType("UserModel")},
code.SimpleType("error"),
},
},
ExpectedOutput: spec.RepositorySpec{
InterfaceName: "UserRepository",
Methods: []spec.MethodSpec{
{
Name: "FindOneByID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
},
Returns: []code.Type{
code.PointerType{ContainedType: code.SimpleType("UserModel")},
code.SimpleType("error"),
},
Operation: spec.FindOperation{
Mode: spec.QueryModeOne,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "ID", Comparator: spec.ComparatorEqual},
}},
},
},
ExpectedOutput: spec.MethodSpec{
Name: "FindOneByID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
},
Returns: []code.Type{
code.PointerType{ContainedType: code.SimpleType("UserModel")},
code.SimpleType("error"),
},
Operation: spec.FindOperation{
Mode: spec.QueryModeOne,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "ID", Comparator: spec.ComparatorEqual},
}},
},
},
},
{
Name: "FindOneByMultiWordArg method",
Interface: code.Interface{
Name: "UserRepository",
Methods: []code.Method{
{
Name: "FindOneByPhoneNumber",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
},
Returns: []code.Type{
code.PointerType{ContainedType: code.SimpleType("UserModel")},
code.SimpleType("error"),
},
},
Method: code.Method{
Name: "FindOneByPhoneNumber",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
},
Returns: []code.Type{
code.PointerType{ContainedType: code.SimpleType("UserModel")},
code.SimpleType("error"),
},
},
ExpectedOutput: spec.RepositorySpec{
InterfaceName: "UserRepository",
Methods: []spec.MethodSpec{
{
Name: "FindOneByPhoneNumber",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{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{
{Field: "PhoneNumber", Comparator: spec.ComparatorEqual},
}},
},
},
ExpectedOutput: spec.MethodSpec{
Name: "FindOneByPhoneNumber",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{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{
{Field: "PhoneNumber", Comparator: spec.ComparatorEqual},
}},
},
},
},
{
Name: "FindByArg method",
Interface: code.Interface{
Name: "UserRepository",
Methods: []code.Method{
{
Name: "FindByCity",
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"),
},
},
Method: code.Method{
Name: "FindByCity",
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"),
},
},
ExpectedOutput: spec.RepositorySpec{
InterfaceName: "UserRepository",
Methods: []spec.MethodSpec{
{
Name: "FindByCity",
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"),
},
Operation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorEqual},
}},
},
},
ExpectedOutput: spec.MethodSpec{
Name: "FindByCity",
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"),
},
Operation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorEqual},
}},
},
},
},
{
Name: "FindAll method",
Interface: code.Interface{
Name: "UserRepository",
Methods: []code.Method{
{
Name: "FindAll",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
},
Method: code.Method{
Name: "FindAll",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
},
ExpectedOutput: spec.RepositorySpec{
InterfaceName: "UserRepository",
Methods: []spec.MethodSpec{
{
Name: "FindAll",
Params: []code.Param{
{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,
},
},
ExpectedOutput: spec.MethodSpec{
Name: "FindAll",
Params: []code.Param{
{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,
},
},
},
{
Name: "FindByArgAndArg method",
Interface: code.Interface{
Name: "UserRepository",
Methods: []code.Method{
{
Name: "FindByCityAndGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
},
Method: code.Method{
Name: "FindByCityAndGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
},
ExpectedOutput: spec.RepositorySpec{
InterfaceName: "UserRepository",
Methods: []spec.MethodSpec{
{
Name: "FindByCityAndGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
Operation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{
Operator: spec.OperatorAnd,
Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorEqual},
{Field: "Gender", Comparator: spec.ComparatorEqual},
},
},
ExpectedOutput: spec.MethodSpec{
Name: "FindByCityAndGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
Operation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{
Operator: spec.OperatorAnd,
Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorEqual},
{Field: "Gender", Comparator: spec.ComparatorEqual},
},
},
},
@ -233,46 +200,36 @@ func TestParseRepositoryInterface(t *testing.T) {
},
{
Name: "FindByArgOrArg method",
Interface: code.Interface{
Name: "UserRepository",
Methods: []code.Method{
{
Name: "FindByCityOrGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
},
Method: code.Method{
Name: "FindByCityOrGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
},
ExpectedOutput: spec.RepositorySpec{
InterfaceName: "UserRepository",
Methods: []spec.MethodSpec{
{
Name: "FindByCityOrGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
Operation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{
Operator: spec.OperatorOr,
Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorEqual},
{Field: "Gender", Comparator: spec.ComparatorEqual},
},
},
ExpectedOutput: spec.MethodSpec{
Name: "FindByCityOrGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
Operation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{
Operator: spec.OperatorOr,
Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorEqual},
{Field: "Gender", Comparator: spec.ComparatorEqual},
},
},
},
@ -280,240 +237,164 @@ func TestParseRepositoryInterface(t *testing.T) {
},
{
Name: "FindByArgNot method",
Interface: code.Interface{
Name: "UserRepository",
Methods: []code.Method{
{
Name: "FindByCityNot",
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"),
},
},
Method: code.Method{
Name: "FindByCityNot",
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"),
},
},
ExpectedOutput: spec.RepositorySpec{
InterfaceName: "UserRepository",
Methods: []spec.MethodSpec{
{
Name: "FindByCityNot",
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"),
},
Operation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorNot},
}},
},
},
ExpectedOutput: spec.MethodSpec{
Name: "FindByCityNot",
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"),
},
Operation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorNot},
}},
},
},
},
{
Name: "FindByArgLessThan method",
Interface: code.Interface{
Name: "UserRepository",
Methods: []code.Method{
{
Name: "FindByAgeLessThan",
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"),
},
},
Method: code.Method{
Name: "FindByAgeLessThan",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
},
ExpectedOutput: spec.RepositorySpec{
InterfaceName: "UserRepository",
Methods: []spec.MethodSpec{
{
Name: "FindByAgeLessThan",
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"),
},
Operation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Comparator: spec.ComparatorLessThan},
}},
},
},
ExpectedOutput: spec.MethodSpec{
Name: "FindByAgeLessThan",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
Operation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Comparator: spec.ComparatorLessThan},
}},
},
},
},
{
Name: "FindByArgLessThanEqual method",
Interface: code.Interface{
Name: "UserRepository",
Methods: []code.Method{
{
Name: "FindByAgeLessThanEqual",
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"),
},
},
Method: code.Method{
Name: "FindByAgeLessThanEqual",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
},
ExpectedOutput: spec.RepositorySpec{
InterfaceName: "UserRepository",
Methods: []spec.MethodSpec{
{
Name: "FindByAgeLessThanEqual",
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"),
},
Operation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Comparator: spec.ComparatorLessThanEqual},
}},
},
},
ExpectedOutput: spec.MethodSpec{
Name: "FindByAgeLessThanEqual",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
Operation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Comparator: spec.ComparatorLessThanEqual},
}},
},
},
},
{
Name: "FindByArgGreaterThan method",
Interface: code.Interface{
Name: "UserRepository",
Methods: []code.Method{
{
Name: "FindByAgeGreaterThan",
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"),
},
},
Method: code.Method{
Name: "FindByAgeGreaterThan",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
},
ExpectedOutput: spec.RepositorySpec{
InterfaceName: "UserRepository",
Methods: []spec.MethodSpec{
{
Name: "FindByAgeGreaterThan",
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"),
},
Operation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Comparator: spec.ComparatorGreaterThan},
}},
},
},
ExpectedOutput: spec.MethodSpec{
Name: "FindByAgeGreaterThan",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
Operation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Comparator: spec.ComparatorGreaterThan},
}},
},
},
},
{
Name: "FindByArgGreaterThanEqual method",
Interface: code.Interface{
Name: "UserRepository",
Methods: []code.Method{
{
Name: "FindByAgeGreaterThanEqual",
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"),
},
},
Method: code.Method{
Name: "FindByAgeGreaterThanEqual",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
},
ExpectedOutput: spec.RepositorySpec{
InterfaceName: "UserRepository",
Methods: []spec.MethodSpec{
{
Name: "FindByAgeGreaterThanEqual",
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"),
},
Operation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Comparator: spec.ComparatorGreaterThanEqual},
}},
},
},
ExpectedOutput: spec.MethodSpec{
Name: "FindByAgeGreaterThanEqual",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
Operation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Comparator: spec.ComparatorGreaterThanEqual},
}},
},
},
},
}
structModel := code.Struct{
Name: "UserModel",
Fields: code.StructFields{
{
Name: "ID",
Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"},
},
{
Name: "PhoneNumber",
Type: code.SimpleType("string"),
},
{
Name: "Gender",
Type: code.SimpleType("Gender"),
},
{
Name: "City",
Type: code.SimpleType("string"),
},
{
Name: "Age",
Type: code.SimpleType("int"),
},
},
}
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
actualSpec, err := spec.ParseRepositoryInterface(structModel, testCase.Interface)
actualSpec, err := spec.ParseInterfaceMethod(structModel, testCase.Method)
if err != nil {
t.Errorf("Error = %s", err)
@ -524,3 +405,176 @@ func TestParseRepositoryInterface(t *testing.T) {
})
}
}
type ParseInterfaceMethodInvalidTestCase struct {
Name string
Method code.Method
ExpectedError error
}
func TestParseInterfaceMethodInvalid(t *testing.T) {
testTable := []ParseInterfaceMethodInvalidTestCase{
{
Name: "unknown operation",
Method: code.Method{
Name: "SearchByID",
},
ExpectedError: spec.UnknownOperationError,
},
{
Name: "unsupported find method name",
Method: code.Method{
Name: "Find",
},
ExpectedError: spec.UnsupportedNameError,
},
{
Name: "invalid number of returns",
Method: code.Method{
Name: "FindOneByID",
Returns: []code.Type{
code.SimpleType("UserModel"),
code.SimpleType("int"),
code.SimpleType("error"),
},
},
ExpectedError: spec.UnsupportedReturnError,
},
{
Name: "unsupported return values from find method",
Method: code.Method{
Name: "FindOneByID",
Returns: []code.Type{
code.SimpleType("UserModel"),
code.SimpleType("error"),
},
},
ExpectedError: spec.UnsupportedReturnError,
},
{
Name: "error return not provided",
Method: code.Method{
Name: "FindOneByID",
Returns: []code.Type{
code.SimpleType("UserModel"),
code.SimpleType("int"),
},
},
ExpectedError: spec.UnsupportedReturnError,
},
{
Name: "misplaced operator token (leftmost)",
Method: code.Method{
Name: "FindByAndGender",
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
},
ExpectedError: spec.InvalidQueryError,
},
{
Name: "misplaced operator token (rightmost)",
Method: code.Method{
Name: "FindByGenderAnd",
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
},
ExpectedError: spec.InvalidQueryError,
},
{
Name: "misplaced operator token (double operator)",
Method: code.Method{
Name: "FindByGenderAndAndCity",
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
},
ExpectedError: spec.InvalidQueryError,
},
{
Name: "ambiguous query",
Method: code.Method{
Name: "FindByGenderAndCityOrAge",
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
},
ExpectedError: spec.InvalidQueryError,
},
{
Name: "no context parameter",
Method: code.Method{
Name: "FindByGender",
Params: []code.Param{
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
},
ExpectedError: spec.ContextParamRequiredError,
},
{
Name: "mismatched number of parameters",
Method: code.Method{
Name: "FindByCountry",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
{Type: code.SimpleType("string")},
},
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
},
ExpectedError: spec.InvalidParamError,
},
{
Name: "struct field not found",
Method: code.Method{
Name: "FindByCountry",
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.StructFieldNotFoundError,
},
{
Name: "mismatched method parameter type",
Method: code.Method{
Name: "FindByGender",
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.InvalidParamError,
},
}
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
_, err := spec.ParseInterfaceMethod(structModel, testCase.Method)
if err != testCase.ExpectedError {
t.Errorf("\nExpected = %v\nReceived = %v", testCase.ExpectedError, err)
}
})
}
}