package api import ( "reflect" "gitlab.com/uafrica/go-utils/errors" ) type handler struct { RequestParamsType reflect.Type RequestBodyType reflect.Type ResponseType reflect.Type FuncValue reflect.Value } func NewHandler(fnc interface{}) (handler, error) { h := handler{} fncType := reflect.TypeOf(fnc) if fncType.NumIn() < 2 || fncType.NumIn() > 3 { return h, errors.Errorf("takes %d args instead of (Context, Params[, Body])", fncType.NumIn()) } if fncType.NumOut() < 1 || fncType.NumOut() > 2 { return h, errors.Errorf("returns %d results instead of ([Response,] error)", fncType.NumOut()) } //arg[0] must implement interface lambda_helpers.Context if _, ok := reflect.New(fncType.In(0)).Interface().(IContext); !ok { return h, errors.Errorf("first arg %v does not implement lambda_helpers.IContext", fncType.In(0)) } //arg[1] must be a struct for params. It may be an empty struct, but //all public fields require a json tag which we will use to math the URL param name if err := validateStructType(fncType.In(1)); err != nil { return h, errors.Errorf("second arg %v is not valid params struct type", fncType.In(1)) } h.RequestParamsType = fncType.In(1) //arg[2] is optional and must be a struct for request body. It may be an empty struct, but //all public fields require a json tag which we will use to unmarshal the request body from JSON if fncType.NumIn() >= 3 { if fncType.In(2).Kind() == reflect.Slice { if err := validateStructType(fncType.In(2).Elem()); err != nil { return h, errors.Errorf("third arg %v is not valid body []struct type", fncType.In(2)) } } else { if err := validateStructType(fncType.In(2)); err != nil { return h, errors.Errorf("third arg %v is not valid body struct type", fncType.In(2)) } } //todo: check special fields for claims, and see if also applies to params struct... //AccountID must be int64 or *int64 with tag =??? //UserID must be int64 or *int64 with tag =??? //Username must be string with tag =??? h.RequestBodyType = fncType.In(2) } //if 2 results, first must be response struct or array of response structs that will be marshalled to JSON if fncType.NumOut() > 1 { if fncType.Out(0).Kind() == reflect.Slice { if err := validateStructType(fncType.Out(0).Elem()); err != nil { return h, errors.Errorf("first result %v is not valid response []struct type", fncType.Out(0)) } } else { if err := validateStructType(fncType.Out(0)); err != nil { return h, errors.Errorf("first result %v is not valid response struct type", fncType.Out(0)) } } h.ResponseType = fncType.Out(0) } //last result must be error if _, ok := reflect.New(fncType.Out(fncType.NumOut() - 1)).Interface().(*error); !ok { return h, errors.Errorf("last result %v is not error type", fncType.Out(fncType.NumOut()-1)) } h.FuncValue = reflect.ValueOf(fnc) return h, nil } func validateStructType(t reflect.Type) error { if t.Kind() != reflect.Struct { return errors.Errorf("%v is %v, not a struct", t, t.Kind()) } // for i := 0; i < t.NumField(); i++ { // f := t.Field(i) // if f.... check tags recursively... for now, not too strict ... add checks if we see issues that break the API, to help dev to fix before we deploy, or to prevent bad habits... // } return nil }