aicra/internal/dynfunc/spec.go

150 lines
3.2 KiB
Go

package dynfunc
import (
"fmt"
"reflect"
"strings"
"git.xdrm.io/go/aicra/api"
"git.xdrm.io/go/aicra/internal/config"
)
type spec struct {
Input map[string]reflect.Type
Output map[string]reflect.Type
// HasContext defines whether the given handler has api.Ctx as first argument
HasContext bool
}
// builds a spec from the configuration service
func makeSpec(service config.Service) *spec {
s := &spec{
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 *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
}
// none required
if len(s.Input) == 0 {
return nil
}
// too much arguments
if impl.NumIn() != requiredInput {
return errMissingHandlerArgumentParam
}
// arg must be a struct
structArg := impl.In(structIndex)
if structArg.Kind() != reflect.Struct {
return errMissingParamArgument
}
// check for invalid param
for name, ptype := range s.Input {
if name[0] == strings.ToLower(name)[0] {
return fmt.Errorf("%s: %w", name, errUnexportedName)
}
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 instead of %s)", name, errWrongParamTypeFromConfig, field.Type, ptype)
}
}
return nil
}
// checks for HandlerFn output arguments
func (s spec) checkOutput(impl reflect.Type) error {
if impl.NumOut() < 1 {
return errMissingHandlerOutput
}
// last output must be api.Err
errOutput := impl.Out(impl.NumOut() - 1)
if !errOutput.AssignableTo(reflect.TypeOf(api.ErrUnknown)) {
return errMissingHandlerErrorOutput
}
// no output -> ok
if len(s.Output) == 0 {
return nil
}
if impl.NumOut() != 2 {
return errMissingParamOutput
}
// fail if first output is not a pointer to struct
structOutputPtr := impl.Out(0)
if structOutputPtr.Kind() != reflect.Ptr {
return errMissingParamOutput
}
structOutput := structOutputPtr.Elem()
if structOutput.Kind() != reflect.Struct {
return errMissingParamOutput
}
// fail on invalid output
for name, ptype := range s.Output {
if name[0] == strings.ToLower(name)[0] {
return fmt.Errorf("%s: %w", name, errUnexportedName)
}
field, exists := structOutput.FieldByName(name)
if !exists {
return fmt.Errorf("%s: %w", name, errMissingOutputFromConfig)
}
// ignore types evalutating to nil
if ptype == nil {
continue
}
if !field.Type.ConvertibleTo(ptype) {
return fmt.Errorf("%s: %w (%s instead of %s)", name, errWrongParamTypeFromConfig, field.Type, ptype)
}
}
return nil
}