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{}
|
||||
|
||||
// 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
if param.Validator != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue