aicra/internal/dynfunc/signature.go

149 lines
3.2 KiB
Go
Raw Permalink Normal View History

package dynfunc
import (
"fmt"
"reflect"
2020-03-29 17:13:07 +00:00
"strings"
"git.xdrm.io/go/aicra/api"
"git.xdrm.io/go/aicra/internal/config"
)
// signature represents input/output arguments for a dynamic function
type signature struct {
2020-04-04 10:45:36 +00:00
Input map[string]reflect.Type
Output map[string]reflect.Type
}
// builds a spec from the configuration service
func signatureFromService(service config.Service) *signature {
s := &signature{
Input: make(map[string]reflect.Type),
Output: make(map[string]reflect.Type),
}
for _, param := range service.Input {
if len(param.Rename) < 1 {
continue
}
// make a pointer if optional
if param.Optional {
s.Input[param.Rename] = reflect.PtrTo(param.ExtractType)
continue
}
s.Input[param.Rename] = param.ExtractType
}
for _, param := range service.Output {
if len(param.Rename) < 1 {
continue
}
s.Output[param.Rename] = param.ExtractType
}
return s
}
// checks for HandlerFn input arguments
func (s *signature) 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
}
// none required
if len(s.Input) == 0 {
return nil
}
// too much arguments
if impl.NumIn() != requiredInput {
2020-04-04 10:46:43 +00:00
return errMissingHandlerArgumentParam
}
// arg must be a struct
structArg := impl.In(structIndex)
if structArg.Kind() != reflect.Struct {
2020-04-04 10:46:43 +00:00
return errMissingParamArgument
}
2020-03-29 17:13:07 +00:00
// check for invalid param
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
}
field, exists := structArg.FieldByName(name)
if !exists {
2020-04-04 10:46:43 +00:00
return fmt.Errorf("%s: %w", name, errMissingParamFromConfig)
}
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)
}
}
return nil
}
// checks for HandlerFn output arguments
func (s signature) checkOutput(impl reflect.Type) error {
if impl.NumOut() < 1 {
2020-04-04 10:46:43 +00:00
return errMissingHandlerOutput
}
// last output must be api.Err
errOutput := impl.Out(impl.NumOut() - 1)
if !errOutput.AssignableTo(reflect.TypeOf(api.ErrUnknown)) {
2020-04-04 10:46:43 +00:00
return errMissingHandlerErrorOutput
}
// no output -> ok
if len(s.Output) == 0 {
return nil
}
if impl.NumOut() != 2 {
2020-04-04 10:46:43 +00:00
return errMissingParamOutput
}
// fail if first output is not a pointer to struct
structOutputPtr := impl.Out(0)
if structOutputPtr.Kind() != reflect.Ptr {
2020-04-04 10:46:43 +00:00
return errMissingParamOutput
}
structOutput := structOutputPtr.Elem()
if structOutput.Kind() != reflect.Struct {
2020-04-04 10:46:43 +00:00
return errMissingParamOutput
}
// 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
}
field, exists := structOutput.FieldByName(name)
if !exists {
2020-04-04 10:46:43 +00:00
return fmt.Errorf("%s: %w", name, errMissingOutputFromConfig)
}
// 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)
}
}
return nil
}