diff --git a/api/error.defaults.go b/api/error.defaults.go index 33ff0fa..4a9ee0e 100644 --- a/api/error.defaults.go +++ b/api/error.defaults.go @@ -1,70 +1,94 @@ package api var ( - // ErrorSuccess represents a generic successful service execution - ErrorSuccess = func() Error { return Error{0, "all right", nil} } - - // ErrorFailure is the most generic error - ErrorFailure = func() Error { return Error{1, "it failed", nil} } - // ErrorUnknown represents any error which cause is unknown. // It might also be used for debug purposes as this error // has to be used the less possible - ErrorUnknown = func() Error { return Error{-1, "", nil} } + ErrorUnknown Error = -1 + + // ErrorSuccess represents a generic successful service execution + ErrorSuccess Error = 0 + + // ErrorFailure is the most generic error + ErrorFailure Error = 1 // ErrorNoMatchFound has to be set when trying to fetch data and there is no result - ErrorNoMatchFound = func() Error { return Error{2, "no resource found", nil} } + ErrorNoMatchFound Error = 2 // ErrorAlreadyExists has to be set when trying to insert data, but identifiers or // unique fields already exists - ErrorAlreadyExists = func() Error { return Error{3, "resource already exists", nil} } + ErrorAlreadyExists Error = 3 // ErrorConfig has to be set when there is a configuration error - ErrorConfig = func() Error { return Error{4, "configuration error", nil} } + ErrorConfig Error = 4 // ErrorUpload has to be set when a file upload failed - ErrorUpload = func() Error { return Error{100, "upload failed", nil} } + ErrorUpload Error = 100 // ErrorDownload has to be set when a file download failed - ErrorDownload = func() Error { return Error{101, "download failed", nil} } + ErrorDownload Error = 101 // MissingDownloadHeaders has to be set when the implementation // of a service of type 'download' (which returns a file instead of // a set or output fields) is missing its HEADER field - MissingDownloadHeaders = func() Error { return Error{102, "download headers are missing", nil} } + MissingDownloadHeaders Error = 102 // ErrorMissingDownloadBody has to be set when the implementation // of a service of type 'download' (which returns a file instead of // a set or output fields) is missing its BODY field - ErrorMissingDownloadBody = func() Error { return Error{103, "download body is missing", nil} } + ErrorMissingDownloadBody Error = 103 // ErrorUnknownService is set when there is no service matching // the http request URI. - ErrorUnknownService = func() Error { return Error{200, "unknown service", nil} } + ErrorUnknownService Error = 200 // ErrorUncallableService is set when there the requested service's // implementation (plugin file) is not found/callable - ErrorUncallableService = func() Error { return Error{202, "uncallable service", nil} } + ErrorUncallableService Error = 202 + + // ErrorNotImplemented is set when a handler is not implemented yet + ErrorNotImplemented Error = 203 // ErrorPermission is set when there is a permission error by default // the api returns a permission error when the current scope (built // by middlewares) does not match the scope required in the config. // You can add your own permission policy and use this error - ErrorPermission = func() Error { return Error{300, "permission error", nil} } + ErrorPermission Error = 300 // ErrorToken has to be set (usually in authentication middleware) to tell // the user that this authentication token is expired or invalid - ErrorToken = func() Error { return Error{301, "token error", nil} } + ErrorToken Error = 301 // ErrorMissingParam is set when a *required* parameter is missing from the // http request - ErrorMissingParam = func() Error { return Error{400, "missing parameter", nil} } + ErrorMissingParam Error = 400 // ErrorInvalidParam is set when a given parameter fails its type check as // defined in the config file. - ErrorInvalidParam = func() Error { return Error{401, "invalid parameter", nil} } + ErrorInvalidParam Error = 401 // ErrorInvalidDefaultParam is set when an optional parameter's default value // does not match its type. - ErrorInvalidDefaultParam = func() Error { return Error{402, "invalid default param", nil} } + ErrorInvalidDefaultParam Error = 402 ) + +var errorReasons = map[Error]string{ + ErrorUnknown: "unknown error", + ErrorSuccess: "all right", + ErrorFailure: "it failed", + ErrorNoMatchFound: "resource found", + ErrorAlreadyExists: "already exists", + ErrorConfig: "configuration error", + ErrorUpload: "upload failed", + ErrorDownload: "download failed", + MissingDownloadHeaders: "download headers are missing", + ErrorMissingDownloadBody: "download body is missing", + ErrorUnknownService: "unknown service", + ErrorUncallableService: "uncallable service", + ErrorNotImplemented: "not implemented", + ErrorPermission: "permission error", + ErrorToken: "token error", + ErrorMissingParam: "missing parameter", + ErrorInvalidParam: "invalid parameter", + ErrorInvalidDefaultParam: "invalid default param", +} diff --git a/api/error.go b/api/error.go index 6bee525..ef6c9fd 100644 --- a/api/error.go +++ b/api/error.go @@ -1,40 +1,42 @@ package api import ( + "encoding/json" "fmt" ) // Error represents an http response error following the api format. // These are used by the services to set the *execution status* // directly into the response as JSON alongside response output fields. -type Error struct { - Code int `json:"code"` - Reason string `json:"reason"` - Arguments []interface{} `json:"arguments"` -} +type Error int -// SetArguments set one or multiple arguments to the error -// to be displayed back to API caller -func (e *Error) SetArguments(arg0 interface{}, args ...interface{}) { - - // 1. clear arguments */ - e.Arguments = make([]interface{}, 0) - - // 2. add arg[0] - e.Arguments = append(e.Arguments, arg0) - - // 3. add optional other arguments - for _, arg := range args { - e.Arguments = append(e.Arguments, arg) - } - -} - -// Implements 'error' +// Error implements the error interface func (e Error) Error() string { - if e.Arguments == nil || len(e.Arguments) < 1 { - return fmt.Sprintf("[%d] %s", e.Code, e.Reason) + // use unknown error if no reason + reason, ok := errorReasons[e] + if !ok { + return ErrorUnknown.Error() } - return fmt.Sprintf("[%d] %s (%v)", e.Code, e.Reason, e.Arguments) + return fmt.Sprintf("[%d] %s", e, reason) +} + +// MarshalJSON implements encoding/json.Marshaler interface +func (e Error) MarshalJSON() ([]byte, error) { + // use unknown error if no reason + reason, ok := errorReasons[e] + if !ok { + return ErrorUnknown.MarshalJSON() + } + + // format to proper struct + formatted := struct { + Code int `json:"code"` + Reason string `json:"reason"` + }{ + Code: int(e), + Reason: reason, + } + + return json.Marshal(formatted) } diff --git a/api/handler.go b/api/handler.go index b58d3ce..c6e5cc9 100644 --- a/api/handler.go +++ b/api/handler.go @@ -4,28 +4,23 @@ import ( "strings" ) -// HandlerFunc manages an API request -type HandlerFunc func(Request, *Response) +// HandlerFn defines the handler signature +type HandlerFn func(req Request, res *Response) Error // Handler is an API handler ready to be bound type Handler struct { path string method string - handle HandlerFunc + Fn HandlerFn } // NewHandler builds a handler from its http method and path -func NewHandler(method, path string, handlerFunc HandlerFunc) *Handler { +func NewHandler(method, path string, fn HandlerFn) (*Handler, error) { return &Handler{ path: path, method: strings.ToUpper(method), - handle: handlerFunc, - } -} - -// Handle fires a handler -func (h *Handler) Handle(req Request, res *Response) { - h.handle(req, res) + Fn: fn, + }, nil } // GetMethod returns the handler's HTTP method diff --git a/api/response.go b/api/response.go index b6f9935..242be3d 100644 --- a/api/response.go +++ b/api/response.go @@ -16,29 +16,20 @@ type Response struct { err Error } -// NewResponse creates an empty response. An optional error can be passed as its first argument. -func NewResponse(errors ...Error) *Response { - res := &Response{ +// EmptyResponse creates an empty response. +func EmptyResponse() *Response { + return &Response{ Status: http.StatusOK, Data: make(ResponseData), - err: ErrorFailure(), + err: ErrorFailure, Headers: make(http.Header), } - - // optional error - if len(errors) == 1 { - res.err = errors[0] - } - - return res } -// SetError sets the error from a base error with error arguments. -func (res *Response) SetError(baseError Error, arguments ...interface{}) { - if len(arguments) > 0 { - baseError.SetArguments(arguments[0], arguments[1:]...) - } - res.err = baseError +// WithError sets the error from a base error with error arguments. +func (res *Response) WithError(err Error) *Response { + res.err = err + return res } // Error implements the error interface and dispatches to internal error. diff --git a/http.go b/http.go index 9c0914e..8db6033 100644 --- a/http.go +++ b/http.go @@ -18,7 +18,7 @@ func (server httpServer) ServeHTTP(res http.ResponseWriter, req *http.Request) { // 1. find a matching service in the config service := server.config.Find(req) if service == nil { - response := api.NewResponse(api.ErrorUnknownService()) + response := api.EmptyResponse().WithError(api.ErrorUnknownService) response.ServeHTTP(res, req) logError(response) return @@ -30,7 +30,7 @@ func (server httpServer) ServeHTTP(res http.ResponseWriter, req *http.Request) { // 3. extract URI data err := dataset.ExtractURI(req) if err != nil { - response := api.NewResponse(api.ErrorMissingParam()) + response := api.EmptyResponse().WithError(api.ErrorMissingParam) response.ServeHTTP(res, req) logError(response) return @@ -39,7 +39,7 @@ func (server httpServer) ServeHTTP(res http.ResponseWriter, req *http.Request) { // 4. extract query data err = dataset.ExtractQuery(req) if err != nil { - response := api.NewResponse(api.ErrorMissingParam()) + response := api.EmptyResponse().WithError(api.ErrorMissingParam) response.ServeHTTP(res, req) logError(response) return @@ -48,7 +48,7 @@ func (server httpServer) ServeHTTP(res http.ResponseWriter, req *http.Request) { // 5. extract form/json data err = dataset.ExtractForm(req) if err != nil { - response := api.NewResponse(api.ErrorMissingParam()) + response := api.EmptyResponse().WithError(api.ErrorMissingParam) response.ServeHTTP(res, req) logError(response) return @@ -68,15 +68,13 @@ func (server httpServer) ServeHTTP(res http.ResponseWriter, req *http.Request) { // 7. fail if found no handler if foundHandler == nil { if found { - r := api.NewResponse() - r.SetError(api.ErrorUncallableService(), service.Method, service.Pattern) + r := api.EmptyResponse().WithError(api.ErrorUncallableService) r.ServeHTTP(res, req) logError(r) return } - r := api.NewResponse() - r.SetError(api.ErrorUnknownService(), service.Method, service.Pattern) + r := api.EmptyResponse().WithError(api.ErrorUnknownService) r.ServeHTTP(res, req) logError(r) return @@ -93,8 +91,9 @@ func (server httpServer) ServeHTTP(res http.ResponseWriter, req *http.Request) { apireq.Param = dataset.Data // 10. execute - response := api.NewResponse() - foundHandler.Handle(*apireq, response) + response := api.EmptyResponse() + apiErr := foundHandler.Fn(*apireq, response) + response.WithError(apiErr) // 11. apply headers res.Header().Set("Content-Type", "application/json; charset=utf-8") diff --git a/server.go b/server.go index 10303fa..a8bd3d0 100644 --- a/server.go +++ b/server.go @@ -47,13 +47,11 @@ func New(configPath string, dtypes ...datatype.T) (*Server, error) { } // HandleFunc sets a new handler for an HTTP method to a path -func (s *Server) HandleFunc(httpMethod, path string, handlerFunc api.HandlerFunc) { - handler := api.NewHandler(httpMethod, path, handlerFunc) - s.handlers = append(s.handlers, handler) -} - -// Handle sets a new handler -func (s *Server) Handle(handler *api.Handler) { +func (s *Server) Handle(httpMethod, path string, fn api.HandlerFn) { + handler, err := api.NewHandler(httpMethod, path, fn) + if err != nil { + panic(err) + } s.handlers = append(s.handlers, handler) }