package httpjson import ( "context" "encoding/json" "errors" "net/http" "reflect" ) // ErrorWriter is responsible for writing the provided error value // to the response. type ErrorWriter func(context.Context, http.ResponseWriter, error) // DefaultResponse will be sent as the response body // when the handler function signature // has no return value. var DefaultResponse = json.RawMessage(`{"message":"ok"}`) // handler is an http.Handler that calls a function for each request. // It uses the signature of the function to decide how to interpret type handler struct { fv reflect.Value inType reflect.Type hasCtx bool errFunc ErrorWriter } // Handler returns an HTTP handler for function f. // See the package doc for details on allowed signatures for f. // If f returns a non-nil error, the handler will call errFunc. func Handler(f interface{}, errFunc ErrorWriter) (http.Handler, error) { fv := reflect.ValueOf(f) hasCtx, inType, err := funcInputType(fv) if err != nil { return nil, err } h := &handler{fv, inType, hasCtx, errFunc} return h, nil } func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { var a []reflect.Value if h.hasCtx { ctx := req.Context() ctx = context.WithValue(ctx, reqKey, req) ctx = context.WithValue(ctx, respKey, w) a = append(a, reflect.ValueOf(ctx)) } if h.inType != nil { inPtr := reflect.New(h.inType) err := Read(req.Body, inPtr.Interface()) if err != nil { h.errFunc(req.Context(), w, err) return } a = append(a, inPtr.Elem()) } rv := h.fv.Call(a) var ( res interface{} err error ) switch n := len(rv); { case n == 0: res = &DefaultResponse case n == 1 && !h.fv.Type().Out(0).Implements(errorType): res = rv[0].Interface() case n == 1 && h.fv.Type().Out(0).Implements(errorType): // out param is of type error; its value can still be nil res = &DefaultResponse err, _ = rv[0].Interface().(error) case n == 2: res = rv[0].Interface() err, _ = rv[1].Interface().(error) } if err != nil { h.errFunc(req.Context(), w, err) return } Write(req.Context(), w, 200, res) } var ( errorType = reflect.TypeOf((*error)(nil)).Elem() contextType = reflect.TypeOf((*context.Context)(nil)).Elem() ) func funcInputType(fv reflect.Value) (hasCtx bool, t reflect.Type, err error) { ft := fv.Type() if ft.Kind() != reflect.Func || ft.IsVariadic() { return false, nil, errors.New("need nonvariadic func in " + ft.String()) } off := 0 // or 1 with context hasCtx = ft.NumIn() >= 1 && ft.In(0).Implements(contextType) if hasCtx { off = 1 } if ft.NumIn() > off+1 { return false, nil, errors.New("too many params in " + ft.String()) } if ft.NumIn() == off+1 { t = ft.In(ft.NumIn() - 1) } if n := ft.NumOut(); n == 2 && !ft.Out(1).Implements(errorType) { return false, nil, errors.New("second return value must be error in " + ft.String()) } else if n > 2 { return false, nil, errors.New("need at most two return values in " + ft.String()) } return hasCtx, t, nil }