repogen/internal/spec/parser.go

475 lines
11 KiB
Go
Raw Normal View History

package spec
import (
"github.com/fatih/camelcase"
"github.com/sunboyy/repogen/internal/code"
)
// ParseInterfaceMethod returns repository method spec from declared interface method
func ParseInterfaceMethod(structs code.Structs, structModel code.Struct, method code.Method) (MethodSpec, error) {
parser := interfaceMethodParser{
fieldResolver: fieldResolver{
Structs: structs,
},
StructModel: structModel,
Method: method,
}
return parser.Parse()
}
type interfaceMethodParser struct {
fieldResolver fieldResolver
StructModel code.Struct
Method code.Method
}
func (p interfaceMethodParser) Parse() (MethodSpec, error) {
2021-02-07 05:51:51 +00:00
operation, err := p.parseMethod()
if err != nil {
return MethodSpec{}, err
}
return MethodSpec{
Name: p.Method.Name,
Params: p.Method.Params,
Returns: p.Method.Returns,
Operation: operation,
}, nil
}
func (p interfaceMethodParser) parseMethod() (Operation, error) {
methodNameTokens := camelcase.Split(p.Method.Name)
switch methodNameTokens[0] {
2021-02-01 14:39:20 +00:00
case "Insert":
2021-02-07 05:51:51 +00:00
return p.parseInsertOperation(methodNameTokens[1:])
case "Find":
2021-02-07 05:51:51 +00:00
return p.parseFindOperation(methodNameTokens[1:])
2021-01-27 12:15:25 +00:00
case "Update":
2021-02-07 05:51:51 +00:00
return p.parseUpdateOperation(methodNameTokens[1:])
2021-01-26 13:23:52 +00:00
case "Delete":
2021-02-07 05:51:51 +00:00
return p.parseDeleteOperation(methodNameTokens[1:])
2021-02-06 11:05:47 +00:00
case "Count":
2021-02-07 05:51:51 +00:00
return p.parseCountOperation(methodNameTokens[1:])
}
2021-02-14 04:48:09 +00:00
return nil, NewUnknownOperationError(methodNameTokens[0])
}
2021-02-07 05:51:51 +00:00
func (p interfaceMethodParser) parseInsertOperation(tokens []string) (Operation, error) {
2021-02-01 14:39:20 +00:00
mode, err := p.extractInsertReturns(p.Method.Returns)
if err != nil {
2021-02-07 05:51:51 +00:00
return nil, err
2021-02-01 14:39:20 +00:00
}
if err := p.validateContextParam(); err != nil {
2021-02-07 05:51:51 +00:00
return nil, err
2021-02-01 14:39:20 +00:00
}
pointerType := code.PointerType{ContainedType: p.StructModel.ReferencedType()}
if mode == QueryModeOne && p.Method.Params[1].Type != pointerType {
2021-02-07 05:51:51 +00:00
return nil, InvalidParamError
2021-02-01 14:39:20 +00:00
}
arrayType := code.ArrayType{ContainedType: pointerType}
if mode == QueryModeMany && p.Method.Params[1].Type != arrayType {
2021-02-07 05:51:51 +00:00
return nil, InvalidParamError
2021-02-01 14:39:20 +00:00
}
2021-02-07 05:51:51 +00:00
return InsertOperation{
2021-02-06 11:05:47 +00:00
Mode: mode,
2021-02-07 05:51:51 +00:00
}, nil
2021-02-01 14:39:20 +00:00
}
func (p interfaceMethodParser) extractInsertReturns(returns []code.Type) (QueryMode, error) {
if len(returns) != 2 {
return "", UnsupportedReturnError
}
if returns[1] != code.SimpleType("error") {
return "", UnsupportedReturnError
}
interfaceType, ok := returns[0].(code.InterfaceType)
if ok {
if len(interfaceType.Methods) != 0 {
return "", UnsupportedReturnError
}
return QueryModeOne, nil
}
arrayType, ok := returns[0].(code.ArrayType)
if ok {
interfaceType, ok := arrayType.ContainedType.(code.InterfaceType)
if !ok || len(interfaceType.Methods) != 0 {
return "", UnsupportedReturnError
}
return QueryModeMany, nil
}
return "", UnsupportedReturnError
}
2021-02-07 05:51:51 +00:00
func (p interfaceMethodParser) parseFindOperation(tokens []string) (Operation, error) {
2021-02-06 11:05:47 +00:00
mode, err := p.extractModelOrSliceReturns(p.Method.Returns)
if err != nil {
2021-02-07 05:51:51 +00:00
return nil, err
}
queryTokens, sortTokens := p.splitQueryAndSortTokens(tokens)
querySpec, err := p.parseQuery(queryTokens, 1)
if err != nil {
return nil, err
}
sorts, err := p.parseSort(sortTokens)
if err != nil {
2021-02-07 05:51:51 +00:00
return nil, err
}
2021-01-27 12:15:25 +00:00
if err := p.validateContextParam(); err != nil {
2021-02-07 05:51:51 +00:00
return nil, err
2021-01-27 12:15:25 +00:00
}
if err := p.validateQueryFromParams(p.Method.Params[1:], querySpec); err != nil {
2021-02-07 05:51:51 +00:00
return nil, err
}
2021-02-07 05:51:51 +00:00
return FindOperation{
2021-02-06 11:05:47 +00:00
Mode: mode,
Query: querySpec,
Sorts: sorts,
2021-02-07 05:51:51 +00:00
}, nil
}
func (p interfaceMethodParser) parseSort(rawTokens []string) ([]Sort, error) {
if len(rawTokens) == 0 {
return nil, nil
}
sortTokens, ok := splitByAnd(rawTokens[2:])
if !ok {
return nil, NewInvalidSortError(rawTokens)
}
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
}
func (p interfaceMethodParser) parseSortToken(t []string) (Sort, error) {
if len(t) > 1 && t[len(t)-1] == "Asc" {
return p.createSort(t[:len(t)-1], OrderingAscending)
}
if len(t) > 1 && t[len(t)-1] == "Desc" {
return p.createSort(t[:len(t)-1], OrderingDescending)
}
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) {
var queryTokens []string
var sortTokens []string
for i, token := range tokens {
if len(tokens) > i && token == "Order" && tokens[i+1] == "By" {
sortTokens = tokens[i:]
break
} else {
queryTokens = append(queryTokens, token)
}
}
return queryTokens, sortTokens
}
2021-02-06 11:05:47 +00:00
func (p interfaceMethodParser) extractModelOrSliceReturns(returns []code.Type) (QueryMode, error) {
if len(returns) != 2 {
return "", UnsupportedReturnError
}
if returns[1] != code.SimpleType("error") {
return "", UnsupportedReturnError
}
pointerType, ok := returns[0].(code.PointerType)
if ok {
simpleType := pointerType.ContainedType
if simpleType == code.SimpleType(p.StructModel.Name) {
return QueryModeOne, nil
}
return "", UnsupportedReturnError
}
arrayType, ok := returns[0].(code.ArrayType)
if ok {
pointerType, ok := arrayType.ContainedType.(code.PointerType)
if ok {
simpleType := pointerType.ContainedType
if simpleType == code.SimpleType(p.StructModel.Name) {
return QueryModeMany, nil
}
return "", UnsupportedReturnError
}
}
return "", UnsupportedReturnError
}
2021-02-07 05:51:51 +00:00
func (p interfaceMethodParser) parseUpdateOperation(tokens []string) (Operation, error) {
2021-02-06 11:05:47 +00:00
mode, err := p.extractIntOrBoolReturns(p.Method.Returns)
2021-01-27 12:15:25 +00:00
if err != nil {
2021-02-07 05:51:51 +00:00
return nil, err
2021-01-27 12:15:25 +00:00
}
if err := p.validateContextParam(); err != nil {
return nil, err
}
updateTokens, queryTokens := p.splitUpdateAndQueryTokens(tokens)
update, err := p.parseUpdate(updateTokens)
if err != nil {
return nil, err
}
querySpec, err := p.parseQuery(queryTokens, 1+update.NumberOfArguments())
if err != nil {
return nil, err
}
if err := p.validateQueryFromParams(p.Method.Params[update.NumberOfArguments()+1:], querySpec); err != nil {
return nil, err
}
return UpdateOperation{
Update: update,
Mode: mode,
Query: querySpec,
}, nil
}
func (p interfaceMethodParser) parseUpdate(tokens []string) (Update, error) {
if len(tokens) == 0 {
requiredType := code.PointerType{ContainedType: p.StructModel.ReferencedType()}
if len(p.Method.Params) <= 1 || p.Method.Params[1].Type != requiredType {
return nil, InvalidUpdateFieldsError
}
return UpdateModel{}, nil
}
2021-02-07 05:51:51 +00:00
updateFieldTokens, ok := splitByAnd(tokens)
if !ok {
2021-02-07 05:51:51 +00:00
return nil, InvalidUpdateFieldsError
2021-01-27 12:15:25 +00:00
}
var updateFields UpdateFields
paramIndex := 1
for _, updateFieldToken := range updateFieldTokens {
updateFieldReference, ok := p.fieldResolver.ResolveStructField(p.StructModel, updateFieldToken)
2021-01-27 12:15:25 +00:00
if !ok {
return nil, NewStructFieldNotFoundError(updateFieldToken)
2021-01-27 12:15:25 +00:00
}
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
2021-01-27 12:15:25 +00:00
}
}
return updateFields, nil
}
func splitByAnd(tokens []string) ([][]string, bool) {
var updateFieldTokens [][]string
var aggregatedToken []string
for _, token := range tokens {
if token != "And" {
aggregatedToken = append(aggregatedToken, token)
} else if len(aggregatedToken) == 0 {
return nil, false
} else {
updateFieldTokens = append(updateFieldTokens, aggregatedToken)
aggregatedToken = nil
}
}
if len(aggregatedToken) == 0 {
return nil, false
}
updateFieldTokens = append(updateFieldTokens, aggregatedToken)
return updateFieldTokens, true
2021-01-27 12:15:25 +00:00
}
func (p interfaceMethodParser) splitUpdateAndQueryTokens(tokens []string) ([]string, []string) {
var updateTokens []string
2021-02-07 05:51:51 +00:00
var queryTokens []string
for i, token := range tokens {
if token == "By" || token == "All" {
queryTokens = tokens[i:]
break
} else {
updateTokens = append(updateTokens, token)
2021-02-07 05:51:51 +00:00
}
2021-01-26 13:23:52 +00:00
}
return updateTokens, queryTokens
2021-02-07 05:51:51 +00:00
}
func (p interfaceMethodParser) parseDeleteOperation(tokens []string) (Operation, error) {
2021-02-06 11:05:47 +00:00
mode, err := p.extractIntOrBoolReturns(p.Method.Returns)
2021-01-26 13:23:52 +00:00
if err != nil {
2021-02-07 05:51:51 +00:00
return nil, err
2021-01-26 13:23:52 +00:00
}
querySpec, err := p.parseQuery(tokens, 1)
2021-01-26 13:23:52 +00:00
if err != nil {
2021-02-07 05:51:51 +00:00
return nil, err
2021-01-26 13:23:52 +00:00
}
2021-01-27 12:15:25 +00:00
if err := p.validateContextParam(); err != nil {
2021-02-07 05:51:51 +00:00
return nil, err
2021-01-27 12:15:25 +00:00
}
if err := p.validateQueryFromParams(p.Method.Params[1:], querySpec); err != nil {
2021-02-07 05:51:51 +00:00
return nil, err
2021-01-26 13:23:52 +00:00
}
2021-02-07 05:51:51 +00:00
return DeleteOperation{
2021-02-06 11:05:47 +00:00
Mode: mode,
Query: querySpec,
2021-02-07 05:51:51 +00:00
}, nil
2021-01-26 13:23:52 +00:00
}
2021-02-07 05:51:51 +00:00
func (p interfaceMethodParser) parseCountOperation(tokens []string) (Operation, error) {
2021-02-06 11:05:47 +00:00
if err := p.validateCountReturns(p.Method.Returns); err != nil {
2021-02-07 05:51:51 +00:00
return nil, err
2021-01-26 13:23:52 +00:00
}
querySpec, err := p.parseQuery(tokens, 1)
2021-02-06 11:05:47 +00:00
if err != nil {
2021-02-07 05:51:51 +00:00
return nil, err
2021-01-26 13:23:52 +00:00
}
2021-02-06 11:05:47 +00:00
if err := p.validateContextParam(); err != nil {
2021-02-07 05:51:51 +00:00
return nil, err
2021-02-06 11:05:47 +00:00
}
if err := p.validateQueryFromParams(p.Method.Params[1:], querySpec); err != nil {
2021-02-07 05:51:51 +00:00
return nil, err
2021-02-06 11:05:47 +00:00
}
2021-02-07 05:51:51 +00:00
return CountOperation{
2021-02-06 11:05:47 +00:00
Query: querySpec,
2021-02-07 05:51:51 +00:00
}, nil
2021-01-26 13:23:52 +00:00
}
2021-02-06 11:05:47 +00:00
func (p interfaceMethodParser) validateCountReturns(returns []code.Type) error {
if len(returns) != 2 {
return UnsupportedReturnError
}
2021-02-06 11:05:47 +00:00
if returns[0] != code.SimpleType("int") {
return UnsupportedReturnError
}
2021-02-06 11:05:47 +00:00
if returns[1] != code.SimpleType("error") {
return UnsupportedReturnError
}
2021-02-06 11:05:47 +00:00
return nil
}
func (p interfaceMethodParser) extractIntOrBoolReturns(returns []code.Type) (QueryMode, error) {
if len(returns) != 2 {
return "", UnsupportedReturnError
}
2021-02-06 11:05:47 +00:00
if returns[1] != code.SimpleType("error") {
return "", UnsupportedReturnError
}
2021-01-19 12:26:26 +00:00
2021-02-06 11:05:47 +00:00
simpleType, ok := returns[0].(code.SimpleType)
if ok {
if simpleType == code.SimpleType("bool") {
return QueryModeOne, nil
}
if simpleType == code.SimpleType("int") {
return QueryModeMany, nil
}
}
2021-02-06 11:05:47 +00:00
return "", UnsupportedReturnError
}
2021-01-27 12:15:25 +00:00
func (p interfaceMethodParser) validateContextParam() error {
contextType := code.ExternalType{PackageAlias: "context", Name: "Context"}
if len(p.Method.Params) == 0 || p.Method.Params[0].Type != contextType {
return ContextParamRequiredError
}
2021-01-27 12:15:25 +00:00
return nil
}
2021-01-27 12:15:25 +00:00
func (p interfaceMethodParser) validateQueryFromParams(params []code.Param, querySpec QuerySpec) error {
if querySpec.NumberOfArguments() != len(params) {
return InvalidParamError
}
2021-01-27 12:15:25 +00:00
var currentParamIndex int
for _, predicate := range querySpec.Predicates {
2021-02-23 12:10:25 +00:00
if (predicate.Comparator == ComparatorTrue || predicate.Comparator == ComparatorFalse) &&
predicate.FieldReference.ReferencedField().Type != code.SimpleType("bool") {
return NewIncompatibleComparatorError(predicate.Comparator,
predicate.FieldReference.ReferencedField())
2021-02-23 12:10:25 +00:00
}
for i := 0; i < predicate.Comparator.NumberOfArguments(); i++ {
2021-01-27 12:15:25 +00:00
if params[currentParamIndex].Type != predicate.Comparator.ArgumentTypeFromFieldType(
predicate.FieldReference.ReferencedField().Type) {
return InvalidParamError
}
currentParamIndex++
}
}
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)
}