2020-04-04 08:36:52 +00:00
|
|
|
package dynfunc
|
2020-03-29 13:00:22 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"reflect"
|
2020-03-29 17:13:07 +00:00
|
|
|
"strings"
|
2020-03-29 13:00:22 +00:00
|
|
|
|
|
|
|
"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 {
|
2020-03-29 17:33:26 +00:00
|
|
|
if len(param.Rename) < 1 {
|
|
|
|
continue
|
|
|
|
}
|
2020-03-29 13:00:22 +00:00
|
|
|
// 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 {
|
2020-03-29 17:33:26 +00:00
|
|
|
if len(param.Rename) < 1 {
|
|
|
|
continue
|
|
|
|
}
|
2020-03-29 13:00:22 +00:00
|
|
|
spec.Output[param.Rename] = param.ExtractType
|
|
|
|
}
|
|
|
|
|
|
|
|
return spec
|
|
|
|
}
|
|
|
|
|
|
|
|
// checks for HandlerFn input arguments
|
2020-03-29 14:58:53 +00:00
|
|
|
func (s spec) checkInput(fnv reflect.Value) error {
|
|
|
|
fnt := fnv.Type()
|
2020-03-29 13:00:22 +00:00
|
|
|
|
|
|
|
// no input -> ok
|
|
|
|
if len(s.Input) == 0 {
|
2020-04-04 08:02:48 +00:00
|
|
|
if fnt.NumIn() > 0 {
|
|
|
|
return ErrUnexpectedInput
|
|
|
|
}
|
2020-03-29 13:00:22 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-03-29 14:58:53 +00:00
|
|
|
if fnt.NumIn() != 1 {
|
2020-03-29 13:00:22 +00:00
|
|
|
return ErrMissingHandlerArgumentParam
|
|
|
|
}
|
|
|
|
|
2020-03-29 14:58:53 +00:00
|
|
|
// arg must be a struct
|
|
|
|
structArg := fnt.In(0)
|
2020-03-29 13:00:22 +00:00
|
|
|
if structArg.Kind() != reflect.Struct {
|
|
|
|
return ErrMissingParamArgument
|
|
|
|
}
|
|
|
|
|
2020-03-29 17:13:07 +00:00
|
|
|
// check for invalid param
|
2020-03-29 13:00:22 +00:00
|
|
|
for name, ptype := range s.Input {
|
2020-03-29 17:13:07 +00:00
|
|
|
if name[0] == strings.ToLower(name)[0] {
|
|
|
|
return fmt.Errorf("%s: %w", name, ErrUnexportedName)
|
|
|
|
}
|
|
|
|
|
2020-03-29 13:00:22 +00:00
|
|
|
field, exists := structArg.FieldByName(name)
|
|
|
|
if !exists {
|
|
|
|
return fmt.Errorf("%s: %w", name, ErrMissingParamFromConfig)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !ptype.AssignableTo(field.Type) {
|
2020-03-29 14:58:53 +00:00
|
|
|
return fmt.Errorf("%s: %w (%s instead of %s)", name, ErrWrongParamTypeFromConfig, field.Type, ptype)
|
2020-03-29 13:00:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// checks for HandlerFn output arguments
|
2020-03-29 14:58:53 +00:00
|
|
|
func (s spec) checkOutput(fnv reflect.Value) error {
|
|
|
|
fnt := fnv.Type()
|
2020-03-29 13:00:22 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-03-29 14:58:53 +00:00
|
|
|
// fail if first output is not a pointer to struct
|
|
|
|
structOutputPtr := fnt.Out(0)
|
|
|
|
if structOutputPtr.Kind() != reflect.Ptr {
|
|
|
|
return ErrMissingParamOutput
|
|
|
|
}
|
|
|
|
|
|
|
|
structOutput := structOutputPtr.Elem()
|
2020-03-29 13:00:22 +00:00
|
|
|
if structOutput.Kind() != reflect.Struct {
|
2020-03-29 14:58:53 +00:00
|
|
|
return ErrMissingParamOutput
|
2020-03-29 13:00:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// fail on invalid output
|
|
|
|
for name, ptype := range s.Output {
|
2020-03-29 17:13:07 +00:00
|
|
|
if name[0] == strings.ToLower(name)[0] {
|
|
|
|
return fmt.Errorf("%s: %w", name, ErrUnexportedName)
|
|
|
|
}
|
|
|
|
|
2020-03-29 13:00:22 +00:00
|
|
|
field, exists := structOutput.FieldByName(name)
|
|
|
|
if !exists {
|
|
|
|
return fmt.Errorf("%s: %w", name, ErrMissingOutputFromConfig)
|
|
|
|
}
|
|
|
|
|
2020-03-29 14:58:53 +00:00
|
|
|
// ignore types evalutating to nil
|
|
|
|
if ptype == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-03-29 17:23:02 +00:00
|
|
|
if !field.Type.ConvertibleTo(ptype) {
|
2020-03-29 17:22:43 +00:00
|
|
|
return fmt.Errorf("%s: %w (%s instead of %s)", name, ErrWrongParamTypeFromConfig, field.Type, ptype)
|
2020-03-29 13:00:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|