Compare commits

..

No commits in common. "d3e8d48bc3ba7d3cf315cdc8d5b0949896d43698" and "2f9534a3b05ad1d291ec8d0ed18195c105f18856" have entirely different histories.

6 changed files with 92 additions and 101 deletions

View File

@ -1,94 +1,70 @@
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 Error = -1 ErrorUnknown = func() Error { return Error{-1, "", nil} }
// 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 Error = 2 ErrorNoMatchFound = func() Error { return Error{2, "no resource found", nil} }
// 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 Error = 3 ErrorAlreadyExists = func() Error { return Error{3, "resource already exists", nil} }
// ErrorConfig has to be set when there is a configuration error // ErrorConfig has to be set when there is a configuration error
ErrorConfig Error = 4 ErrorConfig = func() Error { return Error{4, "configuration error", nil} }
// ErrorUpload has to be set when a file upload failed // ErrorUpload has to be set when a file upload failed
ErrorUpload Error = 100 ErrorUpload = func() Error { return Error{100, "upload failed", nil} }
// ErrorDownload has to be set when a file download failed // ErrorDownload has to be set when a file download failed
ErrorDownload Error = 101 ErrorDownload = func() Error { return Error{101, "download failed", nil} }
// 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 Error = 102 MissingDownloadHeaders = func() Error { return Error{102, "download headers are missing", nil} }
// 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 Error = 103 ErrorMissingDownloadBody = func() Error { return Error{103, "download body is missing", nil} }
// 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 Error = 200 ErrorUnknownService = func() Error { return Error{200, "unknown service", nil} }
// 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 Error = 202 ErrorUncallableService = func() Error { return Error{202, "uncallable service", nil} }
// 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 Error = 300 ErrorPermission = func() Error { return Error{300, "permission error", nil} }
// 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 Error = 301 ErrorToken = func() Error { return Error{301, "token error", nil} }
// 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 Error = 400 ErrorMissingParam = func() Error { return Error{400, "missing parameter", nil} }
// 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 Error = 401 ErrorInvalidParam = func() Error { return Error{401, "invalid parameter", nil} }
// 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 Error = 402 ErrorInvalidDefaultParam = func() Error { return Error{402, "invalid default param", nil} }
) )
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",
}

View File

@ -1,42 +1,40 @@
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 int type Error struct {
// 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)
} }

View File

@ -4,23 +4,28 @@ import (
"strings" "strings"
) )
// HandlerFn defines the handler signature // HandlerFunc manages an API request
type HandlerFn func(req Request, res *Response) Error type HandlerFunc func(Request, *Response)
// 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
Fn HandlerFn handle HandlerFunc
} }
// 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, fn HandlerFn) (*Handler, error) { func NewHandler(method, path string, handlerFunc HandlerFunc) *Handler {
return &Handler{ return &Handler{
path: path, path: path,
method: strings.ToUpper(method), method: strings.ToUpper(method),
Fn: fn, handle: handlerFunc,
}, 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

View File

@ -16,20 +16,29 @@ type Response struct {
err Error err Error
} }
// EmptyResponse creates an empty response. // NewResponse creates an empty response. An optional error can be passed as its first argument.
func EmptyResponse() *Response { func NewResponse(errors ...Error) *Response {
return &Response{ res := &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
} }
// WithError sets the error from a base error with error arguments. // SetError sets the error from a base error with error arguments.
func (res *Response) WithError(err Error) *Response { func (res *Response) SetError(baseError Error, arguments ...interface{}) {
res.err = err if len(arguments) > 0 {
return res baseError.SetArguments(arguments[0], arguments[1:]...)
}
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
View File

@ -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.EmptyResponse().WithError(api.ErrorUnknownService) response := api.NewResponse(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.EmptyResponse().WithError(api.ErrorMissingParam) response := api.NewResponse(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.EmptyResponse().WithError(api.ErrorMissingParam) response := api.NewResponse(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.EmptyResponse().WithError(api.ErrorMissingParam) response := api.NewResponse(api.ErrorMissingParam())
response.ServeHTTP(res, req) response.ServeHTTP(res, req)
logError(response) logError(response)
return return
@ -68,13 +68,15 @@ 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.EmptyResponse().WithError(api.ErrorUncallableService) r := api.NewResponse()
r.SetError(api.ErrorUncallableService(), service.Method, service.Pattern)
r.ServeHTTP(res, req) r.ServeHTTP(res, req)
logError(r) logError(r)
return return
} }
r := api.EmptyResponse().WithError(api.ErrorUnknownService) r := api.NewResponse()
r.SetError(api.ErrorUnknownService(), service.Method, service.Pattern)
r.ServeHTTP(res, req) r.ServeHTTP(res, req)
logError(r) logError(r)
return return
@ -91,9 +93,8 @@ func (server httpServer) ServeHTTP(res http.ResponseWriter, req *http.Request) {
apireq.Param = dataset.Data apireq.Param = dataset.Data
// 10. execute // 10. execute
response := api.EmptyResponse() response := api.NewResponse()
apiErr := foundHandler.Fn(*apireq, response) foundHandler.Handle(*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")

View File

@ -47,11 +47,13 @@ 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) Handle(httpMethod, path string, fn api.HandlerFn) { func (s *Server) HandleFunc(httpMethod, path string, handlerFunc api.HandlerFunc) {
handler, err := api.NewHandler(httpMethod, path, fn) handler := api.NewHandler(httpMethod, path, handlerFunc)
if err != nil { s.handlers = append(s.handlers, handler)
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)
} }