Merge branch 'refactor/config-validator' of go/aicra into 0.3.0
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
add validator interface to unify and for readability in internal/config
This commit is contained in:
commit
2f9534a3b0
|
@ -6,7 +6,7 @@ import "git.xdrm.io/go/aicra/datatype"
|
||||||
type AnyDataType struct{}
|
type AnyDataType struct{}
|
||||||
|
|
||||||
// Build returns the validator
|
// 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
|
// nothing if type not handled
|
||||||
if typeName != "any" {
|
if typeName != "any" {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -6,7 +6,7 @@ import "git.xdrm.io/go/aicra/datatype"
|
||||||
type BoolDataType struct{}
|
type BoolDataType struct{}
|
||||||
|
|
||||||
// Build returns the validator
|
// 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
|
// nothing if type not handled
|
||||||
if typeName != "bool" {
|
if typeName != "bool" {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
type FloatDataType struct{}
|
type FloatDataType struct{}
|
||||||
|
|
||||||
// Build returns the validator
|
// 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
|
// nothing if type not handled
|
||||||
if typeName != "float64" && typeName != "float" {
|
if typeName != "float64" && typeName != "float" {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
type IntDataType struct{}
|
type IntDataType struct{}
|
||||||
|
|
||||||
// Build returns the validator
|
// 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
|
// nothing if type not handled
|
||||||
if typeName != "int" {
|
if typeName != "int" {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -15,7 +15,7 @@ type StringDataType struct{}
|
||||||
|
|
||||||
// Build returns the validator.
|
// Build returns the validator.
|
||||||
// availables type names are : `string`, `string(length)` and `string(minLength, maxLength)`.
|
// 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"
|
simple := typeName == "string"
|
||||||
fixedLengthMatches := fixedLengthRegex.FindStringSubmatch(typeName)
|
fixedLengthMatches := fixedLengthRegex.FindStringSubmatch(typeName)
|
||||||
variableLengthMatches := variableLengthRegex.FindStringSubmatch(typeName)
|
variableLengthMatches := variableLengthRegex.FindStringSubmatch(typeName)
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
type UintDataType struct{}
|
type UintDataType struct{}
|
||||||
|
|
||||||
// Build returns the validator
|
// 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
|
// nothing if type not handled
|
||||||
if typeName != "uint" {
|
if typeName != "uint" {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -4,9 +4,9 @@ package datatype
|
||||||
// and casts the value into a compatible type
|
// and casts the value into a compatible type
|
||||||
type Validator func(value interface{}) (cast interface{}, valid bool)
|
type Validator func(value interface{}) (cast interface{}, valid bool)
|
||||||
|
|
||||||
// T builds a T from the type definition (from the
|
// T builds a T from the type definition (from the configuration field "type") and returns NIL if the type
|
||||||
// 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)
|
||||||
// definition does not match this T
|
// to be able to access other datatypes
|
||||||
type T interface {
|
type T interface {
|
||||||
Build(typeDefinition string) Validator
|
Build(typeDefinition string, registry ...T) Validator
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@ package config
|
||||||
|
|
||||||
import "git.xdrm.io/go/aicra/datatype"
|
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
|
// missing description
|
||||||
if len(param.Description) < 1 {
|
if len(param.Description) < 1 {
|
||||||
return ErrMissingParamDesc
|
return ErrMissingParamDesc
|
||||||
|
@ -14,7 +14,7 @@ func (param *Parameter) checkAndFormat() error {
|
||||||
return ErrMissingParamType
|
return ErrMissingParamType
|
||||||
}
|
}
|
||||||
|
|
||||||
// set optional + type
|
// optional type transform
|
||||||
if param.Type[0] == '?' {
|
if param.Type[0] == '?' {
|
||||||
param.Optional = true
|
param.Optional = true
|
||||||
param.Type = param.Type[1:]
|
param.Type = param.Type[1:]
|
||||||
|
@ -22,14 +22,3 @@ func (param *Parameter) checkAndFormat() error {
|
||||||
|
|
||||||
return nil
|
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)
|
|
||||||
if param.Validator != nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.xdrm.io/go/aicra/datatype"
|
"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),
|
Types: make([]datatype.T, 0),
|
||||||
Services: make([]*Service, 0),
|
Services: make([]*Service, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
// add data types
|
// add data types
|
||||||
for _, dtype := range dtypes {
|
for _, dtype := range dtypes {
|
||||||
server.Types = append(server.Types, dtype)
|
server.Types = append(server.Types, dtype)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse JSON
|
|
||||||
if err := json.NewDecoder(r).Decode(&server.Services); err != nil {
|
if err := json.NewDecoder(r).Decode(&server.Services); err != nil {
|
||||||
return nil, fmt.Errorf("%s: %w", ErrRead, err)
|
return nil, fmt.Errorf("%s: %w", ErrRead, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check services
|
if err := server.Validate(); err != nil {
|
||||||
if err := server.checkAndFormat(); err != nil {
|
|
||||||
return nil, fmt.Errorf("%s: %w", ErrFormat, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check collisions
|
|
||||||
if err := server.collide(); err != nil {
|
|
||||||
return nil, fmt.Errorf("%s: %w", ErrFormat, err)
|
return nil, fmt.Errorf("%s: %w", ErrFormat, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return server, nil
|
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
|
// Find a service matching an incoming HTTP request
|
||||||
func (server Server) Find(r *http.Request) *Service {
|
func (server Server) Find(r *http.Request) *Service {
|
||||||
for _, service := range server.Services {
|
for _, service := range server.Services {
|
||||||
|
@ -157,42 +167,3 @@ func (server *Server) collide() error {
|
||||||
|
|
||||||
return nil
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -30,7 +30,88 @@ func (svc *Service) Match(req *http.Request) bool {
|
||||||
return true
|
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 {
|
for _, available := range availableHTTPMethods {
|
||||||
if svc.Method == available {
|
if svc.Method == available {
|
||||||
return nil
|
return nil
|
||||||
|
@ -39,7 +120,7 @@ func (svc *Service) checkMethod() error {
|
||||||
return ErrUnknownMethod
|
return ErrUnknownMethod
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *Service) checkPattern() error {
|
func (svc *Service) isPatternValid() error {
|
||||||
length := len(svc.Pattern)
|
length := len(svc.Pattern)
|
||||||
|
|
||||||
// empty pattern
|
// empty pattern
|
||||||
|
@ -87,7 +168,7 @@ func (svc *Service) checkPattern() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *Service) checkAndFormatInput(types []datatype.T) error {
|
func (svc *Service) validateInput(types []datatype.T) error {
|
||||||
|
|
||||||
// ignore no parameter
|
// ignore no parameter
|
||||||
if svc.Input == nil || len(svc.Input) < 1 {
|
if svc.Input == nil || len(svc.Input) < 1 {
|
||||||
|
@ -141,7 +222,7 @@ func (svc *Service) checkAndFormatInput(types []datatype.T) error {
|
||||||
param.Rename = paramName
|
param.Rename = paramName
|
||||||
}
|
}
|
||||||
|
|
||||||
err := param.checkAndFormat()
|
err := param.Validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", paramName, err)
|
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)
|
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)
|
return fmt.Errorf("%s: %w", paramName, ErrUnknownDataType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,48 +265,3 @@ func (svc *Service) checkAndFormatInput(types []datatype.T) error {
|
||||||
|
|
||||||
return nil
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,6 +8,11 @@ import (
|
||||||
|
|
||||||
var availableHTTPMethods = []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete}
|
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
|
// Server represents a full server configuration
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Types []datatype.T
|
Types []datatype.T
|
||||||
|
|
|
@ -64,7 +64,7 @@ func (s Server) ToHTTPServer() (*httpServer, error) {
|
||||||
for _, service := range s.config.Services {
|
for _, service := range s.config.Services {
|
||||||
found := false
|
found := false
|
||||||
for _, handler := range s.handlers {
|
for _, handler := range s.handlers {
|
||||||
if handler.GetPath() == service.Pattern {
|
if handler.GetMethod() == service.Method && handler.GetPath() == service.Pattern {
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue