2020-04-04 08:36:52 +00:00
|
|
|
package dynfunc
|
2020-03-29 13:00:22 +00:00
|
|
|
|
|
|
|
import (
|
2021-06-20 00:14:31 +00:00
|
|
|
"context"
|
2020-03-29 13:00:22 +00:00
|
|
|
"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"
|
|
|
|
)
|
|
|
|
|
2021-06-19 22:47:04 +00:00
|
|
|
// Signature represents input/output arguments for service from the aicra configuration
|
|
|
|
type Signature struct {
|
|
|
|
// Input arguments of the service
|
|
|
|
Input map[string]reflect.Type
|
|
|
|
// Output arguments of the service
|
2020-04-04 10:45:36 +00:00
|
|
|
Output map[string]reflect.Type
|
|
|
|
}
|
|
|
|
|
2021-06-19 22:47:04 +00:00
|
|
|
// BuildSignature builds a signature for a service configuration
|
|
|
|
func BuildSignature(service config.Service) *Signature {
|
|
|
|
s := &Signature{
|
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
|
|
|
}
|
|
|
|
|
2021-06-19 22:47:04 +00:00
|
|
|
// ValidateInput validates a handler's input arguments against the service signature
|
|
|
|
func (s *Signature) ValidateInput(handlerType reflect.Type) error {
|
2021-06-20 00:14:31 +00:00
|
|
|
ctxType := reflect.TypeOf((*context.Context)(nil)).Elem()
|
2021-04-18 17:25:31 +00:00
|
|
|
|
2021-06-20 00:14:31 +00:00
|
|
|
// missing or invalid first arg: context.Context
|
|
|
|
if handlerType.NumIn() < 1 {
|
2021-06-19 22:47:04 +00:00
|
|
|
return errMissingHandlerContextArgument
|
2021-04-18 17:25:31 +00:00
|
|
|
}
|
2021-06-20 00:14:31 +00:00
|
|
|
firstArgType := handlerType.In(0)
|
|
|
|
|
|
|
|
if !firstArgType.Implements(ctxType) {
|
|
|
|
return fmt.Errorf("fock")
|
|
|
|
}
|
2020-03-29 13:00:22 +00:00
|
|
|
|
2021-06-19 22:47:04 +00:00
|
|
|
// no input required
|
2020-03-29 13:00:22 +00:00
|
|
|
if len(s.Input) == 0 {
|
2021-06-19 22:47:04 +00:00
|
|
|
// input struct provided
|
|
|
|
if handlerType.NumIn() > 1 {
|
|
|
|
return errUnexpectedInput
|
|
|
|
}
|
2020-03-29 13:00:22 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-04-18 17:25:31 +00:00
|
|
|
// too much arguments
|
2021-06-19 22:47:04 +00:00
|
|
|
if handlerType.NumIn() > 2 {
|
|
|
|
return errMissingHandlerInputArgument
|
2020-03-29 13:00:22 +00:00
|
|
|
}
|
|
|
|
|
2020-03-29 14:58:53 +00:00
|
|
|
// arg must be a struct
|
2021-06-19 22:47:04 +00:00
|
|
|
inStruct := handlerType.In(1)
|
|
|
|
if inStruct.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
|
|
|
}
|
|
|
|
|
2021-06-19 22:47:04 +00:00
|
|
|
field, exists := inStruct.FieldByName(name)
|
2020-03-29 13:00:22 +00:00
|
|
|
if !exists {
|
2021-06-19 22:47:04 +00:00
|
|
|
return fmt.Errorf("%s: %w", name, errMissingConfigArgument)
|
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
|
|
|
|
}
|
|
|
|
|
2021-06-19 22:47:04 +00:00
|
|
|
// ValidateOutput validates a handler's output arguments against the service signature
|
|
|
|
func (s Signature) ValidateOutput(handlerType reflect.Type) error {
|
|
|
|
errType := reflect.TypeOf(api.ErrUnknown)
|
|
|
|
|
|
|
|
if handlerType.NumOut() < 1 {
|
|
|
|
return errMissingHandlerErrorArgument
|
2020-03-29 13:00:22 +00:00
|
|
|
}
|
|
|
|
|
2021-03-28 16:49:23 +00:00
|
|
|
// last output must be api.Err
|
2021-06-19 22:47:04 +00:00
|
|
|
lastArgType := handlerType.Out(handlerType.NumOut() - 1)
|
|
|
|
if !lastArgType.AssignableTo(errType) {
|
|
|
|
return errMissingHandlerErrorArgument
|
2020-03-29 13:00:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// no output -> ok
|
|
|
|
if len(s.Output) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-06-19 22:47:04 +00:00
|
|
|
if handlerType.NumOut() < 2 {
|
|
|
|
return errMissingHandlerOutputArgument
|
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-06-19 22:47:04 +00:00
|
|
|
outStructPtr := handlerType.Out(0)
|
|
|
|
if outStructPtr.Kind() != reflect.Ptr {
|
|
|
|
return errWrongOutputArgumentType
|
2020-03-29 14:58:53 +00:00
|
|
|
}
|
|
|
|
|
2021-06-19 22:47:04 +00:00
|
|
|
outStruct := outStructPtr.Elem()
|
|
|
|
if outStruct.Kind() != reflect.Struct {
|
|
|
|
return errWrongOutputArgumentType
|
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
|
|
|
}
|
|
|
|
|
2021-06-19 22:47:04 +00:00
|
|
|
field, exists := outStruct.FieldByName(name)
|
2020-03-29 13:00:22 +00:00
|
|
|
if !exists {
|
2021-06-19 22:47:04 +00:00
|
|
|
return fmt.Errorf("%s: %w", name, errMissingConfigArgument)
|
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
|
|
|
|
}
|