From 8cfa2235d6377bd58be2fc9dc08aad34ab898bd9 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sat, 28 Mar 2020 18:48:27 +0100 Subject: [PATCH 01/12] add Kind() method to datatype.T interface and to config parameter --- datatype/builtin/any.go | 11 ++++++++++- datatype/builtin/bool.go | 11 ++++++++++- datatype/builtin/float.go | 6 ++++++ datatype/builtin/int.go | 6 ++++++ datatype/builtin/string.go | 6 ++++++ datatype/builtin/uint.go | 6 ++++++ datatype/types.go | 3 +++ internal/config/service.go | 1 + internal/config/types.go | 6 ++++-- 9 files changed, 52 insertions(+), 4 deletions(-) diff --git a/datatype/builtin/any.go b/datatype/builtin/any.go index e12dd10..d96b26d 100644 --- a/datatype/builtin/any.go +++ b/datatype/builtin/any.go @@ -1,10 +1,19 @@ package builtin -import "git.xdrm.io/go/aicra/datatype" +import ( + "reflect" + + "git.xdrm.io/go/aicra/datatype" +) // AnyDataType is what its name tells type AnyDataType struct{} +// Kind returns the kind of data +func (AnyDataType) Kind() reflect.Kind { + return reflect.Interface +} + // Build returns the validator func (AnyDataType) Build(typeName string, registry ...datatype.T) datatype.Validator { // nothing if type not handled diff --git a/datatype/builtin/bool.go b/datatype/builtin/bool.go index 1d5e225..112158c 100644 --- a/datatype/builtin/bool.go +++ b/datatype/builtin/bool.go @@ -1,10 +1,19 @@ package builtin -import "git.xdrm.io/go/aicra/datatype" +import ( + "reflect" + + "git.xdrm.io/go/aicra/datatype" +) // BoolDataType is what its name tells type BoolDataType struct{} +// Kind returns the kind of data +func (BoolDataType) Kind() reflect.Kind { + return reflect.Bool +} + // Build returns the validator func (BoolDataType) Build(typeName string, registry ...datatype.T) datatype.Validator { // nothing if type not handled diff --git a/datatype/builtin/float.go b/datatype/builtin/float.go index d2f3204..3eb4c23 100644 --- a/datatype/builtin/float.go +++ b/datatype/builtin/float.go @@ -2,6 +2,7 @@ package builtin import ( "encoding/json" + "reflect" "git.xdrm.io/go/aicra/datatype" ) @@ -9,6 +10,11 @@ import ( // FloatDataType is what its name tells type FloatDataType struct{} +// Kind returns the kind of data +func (FloatDataType) Kind() reflect.Kind { + return reflect.Float64 +} + // Build returns the validator func (FloatDataType) Build(typeName string, registry ...datatype.T) datatype.Validator { // nothing if type not handled diff --git a/datatype/builtin/int.go b/datatype/builtin/int.go index 7dfdbf9..6559127 100644 --- a/datatype/builtin/int.go +++ b/datatype/builtin/int.go @@ -3,6 +3,7 @@ package builtin import ( "encoding/json" "math" + "reflect" "git.xdrm.io/go/aicra/datatype" ) @@ -10,6 +11,11 @@ import ( // IntDataType is what its name tells type IntDataType struct{} +// Kind returns the kind of data +func (IntDataType) Kind() reflect.Kind { + return reflect.Int +} + // Build returns the validator func (IntDataType) Build(typeName string, registry ...datatype.T) datatype.Validator { // nothing if type not handled diff --git a/datatype/builtin/string.go b/datatype/builtin/string.go index 1a94b82..b09b184 100644 --- a/datatype/builtin/string.go +++ b/datatype/builtin/string.go @@ -1,6 +1,7 @@ package builtin import ( + "reflect" "regexp" "strconv" @@ -13,6 +14,11 @@ var variableLengthRegex = regexp.MustCompile(`^string\((\d+), ?(\d+)\)$`) // StringDataType is what its name tells type StringDataType struct{} +// Kind returns the kind of data +func (StringDataType) Kind() reflect.Kind { + return reflect.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 { diff --git a/datatype/builtin/uint.go b/datatype/builtin/uint.go index 53f71de..d88fb5e 100644 --- a/datatype/builtin/uint.go +++ b/datatype/builtin/uint.go @@ -3,6 +3,7 @@ package builtin import ( "encoding/json" "math" + "reflect" "git.xdrm.io/go/aicra/datatype" ) @@ -10,6 +11,11 @@ import ( // UintDataType is what its name tells type UintDataType struct{} +// Kind returns the kind of data +func (UintDataType) Kind() reflect.Kind { + return reflect.Uint +} + // Build returns the validator func (UintDataType) Build(typeName string, registry ...datatype.T) datatype.Validator { // nothing if type not handled diff --git a/datatype/types.go b/datatype/types.go index fe529f1..a4761bb 100644 --- a/datatype/types.go +++ b/datatype/types.go @@ -1,5 +1,7 @@ package datatype +import "reflect" + // Validator returns whether a given value fulfills a datatype // and casts the value into a compatible type type Validator func(value interface{}) (cast interface{}, valid bool) @@ -8,5 +10,6 @@ type Validator func(value interface{}) (cast interface{}, valid bool) // 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 { + Kind() reflect.Kind Build(typeDefinition string, registry ...T) Validator } diff --git a/internal/config/service.go b/internal/config/service.go index 9ea781a..d65251d 100644 --- a/internal/config/service.go +++ b/internal/config/service.go @@ -238,6 +238,7 @@ func (svc *Service) validateInput(types []datatype.T) error { param.Validator = dtype.Build(param.Type, types...) if param.Validator != nil { datatypeFound = true + param.Kind = dtype.Kind() break } } diff --git a/internal/config/types.go b/internal/config/types.go index 4b356a5..c3cc6f8 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -2,6 +2,7 @@ package config import ( "net/http" + "reflect" "git.xdrm.io/go/aicra/datatype" ) @@ -26,8 +27,7 @@ type Service struct { Scope [][]string `json:"scope"` Description string `json:"info"` Input map[string]*Parameter `json:"in"` - // Download *bool `json:"download"` - // Output map[string]*Parameter `json:"out"` + // Output map[string]*Parameter `json:"out"` // references to url parameters // format: '/uri/{param}' @@ -46,6 +46,8 @@ type Parameter struct { Description string `json:"info"` Type string `json:"type"` Rename string `json:"name,omitempty"` + // Kind of data the datatype returns + Kind reflect.Kind // Optional is set to true when the type is prefixed with '?' Optional bool From 76cc2f52796b2a9fdb4af1a2d9f80d764bb804fb Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sat, 28 Mar 2020 19:11:23 +0100 Subject: [PATCH 02/12] replace datatype.Kind() with Type() --- datatype/builtin/any.go | 6 +++--- datatype/builtin/bool.go | 6 +++--- datatype/builtin/float.go | 6 +++--- datatype/builtin/int.go | 6 +++--- datatype/builtin/string.go | 6 +++--- datatype/builtin/uint.go | 6 +++--- datatype/types.go | 2 +- internal/config/service.go | 2 +- internal/config/types.go | 16 ++++++++-------- 9 files changed, 28 insertions(+), 28 deletions(-) diff --git a/datatype/builtin/any.go b/datatype/builtin/any.go index d96b26d..e1139b0 100644 --- a/datatype/builtin/any.go +++ b/datatype/builtin/any.go @@ -9,9 +9,9 @@ import ( // AnyDataType is what its name tells type AnyDataType struct{} -// Kind returns the kind of data -func (AnyDataType) Kind() reflect.Kind { - return reflect.Interface +// Type returns the type of data +func (AnyDataType) Type() reflect.Type { + return reflect.TypeOf(interface{}(nil)) } // Build returns the validator diff --git a/datatype/builtin/bool.go b/datatype/builtin/bool.go index 112158c..4d95547 100644 --- a/datatype/builtin/bool.go +++ b/datatype/builtin/bool.go @@ -9,9 +9,9 @@ import ( // BoolDataType is what its name tells type BoolDataType struct{} -// Kind returns the kind of data -func (BoolDataType) Kind() reflect.Kind { - return reflect.Bool +// Type returns the type of data +func (BoolDataType) Type() reflect.Type { + return reflect.TypeOf(true) } // Build returns the validator diff --git a/datatype/builtin/float.go b/datatype/builtin/float.go index 3eb4c23..5016f10 100644 --- a/datatype/builtin/float.go +++ b/datatype/builtin/float.go @@ -10,9 +10,9 @@ import ( // FloatDataType is what its name tells type FloatDataType struct{} -// Kind returns the kind of data -func (FloatDataType) Kind() reflect.Kind { - return reflect.Float64 +// Type returns the type of data +func (FloatDataType) Type() reflect.Type { + return reflect.TypeOf(float64(0)) } // Build returns the validator diff --git a/datatype/builtin/int.go b/datatype/builtin/int.go index 6559127..36b1038 100644 --- a/datatype/builtin/int.go +++ b/datatype/builtin/int.go @@ -11,9 +11,9 @@ import ( // IntDataType is what its name tells type IntDataType struct{} -// Kind returns the kind of data -func (IntDataType) Kind() reflect.Kind { - return reflect.Int +// Type returns the type of data +func (IntDataType) Type() reflect.Type { + return reflect.TypeOf(int(0)) } // Build returns the validator diff --git a/datatype/builtin/string.go b/datatype/builtin/string.go index b09b184..02be6ae 100644 --- a/datatype/builtin/string.go +++ b/datatype/builtin/string.go @@ -14,9 +14,9 @@ var variableLengthRegex = regexp.MustCompile(`^string\((\d+), ?(\d+)\)$`) // StringDataType is what its name tells type StringDataType struct{} -// Kind returns the kind of data -func (StringDataType) Kind() reflect.Kind { - return reflect.String +// Type returns the type of data +func (StringDataType) Type() reflect.Type { + return reflect.TypeOf(string("")) } // Build returns the validator. diff --git a/datatype/builtin/uint.go b/datatype/builtin/uint.go index d88fb5e..e59d2c1 100644 --- a/datatype/builtin/uint.go +++ b/datatype/builtin/uint.go @@ -11,9 +11,9 @@ import ( // UintDataType is what its name tells type UintDataType struct{} -// Kind returns the kind of data -func (UintDataType) Kind() reflect.Kind { - return reflect.Uint +// Type returns the type of data +func (UintDataType) Type() reflect.Type { + return reflect.TypeOf(uint(0)) } // Build returns the validator diff --git a/datatype/types.go b/datatype/types.go index a4761bb..c258b2f 100644 --- a/datatype/types.go +++ b/datatype/types.go @@ -10,6 +10,6 @@ type Validator func(value interface{}) (cast interface{}, valid bool) // 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 { - Kind() reflect.Kind + Type() reflect.Type Build(typeDefinition string, registry ...T) Validator } diff --git a/internal/config/service.go b/internal/config/service.go index d65251d..7c20724 100644 --- a/internal/config/service.go +++ b/internal/config/service.go @@ -238,7 +238,7 @@ func (svc *Service) validateInput(types []datatype.T) error { param.Validator = dtype.Build(param.Type, types...) if param.Validator != nil { datatypeFound = true - param.Kind = dtype.Kind() + param.ExtractType = dtype.Type() break } } diff --git a/internal/config/types.go b/internal/config/types.go index c3cc6f8..783959a 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -22,12 +22,12 @@ type Server struct { // Service represents a service definition (from api.json) type Service struct { - Method string `json:"method"` - Pattern string `json:"path"` - Scope [][]string `json:"scope"` - Description string `json:"info"` - Input map[string]*Parameter `json:"in"` - // Output map[string]*Parameter `json:"out"` + Method string `json:"method"` + Pattern string `json:"path"` + Scope [][]string `json:"scope"` + Description string `json:"info"` + Input map[string]*Parameter `json:"in"` + Output map[string]interface{} `json:"out"` // references to url parameters // format: '/uri/{param}' @@ -46,8 +46,8 @@ type Parameter struct { Description string `json:"info"` Type string `json:"type"` Rename string `json:"name,omitempty"` - // Kind of data the datatype returns - Kind reflect.Kind + // ExtractType is the type of data the datatype returns + ExtractType reflect.Type // Optional is set to true when the type is prefixed with '?' Optional bool From b15bb578cefb9482eb6c6006e50f52640bbfb5ed Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 29 Mar 2020 14:12:47 +0200 Subject: [PATCH 03/12] delegate from internal.service to parameter.Validate() --- internal/config/parameter.go | 14 ++++++++++++++ internal/config/service.go | 18 ++---------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/internal/config/parameter.go b/internal/config/parameter.go index 4037ee6..757a09e 100644 --- a/internal/config/parameter.go +++ b/internal/config/parameter.go @@ -20,5 +20,19 @@ func (param *Parameter) Validate(datatypes ...datatype.T) error { param.Type = param.Type[1:] } + // assign the datatype + datatypeFound := false + for _, dtype := range datatypes { + param.Validator = dtype.Build(param.Type, datatypes...) + if param.Validator != nil { + datatypeFound = true + param.ExtractType = dtype.Type() + break + } + } + if !datatypeFound { + return ErrUnknownDataType + } + return nil } diff --git a/internal/config/service.go b/internal/config/service.go index 7c20724..224de81 100644 --- a/internal/config/service.go +++ b/internal/config/service.go @@ -222,7 +222,7 @@ func (svc *Service) validateInput(types []datatype.T) error { param.Rename = paramName } - err := param.Validate() + err := param.Validate(types...) if err != nil { return fmt.Errorf("%s: %w", paramName, err) } @@ -232,21 +232,7 @@ func (svc *Service) validateInput(types []datatype.T) error { return fmt.Errorf("%s: %w", paramName, ErrIllegalOptionalURIParam) } - // assign the datatype - datatypeFound := false - for _, dtype := range types { - param.Validator = dtype.Build(param.Type, types...) - if param.Validator != nil { - datatypeFound = true - param.ExtractType = dtype.Type() - break - } - } - if !datatypeFound { - return fmt.Errorf("%s: %w", paramName, ErrUnknownDataType) - } - - // check for name/rename conflict + // fail on name/rename conflict for paramName2, param2 := range svc.Input { // ignore self if paramName == paramName2 { From ca2be1415d7dc68b68cc915c5e83654d3264c448 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 29 Mar 2020 14:18:05 +0200 Subject: [PATCH 04/12] enforce 'name' for capture or query parameters --- internal/config/config_test.go | 80 ++++++++++++++++++++++++++-------- internal/config/errors.go | 3 ++ internal/config/service.go | 9 +++- 3 files changed, 72 insertions(+), 20 deletions(-) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index ca1569e..0bc3e2a 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -481,6 +481,46 @@ func TestParseParameters(t *testing.T) { ]`, nil, }, + // missing rename + { + `[ + { + "method": "GET", + "path": "/{uri}", + "info": "info", + "in": { + "{uri}": { "info": "valid", "type": "any" } + } + } + ]`, + ErrMandatoryRename, + }, + { + `[ + { + "method": "GET", + "path": "/", + "info": "info", + "in": { + "GET@abc": { "info": "valid", "type": "any" } + } + } + ]`, + ErrMandatoryRename, + }, + { + `[ + { + "method": "GET", + "path": "/", + "info": "info", + "in": { + "GET@abc": { "info": "valid", "type": "any", "name": "abc" } + } + } + ]`, + nil, + }, { // URI parameter `[ @@ -616,7 +656,7 @@ func TestServiceCollision(t *testing.T) { }, { "method": "GET", "path": "/a/{c}", "info": "info", "in": { - "{c}": { "info":"info", "type": "string" } + "{c}": { "info":"info", "type": "string", "name": "c" } } } ]`, @@ -629,7 +669,7 @@ func TestServiceCollision(t *testing.T) { }, { "method": "GET", "path": "/a/{c}", "info": "info", "in": { - "{c}": { "info":"info", "type": "uint" } + "{c}": { "info":"info", "type": "uint", "name": "c" } } } ]`, @@ -642,7 +682,7 @@ func TestServiceCollision(t *testing.T) { }, { "method": "GET", "path": "/a/{c}/d", "info": "info", "in": { - "{c}": { "info":"info", "type": "string" } + "{c}": { "info":"info", "type": "string", "name": "c" } } } ]`, @@ -655,7 +695,7 @@ func TestServiceCollision(t *testing.T) { }, { "method": "GET", "path": "/a/{c}", "info": "info", "in": { - "{c}": { "info":"info", "type": "string" } + "{c}": { "info":"info", "type": "string", "name": "c" } } } ]`, @@ -668,7 +708,7 @@ func TestServiceCollision(t *testing.T) { }, { "method": "GET", "path": "/a/{c}", "info": "info", "in": { - "{c}": { "info":"info", "type": "string" } + "{c}": { "info":"info", "type": "string", "name": "c" } } } ]`, @@ -681,7 +721,7 @@ func TestServiceCollision(t *testing.T) { }, { "method": "GET", "path": "/a/{c}/d", "info": "info", "in": { - "{c}": { "info":"info", "type": "string" } + "{c}": { "info":"info", "type": "string", "name": "c" } } } ]`, @@ -694,7 +734,7 @@ func TestServiceCollision(t *testing.T) { }, { "method": "GET", "path": "/a/{c}", "info": "info", "in": { - "{c}": { "info":"info", "type": "uint" } + "{c}": { "info":"info", "type": "uint", "name": "c" } } } ]`, @@ -707,7 +747,7 @@ func TestServiceCollision(t *testing.T) { }, { "method": "GET", "path": "/a/{c}", "info": "info", "in": { - "{c}": { "info":"info", "type": "uint" } + "{c}": { "info":"info", "type": "uint", "name": "c" } } } ]`, @@ -720,7 +760,7 @@ func TestServiceCollision(t *testing.T) { }, { "method": "GET", "path": "/a/{c}/d", "info": "info", "in": { - "{c}": { "info":"info", "type": "uint" } + "{c}": { "info":"info", "type": "uint", "name": "c" } } } ]`, @@ -733,7 +773,7 @@ func TestServiceCollision(t *testing.T) { }, { "method": "GET", "path": "/a/{c}/d", "info": "info", "in": { - "{c}": { "info":"info", "type": "uint" } + "{c}": { "info":"info", "type": "uint", "name": "c" } } } ]`, @@ -743,12 +783,12 @@ func TestServiceCollision(t *testing.T) { `[ { "method": "GET", "path": "/a/{b}", "info": "info", "in": { - "{b}": { "info":"info", "type": "uint" } + "{b}": { "info":"info", "type": "uint", "name": "b" } } }, { "method": "GET", "path": "/a/{c}", "info": "info", "in": { - "{c}": { "info":"info", "type": "uint" } + "{c}": { "info":"info", "type": "uint", "name": "c" } } } ]`, @@ -758,12 +798,12 @@ func TestServiceCollision(t *testing.T) { `[ { "method": "GET", "path": "/a/{b}", "info": "info", "in": { - "{b}": { "info":"info", "type": "uint" } + "{b}": { "info":"info", "type": "uint", "name": "b" } } }, { "method": "PUT", "path": "/a/{c}", "info": "info", "in": { - "{c}": { "info":"info", "type": "uint" } + "{c}": { "info":"info", "type": "uint", "name": "c" } } } ]`, @@ -850,7 +890,8 @@ func TestMatchSimple(t *testing.T) { "in": { "{id}": { "info": "info", - "type": "bool" + "type": "bool", + "name": "id" } } } ]`, @@ -865,7 +906,8 @@ func TestMatchSimple(t *testing.T) { "in": { "{id}": { "info": "info", - "type": "int" + "type": "int", + "name": "id" } } } ]`, @@ -880,7 +922,8 @@ func TestMatchSimple(t *testing.T) { "in": { "{valid}": { "info": "info", - "type": "bool" + "type": "bool", + "name": "valid" } } } ]`, @@ -895,7 +938,8 @@ func TestMatchSimple(t *testing.T) { "in": { "{valid}": { "info": "info", - "type": "bool" + "type": "bool", + "name": "valid" } } } ]`, diff --git a/internal/config/errors.go b/internal/config/errors.go index a4741e4..7ed4597 100644 --- a/internal/config/errors.go +++ b/internal/config/errors.go @@ -29,6 +29,9 @@ const ErrInvalidPatternBraceCapture = cerr("invalid uri capturing braces") // ErrUnspecifiedBraceCapture - a parameter brace capture is not specified in the pattern const ErrUnspecifiedBraceCapture = cerr("capturing brace missing in the path") +// ErrMandatoryRename - capture/query parameters must have a rename +const ErrMandatoryRename = cerr("capture and query parameters must have a 'name'") + // ErrUndefinedBraceCapture - a parameter brace capture in the pattern is not defined in parameters const ErrUndefinedBraceCapture = cerr("capturing brace missing input definition") diff --git a/internal/config/service.go b/internal/config/service.go index 224de81..0fc3164 100644 --- a/internal/config/service.go +++ b/internal/config/service.go @@ -183,7 +183,7 @@ func (svc *Service) validateInput(types []datatype.T) error { } // fail if brace capture does not exists in pattern - iscapture := false + var iscapture, isquery bool if matches := braceRegex.FindAllStringSubmatch(paramName, -1); len(matches) > 0 && len(matches[0]) > 1 { braceName := matches[0][1] @@ -209,7 +209,7 @@ func (svc *Service) validateInput(types []datatype.T) error { svc.Query = make(map[string]*Parameter) } svc.Query[queryName] = param - + isquery = true } else { if svc.Form == nil { svc.Form = make(map[string]*Parameter) @@ -217,6 +217,11 @@ func (svc *Service) validateInput(types []datatype.T) error { svc.Form[paramName] = param } + // fail if capture or query without rename + if len(param.Rename) < 1 && (iscapture || isquery) { + return fmt.Errorf("%s: %w", paramName, ErrMandatoryRename) + } + // use param name if no rename if len(param.Rename) < 1 { param.Rename = paramName From 974f58fb8e83539b3a3c4a67d6a53d7076a062eb Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 29 Mar 2020 14:18:38 +0200 Subject: [PATCH 05/12] parse 'out' for internal config --- internal/config/types.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/config/types.go b/internal/config/types.go index 783959a..f3532a4 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -22,12 +22,12 @@ type Server struct { // Service represents a service definition (from api.json) type Service struct { - Method string `json:"method"` - Pattern string `json:"path"` - Scope [][]string `json:"scope"` - Description string `json:"info"` - Input map[string]*Parameter `json:"in"` - Output map[string]interface{} `json:"out"` + Method string `json:"method"` + Pattern string `json:"path"` + Scope [][]string `json:"scope"` + Description string `json:"info"` + Input map[string]*Parameter `json:"in"` + Output map[string]*Parameter `json:"out"` // references to url parameters // format: '/uri/{param}' From 081e48002fc5f02f7c95ee96cf7bbd2959088003 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 29 Mar 2020 15:00:22 +0200 Subject: [PATCH 06/12] create dynamic package to handle reflection at runtime to check for handlers signature --- dynamic/errors.go | 51 +++++++++++++++++++ dynamic/handler.go | 44 +++++++++++++++++ dynamic/spec.go | 121 +++++++++++++++++++++++++++++++++++++++++++++ dynamic/types.go | 17 +++++++ 4 files changed, 233 insertions(+) create mode 100644 dynamic/errors.go create mode 100644 dynamic/handler.go create mode 100644 dynamic/spec.go create mode 100644 dynamic/types.go diff --git a/dynamic/errors.go b/dynamic/errors.go new file mode 100644 index 0000000..60acaec --- /dev/null +++ b/dynamic/errors.go @@ -0,0 +1,51 @@ +package dynamic + +// cerr allows you to create constant "const" error with type boxing. +type cerr string + +// Error implements the error builtin interface. +func (err cerr) Error() string { + return string(err) +} + +// ErrHandlerNotFunc - handler is not a func +const ErrHandlerNotFunc = cerr("handler must be a func") + +// ErrNoServiceForHandler - no service matching this handler +const ErrNoServiceForHandler = cerr("no service found for this handler") + +// ErrMissingHandlerArgument - missing arguments for handler +const ErrMissingHandlerArgument = cerr("handler must have at least 1 arguments") + +// ErrMissingHandlerArgumentParam - missing params arguments for handler +const ErrMissingHandlerArgumentParam = cerr("missing handler argument 2 : parameter struct") + +// ErrMissingHandlerOutput - missing output for handler +const ErrMissingHandlerOutput = cerr("handler must have at least 1 output") + +// ErrMissingHandlerOutputError - missing error output for handler +const ErrMissingHandlerOutputError = cerr("handler must have its last output of type api.Error") + +// ErrMissingRequestArgument - missing request argument for handler +const ErrMissingRequestArgument = cerr("handler first argument must be of type api.Request") + +// ErrMissingParamArgument - missing parameters argument for handler +const ErrMissingParamArgument = cerr("handler second argument must be a struct") + +// ErrMissingParamOutput - missing output argument for handler +const ErrMissingParamOutput = cerr("handler first output must be a struct") + +// ErrMissingParamFromConfig - missing a parameter in handler struct +const ErrMissingParamFromConfig = cerr("missing a parameter from configuration") + +// ErrMissingOutputFromConfig - missing a parameter in handler struct +const ErrMissingOutputFromConfig = cerr("missing a parameter from configuration") + +// ErrWrongParamTypeFromConfig - a configuration parameter type is invalid in the handler param struct +const ErrWrongParamTypeFromConfig = cerr("invalid parameter struct type from configuration") + +// ErrWrongOutputTypeFromConfig - a configuration output type is invalid in the handler output struct +const ErrWrongOutputTypeFromConfig = cerr("invalid output struct type from configuration") + +// ErrMissingHandlerErrorOutput - missing handler output error +const ErrMissingHandlerErrorOutput = cerr("last output must be of type api.Error") diff --git a/dynamic/handler.go b/dynamic/handler.go new file mode 100644 index 0000000..2be0957 --- /dev/null +++ b/dynamic/handler.go @@ -0,0 +1,44 @@ +package dynamic + +import ( + "fmt" + "reflect" + + "git.xdrm.io/go/aicra/internal/config" +) + +// Build a handler from a service configuration and a HandlerFn +// +// a HandlerFn must have as a signature : `func(api.Request, inputStruct) (outputStruct, api.Error)` +// - `inputStruct` is a struct{} containing a field for each service input (with valid reflect.Type) +// - `outputStruct` is a struct{} containing a field for each service output (with valid reflect.Type) +// +// Special cases: +// - it there is no input, `inputStruct` can be omitted +// - it there is no output, `outputStruct` can be omitted +func Build(fn HandlerFn, service config.Service) (*Handler, error) { + h := &Handler{ + spec: makeSpec(service), + fn: fn, + } + + fnt := reflect.TypeOf(fn) + + if fnt.Kind() != reflect.Func { + return nil, ErrHandlerNotFunc + } + + if err := h.spec.checkInput(fnt); err != nil { + return nil, fmt.Errorf("input: %w", err) + } + if err := h.spec.checkOutput(fnt); err != nil { + return nil, fmt.Errorf("output: %w", err) + } + + return h, nil +} + +// Handle +func (h *Handler) Handle() { + +} diff --git a/dynamic/spec.go b/dynamic/spec.go new file mode 100644 index 0000000..e7e5016 --- /dev/null +++ b/dynamic/spec.go @@ -0,0 +1,121 @@ +package dynamic + +import ( + "fmt" + "reflect" + + "git.xdrm.io/go/aicra/api" + "git.xdrm.io/go/aicra/internal/config" +) + +// builds a spec from the configuration service +func makeSpec(service config.Service) spec { + spec := spec{ + Input: make(map[string]reflect.Type), + Output: make(map[string]reflect.Type), + } + + for _, param := range service.Input { + // make a pointer if optional + if param.Optional { + spec.Input[param.Rename] = reflect.PtrTo(param.ExtractType) + continue + } + spec.Input[param.Rename] = param.ExtractType + } + + for _, param := range service.Output { + // make a pointer if optional + if param.Optional { + spec.Output[param.Rename] = reflect.PtrTo(param.ExtractType) + continue + } + spec.Output[param.Rename] = param.ExtractType + } + + return spec +} + +// checks for HandlerFn input arguments +func (s spec) checkInput(fnt reflect.Type) error { + if fnt.NumIn() != 1 { + return ErrMissingHandlerArgument + } + + // arg[0] must be api.Request + requestArg := fnt.In(0) + if !requestArg.AssignableTo(reflect.TypeOf(api.Request{})) { + return ErrMissingRequestArgument + } + + // no input -> ok + if len(s.Input) == 0 { + return nil + } + + if fnt.NumIn() < 2 { + return ErrMissingHandlerArgumentParam + } + + // arg[1] must be a struct + structArg := fnt.In(1) + if structArg.Kind() != reflect.Struct { + return ErrMissingParamArgument + } + + // check for invlaid param + for name, ptype := range s.Input { + field, exists := structArg.FieldByName(name) + if !exists { + return fmt.Errorf("%s: %w", name, ErrMissingParamFromConfig) + } + + if !ptype.AssignableTo(field.Type) { + return fmt.Errorf("%s: %w (%s)", name, ErrWrongParamTypeFromConfig, ptype) + } + } + + return nil +} + +// checks for HandlerFn output arguments +func (s spec) checkOutput(fnt reflect.Type) error { + if fnt.NumOut() < 1 { + return ErrMissingHandlerOutput + } + + // last output must be api.Error + errOutput := fnt.Out(fnt.NumOut() - 1) + if !errOutput.AssignableTo(reflect.TypeOf(api.ErrorUnknown)) { + return ErrMissingHandlerErrorOutput + } + + // no output -> ok + if len(s.Output) == 0 { + return nil + } + + if fnt.NumOut() != 2 { + return ErrMissingParamOutput + } + + // fail if first output is not a struct + structOutput := fnt.Out(0) + if structOutput.Kind() != reflect.Struct { + return ErrMissingParamArgument + } + + // fail on invalid output + for name, ptype := range s.Output { + field, exists := structOutput.FieldByName(name) + if !exists { + return fmt.Errorf("%s: %w", name, ErrMissingOutputFromConfig) + } + + if !ptype.AssignableTo(field.Type) { + return fmt.Errorf("%s: %w (%s)", name, ErrWrongOutputTypeFromConfig, ptype) + } + } + + return nil +} diff --git a/dynamic/types.go b/dynamic/types.go new file mode 100644 index 0000000..a180e63 --- /dev/null +++ b/dynamic/types.go @@ -0,0 +1,17 @@ +package dynamic + +import "reflect" + +// HandlerFn defines a dynamic handler function +type HandlerFn interface{} + +// Handler represents a dynamic api handler +type Handler struct { + spec spec + fn HandlerFn +} + +type spec struct { + Input map[string]reflect.Type + Output map[string]reflect.Type +} From e7dd1e7a56921aa006c674230e365bcc7a56e856 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 29 Mar 2020 15:04:12 +0200 Subject: [PATCH 07/12] migrate handler from api to aicra; check for service when setting handler --- api/handler.go | 34 ---------------------------------- errors.go | 15 +++++++++++++++ handler.go | 32 ++++++++++++++++++++++++++++++++ server.go | 31 ++++++++++++++++++++++--------- 4 files changed, 69 insertions(+), 43 deletions(-) delete mode 100644 api/handler.go create mode 100644 errors.go create mode 100644 handler.go diff --git a/api/handler.go b/api/handler.go deleted file mode 100644 index c6e5cc9..0000000 --- a/api/handler.go +++ /dev/null @@ -1,34 +0,0 @@ -package api - -import ( - "strings" -) - -// HandlerFn defines the handler signature -type HandlerFn func(req Request, res *Response) Error - -// Handler is an API handler ready to be bound -type Handler struct { - path string - method string - Fn HandlerFn -} - -// NewHandler builds a handler from its http method and path -func NewHandler(method, path string, fn HandlerFn) (*Handler, error) { - return &Handler{ - path: path, - method: strings.ToUpper(method), - Fn: fn, - }, nil -} - -// GetMethod returns the handler's HTTP method -func (h *Handler) GetMethod() string { - return h.method -} - -// GetPath returns the handler's path -func (h *Handler) GetPath() string { - return h.path -} diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..252f8ee --- /dev/null +++ b/errors.go @@ -0,0 +1,15 @@ +package aicra + +// cerr allows you to create constant "const" error with type boxing. +type cerr string + +// Error implements the error builtin interface. +func (err cerr) Error() string { + return string(err) +} + +// ErrNoServiceForHandler - no service matching this handler +const ErrNoServiceForHandler = cerr("no service found for this handler") + +// ErrNoHandlerForService - no handler matching this service +const ErrNoHandlerForService = cerr("no handler found for this service") diff --git a/handler.go b/handler.go new file mode 100644 index 0000000..8af4f34 --- /dev/null +++ b/handler.go @@ -0,0 +1,32 @@ +package aicra + +import ( + "fmt" + "strings" + + "git.xdrm.io/go/aicra/dynamic" + "git.xdrm.io/go/aicra/internal/config" +) + +type handler struct { + Method string + Path string + dynHandler *dynamic.Handler +} + +// createHandler builds a handler from its http method and path +// also it checks whether the function signature is valid +func createHandler(method, path string, service config.Service, fn dynamic.HandlerFn) (*handler, error) { + method = strings.ToUpper(method) + + dynHandler, err := dynamic.Build(fn, service) + if err != nil { + return nil, fmt.Errorf("%s '%s' handler: %w", method, path, err) + } + + return &handler{ + Path: path, + Method: method, + dynHandler: dynHandler, + }, nil +} diff --git a/server.go b/server.go index a8bd3d0..54e53b4 100644 --- a/server.go +++ b/server.go @@ -5,15 +5,15 @@ import ( "io" "os" - "git.xdrm.io/go/aicra/api" "git.xdrm.io/go/aicra/datatype" + "git.xdrm.io/go/aicra/dynamic" "git.xdrm.io/go/aicra/internal/config" ) // Server represents an AICRA instance featuring: type checkers, services type Server struct { config *config.Server - handlers []*api.Handler + handlers []*handler } // New creates a framework instance from a configuration file @@ -26,7 +26,7 @@ func New(configPath string, dtypes ...datatype.T) (*Server, error) { // 1. init instance var i = &Server{ config: nil, - handlers: make([]*api.Handler, 0), + handlers: make([]*handler, 0), } // 2. open config file @@ -46,13 +46,26 @@ func New(configPath string, dtypes ...datatype.T) (*Server, error) { } -// HandleFunc sets a new handler for an HTTP method to a path -func (s *Server) Handle(httpMethod, path string, fn api.HandlerFn) { - handler, err := api.NewHandler(httpMethod, path, fn) +// Handle sets a new handler for an HTTP method to a path +func (s *Server) Handle(method, path string, fn dynamic.HandlerFn) error { + // find associated service + var found *config.Service = nil + for _, service := range s.config.Services { + if method == service.Method && path == service.Pattern { + found = service + break + } + } + if found == nil { + return fmt.Errorf("%s '%s': %w", method, path, ErrNoServiceForHandler) + } + + handler, err := createHandler(method, path, *found, fn) if err != nil { - panic(err) + return err } s.handlers = append(s.handlers, handler) + return nil } // ToHTTPServer converts the server to a http server @@ -62,13 +75,13 @@ func (s Server) ToHTTPServer() (*httpServer, error) { for _, service := range s.config.Services { found := false for _, handler := range s.handlers { - if handler.GetMethod() == service.Method && handler.GetPath() == service.Pattern { + if handler.Method == service.Method && handler.Path == service.Pattern { found = true break } } if !found { - return nil, fmt.Errorf("missing handler for %s '%s'", service.Method, service.Pattern) + return nil, fmt.Errorf("%s '%s': %w", service.Method, service.Pattern, ErrNoHandlerForService) } } From 00e2a96c799144d8c2a50af92dbbf0d39bbb9509 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 29 Mar 2020 16:22:32 +0200 Subject: [PATCH 08/12] fix: ErrNoMatchFound error --- api/error.defaults.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/error.defaults.go b/api/error.defaults.go index 4a9ee0e..75ea528 100644 --- a/api/error.defaults.go +++ b/api/error.defaults.go @@ -76,7 +76,7 @@ var errorReasons = map[Error]string{ ErrorUnknown: "unknown error", ErrorSuccess: "all right", ErrorFailure: "it failed", - ErrorNoMatchFound: "resource found", + ErrorNoMatchFound: "resource not found", ErrorAlreadyExists: "already exists", ErrorConfig: "configuration error", ErrorUpload: "upload failed", From a3daab7de44804a212809a7df5480bd04ec7c505 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 29 Mar 2020 16:58:53 +0200 Subject: [PATCH 09/12] dynamic handler output struct must be a pointer; no more a regular struct --- dynamic/errors.go | 11 ++++------- dynamic/handler.go | 8 ++++---- dynamic/spec.go | 48 ++++++++++++++++++++++------------------------ 3 files changed, 31 insertions(+), 36 deletions(-) diff --git a/dynamic/errors.go b/dynamic/errors.go index 60acaec..e3d7546 100644 --- a/dynamic/errors.go +++ b/dynamic/errors.go @@ -14,11 +14,8 @@ const ErrHandlerNotFunc = cerr("handler must be a func") // ErrNoServiceForHandler - no service matching this handler const ErrNoServiceForHandler = cerr("no service found for this handler") -// ErrMissingHandlerArgument - missing arguments for handler -const ErrMissingHandlerArgument = cerr("handler must have at least 1 arguments") - // ErrMissingHandlerArgumentParam - missing params arguments for handler -const ErrMissingHandlerArgumentParam = cerr("missing handler argument 2 : parameter struct") +const ErrMissingHandlerArgumentParam = cerr("missing handler argument : parameter struct") // ErrMissingHandlerOutput - missing output for handler const ErrMissingHandlerOutput = cerr("handler must have at least 1 output") @@ -33,7 +30,7 @@ const ErrMissingRequestArgument = cerr("handler first argument must be of type a const ErrMissingParamArgument = cerr("handler second argument must be a struct") // ErrMissingParamOutput - missing output argument for handler -const ErrMissingParamOutput = cerr("handler first output must be a struct") +const ErrMissingParamOutput = cerr("handler first output must be a *struct") // ErrMissingParamFromConfig - missing a parameter in handler struct const ErrMissingParamFromConfig = cerr("missing a parameter from configuration") @@ -42,10 +39,10 @@ const ErrMissingParamFromConfig = cerr("missing a parameter from configuration") const ErrMissingOutputFromConfig = cerr("missing a parameter from configuration") // ErrWrongParamTypeFromConfig - a configuration parameter type is invalid in the handler param struct -const ErrWrongParamTypeFromConfig = cerr("invalid parameter struct type from configuration") +const ErrWrongParamTypeFromConfig = cerr("invalid struct field type") // ErrWrongOutputTypeFromConfig - a configuration output type is invalid in the handler output struct -const ErrWrongOutputTypeFromConfig = cerr("invalid output struct type from configuration") +const ErrWrongOutputTypeFromConfig = cerr("invalid struct field type") // ErrMissingHandlerErrorOutput - missing handler output error const ErrMissingHandlerErrorOutput = cerr("last output must be of type api.Error") diff --git a/dynamic/handler.go b/dynamic/handler.go index 2be0957..b2b8a3b 100644 --- a/dynamic/handler.go +++ b/dynamic/handler.go @@ -22,16 +22,16 @@ func Build(fn HandlerFn, service config.Service) (*Handler, error) { fn: fn, } - fnt := reflect.TypeOf(fn) + fnv := reflect.ValueOf(fn) - if fnt.Kind() != reflect.Func { + if fnv.Type().Kind() != reflect.Func { return nil, ErrHandlerNotFunc } - if err := h.spec.checkInput(fnt); err != nil { + if err := h.spec.checkInput(fnv); err != nil { return nil, fmt.Errorf("input: %w", err) } - if err := h.spec.checkOutput(fnt); err != nil { + if err := h.spec.checkOutput(fnv); err != nil { return nil, fmt.Errorf("output: %w", err) } diff --git a/dynamic/spec.go b/dynamic/spec.go index e7e5016..354df52 100644 --- a/dynamic/spec.go +++ b/dynamic/spec.go @@ -25,11 +25,6 @@ func makeSpec(service config.Service) spec { } for _, param := range service.Output { - // make a pointer if optional - if param.Optional { - spec.Output[param.Rename] = reflect.PtrTo(param.ExtractType) - continue - } spec.Output[param.Rename] = param.ExtractType } @@ -37,28 +32,20 @@ func makeSpec(service config.Service) spec { } // checks for HandlerFn input arguments -func (s spec) checkInput(fnt reflect.Type) error { - if fnt.NumIn() != 1 { - return ErrMissingHandlerArgument - } - - // arg[0] must be api.Request - requestArg := fnt.In(0) - if !requestArg.AssignableTo(reflect.TypeOf(api.Request{})) { - return ErrMissingRequestArgument - } +func (s spec) checkInput(fnv reflect.Value) error { + fnt := fnv.Type() // no input -> ok if len(s.Input) == 0 { return nil } - if fnt.NumIn() < 2 { + if fnt.NumIn() != 1 { return ErrMissingHandlerArgumentParam } - // arg[1] must be a struct - structArg := fnt.In(1) + // arg must be a struct + structArg := fnt.In(0) if structArg.Kind() != reflect.Struct { return ErrMissingParamArgument } @@ -71,7 +58,7 @@ func (s spec) checkInput(fnt reflect.Type) error { } if !ptype.AssignableTo(field.Type) { - return fmt.Errorf("%s: %w (%s)", name, ErrWrongParamTypeFromConfig, ptype) + return fmt.Errorf("%s: %w (%s instead of %s)", name, ErrWrongParamTypeFromConfig, field.Type, ptype) } } @@ -79,7 +66,8 @@ func (s spec) checkInput(fnt reflect.Type) error { } // checks for HandlerFn output arguments -func (s spec) checkOutput(fnt reflect.Type) error { +func (s spec) checkOutput(fnv reflect.Value) error { + fnt := fnv.Type() if fnt.NumOut() < 1 { return ErrMissingHandlerOutput } @@ -99,10 +87,15 @@ func (s spec) checkOutput(fnt reflect.Type) error { return ErrMissingParamOutput } - // fail if first output is not a struct - structOutput := fnt.Out(0) + // fail if first output is not a pointer to struct + structOutputPtr := fnt.Out(0) + if structOutputPtr.Kind() != reflect.Ptr { + return ErrMissingParamOutput + } + + structOutput := structOutputPtr.Elem() if structOutput.Kind() != reflect.Struct { - return ErrMissingParamArgument + return ErrMissingParamOutput } // fail on invalid output @@ -112,8 +105,13 @@ func (s spec) checkOutput(fnt reflect.Type) error { return fmt.Errorf("%s: %w", name, ErrMissingOutputFromConfig) } - if !ptype.AssignableTo(field.Type) { - return fmt.Errorf("%s: %w (%s)", name, ErrWrongOutputTypeFromConfig, ptype) + // ignore types evalutating to nil + if ptype == nil { + continue + } + + if !ptype.ConvertibleTo(field.Type) { + return fmt.Errorf("%s: %w (%s instead of %s)", name, ErrWrongOutputTypeFromConfig, field.Type, ptype) } } From a5424d894118a9662f829ce4f6eed00b5c267f38 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 29 Mar 2020 16:59:32 +0200 Subject: [PATCH 10/12] parse output in internal/config to find the datatype's reflect.Type() --- internal/config/errors.go | 3 ++ internal/config/parameter.go | 10 +++---- internal/config/service.go | 55 ++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/internal/config/errors.go b/internal/config/errors.go index 7ed4597..2a2df17 100644 --- a/internal/config/errors.go +++ b/internal/config/errors.go @@ -41,6 +41,9 @@ const ErrMissingDescription = cerr("missing description") // ErrIllegalOptionalURIParam - an URI parameter cannot be optional const ErrIllegalOptionalURIParam = cerr("URI parameter cannot be optional") +// ErrOptionalOption - an output is optional +const ErrOptionalOption = cerr("output cannot be optional") + // ErrMissingParamDesc - a parameter is missing its description const ErrMissingParamDesc = cerr("missing parameter description") diff --git a/internal/config/parameter.go b/internal/config/parameter.go index 757a09e..1f8be92 100644 --- a/internal/config/parameter.go +++ b/internal/config/parameter.go @@ -1,6 +1,8 @@ package config -import "git.xdrm.io/go/aicra/datatype" +import ( + "git.xdrm.io/go/aicra/datatype" +) // Validate implements the validator interface func (param *Parameter) Validate(datatypes ...datatype.T) error { @@ -21,16 +23,14 @@ func (param *Parameter) Validate(datatypes ...datatype.T) error { } // assign the datatype - datatypeFound := false for _, dtype := range datatypes { param.Validator = dtype.Build(param.Type, datatypes...) + param.ExtractType = dtype.Type() if param.Validator != nil { - datatypeFound = true - param.ExtractType = dtype.Type() break } } - if !datatypeFound { + if param.Validator == nil { return ErrUnknownDataType } diff --git a/internal/config/service.go b/internal/config/service.go index 0fc3164..87815d7 100644 --- a/internal/config/service.go +++ b/internal/config/service.go @@ -108,6 +108,12 @@ func (svc *Service) Validate(datatypes ...datatype.T) error { } } + // check output + err = svc.validateOutput(datatypes) + if err != nil { + return fmt.Errorf("field 'out': %w", err) + } + return nil } @@ -257,3 +263,52 @@ func (svc *Service) validateInput(types []datatype.T) error { return nil } + +func (svc *Service) validateOutput(types []datatype.T) error { + + // ignore no parameter + if svc.Output == nil || len(svc.Output) < 1 { + svc.Output = make(map[string]*Parameter, 0) + return nil + } + + // for each parameter + for paramName, param := range svc.Output { + if len(paramName) < 1 { + return fmt.Errorf("%s: %w", paramName, ErrIllegalParamName) + } + + // use param name if no rename + if len(param.Rename) < 1 { + param.Rename = paramName + } + + err := param.Validate(types...) + if err != nil { + return fmt.Errorf("%s: %w", paramName, err) + } + + if param.Optional { + return fmt.Errorf("%s: %w", paramName, ErrOptionalOption) + } + + // fail on name/rename conflict + for paramName2, param2 := range svc.Output { + // ignore self + if paramName == paramName2 { + continue + } + + // 3.2.1. Same rename field + // 3.2.2. Not-renamed field matches a renamed field + // 3.2.3. Renamed field matches name + if param.Rename == param2.Rename || paramName == param2.Rename || paramName2 == param.Rename { + return fmt.Errorf("%s: %w", paramName, ErrParamNameConflict) + } + + } + + } + + return nil +} From d7acf771add9835bb8e6f2ea3eb410e436394ca1 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 29 Mar 2020 17:01:02 +0200 Subject: [PATCH 11/12] implement the dynamic handler : fill input struct, do the call, fill return struct --- dynamic/handler.go | 50 ++++++++++++++++++++++++++++++++++++++++++++-- http.go | 13 +++++++----- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/dynamic/handler.go b/dynamic/handler.go index b2b8a3b..54bded1 100644 --- a/dynamic/handler.go +++ b/dynamic/handler.go @@ -4,6 +4,7 @@ import ( "fmt" "reflect" + "git.xdrm.io/go/aicra/api" "git.xdrm.io/go/aicra/internal/config" ) @@ -38,7 +39,52 @@ func Build(fn HandlerFn, service config.Service) (*Handler, error) { return h, nil } -// Handle -func (h *Handler) Handle() { +// Handle binds input @data into HandleFn and returns map output +func (h *Handler) Handle(data map[string]interface{}) (map[string]interface{}, api.Error) { + fnv := reflect.ValueOf(h.fn) + callArgs := []reflect.Value{} + + // bind input data + if fnv.Type().NumIn() > 0 { + // create zero value struct + callStructPtr := reflect.New(fnv.Type().In(0)) + callStruct := callStructPtr.Elem() + + // set each field + for name := range h.spec.Input { + field := callStruct.FieldByName(name) + if !field.CanSet() { + continue + } + + // get value from @data + value, inData := data[name] + if !inData { + continue + } + field.Set(reflect.ValueOf(value).Convert(field.Type())) + } + callArgs = append(callArgs, callStruct) + } + + // call the HandlerFn + output := fnv.Call(callArgs) + + // no output OR pointer to output struct is nil + outdata := make(map[string]interface{}) + if len(h.spec.Output) < 1 || output[0].IsNil() { + return outdata, api.Error(output[len(output)-1].Int()) + } + + // extract struct from pointer + returnStruct := output[0].Elem() + + for name := range h.spec.Output { + field := returnStruct.FieldByName(name) + outdata[name] = field.Interface() + } + + // extract api.Error + return outdata, api.Error(output[len(output)-1].Int()) } diff --git a/http.go b/http.go index 8db6033..c131eb8 100644 --- a/http.go +++ b/http.go @@ -55,11 +55,11 @@ func (server httpServer) ServeHTTP(res http.ResponseWriter, req *http.Request) { } // 6. find a matching handler - var foundHandler *api.Handler + var foundHandler *handler var found bool for _, handler := range server.handlers { - if handler.GetMethod() == service.Method && handler.GetPath() == service.Pattern { + if handler.Method == service.Method && handler.Path == service.Pattern { foundHandler = handler found = true } @@ -91,9 +91,12 @@ func (server httpServer) ServeHTTP(res http.ResponseWriter, req *http.Request) { apireq.Param = dataset.Data // 10. execute - response := api.EmptyResponse() - apiErr := foundHandler.Fn(*apireq, response) - response.WithError(apiErr) + returned, apiErr := foundHandler.dynHandler.Handle(dataset.Data) + response := api.EmptyResponse().WithError(apiErr) + for key, value := range returned { + + response.SetData(name, value) + } // 11. apply headers res.Header().Set("Content-Type", "application/json; charset=utf-8") From 8a0a20294cb2cf20714df2dca3de810fca072e5f Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 29 Mar 2020 17:01:24 +0200 Subject: [PATCH 12/12] rename output fields to original name (not rename) --- http.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/http.go b/http.go index c131eb8..d078ea1 100644 --- a/http.go +++ b/http.go @@ -95,7 +95,12 @@ func (server httpServer) ServeHTTP(res http.ResponseWriter, req *http.Request) { response := api.EmptyResponse().WithError(apiErr) for key, value := range returned { - response.SetData(name, value) + // find original name from rename + for name, param := range service.Output { + if param.Rename == key { + response.SetData(name, value) + } + } } // 11. apply headers