diff --git a/README.md b/README.md index 9c03075..d0dd2d2 100644 --- a/README.md +++ b/README.md @@ -89,16 +89,16 @@ import ( "github.com/xdrm-io/aicra" "github.com/xdrm-io/aicra/api" - "github.com/xdrm-io/aicra/datatype/builtin" + "github.com/xdrm-io/aicra/validator/builtin" ) func main() { builder := &aicra.Builder{} - // register data validators - builder.AddType(builtin.BoolDataType{}) - builder.AddType(builtin.UintDataType{}) - builder.AddType(builtin.StringDataType{}) + // add custom type validators + builder.Validate(validator.BoolDataType{}) + builder.Validate(validator.UintDataType{}) + builder.Validate(validator.StringDataType{}) // load your configuration config, err := os.Open("api.json") diff --git a/builder.go b/builder.go index 0fc8d3b..fc17243 100644 --- a/builder.go +++ b/builder.go @@ -5,9 +5,9 @@ import ( "io" "net/http" - "github.com/xdrm-io/aicra/datatype" "github.com/xdrm-io/aicra/internal/config" "github.com/xdrm-io/aicra/internal/dynfunc" + "github.com/xdrm-io/aicra/validator" ) // Builder for an aicra server @@ -31,18 +31,18 @@ type apiHandler struct { dyn *dynfunc.Handler } -// AddType adds an available datatype to the api definition -func (b *Builder) AddType(t datatype.T) error { +// Validate adds an available Type to validate in the api definition +func (b *Builder) Validate(t validator.Type) error { if b.conf == nil { b.conf = &config.Server{} } if b.conf.Services != nil { return errLateType } - if b.conf.Types == nil { - b.conf.Types = make([]datatype.T, 0) + if b.conf.Validators == nil { + b.conf.Validators = make([]validator.Type, 0) } - b.conf.Types = append(b.conf.Types, t) + b.conf.Validators = append(b.conf.Validators, t) return nil } diff --git a/builder_test.go b/builder_test.go index 8bae2ac..fbbebe0 100644 --- a/builder_test.go +++ b/builder_test.go @@ -8,26 +8,26 @@ import ( "testing" "github.com/xdrm-io/aicra/api" - "github.com/xdrm-io/aicra/datatype/builtin" + "github.com/xdrm-io/aicra/validator" ) func addBuiltinTypes(b *Builder) error { - if err := b.AddType(builtin.AnyDataType{}); err != nil { + if err := b.Validate(validator.AnyType{}); err != nil { return err } - if err := b.AddType(builtin.BoolDataType{}); err != nil { + if err := b.Validate(validator.BoolType{}); err != nil { return err } - if err := b.AddType(builtin.FloatDataType{}); err != nil { + if err := b.Validate(validator.FloatType{}); err != nil { return err } - if err := b.AddType(builtin.IntDataType{}); err != nil { + if err := b.Validate(validator.IntType{}); err != nil { return err } - if err := b.AddType(builtin.StringDataType{}); err != nil { + if err := b.Validate(validator.StringType{}); err != nil { return err } - if err := b.AddType(builtin.UintDataType{}); err != nil { + if err := b.Validate(validator.UintType{}); err != nil { return err } return nil @@ -35,7 +35,7 @@ func addBuiltinTypes(b *Builder) error { func TestAddType(t *testing.T) { builder := &Builder{} - err := builder.AddType(builtin.BoolDataType{}) + err := builder.Validate(validator.BoolType{}) if err != nil { t.Fatalf("unexpected error: %s", err) } @@ -43,7 +43,7 @@ func TestAddType(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %s", err) } - err = builder.AddType(builtin.FloatDataType{}) + err = builder.Validate(validator.FloatType{}) if err != errLateType { t.Fatalf("expected <%v> got <%v>", errLateType, err) } diff --git a/datatype/builtin/any.go b/datatype/builtin/any.go deleted file mode 100644 index fdfd5f9..0000000 --- a/datatype/builtin/any.go +++ /dev/null @@ -1,26 +0,0 @@ -package builtin - -import ( - "reflect" - - "github.com/xdrm-io/aicra/datatype" -) - -// AnyDataType is what its name tells -type AnyDataType struct{} - -// Type returns the type of data -func (AnyDataType) Type() reflect.Type { - return reflect.TypeOf(interface{}(nil)) -} - -// Build returns the validator -func (AnyDataType) Build(typeName string, registry ...datatype.T) datatype.Validator { - // nothing if type not handled - if typeName != "any" { - return nil - } - return func(value interface{}) (interface{}, bool) { - return value, true - } -} diff --git a/datatype/datatype.go b/datatype/datatype.go deleted file mode 100644 index 2e0a264..0000000 --- a/datatype/datatype.go +++ /dev/null @@ -1,23 +0,0 @@ -package datatype - -import ( - "reflect" -) - -// Validator returns whether a given value fulfills the datatype -// and casts the value into a common go type. -// -// for example, if a validator checks for upper case strings, -// whether the value is a []byte, a string or a []rune, if the -// value matches the validator's checks, it will be cast it into -// a common go type, say, string. -type Validator func(value interface{}) (cast interface{}, valid bool) - -// T represents a datatype. The Build function returns a Validator if -// it manages types with the name `typeDefinition` (from the configuration field "type"); else it or returns NIL if the type -// definition does not match this datatype; the registry is passed to allow recursive datatypes (e.g. slices, structs, etc) -// The datatype's validator (when input is valid) must return a cast's go type matching the `Type() reflect.Type` -type T interface { - Type() reflect.Type - Build(typeDefinition string, registry ...T) Validator -} diff --git a/handler_test.go b/handler_test.go index 00311f1..fe3327e 100644 --- a/handler_test.go +++ b/handler_test.go @@ -12,26 +12,26 @@ import ( "github.com/xdrm-io/aicra" "github.com/xdrm-io/aicra/api" - "github.com/xdrm-io/aicra/datatype/builtin" + "github.com/xdrm-io/aicra/validator" ) func addBuiltinTypes(b *aicra.Builder) error { - if err := b.AddType(builtin.AnyDataType{}); err != nil { + if err := b.Validate(validator.AnyType{}); err != nil { return err } - if err := b.AddType(builtin.BoolDataType{}); err != nil { + if err := b.Validate(validator.BoolType{}); err != nil { return err } - if err := b.AddType(builtin.FloatDataType{}); err != nil { + if err := b.Validate(validator.FloatType{}); err != nil { return err } - if err := b.AddType(builtin.IntDataType{}); err != nil { + if err := b.Validate(validator.IntType{}); err != nil { return err } - if err := b.AddType(builtin.StringDataType{}); err != nil { + if err := b.Validate(validator.StringType{}); err != nil { return err } - if err := b.AddType(builtin.UintDataType{}); err != nil { + if err := b.Validate(validator.UintType{}); err != nil { return err } return nil diff --git a/internal/config/config.go b/internal/config/config.go index 1ac6fcc..cc529d0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -7,13 +7,13 @@ import ( "net/http" "strings" - "github.com/xdrm-io/aicra/datatype" + "github.com/xdrm-io/aicra/validator" ) // Server definition type Server struct { - Types []datatype.T - Services []*Service + Validators []validator.Type + Services []*Service } // Parse a configuration into a server. Server.Types must be set beforehand to @@ -32,9 +32,9 @@ func (srv *Server) Parse(r io.Reader) error { } // validate implements the validator interface -func (server Server) validate(datatypes ...datatype.T) error { +func (server Server) validate(datatypes ...validator.Type) error { for _, service := range server.Services { - err := service.validate(server.Types...) + err := service.validate(server.Validators...) if err != nil { return fmt.Errorf("%s '%s': %w", service.Method, service.Pattern, err) } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 030e027..5db787c 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - "github.com/xdrm-io/aicra/datatype/builtin" + "github.com/xdrm-io/aicra/validator" ) func TestLegalServiceName(t *testing.T) { @@ -239,7 +239,7 @@ func TestParamEmptyRenameNoRename(t *testing.T) { } ]`) srv := &Server{} - srv.Types = append(srv.Types, builtin.AnyDataType{}) + srv.Validators = append(srv.Validators, validator.AnyType{}) err := srv.Parse(r) if err != nil { t.Errorf("unexpected error: '%s'", err) @@ -275,8 +275,8 @@ func TestOptionalParam(t *testing.T) { } ]`) srv := &Server{} - srv.Types = append(srv.Types, builtin.AnyDataType{}) - srv.Types = append(srv.Types, builtin.BoolDataType{}) + srv.Validators = append(srv.Validators, validator.AnyType{}) + srv.Validators = append(srv.Validators, validator.BoolType{}) err := srv.Parse(r) if err != nil { t.Errorf("unexpected error: '%s'", err) @@ -588,7 +588,7 @@ func TestParseParameters(t *testing.T) { t.Run(fmt.Sprintf("method.%d", i), func(t *testing.T) { srv := &Server{} - srv.Types = append(srv.Types, builtin.AnyDataType{}) + srv.Validators = append(srv.Validators, validator.AnyType{}) err := srv.Parse(strings.NewReader(test.Raw)) if err == nil && test.Error != nil { @@ -827,8 +827,8 @@ func TestServiceCollision(t *testing.T) { t.Run(fmt.Sprintf("method.%d", i), func(t *testing.T) { srv := &Server{} - srv.Types = append(srv.Types, builtin.StringDataType{}) - srv.Types = append(srv.Types, builtin.UintDataType{}) + srv.Validators = append(srv.Validators, validator.StringType{}) + srv.Validators = append(srv.Validators, validator.UintType{}) err := srv.Parse(strings.NewReader(test.Config)) if err == nil && test.Error != nil { @@ -997,9 +997,9 @@ func TestMatchSimple(t *testing.T) { t.Run(fmt.Sprintf("method.%d", i), func(t *testing.T) { srv := &Server{} - srv.Types = append(srv.Types, builtin.AnyDataType{}) - srv.Types = append(srv.Types, builtin.IntDataType{}) - srv.Types = append(srv.Types, builtin.BoolDataType{}) + srv.Validators = append(srv.Validators, validator.AnyType{}) + srv.Validators = append(srv.Validators, validator.IntType{}) + srv.Validators = append(srv.Validators, validator.BoolType{}) err := srv.Parse(strings.NewReader(test.Config)) if err != nil { @@ -1081,9 +1081,9 @@ func TestFindPriority(t *testing.T) { t.Run(fmt.Sprintf("method.%d", i), func(t *testing.T) { srv := &Server{} - srv.Types = append(srv.Types, builtin.AnyDataType{}) - srv.Types = append(srv.Types, builtin.IntDataType{}) - srv.Types = append(srv.Types, builtin.BoolDataType{}) + srv.Validators = append(srv.Validators, validator.AnyType{}) + srv.Validators = append(srv.Validators, validator.IntType{}) + srv.Validators = append(srv.Validators, validator.BoolType{}) err := srv.Parse(strings.NewReader(test.Config)) if err != nil { diff --git a/internal/config/parameter.go b/internal/config/parameter.go index d0e5ab7..a22227b 100644 --- a/internal/config/parameter.go +++ b/internal/config/parameter.go @@ -3,7 +3,7 @@ package config import ( "reflect" - "github.com/xdrm-io/aicra/datatype" + "github.com/xdrm-io/aicra/validator" ) // Parameter represents a parameter definition (from api.json) @@ -12,13 +12,13 @@ type Parameter struct { Type string `json:"type"` Rename string `json:"name,omitempty"` Optional bool - // ExtractType is the type the Validator will cast into - ExtractType reflect.Type + // GoType is the type the Validator will cast into + GoType reflect.Type // Validator is inferred from the "type" property - Validator datatype.Validator + Validator validator.ValidateFunc } -func (param *Parameter) validate(datatypes ...datatype.T) error { +func (param *Parameter) validate(datatypes ...validator.Type) error { if len(param.Description) < 1 { return errMissingParamDesc } @@ -35,8 +35,8 @@ func (param *Parameter) validate(datatypes ...datatype.T) error { // find validator for _, dtype := range datatypes { - param.Validator = dtype.Build(param.Type, datatypes...) - param.ExtractType = dtype.Type() + param.Validator = dtype.Validator(param.Type, datatypes...) + param.GoType = dtype.GoType() if param.Validator != nil { break } diff --git a/internal/config/service.go b/internal/config/service.go index 3b051ba..6d10d27 100644 --- a/internal/config/service.go +++ b/internal/config/service.go @@ -6,7 +6,7 @@ import ( "regexp" "strings" - "github.com/xdrm-io/aicra/datatype" + "github.com/xdrm-io/aicra/validator" ) var braceRegex = regexp.MustCompile(`^{([a-z_-]+)}$`) @@ -97,7 +97,7 @@ func (svc *Service) matchPattern(uri string) bool { } // Validate implements the validator interface -func (svc *Service) validate(datatypes ...datatype.T) error { +func (svc *Service) validate(datatypes ...validator.Type) error { // check method err := svc.isMethodAvailable() if err != nil { @@ -195,7 +195,7 @@ func (svc *Service) isPatternValid() error { return nil } -func (svc *Service) validateInput(types []datatype.T) error { +func (svc *Service) validateInput(types []validator.Type) error { // ignore no parameter if svc.Input == nil || len(svc.Input) < 1 { @@ -285,7 +285,7 @@ func (svc *Service) validateInput(types []datatype.T) error { return nil } -func (svc *Service) validateOutput(types []datatype.T) error { +func (svc *Service) validateOutput(types []validator.Type) error { // ignore no parameter if svc.Output == nil || len(svc.Output) < 1 { diff --git a/internal/dynfunc/signature.go b/internal/dynfunc/signature.go index 35783ab..d5c30aa 100644 --- a/internal/dynfunc/signature.go +++ b/internal/dynfunc/signature.go @@ -31,17 +31,17 @@ func BuildSignature(service config.Service) *Signature { } // make a pointer if optional if param.Optional { - s.Input[param.Rename] = reflect.PtrTo(param.ExtractType) + s.Input[param.Rename] = reflect.PtrTo(param.GoType) continue } - s.Input[param.Rename] = param.ExtractType + s.Input[param.Rename] = param.GoType } for _, param := range service.Output { if len(param.Rename) < 1 { continue } - s.Output[param.Rename] = param.ExtractType + s.Output[param.Rename] = param.GoType } return s diff --git a/validator/any.go b/validator/any.go new file mode 100644 index 0000000..c9b2ef0 --- /dev/null +++ b/validator/any.go @@ -0,0 +1,24 @@ +package validator + +import ( + "reflect" +) + +// AnyType makes the "any" type available in the aicra configuration +// It considers valid any value +type AnyType struct{} + +// GoType returns the interface{} type +func (AnyType) GoType() reflect.Type { + return reflect.TypeOf(interface{}(nil)) +} + +// Validator that considers any value valid +func (AnyType) Validator(typename string, avail ...Type) ValidateFunc { + if typename != "any" { + return nil + } + return func(value interface{}) (interface{}, bool) { + return value, true + } +} diff --git a/datatype/builtin/any_test.go b/validator/any_test.go similarity index 84% rename from datatype/builtin/any_test.go rename to validator/any_test.go index 8d9ae03..d612047 100644 --- a/datatype/builtin/any_test.go +++ b/validator/any_test.go @@ -1,16 +1,16 @@ -package builtin_test +package validator_test import ( "fmt" "testing" - "github.com/xdrm-io/aicra/datatype/builtin" + "github.com/xdrm-io/aicra/validator" ) func TestAny_AvailableTypes(t *testing.T) { t.Parallel() - dt := builtin.AnyDataType{} + dt := validator.AnyType{} tests := []struct { Type string @@ -26,7 +26,7 @@ func TestAny_AvailableTypes(t *testing.T) { } for _, test := range tests { - validator := dt.Build(test.Type) + validator := dt.Validator(test.Type) if validator == nil { if test.Handled { @@ -47,7 +47,7 @@ func TestAny_AlwaysTrue(t *testing.T) { const typeName = "any" - validator := builtin.AnyDataType{}.Build(typeName) + validator := validator.AnyType{}.Validator(typeName) if validator == nil { t.Errorf("expect %q to be handled", typeName) t.Fail() diff --git a/datatype/builtin/bool.go b/validator/bool.go similarity index 51% rename from datatype/builtin/bool.go rename to validator/bool.go index 720c59d..9a93069 100644 --- a/datatype/builtin/bool.go +++ b/validator/bool.go @@ -1,23 +1,24 @@ -package builtin +package validator import ( "reflect" - - "github.com/xdrm-io/aicra/datatype" ) -// BoolDataType is what its name tells -type BoolDataType struct{} +// BoolType makes the "bool" type available in the aicra configuration +// It considers valid: +// - booleans +// - strings containing "true" or "false" +// - []byte containing "true" or "false" +type BoolType struct{} -// Type returns the type of data -func (BoolDataType) Type() reflect.Type { +// GoType returns the `bool` type +func (BoolType) GoType() reflect.Type { return reflect.TypeOf(true) } -// Build returns the validator -func (BoolDataType) Build(typeName string, registry ...datatype.T) datatype.Validator { - // nothing if type not handled - if typeName != "bool" { +// Validator for bool values +func (BoolType) Validator(typename string, avail ...Type) ValidateFunc { + if typename != "bool" { return nil } diff --git a/datatype/builtin/bool_test.go b/validator/bool_test.go similarity index 88% rename from datatype/builtin/bool_test.go rename to validator/bool_test.go index c44b262..8600d34 100644 --- a/datatype/builtin/bool_test.go +++ b/validator/bool_test.go @@ -1,16 +1,16 @@ -package builtin_test +package validator_test import ( "fmt" "testing" - "github.com/xdrm-io/aicra/datatype/builtin" + "github.com/xdrm-io/aicra/validator" ) func TestBool_AvailableTypes(t *testing.T) { t.Parallel() - dt := builtin.BoolDataType{} + dt := validator.BoolType{} tests := []struct { Type string @@ -26,7 +26,7 @@ func TestBool_AvailableTypes(t *testing.T) { for _, test := range tests { t.Run(test.Type, func(t *testing.T) { - validator := dt.Build(test.Type) + validator := dt.Validator(test.Type) if validator == nil { if test.Handled { t.Errorf("expect %q to be handled", test.Type) @@ -49,7 +49,7 @@ func TestBool_Values(t *testing.T) { const typeName = "bool" - validator := builtin.BoolDataType{}.Build(typeName) + validator := validator.BoolType{}.Validator(typeName) if validator == nil { t.Errorf("expect %q to be handled", typeName) t.Fail() diff --git a/datatype/builtin/float.go b/validator/float.go similarity index 52% rename from datatype/builtin/float.go rename to validator/float.go index 6bb39f9..b6d7e53 100644 --- a/datatype/builtin/float.go +++ b/validator/float.go @@ -1,24 +1,27 @@ -package builtin +package validator import ( "encoding/json" "reflect" - - "github.com/xdrm-io/aicra/datatype" ) -// FloatDataType is what its name tells -type FloatDataType struct{} +// FloatType makes the "float" (or "float64") type available in the aicra configuration +// It considers valid: +// - float64 +// - int (since it does not overflow) +// - uint (since it does not overflow) +// - strings containing json-compatible floats +// - []byte containing json-compatible floats +type FloatType struct{} -// Type returns the type of data -func (FloatDataType) Type() reflect.Type { +// GoType returns the `float64` type +func (FloatType) GoType() reflect.Type { return reflect.TypeOf(float64(0)) } -// Build returns the validator -func (FloatDataType) Build(typeName string, registry ...datatype.T) datatype.Validator { - // nothing if type not handled - if typeName != "float64" && typeName != "float" { +// Validator for float64 values +func (FloatType) Validator(typename string, avail ...Type) ValidateFunc { + if typename != "float64" && typename != "float" { return nil } return func(value interface{}) (interface{}, bool) { diff --git a/datatype/builtin/float_test.go b/validator/float_test.go similarity index 91% rename from datatype/builtin/float_test.go rename to validator/float_test.go index 193dd7c..b965441 100644 --- a/datatype/builtin/float_test.go +++ b/validator/float_test.go @@ -1,17 +1,17 @@ -package builtin_test +package validator_test import ( "fmt" "math" "testing" - "github.com/xdrm-io/aicra/datatype/builtin" + "github.com/xdrm-io/aicra/validator" ) func TestFloat64_AvailableTypes(t *testing.T) { t.Parallel() - dt := builtin.FloatDataType{} + dt := validator.FloatType{} tests := []struct { Type string @@ -33,7 +33,7 @@ func TestFloat64_AvailableTypes(t *testing.T) { for _, test := range tests { t.Run(test.Type, func(t *testing.T) { - validator := dt.Build(test.Type) + validator := dt.Validator(test.Type) if validator == nil { if test.Handled { t.Errorf("expect %q to be handled", test.Type) @@ -56,7 +56,7 @@ func TestFloat64_Values(t *testing.T) { const typeName = "float" - validator := builtin.FloatDataType{}.Build(typeName) + validator := validator.FloatType{}.Validator(typeName) if validator == nil { t.Errorf("expect %q to be handled", typeName) t.Fail() diff --git a/datatype/builtin/int.go b/validator/int.go similarity index 63% rename from datatype/builtin/int.go rename to validator/int.go index b1473a6..d7ec83c 100644 --- a/datatype/builtin/int.go +++ b/validator/int.go @@ -1,25 +1,29 @@ -package builtin +package validator import ( "encoding/json" "math" "reflect" - - "github.com/xdrm-io/aicra/datatype" ) -// IntDataType is what its name tells -type IntDataType struct{} +// IntType makes the "int" type available in the aicra configuration +// It considers valid: +// - int +// - float64 (since it does not overflow) +// - uint (since it does not overflow) +// - strings containing json-compatible integers +// - []byte containing json-compatible integers +type IntType struct{} -// Type returns the type of data -func (IntDataType) Type() reflect.Type { +// GoType returns the `int` type +func (IntType) GoType() reflect.Type { return reflect.TypeOf(int(0)) } -// Build returns the validator -func (IntDataType) Build(typeName string, registry ...datatype.T) datatype.Validator { +// Validator for int values +func (IntType) Validator(typename string, avail ...Type) ValidateFunc { // nothing if type not handled - if typeName != "int" { + if typename != "int" { return nil } diff --git a/datatype/builtin/int_test.go b/validator/int_test.go similarity index 91% rename from datatype/builtin/int_test.go rename to validator/int_test.go index 8b7a157..14dc515 100644 --- a/datatype/builtin/int_test.go +++ b/validator/int_test.go @@ -1,17 +1,17 @@ -package builtin_test +package validator_test import ( "fmt" "math" "testing" - "github.com/xdrm-io/aicra/datatype/builtin" + "github.com/xdrm-io/aicra/validator" ) func TestInt_AvailableTypes(t *testing.T) { t.Parallel() - dt := builtin.IntDataType{} + dt := validator.IntType{} tests := []struct { Type string @@ -27,7 +27,7 @@ func TestInt_AvailableTypes(t *testing.T) { for _, test := range tests { t.Run(test.Type, func(t *testing.T) { - validator := dt.Build(test.Type) + validator := dt.Validator(test.Type) if validator == nil { if test.Handled { t.Errorf("expect %q to be handled", test.Type) @@ -50,7 +50,7 @@ func TestInt_Values(t *testing.T) { const typeName = "int" - validator := builtin.IntDataType{}.Build(typeName) + validator := validator.IntType{}.Validator(typeName) if validator == nil { t.Errorf("expect %q to be handled", typeName) t.Fail() diff --git a/datatype/builtin/string.go b/validator/string.go similarity index 63% rename from datatype/builtin/string.go rename to validator/string.go index 902daf7..307c7cd 100644 --- a/datatype/builtin/string.go +++ b/validator/string.go @@ -1,32 +1,37 @@ -package builtin +package validator import ( "reflect" "regexp" "strconv" - - "github.com/xdrm-io/aicra/datatype" ) -var fixedLengthRegex = regexp.MustCompile(`^string\((\d+)\)$`) -var variableLengthRegex = regexp.MustCompile(`^string\((\d+), ?(\d+)\)$`) +var ( + fixedLengthRegex = regexp.MustCompile(`^string\((\d+)\)$`) + variableLengthRegex = regexp.MustCompile(`^string\((\d+), ?(\d+)\)$`) +) -// StringDataType is what its name tells -type StringDataType struct{} +// StringType makes the types beloz available in the aicra configuration: +// - "string" considers any string valid +// - "string(n)" considers any string with an exact size of `n` valid +// - "string(a,b)" considers any string with a size between `a` and `b` valid +// > for the last one, `a` and `b` are included in the valid sizes +type StringType struct{} -// Type returns the type of data -func (StringDataType) Type() reflect.Type { +// GoType returns the `string` type +func (StringType) GoType() reflect.Type { return reflect.TypeOf(string("")) } -// Build returns the validator. -// availables type names are : `string`, `string(length)` and `string(minLength, maxLength)`. -func (s StringDataType) Build(typeName string, registry ...datatype.T) datatype.Validator { - simple := typeName == "string" - fixedLengthMatches := fixedLengthRegex.FindStringSubmatch(typeName) - variableLengthMatches := variableLengthRegex.FindStringSubmatch(typeName) +// Validator for strings with any/fixed/bound sizes +func (s StringType) Validator(typename string, avail ...Type) ValidateFunc { + var ( + simple = (typename == "string") + fixedLengthMatches = fixedLengthRegex.FindStringSubmatch(typename) + variableLengthMatches = variableLengthRegex.FindStringSubmatch(typename) + ) - // nothing if type not handled + // ignore unknown typename if !simple && fixedLengthMatches == nil && variableLengthMatches == nil { return nil } @@ -40,7 +45,7 @@ func (s StringDataType) Build(typeName string, registry ...datatype.T) datatype. if fixedLengthMatches != nil { exLen, ok := s.getFixedLength(fixedLengthMatches) if !ok { - mustFail = true + return nil } min = exLen max = exLen @@ -49,7 +54,7 @@ func (s StringDataType) Build(typeName string, registry ...datatype.T) datatype. } else if variableLengthMatches != nil { exMin, exMax, ok := s.getVariableLength(variableLengthMatches) if !ok { - mustFail = true + return nil } min = exMin max = exMax @@ -84,7 +89,7 @@ func (s StringDataType) Build(typeName string, registry ...datatype.T) datatype. } // getFixedLength returns the fixed length from regex matches and a success state. -func (StringDataType) getFixedLength(regexMatches []string) (int, bool) { +func (StringType) getFixedLength(regexMatches []string) (int, bool) { // incoherence error if regexMatches == nil || len(regexMatches) < 2 { return 0, false @@ -100,7 +105,7 @@ func (StringDataType) getFixedLength(regexMatches []string) (int, bool) { } // getVariableLength returns the length min and max from regex matches and a success state. -func (StringDataType) getVariableLength(regexMatches []string) (int, int, bool) { +func (StringType) getVariableLength(regexMatches []string) (int, int, bool) { // incoherence error if regexMatches == nil || len(regexMatches) < 3 { return 0, 0, false diff --git a/datatype/builtin/string_test.go b/validator/string_test.go similarity index 93% rename from datatype/builtin/string_test.go rename to validator/string_test.go index 4441525..0a038ef 100644 --- a/datatype/builtin/string_test.go +++ b/validator/string_test.go @@ -1,16 +1,16 @@ -package builtin_test +package validator_test import ( "fmt" "testing" - "github.com/xdrm-io/aicra/datatype/builtin" + "github.com/xdrm-io/aicra/validator" ) func TestString_AvailableTypes(t *testing.T) { t.Parallel() - dt := builtin.StringDataType{} + dt := validator.StringType{} tests := []struct { Type string @@ -53,7 +53,7 @@ func TestString_AvailableTypes(t *testing.T) { for _, test := range tests { t.Run(test.Type, func(t *testing.T) { - validator := dt.Build(test.Type) + validator := dt.Validator(test.Type) if validator == nil { if test.Handled { @@ -75,7 +75,7 @@ func TestString_AnyLength(t *testing.T) { const typeName = "string" - validator := builtin.StringDataType{}.Build(typeName) + validator := validator.StringType{}.Validator(typeName) if validator == nil { t.Errorf("expect %q to be handled", typeName) t.Fail() @@ -133,7 +133,7 @@ func TestString_FixedLength(t *testing.T) { for i, test := range tests { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { - validator := builtin.StringDataType{}.Build(test.Type) + validator := validator.StringType{}.Validator(test.Type) if validator == nil { t.Errorf("expect %q to be handled", test.Type) t.Fail() @@ -194,7 +194,7 @@ func TestString_VariableLength(t *testing.T) { for i, test := range tests { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { - validator := builtin.StringDataType{}.Build(test.Type) + validator := validator.StringType{}.Validator(test.Type) if validator == nil { t.Errorf("expect %q to be handled", test.Type) t.Fail() diff --git a/datatype/builtin/uint.go b/validator/uint.go similarity index 65% rename from datatype/builtin/uint.go rename to validator/uint.go index 990d15b..0df72f4 100644 --- a/datatype/builtin/uint.go +++ b/validator/uint.go @@ -1,25 +1,28 @@ -package builtin +package validator import ( "encoding/json" "math" "reflect" - - "github.com/xdrm-io/aicra/datatype" ) -// UintDataType is what its name tells -type UintDataType struct{} +// UintType makes the "uint" type available in the aicra configuration +// It considers valid: +// - uint +// - int (since it does not overflow) +// - float64 (since it does not overflow) +// - strings containing json-compatible integers +// - []byte containing json-compatible integers +type UintType struct{} -// Type returns the type of data -func (UintDataType) Type() reflect.Type { +// GoType returns the `uint` type +func (UintType) GoType() reflect.Type { return reflect.TypeOf(uint(0)) } -// Build returns the validator -func (UintDataType) Build(typeName string, registry ...datatype.T) datatype.Validator { - // nothing if type not handled - if typeName != "uint" { +// Validator for uint values +func (UintType) Validator(other string, avail ...Type) ValidateFunc { + if other != "uint" { return nil } diff --git a/datatype/builtin/uint_test.go b/validator/uint_test.go similarity index 92% rename from datatype/builtin/uint_test.go rename to validator/uint_test.go index 6175321..4fc9be9 100644 --- a/datatype/builtin/uint_test.go +++ b/validator/uint_test.go @@ -1,17 +1,17 @@ -package builtin_test +package validator_test import ( "fmt" "math" "testing" - "github.com/xdrm-io/aicra/datatype/builtin" + "github.com/xdrm-io/aicra/validator" ) func TestUint_AvailableTypes(t *testing.T) { t.Parallel() - dt := builtin.UintDataType{} + dt := validator.UintType{} tests := []struct { Type string @@ -27,7 +27,7 @@ func TestUint_AvailableTypes(t *testing.T) { for _, test := range tests { t.Run(test.Type, func(t *testing.T) { - validator := dt.Build(test.Type) + validator := dt.Validator(test.Type) if validator == nil { if test.Handled { t.Errorf("expect %q to be handled", test.Type) @@ -50,7 +50,7 @@ func TestUint_Values(t *testing.T) { const typeName = "uint" - validator := builtin.UintDataType{}.Build(typeName) + validator := validator.UintType{}.Validator(typeName) if validator == nil { t.Errorf("expect %q to be handled", typeName) t.Fail() diff --git a/validator/validator.go b/validator/validator.go new file mode 100644 index 0000000..fa3d389 --- /dev/null +++ b/validator/validator.go @@ -0,0 +1,55 @@ +package validator + +import ( + "reflect" +) + +// ValidateFunc returns whether a given value fulfills the datatype and casts +// the value into a go type. +// +// for example, if a validator checks for upper case strings, whether the value +// is a []byte, a string or a []rune, if the value matches is all upper-case, it +// will be cast into a go type, say, string. +type ValidateFunc func(value interface{}) (cast interface{}, valid bool) + +// Type defines an available in/out parameter "type" for the aicra configuration +// +// A Type maps to a go type in order to generate the handler signature from the +// aicra configuration +// +// A Type returns a custom validator when the typename matches +type Type interface { + // Validator function when the typename matches. It must return nil when the + // typename does not match + // + // The `typename` argument has to match types used in your aicra configuration + // in parameter definitions ("in", "out") and in the "type" json field. + // + // basic example: + // - `IntType.Validator("string")`` should return nil + // - `IntType.Validator("int")`` should return its ValidateFunc + // + // The `typename` is not returned by a simple method i.e. `TypeName() string` + // because it allows for validation relative to the typename, for instance: + // - `VarcharType.Validator("varchar")` valides any string + // - `VarcharType.Validator("varchar(2)")` validates any string of 2 + // characters + // - `VarcharType.Validator("varchar(1,3)")` validates any string + // with a length between 1 and 3 + // + // The `avail` argument represents all other available Types. It allows a + // Type to use other available Types internally. + // + // recursive example: slices + // - `SliceType.Validator("[]int", avail...)` validates a slice containing + // values that are valide to the `IntType` + // - `SliceType.Validator("[]varchar", avail...)` validates a slice containing + // values that are valid to the `VarcharType` + // + // and so on.. this works for maps, structs, etc + Validator(typename string, avail ...Type) ValidateFunc + + // GoType must return the go type associated with the output type of ValidateFunc. + // It is used to define handlers' signature from the configuration file. + GoType() reflect.Type +}