Add increment update operator
This commit is contained in:
parent
0538f87a91
commit
66c63d4d6c
12 changed files with 330 additions and 42 deletions
|
@ -22,8 +22,6 @@ func (err ParsingError) Error() string {
|
|||
return "update fields are invalid"
|
||||
case ContextParamRequiredError:
|
||||
return "context parameter is required"
|
||||
case PushNonArrayError:
|
||||
return "cannot use push operation in a non-array type"
|
||||
}
|
||||
return string(err)
|
||||
}
|
||||
|
@ -35,7 +33,6 @@ const (
|
|||
InvalidParamError ParsingError = "ERROR_INVALID_PARAM"
|
||||
InvalidUpdateFieldsError ParsingError = "ERROR_INVALID_UPDATE_FIELDS"
|
||||
ContextParamRequiredError ParsingError = "ERROR_CONTEXT_PARAM_REQUIRED"
|
||||
PushNonArrayError ParsingError = "ERROR_PUSH_NON_ARRAY"
|
||||
)
|
||||
|
||||
// NewInvalidQueryError creates invalidQueryError
|
||||
|
@ -64,6 +61,26 @@ func (err invalidSortError) Error() string {
|
|||
return fmt.Sprintf("invalid sort '%s'", err.SortString)
|
||||
}
|
||||
|
||||
// NewArgumentTypeNotMatchedError creates argumentTypeNotMatchedError
|
||||
func NewArgumentTypeNotMatchedError(fieldName string, requiredType code.Type, givenType code.Type) error {
|
||||
return argumentTypeNotMatchedError{
|
||||
FieldName: fieldName,
|
||||
RequiredType: requiredType,
|
||||
GivenType: givenType,
|
||||
}
|
||||
}
|
||||
|
||||
type argumentTypeNotMatchedError struct {
|
||||
FieldName string
|
||||
RequiredType code.Type
|
||||
GivenType code.Type
|
||||
}
|
||||
|
||||
func (err argumentTypeNotMatchedError) Error() string {
|
||||
return fmt.Sprintf("field '%s' requires an argument of type '%s' (got '%s')",
|
||||
err.FieldName, err.RequiredType.Code(), err.GivenType.Code())
|
||||
}
|
||||
|
||||
// NewUnknownOperationError creates unknownOperationError
|
||||
func NewUnknownOperationError(operationName string) error {
|
||||
return unknownOperationError{OperationName: operationName}
|
||||
|
@ -107,3 +124,23 @@ func (err incompatibleComparatorError) Error() string {
|
|||
return fmt.Sprintf("cannot use comparator %s with struct field '%s' of type '%s'",
|
||||
err.Comparator, err.Field.Name, err.Field.Type.Code())
|
||||
}
|
||||
|
||||
// NewIncompatibleUpdateOperatorError creates incompatibleUpdateOperatorError
|
||||
func NewIncompatibleUpdateOperatorError(updateOperator UpdateOperator, fieldReference FieldReference) error {
|
||||
return incompatibleUpdateOperatorError{
|
||||
UpdateOperator: updateOperator,
|
||||
ReferencingCode: fieldReference.ReferencingCode(),
|
||||
ReferencedType: fieldReference.ReferencedField().Type,
|
||||
}
|
||||
}
|
||||
|
||||
type incompatibleUpdateOperatorError struct {
|
||||
UpdateOperator UpdateOperator
|
||||
ReferencingCode string
|
||||
ReferencedType code.Type
|
||||
}
|
||||
|
||||
func (err incompatibleUpdateOperatorError) Error() string {
|
||||
return fmt.Sprintf("cannot use update operator %s with struct field '%s' of type '%s'",
|
||||
err.UpdateOperator, err.ReferencingCode, err.ReferencedType.Code())
|
||||
}
|
||||
|
|
|
@ -43,6 +43,19 @@ func TestError(t *testing.T) {
|
|||
Error: spec.NewInvalidSortError([]string{"Order", "By"}),
|
||||
ExpectedString: "invalid sort 'OrderBy'",
|
||||
},
|
||||
{
|
||||
Name: "ArgumentTypeNotMatchedError",
|
||||
Error: spec.NewArgumentTypeNotMatchedError("Age", code.SimpleType("int"), code.SimpleType("float64")),
|
||||
ExpectedString: "field 'Age' requires an argument of type 'int' (got 'float64')",
|
||||
},
|
||||
{
|
||||
Name: "IncompatibleUpdateOperatorError",
|
||||
Error: spec.NewIncompatibleUpdateOperatorError(spec.UpdateOperatorInc, spec.FieldReference{{
|
||||
Name: "City",
|
||||
Type: code.SimpleType("string"),
|
||||
}}),
|
||||
ExpectedString: "cannot use update operator INC with struct field 'City' of type 'string'",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testTable {
|
||||
|
|
|
@ -14,6 +14,15 @@ func (r FieldReference) ReferencedField() code.StructField {
|
|||
return r[len(r)-1]
|
||||
}
|
||||
|
||||
// ReferencingCode returns a string containing name of the referenced fields concatenating with period (.).
|
||||
func (r FieldReference) ReferencingCode() string {
|
||||
var fieldNames []string
|
||||
for _, field := range r {
|
||||
fieldNames = append(fieldNames, field.Name)
|
||||
}
|
||||
return strings.Join(fieldNames, ".")
|
||||
}
|
||||
|
||||
type fieldResolver struct {
|
||||
Structs code.Structs
|
||||
}
|
||||
|
|
|
@ -381,9 +381,11 @@ func (p interfaceMethodParser) validateQueryFromParams(params []code.Param, quer
|
|||
}
|
||||
|
||||
for i := 0; i < predicate.Comparator.NumberOfArguments(); i++ {
|
||||
if params[currentParamIndex].Type != predicate.Comparator.ArgumentTypeFromFieldType(
|
||||
predicate.FieldReference.ReferencedField().Type) {
|
||||
return InvalidParamError
|
||||
requiredType := predicate.Comparator.ArgumentTypeFromFieldType(predicate.FieldReference.ReferencedField().Type)
|
||||
|
||||
if params[currentParamIndex].Type != requiredType {
|
||||
return NewArgumentTypeNotMatchedError(predicate.FieldReference.ReferencingCode(), requiredType,
|
||||
params[currentParamIndex].Type)
|
||||
}
|
||||
currentParamIndex++
|
||||
}
|
||||
|
|
|
@ -811,6 +811,30 @@ func TestParseInterfaceMethod_Update(t *testing.T) {
|
|||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "UpdateArgPushByArg method",
|
||||
Method: code.Method{
|
||||
Name: "UpdateAgeIncByID",
|
||||
Params: []code.Param{
|
||||
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
|
||||
{Type: code.SimpleType("int")},
|
||||
{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{ageField}, ParamIndex: 1, Operator: spec.UpdateOperatorInc},
|
||||
},
|
||||
Mode: spec.QueryModeOne,
|
||||
Query: spec.QuerySpec{Predicates: []spec.Predicate{
|
||||
{FieldReference: spec.FieldReference{idField}, Comparator: spec.ComparatorEqual, ParamIndex: 2},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "UpdateArgAndArgPushByArg method",
|
||||
Method: code.Method{
|
||||
|
@ -1564,7 +1588,7 @@ func TestParseInterfaceMethod_Find_Invalid(t *testing.T) {
|
|||
code.SimpleType("error"),
|
||||
},
|
||||
},
|
||||
ExpectedError: spec.InvalidParamError,
|
||||
ExpectedError: spec.NewArgumentTypeNotMatchedError(genderField.Name, genderField.Type, code.SimpleType("string")),
|
||||
},
|
||||
{
|
||||
Name: "mismatched method parameter type for special case",
|
||||
|
@ -1579,7 +1603,8 @@ func TestParseInterfaceMethod_Find_Invalid(t *testing.T) {
|
|||
code.SimpleType("error"),
|
||||
},
|
||||
},
|
||||
ExpectedError: spec.InvalidParamError,
|
||||
ExpectedError: spec.NewArgumentTypeNotMatchedError(cityField.Name,
|
||||
code.ArrayType{ContainedType: code.SimpleType("string")}, code.SimpleType("string")),
|
||||
},
|
||||
{
|
||||
Name: "misplaced operator token (leftmost)",
|
||||
|
@ -1716,7 +1741,29 @@ func TestParseInterfaceMethod_Update_Invalid(t *testing.T) {
|
|||
code.SimpleType("error"),
|
||||
},
|
||||
},
|
||||
ExpectedError: spec.PushNonArrayError,
|
||||
ExpectedError: spec.NewIncompatibleUpdateOperatorError(spec.UpdateOperatorPush, spec.FieldReference{{
|
||||
Name: "Gender",
|
||||
Type: code.SimpleType("Gender"),
|
||||
}}),
|
||||
},
|
||||
{
|
||||
Name: "inc operator in non-number field",
|
||||
Method: code.Method{
|
||||
Name: "UpdateCityIncByID",
|
||||
Params: []code.Param{
|
||||
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
|
||||
{Type: code.SimpleType("Gender")},
|
||||
{Type: code.ExternalType{PackageAlias: "primitive", Name: "ObjectID"}},
|
||||
},
|
||||
Returns: []code.Type{
|
||||
code.SimpleType("bool"),
|
||||
code.SimpleType("error"),
|
||||
},
|
||||
},
|
||||
ExpectedError: spec.NewIncompatibleUpdateOperatorError(spec.UpdateOperatorInc, spec.FieldReference{{
|
||||
Name: "City",
|
||||
Type: code.SimpleType("string"),
|
||||
}}),
|
||||
},
|
||||
{
|
||||
Name: "update method without query",
|
||||
|
@ -1762,7 +1809,8 @@ func TestParseInterfaceMethod_Update_Invalid(t *testing.T) {
|
|||
code.SimpleType("error"),
|
||||
},
|
||||
},
|
||||
ExpectedError: spec.InvalidUpdateFieldsError,
|
||||
ExpectedError: spec.NewArgumentTypeNotMatchedError(consentHistoryField.Name, code.SimpleType("ConsentHistoryItem"),
|
||||
code.ArrayType{ContainedType: code.SimpleType("ConsentHistoryItem")}),
|
||||
},
|
||||
{
|
||||
Name: "insufficient function parameters",
|
||||
|
@ -1839,7 +1887,7 @@ func TestParseInterfaceMethod_Update_Invalid(t *testing.T) {
|
|||
code.SimpleType("error"),
|
||||
},
|
||||
},
|
||||
ExpectedError: spec.InvalidUpdateFieldsError,
|
||||
ExpectedError: spec.NewArgumentTypeNotMatchedError(ageField.Name, ageField.Type, code.SimpleType("float64")),
|
||||
},
|
||||
{
|
||||
Name: "struct field does not match parameter in query",
|
||||
|
@ -1855,7 +1903,7 @@ func TestParseInterfaceMethod_Update_Invalid(t *testing.T) {
|
|||
code.SimpleType("error"),
|
||||
},
|
||||
},
|
||||
ExpectedError: spec.InvalidParamError,
|
||||
ExpectedError: spec.NewArgumentTypeNotMatchedError(genderField.Name, genderField.Type, code.SimpleType("string")),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -2019,7 +2067,7 @@ func TestParseInterfaceMethod_Delete_Invalid(t *testing.T) {
|
|||
code.SimpleType("error"),
|
||||
},
|
||||
},
|
||||
ExpectedError: spec.InvalidParamError,
|
||||
ExpectedError: spec.NewArgumentTypeNotMatchedError("Gender", code.SimpleType("Gender"), code.SimpleType("string")),
|
||||
},
|
||||
{
|
||||
Name: "mismatched method parameter type for special case",
|
||||
|
@ -2034,7 +2082,8 @@ func TestParseInterfaceMethod_Delete_Invalid(t *testing.T) {
|
|||
code.SimpleType("error"),
|
||||
},
|
||||
},
|
||||
ExpectedError: spec.InvalidParamError,
|
||||
ExpectedError: spec.NewArgumentTypeNotMatchedError("City",
|
||||
code.ArrayType{ContainedType: code.SimpleType("string")}, code.SimpleType("string")),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -2150,7 +2199,7 @@ func TestParseInterfaceMethod_Count_Invalid(t *testing.T) {
|
|||
code.SimpleType("error"),
|
||||
},
|
||||
},
|
||||
ExpectedError: spec.InvalidParamError,
|
||||
ExpectedError: spec.NewArgumentTypeNotMatchedError("Gender", code.SimpleType("Gender"), code.SimpleType("string")),
|
||||
},
|
||||
{
|
||||
Name: "struct field not found",
|
||||
|
|
|
@ -61,6 +61,7 @@ type UpdateOperator string
|
|||
const (
|
||||
UpdateOperatorSet UpdateOperator = "SET"
|
||||
UpdateOperatorPush UpdateOperator = "PUSH"
|
||||
UpdateOperatorInc UpdateOperator = "INC"
|
||||
)
|
||||
|
||||
// NumberOfArguments returns number of arguments required to perform an update operation
|
||||
|
@ -69,16 +70,13 @@ func (o UpdateOperator) NumberOfArguments() int {
|
|||
}
|
||||
|
||||
// ArgumentType returns type that is required for function parameter
|
||||
func (o UpdateOperator) ArgumentType(fieldType code.Type) (code.Type, error) {
|
||||
func (o UpdateOperator) ArgumentType(fieldType code.Type) code.Type {
|
||||
switch o {
|
||||
case UpdateOperatorPush:
|
||||
arrayType, ok := fieldType.(code.ArrayType)
|
||||
if !ok {
|
||||
return nil, PushNonArrayError
|
||||
}
|
||||
return arrayType.ContainedType, nil
|
||||
arrayType := fieldType.(code.ArrayType)
|
||||
return arrayType.ContainedType
|
||||
default:
|
||||
return fieldType, nil
|
||||
return fieldType
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,14 +145,12 @@ func (p interfaceMethodParser) parseUpdate(tokens []string) (Update, error) {
|
|||
return nil, InvalidUpdateFieldsError
|
||||
}
|
||||
|
||||
requiredType, err := field.Operator.ArgumentType(field.FieldReference.ReferencedField().Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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, InvalidUpdateFieldsError
|
||||
return nil, NewArgumentTypeNotMatchedError(field.FieldReference.ReferencingCode(), requiredType,
|
||||
p.Method.Params[field.ParamIndex+i].Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -166,6 +162,9 @@ func (p interfaceMethodParser) parseUpdateField(t []string, paramIndex int) (Upd
|
|||
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)
|
||||
}
|
||||
|
||||
|
@ -175,9 +174,24 @@ func (p interfaceMethodParser) createUpdateField(t []string, operator UpdateOper
|
|||
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
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue