2021-01-17 03:29:50 +00:00
|
|
|
package mongo
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2021-01-23 13:03:16 +00:00
|
|
|
"io"
|
2021-03-03 14:01:32 +00:00
|
|
|
"strings"
|
2021-01-17 03:29:50 +00:00
|
|
|
"text/template"
|
|
|
|
|
|
|
|
"github.com/sunboyy/repogen/internal/code"
|
|
|
|
"github.com/sunboyy/repogen/internal/spec"
|
|
|
|
)
|
|
|
|
|
2021-01-23 13:03:16 +00:00
|
|
|
// NewGenerator creates a new instance of MongoDB repository generator
|
|
|
|
func NewGenerator(structModel code.Struct, interfaceName string) RepositoryGenerator {
|
|
|
|
return RepositoryGenerator{
|
2021-01-21 11:56:30 +00:00
|
|
|
StructModel: structModel,
|
2021-01-23 13:03:16 +00:00
|
|
|
InterfaceName: interfaceName,
|
2021-01-17 03:29:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-23 13:03:16 +00:00
|
|
|
// RepositoryGenerator provides repository constructor and method generation from provided specification
|
|
|
|
type RepositoryGenerator struct {
|
2021-01-21 11:56:30 +00:00
|
|
|
StructModel code.Struct
|
|
|
|
InterfaceName string
|
2021-01-17 03:29:50 +00:00
|
|
|
}
|
|
|
|
|
2021-01-23 13:03:16 +00:00
|
|
|
// GenerateConstructor generates mongo repository struct implementation and constructor for the struct
|
|
|
|
func (g RepositoryGenerator) GenerateConstructor(buffer io.Writer) error {
|
|
|
|
tmpl, err := template.New("mongo_repository_base").Parse(constructorTemplate)
|
2021-01-17 03:29:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-01-23 13:03:16 +00:00
|
|
|
tmplData := mongoConstructorTemplateData{
|
|
|
|
InterfaceName: g.InterfaceName,
|
|
|
|
ImplStructName: g.structName(),
|
2021-01-17 03:29:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := tmpl.Execute(buffer, tmplData); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-01-23 13:03:16 +00:00
|
|
|
// GenerateMethod generates implementation of from provided method specification
|
|
|
|
func (g RepositoryGenerator) GenerateMethod(methodSpec spec.MethodSpec, buffer io.Writer) error {
|
2021-01-17 03:29:50 +00:00
|
|
|
tmpl, err := template.New("mongo_repository_method").Parse(methodTemplate)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-01-23 13:03:16 +00:00
|
|
|
implementation, err := g.generateMethodImplementation(methodSpec)
|
2021-01-17 03:29:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var paramTypes []code.Type
|
2021-01-27 12:15:25 +00:00
|
|
|
for _, param := range methodSpec.Params {
|
2021-01-17 03:29:50 +00:00
|
|
|
paramTypes = append(paramTypes, param.Type)
|
|
|
|
}
|
|
|
|
|
|
|
|
tmplData := mongoMethodTemplateData{
|
|
|
|
StructName: g.structName(),
|
2021-01-23 13:03:16 +00:00
|
|
|
MethodName: methodSpec.Name,
|
2021-01-17 03:29:50 +00:00
|
|
|
ParamTypes: paramTypes,
|
2021-01-23 13:03:16 +00:00
|
|
|
ReturnTypes: methodSpec.Returns,
|
2021-01-17 03:29:50 +00:00
|
|
|
Implementation: implementation,
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := tmpl.Execute(buffer, tmplData); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-01-23 13:03:16 +00:00
|
|
|
func (g RepositoryGenerator) generateMethodImplementation(methodSpec spec.MethodSpec) (string, error) {
|
2021-01-17 03:29:50 +00:00
|
|
|
switch operation := methodSpec.Operation.(type) {
|
2021-02-01 14:39:20 +00:00
|
|
|
case spec.InsertOperation:
|
|
|
|
return g.generateInsertImplementation(operation)
|
2021-01-17 03:29:50 +00:00
|
|
|
case spec.FindOperation:
|
|
|
|
return g.generateFindImplementation(operation)
|
2021-01-27 12:15:25 +00:00
|
|
|
case spec.UpdateOperation:
|
|
|
|
return g.generateUpdateImplementation(operation)
|
2021-01-26 13:23:52 +00:00
|
|
|
case spec.DeleteOperation:
|
|
|
|
return g.generateDeleteImplementation(operation)
|
2021-02-06 11:05:47 +00:00
|
|
|
case spec.CountOperation:
|
|
|
|
return g.generateCountImplementation(operation)
|
2021-02-14 04:48:09 +00:00
|
|
|
default:
|
|
|
|
return "", NewOperationNotSupportedError(operation.Name())
|
2021-01-17 03:29:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-01 14:39:20 +00:00
|
|
|
func (g RepositoryGenerator) generateInsertImplementation(operation spec.InsertOperation) (string, error) {
|
|
|
|
if operation.Mode == spec.QueryModeOne {
|
|
|
|
return insertOneTemplate, nil
|
|
|
|
}
|
|
|
|
return insertManyTemplate, nil
|
|
|
|
}
|
|
|
|
|
2021-01-23 13:03:16 +00:00
|
|
|
func (g RepositoryGenerator) generateFindImplementation(operation spec.FindOperation) (string, error) {
|
2021-01-26 13:23:52 +00:00
|
|
|
querySpec, err := g.mongoQuerySpec(operation.Query)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
2021-01-17 03:29:50 +00:00
|
|
|
}
|
|
|
|
|
2021-02-26 03:27:00 +00:00
|
|
|
sorts, err := g.mongoSorts(operation.Sorts)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2021-01-17 03:29:50 +00:00
|
|
|
tmplData := mongoFindTemplateData{
|
2021-01-19 12:26:26 +00:00
|
|
|
EntityType: g.StructModel.Name,
|
2021-01-26 13:23:52 +00:00
|
|
|
QuerySpec: querySpec,
|
2021-02-26 03:27:00 +00:00
|
|
|
Sorts: sorts,
|
2021-01-17 03:29:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if operation.Mode == spec.QueryModeOne {
|
2021-02-06 11:05:47 +00:00
|
|
|
return generateFromTemplate("mongo_repository_findone", findOneTemplate, tmplData)
|
2021-01-17 03:29:50 +00:00
|
|
|
}
|
2021-02-06 11:05:47 +00:00
|
|
|
return generateFromTemplate("mongo_repository_findmany", findManyTemplate, tmplData)
|
2021-01-17 03:29:50 +00:00
|
|
|
}
|
|
|
|
|
2021-05-03 07:12:58 +00:00
|
|
|
func (g RepositoryGenerator) mongoSorts(sortSpec []spec.Sort) ([]findSort, error) {
|
|
|
|
var sorts []findSort
|
2021-02-26 03:27:00 +00:00
|
|
|
|
|
|
|
for _, s := range sortSpec {
|
2021-03-03 14:01:32 +00:00
|
|
|
bsonFieldReference, err := g.bsonFieldReference(s.FieldReference)
|
2021-02-26 03:27:00 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-05-03 07:12:58 +00:00
|
|
|
sorts = append(sorts, findSort{
|
2021-03-03 14:01:32 +00:00
|
|
|
BsonTag: bsonFieldReference,
|
2021-02-26 03:27:00 +00:00
|
|
|
Ordering: s.Ordering,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return sorts, nil
|
|
|
|
}
|
|
|
|
|
2021-01-27 12:15:25 +00:00
|
|
|
func (g RepositoryGenerator) generateUpdateImplementation(operation spec.UpdateOperation) (string, error) {
|
2021-02-24 12:02:57 +00:00
|
|
|
update, err := g.getMongoUpdate(operation.Update)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
2021-01-27 12:15:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
querySpec, err := g.mongoQuerySpec(operation.Query)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
tmplData := mongoUpdateTemplateData{
|
2021-02-24 12:02:57 +00:00
|
|
|
Update: update,
|
|
|
|
QuerySpec: querySpec,
|
2021-01-27 12:15:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if operation.Mode == spec.QueryModeOne {
|
2021-02-06 11:05:47 +00:00
|
|
|
return generateFromTemplate("mongo_repository_updateone", updateOneTemplate, tmplData)
|
2021-01-27 12:15:25 +00:00
|
|
|
}
|
2021-02-06 11:05:47 +00:00
|
|
|
return generateFromTemplate("mongo_repository_updatemany", updateManyTemplate, tmplData)
|
2021-01-27 12:15:25 +00:00
|
|
|
}
|
|
|
|
|
2021-02-24 12:02:57 +00:00
|
|
|
func (g RepositoryGenerator) getMongoUpdate(updateSpec spec.Update) (update, error) {
|
|
|
|
switch updateSpec := updateSpec.(type) {
|
|
|
|
case spec.UpdateModel:
|
|
|
|
return updateModel{}, nil
|
|
|
|
case spec.UpdateFields:
|
2021-03-31 11:44:31 +00:00
|
|
|
update := make(updateFields)
|
2021-02-24 12:02:57 +00:00
|
|
|
for _, field := range updateSpec {
|
2021-03-03 14:01:32 +00:00
|
|
|
bsonFieldReference, err := g.bsonFieldReference(field.FieldReference)
|
2021-02-24 12:02:57 +00:00
|
|
|
if err != nil {
|
2021-03-03 14:01:32 +00:00
|
|
|
return querySpec{}, err
|
2021-02-24 12:02:57 +00:00
|
|
|
}
|
2021-03-31 11:44:31 +00:00
|
|
|
|
|
|
|
updateKey := getUpdateOperatorKey(field.Operator)
|
|
|
|
if updateKey == "" {
|
|
|
|
return querySpec{}, NewUpdateOperatorNotSupportedError(field.Operator)
|
|
|
|
}
|
|
|
|
updateField := updateField{
|
|
|
|
BsonTag: bsonFieldReference,
|
|
|
|
ParamIndex: field.ParamIndex,
|
|
|
|
}
|
|
|
|
update[updateKey] = append(update[updateKey], updateField)
|
2021-02-24 12:02:57 +00:00
|
|
|
}
|
|
|
|
return update, nil
|
|
|
|
default:
|
|
|
|
return nil, NewUpdateTypeNotSupportedError(updateSpec)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-31 11:44:31 +00:00
|
|
|
func getUpdateOperatorKey(operator spec.UpdateOperator) string {
|
|
|
|
switch operator {
|
|
|
|
case spec.UpdateOperatorSet:
|
|
|
|
return "$set"
|
|
|
|
case spec.UpdateOperatorPush:
|
|
|
|
return "$push"
|
2021-05-03 07:12:58 +00:00
|
|
|
case spec.UpdateOperatorInc:
|
|
|
|
return "$inc"
|
2021-03-31 11:44:31 +00:00
|
|
|
default:
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-26 13:23:52 +00:00
|
|
|
func (g RepositoryGenerator) generateDeleteImplementation(operation spec.DeleteOperation) (string, error) {
|
|
|
|
querySpec, err := g.mongoQuerySpec(operation.Query)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
tmplData := mongoDeleteTemplateData{
|
|
|
|
QuerySpec: querySpec,
|
|
|
|
}
|
|
|
|
|
|
|
|
if operation.Mode == spec.QueryModeOne {
|
2021-02-06 11:05:47 +00:00
|
|
|
return generateFromTemplate("mongo_repository_deleteone", deleteOneTemplate, tmplData)
|
|
|
|
}
|
|
|
|
return generateFromTemplate("mongo_repository_deletemany", deleteManyTemplate, tmplData)
|
|
|
|
}
|
2021-01-26 13:23:52 +00:00
|
|
|
|
2021-02-06 11:05:47 +00:00
|
|
|
func (g RepositoryGenerator) generateCountImplementation(operation spec.CountOperation) (string, error) {
|
|
|
|
querySpec, err := g.mongoQuerySpec(operation.Query)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2021-01-26 13:23:52 +00:00
|
|
|
|
2021-02-06 11:05:47 +00:00
|
|
|
tmplData := mongoCountTemplateData{
|
|
|
|
QuerySpec: querySpec,
|
2021-01-26 13:23:52 +00:00
|
|
|
}
|
|
|
|
|
2021-02-06 11:05:47 +00:00
|
|
|
return generateFromTemplate("mongo_repository_count", countTemplate, tmplData)
|
2021-01-26 13:23:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (g RepositoryGenerator) mongoQuerySpec(query spec.QuerySpec) (querySpec, error) {
|
|
|
|
var predicates []predicate
|
|
|
|
|
|
|
|
for _, predicateSpec := range query.Predicates {
|
2021-03-03 14:01:32 +00:00
|
|
|
bsonFieldReference, err := g.bsonFieldReference(predicateSpec.FieldReference)
|
2021-01-27 12:15:25 +00:00
|
|
|
if err != nil {
|
|
|
|
return querySpec{}, err
|
2021-01-26 13:23:52 +00:00
|
|
|
}
|
|
|
|
|
2021-01-27 12:15:25 +00:00
|
|
|
predicates = append(predicates, predicate{
|
2021-03-03 14:01:32 +00:00
|
|
|
Field: bsonFieldReference,
|
2021-01-27 12:15:25 +00:00
|
|
|
Comparator: predicateSpec.Comparator,
|
|
|
|
ParamIndex: predicateSpec.ParamIndex,
|
|
|
|
})
|
2021-01-26 13:23:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return querySpec{
|
|
|
|
Operator: query.Operator,
|
|
|
|
Predicates: predicates,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2021-03-03 14:01:32 +00:00
|
|
|
func (g RepositoryGenerator) bsonFieldReference(fieldReference spec.FieldReference) (string, error) {
|
|
|
|
var bsonTags []string
|
|
|
|
for _, field := range fieldReference {
|
|
|
|
tag, err := g.bsonTagFromField(field)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
bsonTags = append(bsonTags, tag)
|
2021-01-27 12:15:25 +00:00
|
|
|
}
|
2021-03-03 14:01:32 +00:00
|
|
|
return strings.Join(bsonTags, "."), nil
|
|
|
|
}
|
2021-01-27 12:15:25 +00:00
|
|
|
|
2021-03-03 14:01:32 +00:00
|
|
|
func (g RepositoryGenerator) bsonTagFromField(field code.StructField) (string, error) {
|
|
|
|
bsonTag, ok := field.Tags["bson"]
|
2021-01-27 12:15:25 +00:00
|
|
|
if !ok {
|
2021-03-03 14:01:32 +00:00
|
|
|
return "", NewBsonTagNotFoundError(field.Name)
|
2021-01-27 12:15:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return bsonTag[0], nil
|
|
|
|
}
|
|
|
|
|
2021-01-23 13:03:16 +00:00
|
|
|
func (g RepositoryGenerator) structName() string {
|
2021-01-21 11:56:30 +00:00
|
|
|
return g.InterfaceName + "Mongo"
|
2021-01-17 03:29:50 +00:00
|
|
|
}
|
2021-02-06 11:05:47 +00:00
|
|
|
|
|
|
|
func generateFromTemplate(name string, templateString string, tmplData interface{}) (string, error) {
|
|
|
|
tmpl, err := template.New(name).Parse(templateString)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer := new(bytes.Buffer)
|
|
|
|
if err := tmpl.Execute(buffer, tmplData); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return buffer.String(), nil
|
|
|
|
}
|