Merge pull request #6 from sunboyy/method-signature-validation
Validate parameter types in method signature
This commit is contained in:
commit
847d5add53
7 changed files with 587 additions and 488 deletions
|
@ -2,7 +2,7 @@ coverage:
|
|||
status:
|
||||
project:
|
||||
default:
|
||||
target: 65%
|
||||
target: 70%
|
||||
threshold: 5%
|
||||
patch:
|
||||
default:
|
||||
|
|
|
@ -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)
|
||||
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,
|
||||
InterfaceName: intf.Name,
|
||||
MethodSpecs: methodSpecs,
|
||||
}
|
||||
|
||||
output, err := generator.Generate()
|
||||
|
@ -35,7 +40,8 @@ func GenerateMongoRepository(packageName string, structModel code.Struct, intf c
|
|||
type mongoRepositoryGenerator struct {
|
||||
PackageName string
|
||||
StructModel code.Struct
|
||||
RepositorySpec spec.RepositorySpec
|
||||
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"
|
||||
}
|
||||
|
|
|
@ -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
35
internal/spec/errors.go
Normal 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"
|
||||
)
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue