From eef94ff9984406685cf90910d5a7f0349a2c3daa Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 22 Mar 2020 16:37:43 +0100 Subject: [PATCH 1/6] also check method when finding missing handlers --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index bc11038..10303fa 100644 --- a/server.go +++ b/server.go @@ -64,7 +64,7 @@ func (s Server) ToHTTPServer() (*httpServer, error) { for _, service := range s.config.Services { found := false for _, handler := range s.handlers { - if handler.GetPath() == service.Pattern { + if handler.GetMethod() == service.Method && handler.GetPath() == service.Pattern { found = true break } From 5f3aa5967de300fb14708ed82e9ed2df63773ccb Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 22 Mar 2020 16:50:10 +0100 Subject: [PATCH 2/6] provide datatype registry to every type to allow for recursive datatypes : slices, maps, structs --- datatype/builtin/any.go | 2 +- datatype/builtin/bool.go | 2 +- datatype/builtin/float.go | 2 +- datatype/builtin/int.go | 2 +- datatype/builtin/string.go | 2 +- datatype/builtin/uint.go | 2 +- datatype/types.go | 8 ++++---- internal/config/parameter.go | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/datatype/builtin/any.go b/datatype/builtin/any.go index c7af7e9..e12dd10 100644 --- a/datatype/builtin/any.go +++ b/datatype/builtin/any.go @@ -6,7 +6,7 @@ import "git.xdrm.io/go/aicra/datatype" type AnyDataType struct{} // Build returns the validator -func (AnyDataType) Build(typeName string) datatype.Validator { +func (AnyDataType) Build(typeName string, registry ...datatype.T) datatype.Validator { // nothing if type not handled if typeName != "any" { return nil diff --git a/datatype/builtin/bool.go b/datatype/builtin/bool.go index 2b0240b..1d5e225 100644 --- a/datatype/builtin/bool.go +++ b/datatype/builtin/bool.go @@ -6,7 +6,7 @@ import "git.xdrm.io/go/aicra/datatype" type BoolDataType struct{} // Build returns the validator -func (BoolDataType) Build(typeName string) datatype.Validator { +func (BoolDataType) Build(typeName string, registry ...datatype.T) datatype.Validator { // nothing if type not handled if typeName != "bool" { return nil diff --git a/datatype/builtin/float.go b/datatype/builtin/float.go index 6f64e58..d2f3204 100644 --- a/datatype/builtin/float.go +++ b/datatype/builtin/float.go @@ -10,7 +10,7 @@ import ( type FloatDataType struct{} // Build returns the validator -func (FloatDataType) Build(typeName string) datatype.Validator { +func (FloatDataType) Build(typeName string, registry ...datatype.T) datatype.Validator { // nothing if type not handled if typeName != "float64" && typeName != "float" { return nil diff --git a/datatype/builtin/int.go b/datatype/builtin/int.go index 453c4f1..7dfdbf9 100644 --- a/datatype/builtin/int.go +++ b/datatype/builtin/int.go @@ -11,7 +11,7 @@ import ( type IntDataType struct{} // Build returns the validator -func (IntDataType) Build(typeName string) datatype.Validator { +func (IntDataType) Build(typeName string, registry ...datatype.T) datatype.Validator { // nothing if type not handled if typeName != "int" { return nil diff --git a/datatype/builtin/string.go b/datatype/builtin/string.go index afee83a..1a94b82 100644 --- a/datatype/builtin/string.go +++ b/datatype/builtin/string.go @@ -15,7 +15,7 @@ type StringDataType struct{} // Build returns the validator. // availables type names are : `string`, `string(length)` and `string(minLength, maxLength)`. -func (s StringDataType) Build(typeName string) datatype.Validator { +func (s StringDataType) Build(typeName string, registry ...datatype.T) datatype.Validator { simple := typeName == "string" fixedLengthMatches := fixedLengthRegex.FindStringSubmatch(typeName) variableLengthMatches := variableLengthRegex.FindStringSubmatch(typeName) diff --git a/datatype/builtin/uint.go b/datatype/builtin/uint.go index 3f5bd76..53f71de 100644 --- a/datatype/builtin/uint.go +++ b/datatype/builtin/uint.go @@ -11,7 +11,7 @@ import ( type UintDataType struct{} // Build returns the validator -func (UintDataType) Build(typeName string) datatype.Validator { +func (UintDataType) Build(typeName string, registry ...datatype.T) datatype.Validator { // nothing if type not handled if typeName != "uint" { return nil diff --git a/datatype/types.go b/datatype/types.go index ad4be63..fe529f1 100644 --- a/datatype/types.go +++ b/datatype/types.go @@ -4,9 +4,9 @@ package datatype // and casts the value into a compatible type type Validator func(value interface{}) (cast interface{}, valid bool) -// T builds a T from the type definition (from the -// configuration field "type") and returns NIL if the type -// definition does not match this T +// T builds a T from the type definition (from the configuration field "type") and returns NIL if the type +// definition does not match this T ; the registry is passed for recursive datatypes (e.g. slices, structs, etc) +// to be able to access other datatypes type T interface { - Build(typeDefinition string) Validator + Build(typeDefinition string, registry ...T) Validator } diff --git a/internal/config/parameter.go b/internal/config/parameter.go index af08e49..9dc43fa 100644 --- a/internal/config/parameter.go +++ b/internal/config/parameter.go @@ -26,7 +26,7 @@ func (param *Parameter) checkAndFormat() error { // assigns the first matching data type from the type definition func (param *Parameter) assignDataType(types []datatype.T) bool { for _, dtype := range types { - param.Validator = dtype.Build(param.Type) + param.Validator = dtype.Build(param.Type, types...) if param.Validator != nil { return true } From 54705b747276a4a6e74f5e27496c2c1c88b9dd1f Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sat, 28 Mar 2020 12:26:11 +0100 Subject: [PATCH 3/6] create validator interface for config --- internal/config/types.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/config/types.go b/internal/config/types.go index 25be16b..4b356a5 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -8,6 +8,11 @@ import ( var availableHTTPMethods = []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete} +// validator unifies the check and format routine +type validator interface { + Validate(...datatype.T) error +} + // Server represents a full server configuration type Server struct { Types []datatype.T From dac9aa4298330f431672236d11be378749796245 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sat, 28 Mar 2020 12:28:58 +0100 Subject: [PATCH 4/6] implement validator interface for config.parameter --- internal/config/parameter.go | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/internal/config/parameter.go b/internal/config/parameter.go index 9dc43fa..4037ee6 100644 --- a/internal/config/parameter.go +++ b/internal/config/parameter.go @@ -2,8 +2,8 @@ package config import "git.xdrm.io/go/aicra/datatype" -func (param *Parameter) checkAndFormat() error { - +// Validate implements the validator interface +func (param *Parameter) Validate(datatypes ...datatype.T) error { // missing description if len(param.Description) < 1 { return ErrMissingParamDesc @@ -14,7 +14,7 @@ func (param *Parameter) checkAndFormat() error { return ErrMissingParamType } - // set optional + type + // optional type transform if param.Type[0] == '?' { param.Optional = true param.Type = param.Type[1:] @@ -22,14 +22,3 @@ func (param *Parameter) checkAndFormat() error { return nil } - -// assigns the first matching data type from the type definition -func (param *Parameter) assignDataType(types []datatype.T) bool { - for _, dtype := range types { - param.Validator = dtype.Build(param.Type, types...) - if param.Validator != nil { - return true - } - } - return false -} From af3ffa7d6a35be0a7ccd91b052cf1bff7efdad01 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sat, 28 Mar 2020 12:30:57 +0100 Subject: [PATCH 5/6] implement validator interface for config.service; rename for readability --- internal/config/service.go | 145 ++++++++++++++++++++++++------------- 1 file changed, 95 insertions(+), 50 deletions(-) diff --git a/internal/config/service.go b/internal/config/service.go index 7e1e6a9..9ea781a 100644 --- a/internal/config/service.go +++ b/internal/config/service.go @@ -30,7 +30,88 @@ func (svc *Service) Match(req *http.Request) bool { return true } -func (svc *Service) checkMethod() error { +// checks if an uri matches the service's pattern +func (svc *Service) matchPattern(uri string) bool { + uriparts := SplitURL(uri) + parts := SplitURL(svc.Pattern) + + // fail if size differ + if len(uriparts) != len(parts) { + return false + } + + // root url '/' + if len(parts) == 0 { + return true + } + + // check part by part + for i, part := range parts { + uripart := uriparts[i] + + isCapture := len(part) > 0 && part[0] == '{' + + // if no capture -> check equality + if !isCapture { + if part != uripart { + return false + } + continue + } + + param, exists := svc.Input[part] + + // fail if no validator + if !exists || param.Validator == nil { + return false + } + + // fail if not type-valid + if _, valid := param.Validator(uripart); !valid { + return false + } + } + + return true +} + +// Validate implements the validator interface +func (svc *Service) Validate(datatypes ...datatype.T) error { + // check method + err := svc.isMethodAvailable() + if err != nil { + return fmt.Errorf("field 'method': %w", err) + } + + // check pattern + svc.Pattern = strings.Trim(svc.Pattern, " \t\r\n") + err = svc.isPatternValid() + if err != nil { + return fmt.Errorf("field 'path': %w", err) + } + + // check description + if len(strings.Trim(svc.Description, " \t\r\n")) < 1 { + return fmt.Errorf("field 'description': %w", ErrMissingDescription) + } + + // check input parameters + err = svc.validateInput(datatypes) + if err != nil { + return fmt.Errorf("field 'in': %w", err) + } + + // fail if a brace capture remains undefined + for _, capture := range svc.Captures { + if capture.Ref == nil { + return fmt.Errorf("field 'in': %s: %w", capture.Name, ErrUndefinedBraceCapture) + } + } + + return nil +} + +func (svc *Service) isMethodAvailable() error { for _, available := range availableHTTPMethods { if svc.Method == available { return nil @@ -39,7 +120,7 @@ func (svc *Service) checkMethod() error { return ErrUnknownMethod } -func (svc *Service) checkPattern() error { +func (svc *Service) isPatternValid() error { length := len(svc.Pattern) // empty pattern @@ -87,7 +168,7 @@ func (svc *Service) checkPattern() error { return nil } -func (svc *Service) checkAndFormatInput(types []datatype.T) error { +func (svc *Service) validateInput(types []datatype.T) error { // ignore no parameter if svc.Input == nil || len(svc.Input) < 1 { @@ -141,7 +222,7 @@ func (svc *Service) checkAndFormatInput(types []datatype.T) error { param.Rename = paramName } - err := param.checkAndFormat() + err := param.Validate() if err != nil { return fmt.Errorf("%s: %w", paramName, err) } @@ -151,7 +232,16 @@ func (svc *Service) checkAndFormatInput(types []datatype.T) error { return fmt.Errorf("%s: %w", paramName, ErrIllegalOptionalURIParam) } - if !param.assignDataType(types) { + // assign the datatype + datatypeFound := false + for _, dtype := range types { + param.Validator = dtype.Build(param.Type, types...) + if param.Validator != nil { + datatypeFound = true + break + } + } + if !datatypeFound { return fmt.Errorf("%s: %w", paramName, ErrUnknownDataType) } @@ -175,48 +265,3 @@ func (svc *Service) checkAndFormatInput(types []datatype.T) error { return nil } - -// checks if an uri matches the service's pattern -func (svc *Service) matchPattern(uri string) bool { - uriparts := SplitURL(uri) - parts := SplitURL(svc.Pattern) - - // fail if size differ - if len(uriparts) != len(parts) { - return false - } - - // root url '/' - if len(parts) == 0 { - return true - } - - // check part by part - for i, part := range parts { - uripart := uriparts[i] - - isCapture := len(part) > 0 && part[0] == '{' - - // if no capture -> check equality - if !isCapture { - if part != uripart { - return false - } - continue - } - - param, exists := svc.Input[part] - - // fail if no validator - if !exists || param.Validator == nil { - return false - } - - // fail if not type-valid - if _, valid := param.Validator(uripart); !valid { - return false - } - } - - return true -} From 49cf06d5d8a52ed8fa9e20e96df41cef6f34858c Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sat, 28 Mar 2020 12:31:44 +0100 Subject: [PATCH 6/6] implement validator interface for config.server; refactor --- internal/config/server.go | 67 +++++++++++---------------------------- 1 file changed, 19 insertions(+), 48 deletions(-) diff --git a/internal/config/server.go b/internal/config/server.go index a0be1e1..d2c7e3a 100644 --- a/internal/config/server.go +++ b/internal/config/server.go @@ -5,7 +5,6 @@ import ( "fmt" "io" "net/http" - "strings" "git.xdrm.io/go/aicra/datatype" ) @@ -17,29 +16,40 @@ func Parse(r io.Reader, dtypes ...datatype.T) (*Server, error) { Types: make([]datatype.T, 0), Services: make([]*Service, 0), } + // add data types for _, dtype := range dtypes { server.Types = append(server.Types, dtype) } - // parse JSON if err := json.NewDecoder(r).Decode(&server.Services); err != nil { return nil, fmt.Errorf("%s: %w", ErrRead, err) } - // check services - if err := server.checkAndFormat(); err != nil { - return nil, fmt.Errorf("%s: %w", ErrFormat, err) - } - - // check collisions - if err := server.collide(); err != nil { + if err := server.Validate(); err != nil { return nil, fmt.Errorf("%s: %w", ErrFormat, err) } return server, nil } +// Validate implements the validator interface +func (server Server) Validate(datatypes ...datatype.T) error { + for _, service := range server.Services { + err := service.Validate(server.Types...) + if err != nil { + return fmt.Errorf("%s '%s': %w", service.Method, service.Pattern, err) + } + } + + // check for collisions + if err := server.collide(); err != nil { + return fmt.Errorf("%s: %w", ErrFormat, err) + } + + return nil +} + // Find a service matching an incoming HTTP request func (server Server) Find(r *http.Request) *Service { for _, service := range server.Services { @@ -157,42 +167,3 @@ func (server *Server) collide() error { return nil } - -// checkAndFormat checks for errors and missing fields and sets default values for optional fields. -func (server Server) checkAndFormat() error { - for _, service := range server.Services { - - // check method - err := service.checkMethod() - if err != nil { - return fmt.Errorf("%s '%s' [method]: %w", service.Method, service.Pattern, err) - } - - // check pattern - service.Pattern = strings.Trim(service.Pattern, " \t\r\n") - err = service.checkPattern() - if err != nil { - return fmt.Errorf("%s '%s' [path]: %w", service.Method, service.Pattern, err) - } - - // check description - if len(strings.Trim(service.Description, " \t\r\n")) < 1 { - return fmt.Errorf("%s '%s' [description]: %w", service.Method, service.Pattern, ErrMissingDescription) - } - - // check input parameters - err = service.checkAndFormatInput(server.Types) - if err != nil { - return fmt.Errorf("%s '%s' [in]: %w", service.Method, service.Pattern, err) - } - - // fail if a brace capture remains undefined - for _, capture := range service.Captures { - if capture.Ref == nil { - return fmt.Errorf("%s '%s' [in]: %s: %w", service.Method, service.Pattern, capture.Name, ErrUndefinedBraceCapture) - } - } - - } - return nil -}