Support referencing field of another struct

This commit is contained in:
sunboyy 2021-03-03 21:01:32 +07:00
parent 706706e91e
commit d4f3591912
14 changed files with 826 additions and 354 deletions

View file

@ -75,8 +75,8 @@ func (err unknownOperationError) Error() string {
}
// NewStructFieldNotFoundError creates structFieldNotFoundError
func NewStructFieldNotFoundError(fieldName string) error {
return structFieldNotFoundError{FieldName: fieldName}
func NewStructFieldNotFoundError(tokens []string) error {
return structFieldNotFoundError{FieldName: strings.Join(tokens, "")}
}
type structFieldNotFoundError struct {

View file

@ -22,13 +22,13 @@ func TestError(t *testing.T) {
},
{
Name: "StructFieldNotFoundError",
Error: spec.NewStructFieldNotFoundError("Country"),
ExpectedString: "struct field 'Country' not found",
Error: spec.NewStructFieldNotFoundError([]string{"Phone", "Number"}),
ExpectedString: "struct field 'PhoneNumber' not found",
},
{
Name: "InvalidQueryError",
Error: spec.NewInvalidQueryError([]string{"By", "And"}),
ExpectedString: "invalid query 'ByAnd'",
Error: spec.NewInvalidQueryError([]string{"And"}),
ExpectedString: "invalid query 'And'",
},
{
Name: "IncompatibleComparatorError",

64
internal/spec/field.go Normal file
View file

@ -0,0 +1,64 @@
package spec
import (
"strings"
"github.com/sunboyy/repogen/internal/code"
)
// FieldReference is a reference path to access to the field
type FieldReference []code.StructField
// ReferencedField returns the last struct field
func (r FieldReference) ReferencedField() code.StructField {
return r[len(r)-1]
}
type fieldResolver struct {
Structs code.Structs
}
func (r fieldResolver) ResolveStructField(structModel code.Struct, tokens []string) (FieldReference, bool) {
fieldName := strings.Join(tokens, "")
field, ok := structModel.Fields.ByName(fieldName)
if ok {
return FieldReference{field}, true
}
for i := len(tokens) - 1; i > 0; i-- {
fieldName := strings.Join(tokens[:i], "")
field, ok := structModel.Fields.ByName(fieldName)
if !ok {
continue
}
fieldSimpleType, ok := getSimpleType(field.Type)
if !ok {
continue
}
childStruct, ok := r.Structs.ByName(fieldSimpleType.Code())
if !ok {
continue
}
fields, ok := r.ResolveStructField(childStruct, tokens[i:])
if !ok {
continue
}
return append(FieldReference{field}, fields...), true
}
return nil, false
}
func getSimpleType(t code.Type) (code.SimpleType, bool) {
switch t := t.(type) {
case code.SimpleType:
return t, true
case code.PointerType:
return getSimpleType(t.ContainedType)
default:
return "", false
}
}

View file

@ -50,8 +50,8 @@ func (o FindOperation) Name() string {
// Sort is a detail of sorting find result
type Sort struct {
FieldName string
Ordering Ordering
FieldReference FieldReference
Ordering Ordering
}
// Ordering is a sort order
@ -110,8 +110,8 @@ func (o UpdateOperation) Name() string {
// UpdateField stores mapping between field name in the model and the parameter index
type UpdateField struct {
Name string
ParamIndex int
FieldReference FieldReference
ParamIndex int
}
// DeleteOperation is a method specification for delete operations

View file

@ -1,15 +1,16 @@
package spec
import (
"strings"
"github.com/fatih/camelcase"
"github.com/sunboyy/repogen/internal/code"
)
// ParseInterfaceMethod returns repository method spec from declared interface method
func ParseInterfaceMethod(structModel code.Struct, method code.Method) (MethodSpec, error) {
func ParseInterfaceMethod(structs code.Structs, structModel code.Struct, method code.Method) (MethodSpec, error) {
parser := interfaceMethodParser{
fieldResolver: fieldResolver{
Structs: structs,
},
StructModel: structModel,
Method: method,
}
@ -18,8 +19,9 @@ func ParseInterfaceMethod(structModel code.Struct, method code.Method) (MethodSp
}
type interfaceMethodParser struct {
StructModel code.Struct
Method code.Method
fieldResolver fieldResolver
StructModel code.Struct
Method code.Method
}
func (p interfaceMethodParser) Parse() (MethodSpec, error) {
@ -115,7 +117,7 @@ func (p interfaceMethodParser) parseFindOperation(tokens []string) (Operation, e
queryTokens, sortTokens := p.splitQueryAndSortTokens(tokens)
querySpec, err := parseQuery(queryTokens, 1)
querySpec, err := p.parseQuery(queryTokens, 1)
if err != nil {
return nil, err
}
@ -145,38 +147,43 @@ func (p interfaceMethodParser) parseSort(rawTokens []string) ([]Sort, error) {
return nil, nil
}
sortTokens := rawTokens[2:]
var sorts []Sort
var aggregatedToken sortToken
for _, token := range sortTokens {
if token != "And" {
aggregatedToken = append(aggregatedToken, token)
} else if len(aggregatedToken) == 0 {
return nil, NewInvalidSortError(rawTokens)
} else {
sorts = append(sorts, aggregatedToken.ToSort())
aggregatedToken = sortToken{}
}
}
if len(aggregatedToken) == 0 {
sortTokens, ok := splitByAnd(rawTokens[2:])
if !ok {
return nil, NewInvalidSortError(rawTokens)
}
sorts = append(sorts, aggregatedToken.ToSort())
var sorts []Sort
for _, token := range sortTokens {
sort, err := p.parseSortToken(token)
if err != nil {
return nil, err
}
sorts = append(sorts, sort)
}
return sorts, nil
}
type sortToken []string
func (t sortToken) ToSort() Sort {
func (p interfaceMethodParser) parseSortToken(t []string) (Sort, error) {
if len(t) > 1 && t[len(t)-1] == "Asc" {
return Sort{FieldName: strings.Join(t[:len(t)-1], ""), Ordering: OrderingAscending}
return p.createSort(t[:len(t)-1], OrderingAscending)
}
if len(t) > 1 && t[len(t)-1] == "Desc" {
return Sort{FieldName: strings.Join(t[:len(t)-1], ""), Ordering: OrderingDescending}
return p.createSort(t[:len(t)-1], OrderingDescending)
}
return Sort{FieldName: strings.Join(t, ""), Ordering: OrderingAscending}
return p.createSort(t, OrderingAscending)
}
func (p interfaceMethodParser) createSort(t []string, ordering Ordering) (Sort, error) {
fields, ok := p.fieldResolver.ResolveStructField(p.StructModel, t)
if !ok {
return Sort{}, NewStructFieldNotFoundError(t)
}
return Sort{
FieldReference: fields,
Ordering: ordering,
}, nil
}
func (p interfaceMethodParser) splitQueryAndSortTokens(tokens []string) ([]string, []string) {
@ -245,7 +252,7 @@ func (p interfaceMethodParser) parseUpdateOperation(tokens []string) (Operation,
return nil, err
}
querySpec, err := parseQuery(queryTokens, 1+update.NumberOfArguments())
querySpec, err := p.parseQuery(queryTokens, 1+update.NumberOfArguments())
if err != nil {
return nil, err
}
@ -270,37 +277,57 @@ func (p interfaceMethodParser) parseUpdate(tokens []string) (Update, error) {
return UpdateModel{}, nil
}
updateFieldTokens, ok := splitByAnd(tokens)
if !ok {
return nil, InvalidUpdateFieldsError
}
var updateFields UpdateFields
paramIndex := 1
var update UpdateFields
var aggregatedToken string
for _, updateFieldToken := range updateFieldTokens {
updateFieldReference, ok := p.fieldResolver.ResolveStructField(p.StructModel, updateFieldToken)
if !ok {
return nil, NewStructFieldNotFoundError(updateFieldToken)
}
updateFields = append(updateFields, UpdateField{
FieldReference: updateFieldReference,
ParamIndex: paramIndex,
})
paramIndex++
}
for _, field := range updateFields {
if len(p.Method.Params) <= field.ParamIndex ||
field.FieldReference.ReferencedField().Type != p.Method.Params[field.ParamIndex].Type {
return nil, InvalidUpdateFieldsError
}
}
return updateFields, nil
}
func splitByAnd(tokens []string) ([][]string, bool) {
var updateFieldTokens [][]string
var aggregatedToken []string
for _, token := range tokens {
if token != "And" {
aggregatedToken += token
aggregatedToken = append(aggregatedToken, token)
} else if len(aggregatedToken) == 0 {
return nil, InvalidUpdateFieldsError
return nil, false
} else {
update = append(update, UpdateField{Name: aggregatedToken, ParamIndex: paramIndex})
paramIndex++
aggregatedToken = ""
updateFieldTokens = append(updateFieldTokens, aggregatedToken)
aggregatedToken = nil
}
}
if len(aggregatedToken) == 0 {
return nil, InvalidUpdateFieldsError
return nil, false
}
update = append(update, UpdateField{Name: aggregatedToken, ParamIndex: paramIndex})
updateFieldTokens = append(updateFieldTokens, aggregatedToken)
for _, field := range update {
structField, ok := p.StructModel.Fields.ByName(field.Name)
if !ok {
return nil, NewStructFieldNotFoundError(field.Name)
}
if len(p.Method.Params) <= field.ParamIndex || structField.Type != p.Method.Params[field.ParamIndex].Type {
return nil, InvalidUpdateFieldsError
}
}
return update, nil
return updateFieldTokens, true
}
func (p interfaceMethodParser) splitUpdateAndQueryTokens(tokens []string) ([]string, []string) {
@ -325,7 +352,7 @@ func (p interfaceMethodParser) parseDeleteOperation(tokens []string) (Operation,
return nil, err
}
querySpec, err := parseQuery(tokens, 1)
querySpec, err := p.parseQuery(tokens, 1)
if err != nil {
return nil, err
}
@ -349,7 +376,7 @@ func (p interfaceMethodParser) parseCountOperation(tokens []string) (Operation,
return nil, err
}
querySpec, err := parseQuery(tokens, 1)
querySpec, err := p.parseQuery(tokens, 1)
if err != nil {
return nil, err
}
@ -420,19 +447,15 @@ func (p interfaceMethodParser) validateQueryFromParams(params []code.Param, quer
var currentParamIndex int
for _, predicate := range querySpec.Predicates {
structField, ok := p.StructModel.Fields.ByName(predicate.Field)
if !ok {
return NewStructFieldNotFoundError(predicate.Field)
}
if (predicate.Comparator == ComparatorTrue || predicate.Comparator == ComparatorFalse) &&
structField.Type != code.SimpleType("bool") {
return NewIncompatibleComparatorError(predicate.Comparator, structField)
predicate.FieldReference.ReferencedField().Type != code.SimpleType("bool") {
return NewIncompatibleComparatorError(predicate.Comparator,
predicate.FieldReference.ReferencedField())
}
for i := 0; i < predicate.Comparator.NumberOfArguments(); i++ {
if params[currentParamIndex].Type != predicate.Comparator.ArgumentTypeFromFieldType(
structField.Type) {
predicate.FieldReference.ReferencedField().Type) {
return InvalidParamError
}
currentParamIndex++
@ -441,3 +464,11 @@ func (p interfaceMethodParser) validateQueryFromParams(params []code.Param, quer
return nil
}
func (p interfaceMethodParser) parseQuery(queryTokens []string, paramIndex int) (QuerySpec, error) {
queryParser := queryParser{
fieldResolver: p.fieldResolver,
StructModel: p.StructModel,
}
return queryParser.parseQuery(queryTokens, paramIndex)
}

View file

@ -8,34 +8,87 @@ import (
"github.com/sunboyy/repogen/internal/spec"
)
var structModel = code.Struct{
Name: "UserModel",
Fields: code.StructFields{
{
Name: "ID",
Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"},
var (
idField = code.StructField{
Name: "ID",
Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"},
}
phoneNumberField = code.StructField{
Name: "PhoneNumber",
Type: code.SimpleType("string"),
}
genderField = code.StructField{
Name: "Gender",
Type: code.SimpleType("Gender"),
}
cityField = code.StructField{
Name: "City",
Type: code.SimpleType("string"),
}
ageField = code.StructField{
Name: "Age",
Type: code.SimpleType("int"),
}
nameField = code.StructField{
Name: "Name",
Type: code.SimpleType("NameModel"),
}
contactField = code.StructField{
Name: "Contact",
Type: code.SimpleType("ContactModel"),
}
referrerField = code.StructField{
Name: "Referrer",
Type: code.PointerType{ContainedType: code.SimpleType("UserModel")},
}
defaultPaymentField = code.StructField{
Name: "DefaultPayment",
Type: code.ExternalType{PackageAlias: "payment", Name: "Payment"},
}
enabledField = code.StructField{
Name: "Enabled",
Type: code.SimpleType("bool"),
}
firstNameField = code.StructField{
Name: "First",
Type: code.SimpleType("string"),
}
lastNameField = code.StructField{
Name: "Last",
Type: code.SimpleType("string"),
}
)
var (
nameStruct = code.Struct{
Name: "NameModel",
Fields: code.StructFields{
firstNameField,
lastNameField,
},
{
Name: "PhoneNumber",
Type: code.SimpleType("string"),
}
structModel = code.Struct{
Name: "UserModel",
Fields: code.StructFields{
idField,
phoneNumberField,
genderField,
cityField,
ageField,
nameField,
contactField,
referrerField,
defaultPaymentField,
enabledField,
},
{
Name: "Gender",
Type: code.SimpleType("Gender"),
},
{
Name: "City",
Type: code.SimpleType("string"),
},
{
Name: "Age",
Type: code.SimpleType("int"),
},
{
Name: "Enabled",
Type: code.SimpleType("bool"),
},
},
}
)
var structs = code.Structs{
nameStruct,
structModel,
}
type ParseInterfaceMethodTestCase struct {
@ -84,7 +137,7 @@ func TestParseInterfaceMethod_Insert(t *testing.T) {
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
actualSpec, err := spec.ParseInterfaceMethod(structModel, testCase.Method)
actualSpec, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
if err != nil {
t.Errorf("Error = %s", err)
@ -120,7 +173,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) {
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeOne,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "ID", Comparator: spec.ComparatorEqual, ParamIndex: 1},
{FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
}},
},
},
@ -140,7 +193,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) {
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeOne,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "PhoneNumber", Comparator: spec.ComparatorEqual, ParamIndex: 1},
{FieldReference: spec.FieldReference{phoneNumberField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
}},
},
},
@ -160,7 +213,47 @@ func TestParseInterfaceMethod_Find(t *testing.T) {
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorEqual, ParamIndex: 1},
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
}},
},
},
{
Name: "FindByDeepArg method",
Method: code.Method{
Name: "FindByNameFirst",
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"),
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{nameField, firstNameField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
}},
},
},
{
Name: "FindByDeepPointerArg method",
Method: code.Method{
Name: "FindByReferrerID",
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"),
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{referrerField, idField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
}},
},
},
@ -199,8 +292,8 @@ func TestParseInterfaceMethod_Find(t *testing.T) {
Query: spec.QuerySpec{
Operator: spec.OperatorAnd,
Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorEqual, ParamIndex: 1},
{Field: "Gender", Comparator: spec.ComparatorEqual, ParamIndex: 2},
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
{FieldReference: spec.FieldReference{genderField}, Comparator: spec.ComparatorEqual, ParamIndex: 2},
},
},
},
@ -224,8 +317,8 @@ func TestParseInterfaceMethod_Find(t *testing.T) {
Query: spec.QuerySpec{
Operator: spec.OperatorOr,
Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorEqual, ParamIndex: 1},
{Field: "Gender", Comparator: spec.ComparatorEqual, ParamIndex: 2},
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
{FieldReference: spec.FieldReference{genderField}, Comparator: spec.ComparatorEqual, ParamIndex: 2},
},
},
},
@ -246,7 +339,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) {
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorNot, ParamIndex: 1},
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorNot, ParamIndex: 1},
}},
},
},
@ -266,7 +359,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) {
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Comparator: spec.ComparatorLessThan, ParamIndex: 1},
{FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorLessThan, ParamIndex: 1},
}},
},
},
@ -286,7 +379,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) {
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Comparator: spec.ComparatorLessThanEqual, ParamIndex: 1},
{FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorLessThanEqual, ParamIndex: 1},
}},
},
},
@ -306,7 +399,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) {
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Comparator: spec.ComparatorGreaterThan, ParamIndex: 1},
{FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorGreaterThan, ParamIndex: 1},
}},
},
},
@ -326,7 +419,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) {
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Comparator: spec.ComparatorGreaterThanEqual, ParamIndex: 1},
{FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorGreaterThanEqual, ParamIndex: 1},
}},
},
},
@ -347,7 +440,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) {
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Comparator: spec.ComparatorBetween, ParamIndex: 1},
{FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorBetween, ParamIndex: 1},
}},
},
},
@ -367,7 +460,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) {
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorIn, ParamIndex: 1},
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorIn, ParamIndex: 1},
}},
},
},
@ -387,7 +480,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) {
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorNotIn, ParamIndex: 1},
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorNotIn, ParamIndex: 1},
}},
},
},
@ -406,7 +499,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) {
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Enabled", Comparator: spec.ComparatorTrue, ParamIndex: 1},
{FieldReference: spec.FieldReference{enabledField}, Comparator: spec.ComparatorTrue, ParamIndex: 1},
}},
},
},
@ -425,7 +518,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) {
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Enabled", Comparator: spec.ComparatorFalse, ParamIndex: 1},
{FieldReference: spec.FieldReference{enabledField}, Comparator: spec.ComparatorFalse, ParamIndex: 1},
}},
},
},
@ -445,10 +538,10 @@ func TestParseInterfaceMethod_Find(t *testing.T) {
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorEqual, ParamIndex: 1},
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
}},
Sorts: []spec.Sort{
{FieldName: "Age", Ordering: spec.OrderingAscending},
{FieldReference: spec.FieldReference{ageField}, Ordering: spec.OrderingAscending},
},
},
},
@ -468,10 +561,10 @@ func TestParseInterfaceMethod_Find(t *testing.T) {
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorEqual, ParamIndex: 1},
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
}},
Sorts: []spec.Sort{
{FieldName: "Age", Ordering: spec.OrderingAscending},
{FieldReference: spec.FieldReference{ageField}, Ordering: spec.OrderingAscending},
},
},
},
@ -491,10 +584,33 @@ func TestParseInterfaceMethod_Find(t *testing.T) {
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorEqual, ParamIndex: 1},
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
}},
Sorts: []spec.Sort{
{FieldName: "Age", Ordering: spec.OrderingDescending},
{FieldReference: spec.FieldReference{ageField}, Ordering: spec.OrderingDescending},
},
},
},
{
Name: "FindByArgOrderByDeepArg method",
Method: code.Method{
Name: "FindByCityOrderByNameFirst",
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"),
},
},
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
}},
Sorts: []spec.Sort{
{FieldReference: spec.FieldReference{nameField, firstNameField}, Ordering: spec.OrderingAscending},
},
},
},
@ -514,11 +630,11 @@ func TestParseInterfaceMethod_Find(t *testing.T) {
ExpectedOperation: spec.FindOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorEqual, ParamIndex: 1},
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
}},
Sorts: []spec.Sort{
{FieldName: "City", Ordering: spec.OrderingAscending},
{FieldName: "Age", Ordering: spec.OrderingDescending},
{FieldReference: spec.FieldReference{cityField}, Ordering: spec.OrderingAscending},
{FieldReference: spec.FieldReference{ageField}, Ordering: spec.OrderingDescending},
},
},
},
@ -526,7 +642,7 @@ func TestParseInterfaceMethod_Find(t *testing.T) {
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
actualSpec, err := spec.ParseInterfaceMethod(structModel, testCase.Method)
actualSpec, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
if err != nil {
t.Errorf("Error = %s", err)
@ -564,7 +680,7 @@ func TestParseInterfaceMethod_Update(t *testing.T) {
Update: spec.UpdateModel{},
Mode: spec.QueryModeOne,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "ID", Comparator: spec.ComparatorEqual, ParamIndex: 2},
{FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 2},
}},
},
},
@ -584,11 +700,11 @@ func TestParseInterfaceMethod_Update(t *testing.T) {
},
ExpectedOperation: spec.UpdateOperation{
Update: spec.UpdateFields{
{Name: "Gender", ParamIndex: 1},
{FieldReference: spec.FieldReference{genderField}, ParamIndex: 1},
},
Mode: spec.QueryModeOne,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "ID", Comparator: spec.ComparatorEqual, ParamIndex: 2},
{FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 2},
}},
},
},
@ -608,11 +724,35 @@ func TestParseInterfaceMethod_Update(t *testing.T) {
},
ExpectedOperation: spec.UpdateOperation{
Update: spec.UpdateFields{
{Name: "Gender", ParamIndex: 1},
{FieldReference: spec.FieldReference{genderField}, ParamIndex: 1},
},
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "ID", Comparator: spec.ComparatorEqual, ParamIndex: 2},
{FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 2},
}},
},
},
{
Name: "UpdateArgByArg one with deeply referenced update field method",
Method: code.Method{
Name: "UpdateNameFirstByID",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
},
Returns: []code.Type{
code.SimpleType("bool"),
code.SimpleType("error"),
},
},
ExpectedOperation: spec.UpdateOperation{
Update: spec.UpdateFields{
{FieldReference: spec.FieldReference{nameField, firstNameField}, ParamIndex: 1},
},
Mode: spec.QueryModeOne,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 2},
}},
},
},
@ -633,12 +773,12 @@ func TestParseInterfaceMethod_Update(t *testing.T) {
},
ExpectedOperation: spec.UpdateOperation{
Update: spec.UpdateFields{
{Name: "Gender", ParamIndex: 1},
{Name: "City", ParamIndex: 2},
{FieldReference: spec.FieldReference{genderField}, ParamIndex: 1},
{FieldReference: spec.FieldReference{cityField}, ParamIndex: 2},
},
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "ID", Comparator: spec.ComparatorEqual, ParamIndex: 3},
{FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 3},
}},
},
},
@ -646,7 +786,7 @@ func TestParseInterfaceMethod_Update(t *testing.T) {
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
actualSpec, err := spec.ParseInterfaceMethod(structModel, testCase.Method)
actualSpec, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
if err != nil {
t.Errorf("Error = %s", err)
@ -682,7 +822,7 @@ func TestParseInterfaceMethod_Delete(t *testing.T) {
ExpectedOperation: spec.DeleteOperation{
Mode: spec.QueryModeOne,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "ID", Comparator: spec.ComparatorEqual, ParamIndex: 1},
{FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
}},
},
},
@ -702,7 +842,7 @@ func TestParseInterfaceMethod_Delete(t *testing.T) {
ExpectedOperation: spec.DeleteOperation{
Mode: spec.QueryModeOne,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "PhoneNumber", Comparator: spec.ComparatorEqual, ParamIndex: 1},
{FieldReference: spec.FieldReference{phoneNumberField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
}},
},
},
@ -722,7 +862,7 @@ func TestParseInterfaceMethod_Delete(t *testing.T) {
ExpectedOperation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorEqual, ParamIndex: 1},
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
}},
},
},
@ -761,8 +901,8 @@ func TestParseInterfaceMethod_Delete(t *testing.T) {
Query: spec.QuerySpec{
Operator: spec.OperatorAnd,
Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorEqual, ParamIndex: 1},
{Field: "Gender", Comparator: spec.ComparatorEqual, ParamIndex: 2},
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
{FieldReference: spec.FieldReference{genderField}, Comparator: spec.ComparatorEqual, ParamIndex: 2},
},
},
},
@ -786,8 +926,8 @@ func TestParseInterfaceMethod_Delete(t *testing.T) {
Query: spec.QuerySpec{
Operator: spec.OperatorOr,
Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorEqual, ParamIndex: 1},
{Field: "Gender", Comparator: spec.ComparatorEqual, ParamIndex: 2},
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
{FieldReference: spec.FieldReference{genderField}, Comparator: spec.ComparatorEqual, ParamIndex: 2},
},
},
},
@ -808,7 +948,7 @@ func TestParseInterfaceMethod_Delete(t *testing.T) {
ExpectedOperation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorNot, ParamIndex: 1},
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorNot, ParamIndex: 1},
}},
},
},
@ -828,7 +968,7 @@ func TestParseInterfaceMethod_Delete(t *testing.T) {
ExpectedOperation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Comparator: spec.ComparatorLessThan, ParamIndex: 1},
{FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorLessThan, ParamIndex: 1},
}},
},
},
@ -848,7 +988,7 @@ func TestParseInterfaceMethod_Delete(t *testing.T) {
ExpectedOperation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Comparator: spec.ComparatorLessThanEqual, ParamIndex: 1},
{FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorLessThanEqual, ParamIndex: 1},
}},
},
},
@ -868,7 +1008,7 @@ func TestParseInterfaceMethod_Delete(t *testing.T) {
ExpectedOperation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Comparator: spec.ComparatorGreaterThan, ParamIndex: 1},
{FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorGreaterThan, ParamIndex: 1},
}},
},
},
@ -888,7 +1028,7 @@ func TestParseInterfaceMethod_Delete(t *testing.T) {
ExpectedOperation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Comparator: spec.ComparatorGreaterThanEqual, ParamIndex: 1},
{FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorGreaterThanEqual, ParamIndex: 1},
}},
},
},
@ -909,7 +1049,7 @@ func TestParseInterfaceMethod_Delete(t *testing.T) {
ExpectedOperation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "Age", Comparator: spec.ComparatorBetween, ParamIndex: 1},
{FieldReference: spec.FieldReference{ageField}, Comparator: spec.ComparatorBetween, ParamIndex: 1},
}},
},
},
@ -929,7 +1069,7 @@ func TestParseInterfaceMethod_Delete(t *testing.T) {
ExpectedOperation: spec.DeleteOperation{
Mode: spec.QueryModeMany,
Query: spec.QuerySpec{Predicates: []spec.Predicate{
{Field: "City", Comparator: spec.ComparatorIn, ParamIndex: 1},
{FieldReference: spec.FieldReference{cityField}, Comparator: spec.ComparatorIn, ParamIndex: 1},
}},
},
},
@ -937,7 +1077,7 @@ func TestParseInterfaceMethod_Delete(t *testing.T) {
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
actualSpec, err := spec.ParseInterfaceMethod(structModel, testCase.Method)
actualSpec, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
if err != nil {
t.Errorf("Error = %s", err)
@ -989,7 +1129,7 @@ func TestParseInterfaceMethod_Count(t *testing.T) {
ExpectedOperation: spec.CountOperation{
Query: spec.QuerySpec{
Predicates: []spec.Predicate{
{Field: "Gender", Comparator: spec.ComparatorEqual, ParamIndex: 1},
{FieldReference: spec.FieldReference{genderField}, Comparator: spec.ComparatorEqual, ParamIndex: 1},
},
},
},
@ -998,7 +1138,7 @@ func TestParseInterfaceMethod_Count(t *testing.T) {
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
actualSpec, err := spec.ParseInterfaceMethod(structModel, testCase.Method)
actualSpec, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
if err != nil {
t.Errorf("Error = %s", err)
@ -1023,7 +1163,7 @@ type ParseInterfaceMethodInvalidTestCase struct {
}
func TestParseInterfaceMethod_Invalid(t *testing.T) {
_, err := spec.ParseInterfaceMethod(structModel, code.Method{
_, err := spec.ParseInterfaceMethod(structs, structModel, code.Method{
Name: "SearchByID",
})
@ -1132,7 +1272,7 @@ func TestParseInterfaceMethod_Insert_Invalid(t *testing.T) {
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
_, err := spec.ParseInterfaceMethod(structModel, testCase.Method)
_, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
if err != testCase.ExpectedError {
t.Errorf("\nExpected = %v\nReceived = %v", testCase.ExpectedError, err)
@ -1197,7 +1337,7 @@ func TestParseInterfaceMethod_Find_Invalid(t *testing.T) {
code.SimpleType("error"),
},
},
ExpectedError: spec.NewInvalidQueryError([]string{"By", "And", "Gender"}),
ExpectedError: spec.NewInvalidQueryError([]string{"And", "Gender"}),
},
{
Name: "misplaced operator token (rightmost)",
@ -1208,7 +1348,7 @@ func TestParseInterfaceMethod_Find_Invalid(t *testing.T) {
code.SimpleType("error"),
},
},
ExpectedError: spec.NewInvalidQueryError([]string{"By", "Gender", "And"}),
ExpectedError: spec.NewInvalidQueryError([]string{"Gender", "And"}),
},
{
Name: "misplaced operator token (double operator)",
@ -1219,7 +1359,7 @@ func TestParseInterfaceMethod_Find_Invalid(t *testing.T) {
code.SimpleType("error"),
},
},
ExpectedError: spec.NewInvalidQueryError([]string{"By", "Gender", "And", "And", "City"}),
ExpectedError: spec.NewInvalidQueryError([]string{"Gender", "And", "And", "City"}),
},
{
Name: "ambiguous query",
@ -1230,7 +1370,7 @@ func TestParseInterfaceMethod_Find_Invalid(t *testing.T) {
code.SimpleType("error"),
},
},
ExpectedError: spec.NewInvalidQueryError([]string{"By", "Gender", "And", "City", "Or", "Age"}),
ExpectedError: spec.NewInvalidQueryError([]string{"Gender", "And", "City", "Or", "Age"}),
},
{
Name: "no context parameter",
@ -1249,7 +1389,7 @@ func TestParseInterfaceMethod_Find_Invalid(t *testing.T) {
{
Name: "mismatched number of parameters",
Method: code.Method{
Name: "FindByCountry",
Name: "FindByCity",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
@ -1275,7 +1415,52 @@ func TestParseInterfaceMethod_Find_Invalid(t *testing.T) {
code.SimpleType("error"),
},
},
ExpectedError: spec.NewStructFieldNotFoundError("Country"),
ExpectedError: spec.NewStructFieldNotFoundError([]string{"Country"}),
},
{
Name: "deeply referenced struct field not found",
Method: code.Method{
Name: "FindByNameMiddle",
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.NewStructFieldNotFoundError([]string{"Name", "Middle"}),
},
{
Name: "deeply referenced struct not found",
Method: code.Method{
Name: "FindByContactPhone",
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.NewStructFieldNotFoundError([]string{"Contact", "Phone"}),
},
{
Name: "deeply referenced external struct field",
Method: code.Method{
Name: "FindByDefaultPaymentMethod",
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.NewStructFieldNotFoundError([]string{"Default", "Payment", "Method"}),
},
{
Name: "incompatible struct field for True comparator",
@ -1374,11 +1559,22 @@ func TestParseInterfaceMethod_Find_Invalid(t *testing.T) {
},
ExpectedError: spec.NewInvalidSortError([]string{"Order", "By", "Age", "And", "And", "Gender"}),
},
{
Name: "sort field not found",
Method: code.Method{
Name: "FindAllOrderByCountry",
Returns: []code.Type{
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
code.SimpleType("error"),
},
},
ExpectedError: spec.NewStructFieldNotFoundError([]string{"Country"}),
},
}
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
_, err := spec.ParseInterfaceMethod(structModel, testCase.Method)
_, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
if err.Error() != testCase.ExpectedError.Error() {
t.Errorf("\nExpected = %v\nReceived = %v", testCase.ExpectedError.Error(), err.Error())
@ -1479,7 +1675,7 @@ func TestParseInterfaceMethod_Update_Invalid(t *testing.T) {
code.SimpleType("error"),
},
},
ExpectedError: spec.NewInvalidQueryError([]string{"By", "ID", "And", "Username", "Or", "Gender"}),
ExpectedError: spec.NewInvalidQueryError([]string{"ID", "And", "Username", "Or", "Gender"}),
},
{
Name: "update model with invalid parameter",
@ -1525,7 +1721,7 @@ func TestParseInterfaceMethod_Update_Invalid(t *testing.T) {
code.SimpleType("error"),
},
},
ExpectedError: spec.NewStructFieldNotFoundError("Country"),
ExpectedError: spec.NewStructFieldNotFoundError([]string{"Country"}),
},
{
Name: "struct field does not match parameter in update fields",
@ -1563,7 +1759,7 @@ func TestParseInterfaceMethod_Update_Invalid(t *testing.T) {
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
_, err := spec.ParseInterfaceMethod(structModel, testCase.Method)
_, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
if err != testCase.ExpectedError {
t.Errorf("\nExpected = %v\nReceived = %v", testCase.ExpectedError, err)
@ -1628,7 +1824,7 @@ func TestParseInterfaceMethod_Delete_Invalid(t *testing.T) {
code.SimpleType("error"),
},
},
ExpectedError: spec.NewInvalidQueryError([]string{"By", "And", "Gender"}),
ExpectedError: spec.NewInvalidQueryError([]string{"And", "Gender"}),
},
{
Name: "misplaced operator token (rightmost)",
@ -1639,7 +1835,7 @@ func TestParseInterfaceMethod_Delete_Invalid(t *testing.T) {
code.SimpleType("error"),
},
},
ExpectedError: spec.NewInvalidQueryError([]string{"By", "Gender", "And"}),
ExpectedError: spec.NewInvalidQueryError([]string{"Gender", "And"}),
},
{
Name: "misplaced operator token (double operator)",
@ -1650,7 +1846,7 @@ func TestParseInterfaceMethod_Delete_Invalid(t *testing.T) {
code.SimpleType("error"),
},
},
ExpectedError: spec.NewInvalidQueryError([]string{"By", "Gender", "And", "And", "City"}),
ExpectedError: spec.NewInvalidQueryError([]string{"Gender", "And", "And", "City"}),
},
{
Name: "ambiguous query",
@ -1661,7 +1857,7 @@ func TestParseInterfaceMethod_Delete_Invalid(t *testing.T) {
code.SimpleType("error"),
},
},
ExpectedError: spec.NewInvalidQueryError([]string{"By", "Gender", "And", "City", "Or", "Age"}),
ExpectedError: spec.NewInvalidQueryError([]string{"Gender", "And", "City", "Or", "Age"}),
},
{
Name: "no context parameter",
@ -1680,7 +1876,7 @@ func TestParseInterfaceMethod_Delete_Invalid(t *testing.T) {
{
Name: "mismatched number of parameters",
Method: code.Method{
Name: "DeleteByCountry",
Name: "DeleteByCity",
Params: []code.Param{
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
{Type: code.SimpleType("string")},
@ -1706,7 +1902,7 @@ func TestParseInterfaceMethod_Delete_Invalid(t *testing.T) {
code.SimpleType("error"),
},
},
ExpectedError: spec.NewStructFieldNotFoundError("Country"),
ExpectedError: spec.NewStructFieldNotFoundError([]string{"Country"}),
},
{
Name: "mismatched method parameter type",
@ -1742,7 +1938,7 @@ func TestParseInterfaceMethod_Delete_Invalid(t *testing.T) {
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
_, err := spec.ParseInterfaceMethod(structModel, testCase.Method)
_, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
if err != testCase.ExpectedError {
t.Errorf("\nExpected = %v\nReceived = %v", testCase.ExpectedError, err)
@ -1867,13 +2063,13 @@ func TestParseInterfaceMethod_Count_Invalid(t *testing.T) {
code.SimpleType("error"),
},
},
ExpectedError: spec.NewStructFieldNotFoundError("Country"),
ExpectedError: spec.NewStructFieldNotFoundError([]string{"Country"}),
},
}
for _, testCase := range testTable {
t.Run(testCase.Name, func(t *testing.T) {
_, err := spec.ParseInterfaceMethod(structModel, testCase.Method)
_, err := spec.ParseInterfaceMethod(structs, structModel, testCase.Method)
if err != testCase.ExpectedError {
t.Errorf("\nExpected = %v\nReceived = %v", testCase.ExpectedError, err)

View file

@ -1,8 +1,6 @@
package spec
import (
"strings"
"github.com/sunboyy/repogen/internal/code"
)
@ -72,48 +70,17 @@ func (c Comparator) NumberOfArguments() int {
// Predicate is a criteria for querying a field
type Predicate struct {
Field string
Comparator Comparator
ParamIndex int
FieldReference FieldReference
Comparator Comparator
ParamIndex int
}
type predicateToken []string
func (t predicateToken) ToPredicate(paramIndex int) Predicate {
if len(t) > 1 && t[len(t)-1] == "Not" {
return Predicate{Field: strings.Join(t[:len(t)-1], ""), Comparator: ComparatorNot, ParamIndex: paramIndex}
}
if len(t) > 2 && t[len(t)-2] == "Less" && t[len(t)-1] == "Than" {
return Predicate{Field: strings.Join(t[:len(t)-2], ""), Comparator: ComparatorLessThan, ParamIndex: paramIndex}
}
if len(t) > 3 && t[len(t)-3] == "Less" && t[len(t)-2] == "Than" && t[len(t)-1] == "Equal" {
return Predicate{Field: strings.Join(t[:len(t)-3], ""), Comparator: ComparatorLessThanEqual, ParamIndex: paramIndex}
}
if len(t) > 2 && t[len(t)-2] == "Greater" && t[len(t)-1] == "Than" {
return Predicate{Field: strings.Join(t[:len(t)-2], ""), Comparator: ComparatorGreaterThan, ParamIndex: paramIndex}
}
if len(t) > 3 && t[len(t)-3] == "Greater" && t[len(t)-2] == "Than" && t[len(t)-1] == "Equal" {
return Predicate{Field: strings.Join(t[:len(t)-3], ""), Comparator: ComparatorGreaterThanEqual, ParamIndex: paramIndex}
}
if len(t) > 2 && t[len(t)-2] == "Not" && t[len(t)-1] == "In" {
return Predicate{Field: strings.Join(t[:len(t)-2], ""), Comparator: ComparatorNotIn, ParamIndex: paramIndex}
}
if len(t) > 1 && t[len(t)-1] == "In" {
return Predicate{Field: strings.Join(t[:len(t)-1], ""), Comparator: ComparatorIn, ParamIndex: paramIndex}
}
if len(t) > 1 && t[len(t)-1] == "Between" {
return Predicate{Field: strings.Join(t[:len(t)-1], ""), Comparator: ComparatorBetween, ParamIndex: paramIndex}
}
if len(t) > 1 && t[len(t)-1] == "True" {
return Predicate{Field: strings.Join(t[:len(t)-1], ""), Comparator: ComparatorTrue, ParamIndex: paramIndex}
}
if len(t) > 1 && t[len(t)-1] == "False" {
return Predicate{Field: strings.Join(t[:len(t)-1], ""), Comparator: ComparatorFalse, ParamIndex: paramIndex}
}
return Predicate{Field: strings.Join(t, ""), Comparator: ComparatorEqual, ParamIndex: paramIndex}
type queryParser struct {
fieldResolver fieldResolver
StructModel code.Struct
}
func parseQuery(rawTokens []string, paramIndex int) (QuerySpec, error) {
func (p queryParser) parseQuery(rawTokens []string, paramIndex int) (QuerySpec, error) {
if len(rawTokens) == 0 {
return QuerySpec{}, QueryRequiredError
}
@ -130,38 +97,104 @@ func parseQuery(rawTokens []string, paramIndex int) (QuerySpec, error) {
tokens = tokens[1:]
}
if len(tokens) == 0 || tokens[0] == "And" || tokens[0] == "Or" {
if len(tokens) == 0 {
return QuerySpec{}, NewInvalidQueryError(rawTokens)
}
operator, predicateTokens, err := p.splitPredicateTokens(tokens)
if err != nil {
return QuerySpec{}, err
}
querySpec := QuerySpec{
Operator: operator,
}
for _, predicateToken := range predicateTokens {
predicate, err := p.parsePredicate(predicateToken, paramIndex)
if err != nil {
return QuerySpec{}, err
}
querySpec.Predicates = append(querySpec.Predicates, predicate)
paramIndex += predicate.Comparator.NumberOfArguments()
}
return querySpec, nil
}
func (p queryParser) splitPredicateTokens(tokens []string) (Operator, [][]string, error) {
var operator Operator
var predicates []Predicate
var aggregatedToken predicateToken
var predicateTokens [][]string
var aggregatedToken []string
for _, token := range tokens {
if token != "And" && token != "Or" {
aggregatedToken = append(aggregatedToken, token)
} else if len(aggregatedToken) == 0 {
return QuerySpec{}, NewInvalidQueryError(rawTokens)
return "", nil, NewInvalidQueryError(tokens)
} else if token == "And" && operator != OperatorOr {
operator = OperatorAnd
predicate := aggregatedToken.ToPredicate(paramIndex)
predicates = append(predicates, predicate)
paramIndex += predicate.Comparator.NumberOfArguments()
aggregatedToken = predicateToken{}
predicateTokens = append(predicateTokens, aggregatedToken)
aggregatedToken = nil
} else if token == "Or" && operator != OperatorAnd {
operator = OperatorOr
predicate := aggregatedToken.ToPredicate(paramIndex)
predicates = append(predicates, predicate)
paramIndex += predicate.Comparator.NumberOfArguments()
aggregatedToken = predicateToken{}
predicateTokens = append(predicateTokens, aggregatedToken)
aggregatedToken = nil
} else {
return QuerySpec{}, NewInvalidQueryError(rawTokens)
return "", nil, NewInvalidQueryError(tokens)
}
}
if len(aggregatedToken) == 0 {
return QuerySpec{}, NewInvalidQueryError(rawTokens)
return "", nil, NewInvalidQueryError(tokens)
}
predicates = append(predicates, aggregatedToken.ToPredicate(paramIndex))
predicateTokens = append(predicateTokens, aggregatedToken)
return QuerySpec{Operator: operator, Predicates: predicates}, nil
return operator, predicateTokens, nil
}
func (p queryParser) parsePredicate(t []string, paramIndex int) (Predicate, error) {
if len(t) > 1 && t[len(t)-1] == "Not" {
return p.createPredicate(t[:len(t)-1], ComparatorNot, paramIndex)
}
if len(t) > 2 && t[len(t)-2] == "Less" && t[len(t)-1] == "Than" {
return p.createPredicate(t[:len(t)-2], ComparatorLessThan, paramIndex)
}
if len(t) > 3 && t[len(t)-3] == "Less" && t[len(t)-2] == "Than" && t[len(t)-1] == "Equal" {
return p.createPredicate(t[:len(t)-3], ComparatorLessThanEqual, paramIndex)
}
if len(t) > 2 && t[len(t)-2] == "Greater" && t[len(t)-1] == "Than" {
return p.createPredicate(t[:len(t)-2], ComparatorGreaterThan, paramIndex)
}
if len(t) > 3 && t[len(t)-3] == "Greater" && t[len(t)-2] == "Than" && t[len(t)-1] == "Equal" {
return p.createPredicate(t[:len(t)-3], ComparatorGreaterThanEqual, paramIndex)
}
if len(t) > 2 && t[len(t)-2] == "Not" && t[len(t)-1] == "In" {
return p.createPredicate(t[:len(t)-2], ComparatorNotIn, paramIndex)
}
if len(t) > 1 && t[len(t)-1] == "In" {
return p.createPredicate(t[:len(t)-1], ComparatorIn, paramIndex)
}
if len(t) > 1 && t[len(t)-1] == "Between" {
return p.createPredicate(t[:len(t)-1], ComparatorBetween, paramIndex)
}
if len(t) > 1 && t[len(t)-1] == "True" {
return p.createPredicate(t[:len(t)-1], ComparatorTrue, paramIndex)
}
if len(t) > 1 && t[len(t)-1] == "False" {
return p.createPredicate(t[:len(t)-1], ComparatorFalse, paramIndex)
}
return p.createPredicate(t, ComparatorEqual, paramIndex)
}
func (p queryParser) createPredicate(t []string, comparator Comparator, paramIndex int) (Predicate, error) {
fields, ok := p.fieldResolver.ResolveStructField(p.StructModel, t)
if !ok {
return Predicate{}, NewStructFieldNotFoundError(t)
}
return Predicate{
FieldReference: fields,
Comparator: comparator,
ParamIndex: paramIndex,
}, nil
}