aicra/dynamic/spec.go

122 lines
2.6 KiB
Go

package dynamic
import (
"fmt"
"reflect"
"git.xdrm.io/go/aicra/api"
"git.xdrm.io/go/aicra/internal/config"
)
// 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 {
// 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 {
// make a pointer if optional
if param.Optional {
spec.Output[param.Rename] = reflect.PtrTo(param.ExtractType)
continue
}
spec.Output[param.Rename] = param.ExtractType
}
return spec
}
// checks for HandlerFn input arguments
func (s spec) checkInput(fnt reflect.Type) error {
if fnt.NumIn() != 1 {
return ErrMissingHandlerArgument
}
// arg[0] must be api.Request
requestArg := fnt.In(0)
if !requestArg.AssignableTo(reflect.TypeOf(api.Request{})) {
return ErrMissingRequestArgument
}
// no input -> ok
if len(s.Input) == 0 {
return nil
}
if fnt.NumIn() < 2 {
return ErrMissingHandlerArgumentParam
}
// arg[1] must be a struct
structArg := fnt.In(1)
if structArg.Kind() != reflect.Struct {
return ErrMissingParamArgument
}
// check for invlaid param
for name, ptype := range s.Input {
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)", name, ErrWrongParamTypeFromConfig, ptype)
}
}
return nil
}
// checks for HandlerFn output arguments
func (s spec) checkOutput(fnt reflect.Type) error {
if fnt.NumOut() < 1 {
return ErrMissingHandlerOutput
}
// last output must be api.Error
errOutput := fnt.Out(fnt.NumOut() - 1)
if !errOutput.AssignableTo(reflect.TypeOf(api.ErrorUnknown)) {
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 struct
structOutput := fnt.Out(0)
if structOutput.Kind() != reflect.Struct {
return ErrMissingParamArgument
}
// fail on invalid output
for name, ptype := range s.Output {
field, exists := structOutput.FieldByName(name)
if !exists {
return fmt.Errorf("%s: %w", name, ErrMissingOutputFromConfig)
}
if !ptype.AssignableTo(field.Type) {
return fmt.Errorf("%s: %w (%s)", name, ErrWrongOutputTypeFromConfig, ptype)
}
}
return nil
}