Add delete operation

This commit is contained in:
sunboyy 2021-01-26 20:23:52 +07:00
parent c5c4542a80
commit e78f9180da
8 changed files with 1278 additions and 88 deletions

View file

@ -3,7 +3,7 @@ coverage:
project:
default:
target: 75%
threshold: 5%
threshold: 4%
patch:
default:
target: 50%

20
internal/mongo/errors.go Normal file
View file

@ -0,0 +1,20 @@
package mongo
// GenerationError is an error from generating MongoDB repository
type GenerationError string
func (err GenerationError) Error() string {
switch err {
case OperationNotSupportedError:
return "operation not supported"
case BsonTagNotFoundError:
return "bson tag not found"
}
return string(err)
}
// generation error constants
const (
OperationNotSupportedError GenerationError = "ERROR_OPERATION_NOT_SUPPORTED"
BsonTagNotFoundError GenerationError = "ERROR_BSON_TAG_NOT_FOUND"
)

View file

@ -2,7 +2,6 @@ package mongo
import (
"bytes"
"errors"
"fmt"
"io"
"text/template"
@ -80,35 +79,24 @@ func (g RepositoryGenerator) generateMethodImplementation(methodSpec spec.Method
switch operation := methodSpec.Operation.(type) {
case spec.FindOperation:
return g.generateFindImplementation(operation)
case spec.DeleteOperation:
return g.generateDeleteImplementation(operation)
}
return "", errors.New("method spec not supported")
return "", OperationNotSupportedError
}
func (g RepositoryGenerator) generateFindImplementation(operation spec.FindOperation) (string, error) {
buffer := new(bytes.Buffer)
var predicates []predicate
for _, predicateSpec := range operation.Query.Predicates {
structField, ok := g.StructModel.Fields.ByName(predicateSpec.Field)
if !ok {
return "", fmt.Errorf("struct field %s not found", predicateSpec.Field)
}
bsonTag, ok := structField.Tags["bson"]
if !ok {
return "", fmt.Errorf("struct field %s does not have bson tag", predicateSpec.Field)
}
predicates = append(predicates, predicate{Field: bsonTag[0], Comparator: predicateSpec.Comparator})
querySpec, err := g.mongoQuerySpec(operation.Query)
if err != nil {
return "", err
}
tmplData := mongoFindTemplateData{
EntityType: g.StructModel.Name,
QuerySpec: querySpec{
Operator: operation.Query.Operator,
Predicates: predicates,
},
QuerySpec: querySpec,
}
if operation.Mode == spec.QueryModeOne {
@ -134,6 +122,64 @@ func (g RepositoryGenerator) generateFindImplementation(operation spec.FindOpera
return buffer.String(), nil
}
func (g RepositoryGenerator) generateDeleteImplementation(operation spec.DeleteOperation) (string, error) {
buffer := new(bytes.Buffer)
querySpec, err := g.mongoQuerySpec(operation.Query)
if err != nil {
return "", err
}
tmplData := mongoDeleteTemplateData{
QuerySpec: querySpec,
}
if operation.Mode == spec.QueryModeOne {
tmpl, err := template.New("mongo_repository_deleteone").Parse(deleteOneTemplate)
if err != nil {
return "", err
}
if err := tmpl.Execute(buffer, tmplData); err != nil {
return "", err
}
} else {
tmpl, err := template.New("mongo_repository_deletemany").Parse(deleteManyTemplate)
if err != nil {
return "", err
}
if err := tmpl.Execute(buffer, tmplData); err != nil {
return "", err
}
}
return buffer.String(), nil
}
func (g RepositoryGenerator) mongoQuerySpec(query spec.QuerySpec) (querySpec, error) {
var predicates []predicate
for _, predicateSpec := range query.Predicates {
structField, ok := g.StructModel.Fields.ByName(predicateSpec.Field)
if !ok {
return querySpec{}, fmt.Errorf("struct field %s not found", predicateSpec.Field)
}
bsonTag, ok := structField.Tags["bson"]
if !ok {
return querySpec{}, BsonTagNotFoundError
}
predicates = append(predicates, predicate{Field: bsonTag[0], Comparator: predicateSpec.Comparator})
}
return querySpec{
Operator: query.Operator,
Predicates: predicates,
}, nil
}
func (g RepositoryGenerator) structName() string {
return g.InterfaceName + "Mongo"
}

View file

@ -10,6 +10,36 @@ import (
"github.com/sunboyy/repogen/internal/testutils"
)
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"}},
},
{
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: "AccessToken",
Type: code.SimpleType("string"),
},
},
}
const expectedConstructorResult = `
import (
"context"
@ -31,31 +61,6 @@ type UserRepositoryMongo struct {
`
func TestGenerateConstructor(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"}},
},
{
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"}},
},
},
}
generator := mongo.NewGenerator(userModel, "UserRepository")
buffer := new(bytes.Buffer)
@ -75,7 +80,7 @@ type GenerateMethodTestCase struct {
ExpectedCode string
}
func TestGenerateMethod(t *testing.T) {
func TestGenerateMethod_Find(t *testing.T) {
testTable := []GenerateMethodTestCase{
{
Name: "simple find one method",
@ -489,33 +494,9 @@ func (r *UserRepositoryMongo) FindByGenderIn(ctx context.Context, arg0 []Gender)
`,
},
}
for _, testCase := range testTable {
t.Run(testCase.Name, func(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"}},
},
{
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"}},
},
},
}
generator := mongo.NewGenerator(userModel, "UserRepository")
buffer := new(bytes.Buffer)
@ -530,3 +511,458 @@ func (r *UserRepositoryMongo) FindByGenderIn(ctx context.Context, arg0 []Gender)
})
}
}
func TestGenerateMethod_Delete(t *testing.T) {
testTable := []GenerateMethodTestCase{
{
Name: "simple delete one method",
MethodSpec: spec.MethodSpec{
Name: "DeleteByID",
Params: []code.Param{
{Name: "ctx", Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Name: "id", Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
},
Returns: []code.Type{code.SimpleType("bool"), code.SimpleType("error")},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeOne,
Query: spec.QuerySpec{
Predicates: []spec.Predicate{
{Comparator: spec.ComparatorEqual, Field: "ID"},
},
},
},
},
ExpectedCode: `
func (r *UserRepositoryMongo) DeleteByID(ctx context.Context, arg0 primitive.ObjectID) (bool, error) {
result, err := r.collection.DeleteOne(ctx, bson.M{
"_id": arg0,
})
if err != nil {
return false, err
}
return result.DeletedCount > 0, nil
}
`,
},
{
Name: "simple delete many method",
MethodSpec: spec.MethodSpec{
Name: "DeleteByGender",
Params: []code.Param{
{Name: "ctx", Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Name: "gender", Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{
Predicates: []spec.Predicate{
{Comparator: spec.ComparatorEqual, Field: "Gender"},
},
},
},
},
ExpectedCode: `
func (r *UserRepositoryMongo) DeleteByGender(ctx context.Context, arg0 Gender) (int, error) {
result, err := r.collection.DeleteMany(ctx, bson.M{
"gender": arg0,
})
if err != nil {
return 0, err
}
return int(result.DeletedCount), nil
}
`,
},
{
Name: "delete with And operator",
MethodSpec: spec.MethodSpec{
Name: "DeleteByGenderAndAge",
Params: []code.Param{
{Name: "ctx", Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Name: "gender", Type: code.SimpleType("Gender")},
{Name: "age", Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{
Operator: spec.OperatorAnd,
Predicates: []spec.Predicate{
{Comparator: spec.ComparatorEqual, Field: "Gender"},
{Comparator: spec.ComparatorEqual, Field: "Age"},
},
},
},
},
ExpectedCode: `
func (r *UserRepositoryMongo) DeleteByGenderAndAge(ctx context.Context, arg0 Gender, arg1 int) (int, error) {
result, err := r.collection.DeleteMany(ctx, bson.M{
"gender": arg0,
"age": arg1,
})
if err != nil {
return 0, err
}
return int(result.DeletedCount), nil
}
`,
},
{
Name: "delete with Or operator",
MethodSpec: spec.MethodSpec{
Name: "DeleteByGenderOrAge",
Params: []code.Param{
{Name: "ctx", Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Name: "gender", Type: code.SimpleType("Gender")},
{Name: "age", Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{
Operator: spec.OperatorOr,
Predicates: []spec.Predicate{
{Comparator: spec.ComparatorEqual, Field: "Gender"},
{Comparator: spec.ComparatorEqual, Field: "Age"},
},
},
},
},
ExpectedCode: `
func (r *UserRepositoryMongo) DeleteByGenderOrAge(ctx context.Context, arg0 Gender, arg1 int) (int, error) {
result, err := r.collection.DeleteMany(ctx, bson.M{
"$or": []bson.M{
{"gender": arg0},
{"age": arg1},
},
})
if err != nil {
return 0, err
}
return int(result.DeletedCount), nil
}
`,
},
{
Name: "delete with Not comparator",
MethodSpec: spec.MethodSpec{
Name: "DeleteByGenderNot",
Params: []code.Param{
{Name: "ctx", Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Name: "gender", Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{
Predicates: []spec.Predicate{
{Comparator: spec.ComparatorNot, Field: "Gender"},
},
},
},
},
ExpectedCode: `
func (r *UserRepositoryMongo) DeleteByGenderNot(ctx context.Context, arg0 Gender) (int, error) {
result, err := r.collection.DeleteMany(ctx, bson.M{
"gender": bson.M{"$ne": arg0},
})
if err != nil {
return 0, err
}
return int(result.DeletedCount), nil
}
`,
},
{
Name: "delete with LessThan comparator",
MethodSpec: spec.MethodSpec{
Name: "DeleteByAgeLessThan",
Params: []code.Param{
{Name: "ctx", Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Name: "age", Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{
Predicates: []spec.Predicate{
{Comparator: spec.ComparatorLessThan, Field: "Age"},
},
},
},
},
ExpectedCode: `
func (r *UserRepositoryMongo) DeleteByAgeLessThan(ctx context.Context, arg0 int) (int, error) {
result, err := r.collection.DeleteMany(ctx, bson.M{
"age": bson.M{"$lt": arg0},
})
if err != nil {
return 0, err
}
return int(result.DeletedCount), nil
}
`,
},
{
Name: "delete with LessThanEqual comparator",
MethodSpec: spec.MethodSpec{
Name: "DeleteByAgeLessThanEqual",
Params: []code.Param{
{Name: "ctx", Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Name: "age", Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{
Predicates: []spec.Predicate{
{Comparator: spec.ComparatorLessThanEqual, Field: "Age"},
},
},
},
},
ExpectedCode: `
func (r *UserRepositoryMongo) DeleteByAgeLessThanEqual(ctx context.Context, arg0 int) (int, error) {
result, err := r.collection.DeleteMany(ctx, bson.M{
"age": bson.M{"$lte": arg0},
})
if err != nil {
return 0, err
}
return int(result.DeletedCount), nil
}
`,
},
{
Name: "delete with GreaterThan comparator",
MethodSpec: spec.MethodSpec{
Name: "DeleteByAgeGreaterThan",
Params: []code.Param{
{Name: "ctx", Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Name: "age", Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{
Predicates: []spec.Predicate{
{Comparator: spec.ComparatorGreaterThan, Field: "Age"},
},
},
},
},
ExpectedCode: `
func (r *UserRepositoryMongo) DeleteByAgeGreaterThan(ctx context.Context, arg0 int) (int, error) {
result, err := r.collection.DeleteMany(ctx, bson.M{
"age": bson.M{"$gt": arg0},
})
if err != nil {
return 0, err
}
return int(result.DeletedCount), nil
}
`,
},
{
Name: "delete with GreaterThanEqual comparator",
MethodSpec: spec.MethodSpec{
Name: "DeleteByAgeGreaterThanEqual",
Params: []code.Param{
{Name: "ctx", Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Name: "age", Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{
Predicates: []spec.Predicate{
{Comparator: spec.ComparatorGreaterThanEqual, Field: "Age"},
},
},
},
},
ExpectedCode: `
func (r *UserRepositoryMongo) DeleteByAgeGreaterThanEqual(ctx context.Context, arg0 int) (int, error) {
result, err := r.collection.DeleteMany(ctx, bson.M{
"age": bson.M{"$gte": arg0},
})
if err != nil {
return 0, err
}
return int(result.DeletedCount), nil
}
`,
},
{
Name: "delete with Between comparator",
MethodSpec: spec.MethodSpec{
Name: "DeleteByAgeBetween",
Params: []code.Param{
{Name: "ctx", Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Name: "fromAge", Type: code.SimpleType("int")},
{Name: "toAge", Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{
Predicates: []spec.Predicate{
{Comparator: spec.ComparatorBetween, Field: "Age"},
},
},
},
},
ExpectedCode: `
func (r *UserRepositoryMongo) DeleteByAgeBetween(ctx context.Context, arg0 int, arg1 int) (int, error) {
result, err := r.collection.DeleteMany(ctx, bson.M{
"age": bson.M{"$gte": arg0, "$lte": arg1},
})
if err != nil {
return 0, err
}
return int(result.DeletedCount), nil
}
`,
},
{
Name: "delete with In comparator",
MethodSpec: spec.MethodSpec{
Name: "DeleteByGenderIn",
Params: []code.Param{
{Name: "ctx", Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Name: "gender", Type: code.ArrayType{ContainedType: code.SimpleType("Gender")}},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{
Predicates: []spec.Predicate{
{Comparator: spec.ComparatorIn, Field: "Gender"},
},
},
},
},
ExpectedCode: `
func (r *UserRepositoryMongo) DeleteByGenderIn(ctx context.Context, arg0 []Gender) (int, error) {
result, err := r.collection.DeleteMany(ctx, bson.M{
"gender": bson.M{"$in": arg0},
})
if err != nil {
return 0, err
}
return int(result.DeletedCount), nil
}
`,
},
}
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
generator := mongo.NewGenerator(userModel, "UserRepository")
buffer := new(bytes.Buffer)
err := generator.GenerateMethod(testCase.MethodSpec, buffer)
if err != nil {
t.Error(err)
}
if err := testutils.ExpectMultiLineString(testCase.ExpectedCode, buffer.String()); err != nil {
t.Error(err)
}
})
}
}
type GenerateMethodInvalidTestCase struct {
Name string
Method spec.MethodSpec
ExpectedError error
}
func TestGenerateMethod_Invalid(t *testing.T) {
testTable := []GenerateMethodInvalidTestCase{
{
Name: "operation not supported",
Method: spec.MethodSpec{
Name: "SearchByID",
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"),
},
Operation: "search",
},
ExpectedError: mongo.OperationNotSupportedError,
},
{
Name: "bson tag not found",
Method: spec.MethodSpec{
Name: "FindByAccessToken",
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.QueryModeOne,
Query: spec.QuerySpec{
Predicates: []spec.Predicate{
{Field: "AccessToken", Comparator: spec.ComparatorEqual},
},
},
},
},
ExpectedError: mongo.BsonTagNotFoundError,
},
}
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
generator := mongo.NewGenerator(userModel, "UserRepository")
buffer := new(bytes.Buffer)
err := generator.GenerateMethod(testCase.Method, buffer)
if err != testCase.ExpectedError {
t.Errorf("\nExpected = %v\nReceived = %v", testCase.ExpectedError, err)
}
})
}
}

View file

@ -70,19 +70,19 @@ func (data mongoMethodTemplateData) Returns() string {
return fmt.Sprintf(" (%s)", strings.Join(returns, ", "))
}
const findOneTemplate = ` var entity {{.EntityType}}
if err := r.collection.FindOne(ctx, bson.M{
{{range $index, $field := .QuerySpec.Predicates}} {{$field.Code $index}},
{{end}} }).Decode(&entity); err != nil {
return nil, err
}
return &entity, nil`
type mongoFindTemplateData struct {
EntityType string
QuerySpec querySpec
}
const findOneTemplate = ` var entity {{.EntityType}}
if err := r.collection.FindOne(ctx, bson.M{
{{.QuerySpec.Code}}
}).Decode(&entity); err != nil {
return nil, err
}
return &entity, nil`
const findManyTemplate = ` cursor, err := r.collection.Find(ctx, bson.M{
{{.QuerySpec.Code}}
})
@ -94,3 +94,23 @@ const findManyTemplate = ` cursor, err := r.collection.Find(ctx, bson.M{
return nil, err
}
return entities, nil`
type mongoDeleteTemplateData struct {
QuerySpec querySpec
}
const deleteOneTemplate = ` result, err := r.collection.DeleteOne(ctx, bson.M{
{{.QuerySpec.Code}}
})
if err != nil {
return false, err
}
return result.DeletedCount > 0, nil`
const deleteManyTemplate = ` result, err := r.collection.DeleteMany(ctx, bson.M{
{{.QuerySpec.Code}}
})
if err != nil {
return 0, err
}
return int(result.DeletedCount), nil`

View file

@ -33,6 +33,12 @@ type FindOperation struct {
Query QuerySpec
}
// DeleteOperation is a method specification for delete operations
type DeleteOperation struct {
Mode QueryMode
Query QuerySpec
}
// QuerySpec is a set of conditions of querying the database
type QuerySpec struct {
Operator Operator

View file

@ -25,6 +25,8 @@ func (p interfaceMethodParser) Parse() (MethodSpec, error) {
switch methodNameTokens[0] {
case "Find":
return p.parseFindMethod(methodNameTokens[1:])
case "Delete":
return p.parseDeleteMethod(methodNameTokens[1:])
}
return MethodSpec{}, UnknownOperationError
}
@ -92,6 +94,58 @@ func (p interfaceMethodParser) extractFindReturns(returns []code.Type) (QueryMod
return "", UnsupportedReturnError
}
func (p interfaceMethodParser) parseDeleteMethod(tokens []string) (MethodSpec, error) {
if len(tokens) == 0 {
return MethodSpec{}, UnsupportedNameError
}
mode, err := p.extractDeleteReturns(p.Method.Returns)
if err != nil {
return MethodSpec{}, err
}
querySpec, err := p.parseQuery(tokens)
if err != nil {
return MethodSpec{}, err
}
if err := p.validateMethodSignature(querySpec); err != nil {
return MethodSpec{}, err
}
return MethodSpec{
Name: p.Method.Name,
Params: p.Method.Params,
Returns: p.Method.Returns,
Operation: DeleteOperation{
Mode: mode,
Query: querySpec,
},
}, nil
}
func (p interfaceMethodParser) extractDeleteReturns(returns []code.Type) (QueryMode, error) {
if len(returns) != 2 {
return "", UnsupportedReturnError
}
if returns[1] != code.SimpleType("error") {
return "", UnsupportedReturnError
}
simpleType, ok := returns[0].(code.SimpleType)
if ok {
if simpleType == code.SimpleType("bool") {
return QueryModeOne, nil
}
if simpleType == code.SimpleType("int") {
return QueryModeMany, nil
}
}
return "", UnsupportedReturnError
}
func (p interfaceMethodParser) parseQuery(tokens []string) (QuerySpec, error) {
if len(tokens) == 0 {
return QuerySpec{}, InvalidQueryError

View file

@ -40,7 +40,7 @@ type ParseInterfaceMethodTestCase struct {
ExpectedOutput spec.MethodSpec
}
func TestParseInterfaceMethod(t *testing.T) {
func TestParseInterfaceMethod_Find(t *testing.T) {
testTable := []ParseInterfaceMethodTestCase{
{
Name: "FindOneByArg method",
@ -470,21 +470,454 @@ func TestParseInterfaceMethod(t *testing.T) {
}
}
func TestParseInterfaceMethod_Delete(t *testing.T) {
testTable := []ParseInterfaceMethodTestCase{
{
Name: "DeleteOneByArg method",
Method: code.Method{
Name: "DeleteOneByID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
},
Returns: []code.Type{
code.SimpleType("bool"),
code.SimpleType("error"),
},
},
ExpectedOutput: spec.MethodSpec{
Name: "DeleteOneByID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
},
Returns: []code.Type{
code.SimpleType("bool"),
code.SimpleType("error"),
},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeOne,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "ID", Comparator: spec.ComparatorEqual},
}},
},
},
},
{
Name: "DeleteOneByMultiWordArg method",
Method: code.Method{
Name: "DeleteOneByPhoneNumber",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
},
Returns: []code.Type{
code.SimpleType("bool"),
code.SimpleType("error"),
},
},
ExpectedOutput: spec.MethodSpec{
Name: "DeleteOneByPhoneNumber",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
},
Returns: []code.Type{
code.SimpleType("bool"),
code.SimpleType("error"),
},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeOne,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "PhoneNumber", Comparator: spec.ComparatorEqual},
}},
},
},
},
{
Name: "DeleteByArg method",
Method: code.Method{
Name: "DeleteByCity",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
},
ExpectedOutput: spec.MethodSpec{
Name: "DeleteByCity",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorEqual},
}},
},
},
},
{
Name: "DeleteAll method",
Method: code.Method{
Name: "DeleteAll",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
},
ExpectedOutput: spec.MethodSpec{
Name: "DeleteAll",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
},
},
},
{
Name: "DeleteByArgAndArg method",
Method: code.Method{
Name: "DeleteByCityAndGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
},
ExpectedOutput: spec.MethodSpec{
Name: "DeleteByCityAndGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{
Operator: spec.OperatorAnd,
Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorEqual},
{Field: "Gender", Comparator: spec.ComparatorEqual},
},
},
},
},
},
{
Name: "DeleteByArgOrArg method",
Method: code.Method{
Name: "DeleteByCityOrGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
},
ExpectedOutput: spec.MethodSpec{
Name: "DeleteByCityOrGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{
Operator: spec.OperatorOr,
Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorEqual},
{Field: "Gender", Comparator: spec.ComparatorEqual},
},
},
},
},
},
{
Name: "DeleteByArgNot method",
Method: code.Method{
Name: "DeleteByCityNot",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
},
ExpectedOutput: spec.MethodSpec{
Name: "DeleteByCityNot",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorNot},
}},
},
},
},
{
Name: "DeleteByArgLessThan method",
Method: code.Method{
Name: "DeleteByAgeLessThan",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
},
ExpectedOutput: spec.MethodSpec{
Name: "DeleteByAgeLessThan",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Comparator: spec.ComparatorLessThan},
}},
},
},
},
{
Name: "DeleteByArgLessThanEqual method",
Method: code.Method{
Name: "DeleteByAgeLessThanEqual",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
},
ExpectedOutput: spec.MethodSpec{
Name: "DeleteByAgeLessThanEqual",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Comparator: spec.ComparatorLessThanEqual},
}},
},
},
},
{
Name: "DeleteByArgGreaterThan method",
Method: code.Method{
Name: "DeleteByAgeGreaterThan",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
},
ExpectedOutput: spec.MethodSpec{
Name: "DeleteByAgeGreaterThan",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Comparator: spec.ComparatorGreaterThan},
}},
},
},
},
{
Name: "DeleteByArgGreaterThanEqual method",
Method: code.Method{
Name: "DeleteByAgeGreaterThanEqual",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
},
ExpectedOutput: spec.MethodSpec{
Name: "DeleteByAgeGreaterThanEqual",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Comparator: spec.ComparatorGreaterThanEqual},
}},
},
},
},
{
Name: "DeleteByArgBetween method",
Method: code.Method{
Name: "DeleteByAgeBetween",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("int")},
{Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
},
ExpectedOutput: spec.MethodSpec{
Name: "DeleteByAgeBetween",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("int")},
{Type: code.SimpleType("int")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Comparator: spec.ComparatorBetween},
}},
},
},
},
{
Name: "DeleteByArgIn method",
Method: code.Method{
Name: "DeleteByCityIn",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.ArrayType{ContainedType: code.SimpleType("string")}},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
},
ExpectedOutput: spec.MethodSpec{
Name: "DeleteByCityIn",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.ArrayType{ContainedType: code.SimpleType("string")}},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
Operation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorIn},
}},
},
},
},
}
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
actualSpec, err := spec.ParseInterfaceMethod(structModel, testCase.Method)
if err != nil {
t.Errorf("Error = %s", err)
}
if !reflect.DeepEqual(actualSpec, testCase.ExpectedOutput) {
t.Errorf("Expected = %v\nReceived = %v", testCase.ExpectedOutput, actualSpec)
}
})
}
}
type ParseInterfaceMethodInvalidTestCase struct {
Name string
Method code.Method
ExpectedError error
}
func TestParseInterfaceMethodInvalid(t *testing.T) {
func TestParseInterfaceMethod_Invalid(t *testing.T) {
_, err := spec.ParseInterfaceMethod(structModel, code.Method{
Name: "SearchByID",
})
if err != spec.UnknownOperationError {
t.Errorf("\nExpected = %v\nReceived = %v", spec.UnknownOperationError, err)
}
}
func TestParseInterfaceMethod_Find_Invalid(t *testing.T) {
testTable := []ParseInterfaceMethodInvalidTestCase{
{
Name: "unknown operation",
Method: code.Method{
Name: "SearchByID",
},
ExpectedError: spec.UnknownOperationError,
},
{
Name: "unsupported find method name",
Method: code.Method{
@ -657,3 +1090,178 @@ func TestParseInterfaceMethodInvalid(t *testing.T) {
})
}
}
func TestParseInterfaceMethod_Delete_Invalid(t *testing.T) {
testTable := []ParseInterfaceMethodInvalidTestCase{
{
Name: "unsupported delete method name",
Method: code.Method{
Name: "Delete",
},
ExpectedError: spec.UnsupportedNameError,
},
{
Name: "invalid number of returns",
Method: code.Method{
Name: "DeleteOneByID",
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: "DeleteOneByID",
Returns: []code.Type{
code.SimpleType("float64"),
code.SimpleType("error"),
},
},
ExpectedError: spec.UnsupportedReturnError,
},
{
Name: "error return not provided",
Method: code.Method{
Name: "DeleteOneByID",
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("bool"),
},
},
ExpectedError: spec.UnsupportedReturnError,
},
{
Name: "misplaced operator token (leftmost)",
Method: code.Method{
Name: "DeleteByAndGender",
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
},
ExpectedError: spec.InvalidQueryError,
},
{
Name: "misplaced operator token (rightmost)",
Method: code.Method{
Name: "DeleteByGenderAnd",
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
},
ExpectedError: spec.InvalidQueryError,
},
{
Name: "misplaced operator token (double operator)",
Method: code.Method{
Name: "DeleteByGenderAndAndCity",
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
},
ExpectedError: spec.InvalidQueryError,
},
{
Name: "ambiguous query",
Method: code.Method{
Name: "DeleteByGenderAndCityOrAge",
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
},
ExpectedError: spec.InvalidQueryError,
},
{
Name: "no context parameter",
Method: code.Method{
Name: "DeleteByGender",
Params: []code.Param{
{Type: code.SimpleType("Gender")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
},
ExpectedError: spec.ContextParamRequiredError,
},
{
Name: "mismatched number of parameters",
Method: code.Method{
Name: "DeleteByCountry",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
{Type: code.SimpleType("string")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
},
ExpectedError: spec.InvalidParamError,
},
{
Name: "struct field not found",
Method: code.Method{
Name: "DeleteByCountry",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
},
ExpectedError: spec.StructFieldNotFoundError,
},
{
Name: "mismatched method parameter type",
Method: code.Method{
Name: "DeleteByGender",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
},
Returns: []code.Type{
code.SimpleType("int"),
code.SimpleType("error"),
},
},
ExpectedError: spec.InvalidParamError,
},
{
Name: "mismatched method parameter type for special case",
Method: code.Method{
Name: "DeleteByCityIn",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
},
Returns: []code.Type{
code.SimpleType("int"),
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)
}
})
}
}