aicra/internal/dynfunc/spec.go

154 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
}
// 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 {
if len(param.Rename) < 1 {
continue
}
// 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 {
if len(param.Rename) < 1 {
continue
}
spec.Output[param.Rename] = param.ExtractType
}
return spec
}
// checks for HandlerFn input arguments
func (s spec) checkInput(fnv reflect.Value) error {
fnt := fnv.Type()
// fail on missing context (first arg)
if fnt.NumIn() < 1 {
return errMissingContext
}
// fail on invalid context argument
ctxArg := fnt.In(0)
if ctxArg != reflect.TypeOf(&api.Context{}) {
return errMissingContext
}
// no input -> ok
if len(s.Input) == 0 {
if fnt.NumIn() != 1 {
return errUnexpectedInput
}
return nil
}
if fnt.NumIn() != 2 {
return errMissingHandlerArgumentParam
}
// arg must be a struct
structArg := fnt.In(1)
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(fnv reflect.Value) error {
fnt := fnv.Type()
if fnt.NumOut() < 1 {
return errMissingHandlerOutput
}
// last output must be api.Err
errOutput := fnt.Out(fnt.NumOut() - 1)
if !errOutput.AssignableTo(reflect.TypeOf(api.ErrUnknown)) {
return errMissingHandlerErrorOutput
}
// no output -> ok
if len(s.Output) == 0 {
return nil
}
if fnt.NumOut() != 2 {
return errMissingParamOutput
}
// fail if first output is not a pointer to struct
structOutputPtr := fnt.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
}