repogen/internal/spec/update.go
2023-05-24 14:04:29 +03:00

203 lines
5.2 KiB
Go

package spec
import "git.kmsign.ru/royalcat/repogen/internal/code"
// UpdateOperation is a method specification for update operations
type UpdateOperation struct {
Update Update
Mode QueryMode
Query QuerySpec
}
// Name returns "Update" operation name
func (o UpdateOperation) Name() string {
return "Update"
}
// Update is an interface of update operation type
type Update interface {
Name() string
NumberOfArguments() int
}
// UpdateModel is a type of update operation that update the whole model
type UpdateModel struct {
}
// Name returns UpdateModel name 'Model'
func (u UpdateModel) Name() string {
return "Model"
}
// NumberOfArguments returns 1
func (u UpdateModel) NumberOfArguments() int {
return 1
}
// UpdateFields is a type of update operation that update specific fields
type UpdateFields []UpdateField
// Name returns UpdateFields name 'Fields'
func (u UpdateFields) Name() string {
return "Fields"
}
// NumberOfArguments returns number of update fields
func (u UpdateFields) NumberOfArguments() int {
return len(u)
}
// UpdateField stores mapping between field name in the model and the parameter
// index.
type UpdateField struct {
FieldReference FieldReference
ParamIndex int
Operator UpdateOperator
}
// UpdateOperator is a custom type that declares update operator to be used in
// an update operation
type UpdateOperator string
// UpdateOperator constants
const (
UpdateOperatorSet UpdateOperator = "SET"
UpdateOperatorPush UpdateOperator = "PUSH"
UpdateOperatorInc UpdateOperator = "INC"
)
// NumberOfArguments returns number of arguments required to perform an update operation
func (o UpdateOperator) NumberOfArguments() int {
return 1
}
// ArgumentType returns type that is required for function parameter
func (o UpdateOperator) ArgumentType(fieldType code.Type) code.Type {
switch o {
case UpdateOperatorPush:
arrayType := fieldType.(code.ArrayType)
return arrayType.ContainedType
default:
return fieldType
}
}
func (p interfaceMethodParser) parseUpdateOperation(tokens []string) (Operation, error) {
mode, err := p.extractIntOrBoolReturns(p.Method.Returns)
if err != nil {
return nil, err
}
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, ErrInvalidUpdateFields
}
return UpdateModel{}, nil
}
updateFieldTokens, ok := splitByAnd(tokens)
if !ok {
return nil, ErrInvalidUpdateFields
}
var updateFields UpdateFields
paramIndex := 1
for _, updateFieldToken := range updateFieldTokens {
updateField, err := p.parseUpdateField(updateFieldToken, paramIndex)
if err != nil {
return nil, err
}
updateFields = append(updateFields, updateField)
paramIndex += updateField.Operator.NumberOfArguments()
}
for _, field := range updateFields {
if len(p.Method.Params) < field.ParamIndex+field.Operator.NumberOfArguments() {
return nil, ErrInvalidUpdateFields
}
requiredType := field.Operator.ArgumentType(field.FieldReference.ReferencedField().Type)
for i := 0; i < field.Operator.NumberOfArguments(); i++ {
if requiredType != p.Method.Params[field.ParamIndex+i].Type {
return nil, NewArgumentTypeNotMatchedError(field.FieldReference.ReferencingCode(), requiredType,
p.Method.Params[field.ParamIndex+i].Type)
}
}
}
return updateFields, nil
}
func (p interfaceMethodParser) parseUpdateField(t []string,
paramIndex int) (UpdateField, error) {
if len(t) > 1 && t[len(t)-1] == "Push" {
return p.createUpdateField(t[:len(t)-1], UpdateOperatorPush, paramIndex)
}
if len(t) > 1 && t[len(t)-1] == "Inc" {
return p.createUpdateField(t[:len(t)-1], UpdateOperatorInc, paramIndex)
}
return p.createUpdateField(t, UpdateOperatorSet, paramIndex)
}
func (p interfaceMethodParser) createUpdateField(t []string,
operator UpdateOperator, paramIndex int) (UpdateField, error) {
fieldReference, ok := p.fieldResolver.ResolveStructField(p.StructModel, t)
if !ok {
return UpdateField{}, NewStructFieldNotFoundError(t)
}
if !p.validateUpdateOperator(fieldReference.ReferencedField().Type, operator) {
return UpdateField{}, NewIncompatibleUpdateOperatorError(operator, fieldReference)
}
return UpdateField{
FieldReference: fieldReference,
ParamIndex: paramIndex,
Operator: operator,
}, nil
}
func (p interfaceMethodParser) validateUpdateOperator(referencedType code.Type, operator UpdateOperator) bool {
switch operator {
case UpdateOperatorPush:
_, ok := referencedType.(code.ArrayType)
return ok
case UpdateOperatorInc:
return referencedType.IsNumber()
}
return true
}