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"
|
|
|
|
)
|
|
|
|
|
2020-04-04 10:45:36 +00:00
|
|
|
type spec struct {
|
|
|
|
Input map[string]reflect.Type
|
|
|
|
Output map[string]reflect.Type
|
|
|
|
}
|
|
|
|
|
2020-03-29 13:00:22 +00:00
|
|
|
// builds a spec from the configuration service
|
2021-04-18 17:25:31 +00:00
|
|
|
func makeSpec(service config.Service) *spec {
|
|
|
|
s := &spec{
|
2020-03-29 13:00:22 +00:00
|
|
|
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 {
|
2021-04-18 17:25:31 +00:00
|
|
|
s.Input[param.Rename] = reflect.PtrTo(param.ExtractType)
|
2020-03-29 13:00:22 +00:00
|
|
|
continue
|
|
|
|
}
|
2021-04-18 17:25:31 +00:00
|
|
|
s.Input[param.Rename] = param.ExtractType
|
2020-03-29 13:00:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, param := range service.Output {
|
2020-03-29 17:33:26 +00:00
|
|
|
if len(param.Rename) < 1 {
|
|
|
|
continue
|
|
|
|
}
|
2021-04-18 17:25:31 +00:00
|
|
|
s.Output[param.Rename] = param.ExtractType
|
2020-03-29 13:00:22 +00:00
|
|
|
}
|
|
|
|
|
2021-04-18 17:25:31 +00:00
|
|
|
return s
|
2020-03-29 13:00:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// checks for HandlerFn input arguments
|
2021-04-18 17:25:31 +00:00
|
|
|
func (s *spec) checkInput(impl reflect.Type, index int) error {
|
|
|
|
var requiredInput, structIndex = index, index
|
|
|
|
if len(s.Input) > 0 { // arguments struct
|
|
|
|
requiredInput++
|
|
|
|
}
|
|
|
|
|
|
|
|
// missing arguments
|
|
|
|
if impl.NumIn() > requiredInput {
|
|
|
|
return errUnexpectedInput
|
|
|
|
}
|
2020-03-29 13:00:22 +00:00
|
|
|
|
2021-04-18 17:25:31 +00:00
|
|
|
// none required
|
2020-03-29 13:00:22 +00:00
|
|
|
if len(s.Input) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-04-18 17:25:31 +00:00
|
|
|
// too much arguments
|
|
|
|
if impl.NumIn() != requiredInput {
|
2020-04-04 10:46:43 +00:00
|
|
|
return errMissingHandlerArgumentParam
|
2020-03-29 13:00:22 +00:00
|
|
|
}
|
|
|
|
|
2020-03-29 14:58:53 +00:00
|
|
|
// arg must be a struct
|
2021-04-18 17:25:31 +00:00
|
|
|
structArg := impl.In(structIndex)
|
2020-03-29 13:00:22 +00:00
|
|
|
if structArg.Kind() != reflect.Struct {
|
2020-04-04 10:46:43 +00:00
|
|
|
return errMissingParamArgument
|
2020-03-29 13:00:22 +00:00
|
|
|
}
|
|
|
|
|
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] {
|
2020-04-04 10:46:43 +00:00
|
|
|
return fmt.Errorf("%s: %w", name, errUnexportedName)
|
2020-03-29 17:13:07 +00:00
|
|
|
}
|
|
|
|
|
2020-03-29 13:00:22 +00:00
|
|
|
field, exists := structArg.FieldByName(name)
|
|
|
|
if !exists {
|
2020-04-04 10:46:43 +00:00
|
|
|
return fmt.Errorf("%s: %w", name, errMissingParamFromConfig)
|
2020-03-29 13:00:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if !ptype.AssignableTo(field.Type) {
|
2020-04-04 10:46: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
|
|
|
|
}
|
|
|
|
|
|
|
|
// checks for HandlerFn output arguments
|
2021-04-18 17:25:31 +00:00
|
|
|
func (s spec) checkOutput(impl reflect.Type) error {
|
|
|
|
if impl.NumOut() < 1 {
|
2020-04-04 10:46:43 +00:00
|
|
|
return errMissingHandlerOutput
|
2020-03-29 13:00:22 +00:00
|
|
|
}
|
|
|
|
|
2021-03-28 16:49:23 +00:00
|
|
|
// last output must be api.Err
|
2021-04-18 17:25:31 +00:00
|
|
|
errOutput := impl.Out(impl.NumOut() - 1)
|
2021-03-28 16:49:23 +00:00
|
|
|
if !errOutput.AssignableTo(reflect.TypeOf(api.ErrUnknown)) {
|
2020-04-04 10:46:43 +00:00
|
|
|
return errMissingHandlerErrorOutput
|
2020-03-29 13:00:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// no output -> ok
|
|
|
|
if len(s.Output) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-04-18 17:25:31 +00:00
|
|
|
if impl.NumOut() != 2 {
|
2020-04-04 10:46:43 +00:00
|
|
|
return errMissingParamOutput
|
2020-03-29 13:00:22 +00:00
|
|
|
}
|
|
|
|
|
2020-03-29 14:58:53 +00:00
|
|
|
// fail if first output is not a pointer to struct
|
2021-04-18 17:25:31 +00:00
|
|
|
structOutputPtr := impl.Out(0)
|
2020-03-29 14:58:53 +00:00
|
|
|
if structOutputPtr.Kind() != reflect.Ptr {
|
2020-04-04 10:46:43 +00:00
|
|
|
return errMissingParamOutput
|
2020-03-29 14:58:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
structOutput := structOutputPtr.Elem()
|
2020-03-29 13:00:22 +00:00
|
|
|
if structOutput.Kind() != reflect.Struct {
|
2020-04-04 10:46:43 +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] {
|
2020-04-04 10:46:43 +00:00
|
|
|
return fmt.Errorf("%s: %w", name, errUnexportedName)
|
2020-03-29 17:13:07 +00:00
|
|
|
}
|
|
|
|
|
2020-03-29 13:00:22 +00:00
|
|
|
field, exists := structOutput.FieldByName(name)
|
|
|
|
if !exists {
|
2020-04-04 10:46:43 +00:00
|
|
|
return fmt.Errorf("%s: %w", name, errMissingOutputFromConfig)
|
2020-03-29 13:00:22 +00:00
|
|
|
}
|
|
|
|
|
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-04-04 10:46: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
|
|
|
|
}
|