Merge branch 'refactor/const-api-errors' of go/aicra into 0.3.0
allow constants for api.Error and update api.Handler signature - api.EmtyResponse().WithError(api.Error) is the new interface for api.Response - handlers return an api.Response that is wrapped into the final response - server.HandleFunc becomes server.Handle
This commit is contained in:
commit
d3e8d48bc3
|
@ -1,70 +1,94 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
var (
|
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.
|
// ErrorUnknown represents any error which cause is unknown.
|
||||||
// It might also be used for debug purposes as this error
|
// It might also be used for debug purposes as this error
|
||||||
// has to be used the less possible
|
// 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 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
|
// ErrorAlreadyExists has to be set when trying to insert data, but identifiers or
|
||||||
// unique fields already exists
|
// 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 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 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 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
|
// MissingDownloadHeaders has to be set when the implementation
|
||||||
// of a service of type 'download' (which returns a file instead of
|
// of a service of type 'download' (which returns a file instead of
|
||||||
// a set or output fields) is missing its HEADER field
|
// 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
|
// ErrorMissingDownloadBody has to be set when the implementation
|
||||||
// of a service of type 'download' (which returns a file instead of
|
// of a service of type 'download' (which returns a file instead of
|
||||||
// a set or output fields) is missing its BODY field
|
// 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
|
// ErrorUnknownService is set when there is no service matching
|
||||||
// the http request URI.
|
// 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
|
// ErrorUncallableService is set when there the requested service's
|
||||||
// implementation (plugin file) is not found/callable
|
// 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
|
// ErrorPermission is set when there is a permission error by default
|
||||||
// the api returns a permission error when the current scope (built
|
// the api returns a permission error when the current scope (built
|
||||||
// by middlewares) does not match the scope required in the config.
|
// by middlewares) does not match the scope required in the config.
|
||||||
// You can add your own permission policy and use this error
|
// 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
|
// ErrorToken has to be set (usually in authentication middleware) to tell
|
||||||
// the user that this authentication token is expired or invalid
|
// 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
|
// ErrorMissingParam is set when a *required* parameter is missing from the
|
||||||
// http request
|
// 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
|
// ErrorInvalidParam is set when a given parameter fails its type check as
|
||||||
// defined in the config file.
|
// 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
|
// ErrorInvalidDefaultParam is set when an optional parameter's default value
|
||||||
// does not match its type.
|
// 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",
|
||||||
|
}
|
||||||
|
|
54
api/error.go
54
api/error.go
|
@ -1,40 +1,42 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error represents an http response error following the api format.
|
// Error represents an http response error following the api format.
|
||||||
// These are used by the services to set the *execution status*
|
// These are used by the services to set the *execution status*
|
||||||
// directly into the response as JSON alongside response output fields.
|
// directly into the response as JSON alongside response output fields.
|
||||||
type Error struct {
|
type Error int
|
||||||
|
|
||||||
|
// Error implements the error interface
|
||||||
|
func (e Error) Error() string {
|
||||||
|
// use unknown error if no reason
|
||||||
|
reason, ok := errorReasons[e]
|
||||||
|
if !ok {
|
||||||
|
return ErrorUnknown.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
Code int `json:"code"`
|
||||||
Reason string `json:"reason"`
|
Reason string `json:"reason"`
|
||||||
Arguments []interface{} `json:"arguments"`
|
}{
|
||||||
}
|
Code: int(e),
|
||||||
|
Reason: reason,
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
return json.Marshal(formatted)
|
||||||
|
|
||||||
// Implements 'error'
|
|
||||||
func (e Error) Error() string {
|
|
||||||
if e.Arguments == nil || len(e.Arguments) < 1 {
|
|
||||||
return fmt.Sprintf("[%d] %s", e.Code, e.Reason)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("[%d] %s (%v)", e.Code, e.Reason, e.Arguments)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,28 +4,23 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HandlerFunc manages an API request
|
// HandlerFn defines the handler signature
|
||||||
type HandlerFunc func(Request, *Response)
|
type HandlerFn func(req Request, res *Response) Error
|
||||||
|
|
||||||
// Handler is an API handler ready to be bound
|
// Handler is an API handler ready to be bound
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
path string
|
path string
|
||||||
method string
|
method string
|
||||||
handle HandlerFunc
|
Fn HandlerFn
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHandler builds a handler from its http method and path
|
// 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{
|
return &Handler{
|
||||||
path: path,
|
path: path,
|
||||||
method: strings.ToUpper(method),
|
method: strings.ToUpper(method),
|
||||||
handle: handlerFunc,
|
Fn: fn,
|
||||||
}
|
}, nil
|
||||||
}
|
|
||||||
|
|
||||||
// Handle fires a handler
|
|
||||||
func (h *Handler) Handle(req Request, res *Response) {
|
|
||||||
h.handle(req, res)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMethod returns the handler's HTTP method
|
// GetMethod returns the handler's HTTP method
|
||||||
|
|
|
@ -16,29 +16,20 @@ type Response struct {
|
||||||
err Error
|
err Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewResponse creates an empty response. An optional error can be passed as its first argument.
|
// EmptyResponse creates an empty response.
|
||||||
func NewResponse(errors ...Error) *Response {
|
func EmptyResponse() *Response {
|
||||||
res := &Response{
|
return &Response{
|
||||||
Status: http.StatusOK,
|
Status: http.StatusOK,
|
||||||
Data: make(ResponseData),
|
Data: make(ResponseData),
|
||||||
err: ErrorFailure(),
|
err: ErrorFailure,
|
||||||
Headers: make(http.Header),
|
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.
|
// WithError sets the error from a base error with error arguments.
|
||||||
func (res *Response) SetError(baseError Error, arguments ...interface{}) {
|
func (res *Response) WithError(err Error) *Response {
|
||||||
if len(arguments) > 0 {
|
res.err = err
|
||||||
baseError.SetArguments(arguments[0], arguments[1:]...)
|
return res
|
||||||
}
|
|
||||||
res.err = baseError
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error implements the error interface and dispatches to internal error.
|
// Error implements the error interface and dispatches to internal error.
|
||||||
|
|
19
http.go
19
http.go
|
@ -18,7 +18,7 @@ func (server httpServer) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||||
// 1. find a matching service in the config
|
// 1. find a matching service in the config
|
||||||
service := server.config.Find(req)
|
service := server.config.Find(req)
|
||||||
if service == nil {
|
if service == nil {
|
||||||
response := api.NewResponse(api.ErrorUnknownService())
|
response := api.EmptyResponse().WithError(api.ErrorUnknownService)
|
||||||
response.ServeHTTP(res, req)
|
response.ServeHTTP(res, req)
|
||||||
logError(response)
|
logError(response)
|
||||||
return
|
return
|
||||||
|
@ -30,7 +30,7 @@ func (server httpServer) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||||
// 3. extract URI data
|
// 3. extract URI data
|
||||||
err := dataset.ExtractURI(req)
|
err := dataset.ExtractURI(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response := api.NewResponse(api.ErrorMissingParam())
|
response := api.EmptyResponse().WithError(api.ErrorMissingParam)
|
||||||
response.ServeHTTP(res, req)
|
response.ServeHTTP(res, req)
|
||||||
logError(response)
|
logError(response)
|
||||||
return
|
return
|
||||||
|
@ -39,7 +39,7 @@ func (server httpServer) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||||
// 4. extract query data
|
// 4. extract query data
|
||||||
err = dataset.ExtractQuery(req)
|
err = dataset.ExtractQuery(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response := api.NewResponse(api.ErrorMissingParam())
|
response := api.EmptyResponse().WithError(api.ErrorMissingParam)
|
||||||
response.ServeHTTP(res, req)
|
response.ServeHTTP(res, req)
|
||||||
logError(response)
|
logError(response)
|
||||||
return
|
return
|
||||||
|
@ -48,7 +48,7 @@ func (server httpServer) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||||
// 5. extract form/json data
|
// 5. extract form/json data
|
||||||
err = dataset.ExtractForm(req)
|
err = dataset.ExtractForm(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response := api.NewResponse(api.ErrorMissingParam())
|
response := api.EmptyResponse().WithError(api.ErrorMissingParam)
|
||||||
response.ServeHTTP(res, req)
|
response.ServeHTTP(res, req)
|
||||||
logError(response)
|
logError(response)
|
||||||
return
|
return
|
||||||
|
@ -68,15 +68,13 @@ func (server httpServer) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||||
// 7. fail if found no handler
|
// 7. fail if found no handler
|
||||||
if foundHandler == nil {
|
if foundHandler == nil {
|
||||||
if found {
|
if found {
|
||||||
r := api.NewResponse()
|
r := api.EmptyResponse().WithError(api.ErrorUncallableService)
|
||||||
r.SetError(api.ErrorUncallableService(), service.Method, service.Pattern)
|
|
||||||
r.ServeHTTP(res, req)
|
r.ServeHTTP(res, req)
|
||||||
logError(r)
|
logError(r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
r := api.NewResponse()
|
r := api.EmptyResponse().WithError(api.ErrorUnknownService)
|
||||||
r.SetError(api.ErrorUnknownService(), service.Method, service.Pattern)
|
|
||||||
r.ServeHTTP(res, req)
|
r.ServeHTTP(res, req)
|
||||||
logError(r)
|
logError(r)
|
||||||
return
|
return
|
||||||
|
@ -93,8 +91,9 @@ func (server httpServer) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||||
apireq.Param = dataset.Data
|
apireq.Param = dataset.Data
|
||||||
|
|
||||||
// 10. execute
|
// 10. execute
|
||||||
response := api.NewResponse()
|
response := api.EmptyResponse()
|
||||||
foundHandler.Handle(*apireq, response)
|
apiErr := foundHandler.Fn(*apireq, response)
|
||||||
|
response.WithError(apiErr)
|
||||||
|
|
||||||
// 11. apply headers
|
// 11. apply headers
|
||||||
res.Header().Set("Content-Type", "application/json; charset=utf-8")
|
res.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
|
12
server.go
12
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
|
// HandleFunc sets a new handler for an HTTP method to a path
|
||||||
func (s *Server) HandleFunc(httpMethod, path string, handlerFunc api.HandlerFunc) {
|
func (s *Server) Handle(httpMethod, path string, fn api.HandlerFn) {
|
||||||
handler := api.NewHandler(httpMethod, path, handlerFunc)
|
handler, err := api.NewHandler(httpMethod, path, fn)
|
||||||
s.handlers = append(s.handlers, handler)
|
if err != nil {
|
||||||
}
|
panic(err)
|
||||||
|
}
|
||||||
// Handle sets a new handler
|
|
||||||
func (s *Server) Handle(handler *api.Handler) {
|
|
||||||
s.handlers = append(s.handlers, handler)
|
s.handlers = append(s.handlers, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue