package routes import ( "context" "encoding/json" "io" "net/http" ) // Validator is implemented by request types to validate themselves. type Validator interface { Valid() error } type RpcHandler[T, U any] func(context.Context, T) (U, error) // RPC creates a typed RPC endpoint (POST, JSON request/response). func RPC[T, U any](path string, handler RpcHandler[T, U]) Route { var req T var res U return Route{ Pattern: "POST " + path, Handler: &rpcHandler[T, U]{ fn: handler, }, ReqType: req, ResType: res, } } type rpcHandler[T, U any] struct { fn RpcHandler[T, U] } func (h *rpcHandler[T, U]) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") enc := json.NewEncoder(w) // Read the request var req T if err := json.NewDecoder(r.Body).Decode(&req); err != nil && err != io.EOF { w.WriteHeader(http.StatusBadRequest) enc.Encode(InvalidJsonError{Message: err.Error()}) return } // Validate the request, if the request type implements Validator. // https://github.com/golang/go/issues/49206 explains the peculiar cast to interface{}. if v, ok := (interface{})(req).(Validator); ok { if err := v.Valid(); err != nil { w.WriteHeader(http.StatusBadRequest) enc.Encode(err) return } } // Call the handler function res, err := h.fn(r.Context(), req) if err != nil { enc.Encode(ErrorResponse{Message: err.Error()}) return } enc.Encode(res) }