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

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`