refactor: unexport api.Response into aicra.response

This commit is contained in:
Adrien Marquès 2021-06-20 21:52:43 +02:00
parent f17622195a
commit cff4106bf5
3 changed files with 82 additions and 91 deletions

View File

@ -1,63 +0,0 @@
package api
import (
"encoding/json"
"net/http"
)
// ResponseData defines format for response parameters to return
type ResponseData map[string]interface{}
// Response represents an API response to be sent
type Response struct {
Data ResponseData
Status int
Headers http.Header
err Err
}
// EmptyResponse creates an empty response.
func EmptyResponse() *Response {
return &Response{
Status: http.StatusOK,
Data: make(ResponseData),
err: ErrFailure,
Headers: make(http.Header),
}
}
// WithError sets the error
func (res *Response) WithError(err Err) *Response {
res.err = err
return res
}
func (res *Response) Error() string {
return res.err.Error()
}
// SetData adds/overrides a new response field
func (res *Response) SetData(name string, value interface{}) {
res.Data[name] = value
}
// MarshalJSON implements the 'json.Marshaler' interface and is used
// to generate the JSON representation of the response
func (res *Response) MarshalJSON() ([]byte, error) {
fmt := make(map[string]interface{})
for k, v := range res.Data {
fmt[k] = v
}
fmt["error"] = res.err
return json.Marshal(fmt)
}
func (res *Response) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
w.WriteHeader(res.err.Status)
encoded, err := json.Marshal(res)
if err != nil {
return err
}
w.Write(encoded)
return nil
}

View File

@ -27,21 +27,21 @@ func (s Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// ServeHTTP implements http.Handler and wraps it in middlewares (adapters) // ServeHTTP implements http.Handler and wraps it in middlewares (adapters)
func (s Handler) resolve(w http.ResponseWriter, r *http.Request) { func (s Handler) resolve(w http.ResponseWriter, r *http.Request) {
// 1ind a matching service from config // match service from config
var service = s.conf.Find(r) var service = s.conf.Find(r)
if service == nil { if service == nil {
handleError(api.ErrUnknownService, w, r) newResponse().WithError(api.ErrUnknownService).ServeHTTP(w, r)
return return
} }
// extract request data // extract request data
var input, err = extractInput(service, *r) var input, err = extractInput(service, *r)
if err != nil { if err != nil {
handleError(api.ErrMissingParam, w, r) newResponse().WithError(api.ErrMissingParam).ServeHTTP(w, r)
return return
} }
// find a matching handler // match handler
var handler *apiHandler var handler *apiHandler
for _, h := range s.handlers { for _, h := range s.handlers {
if h.Method == service.Method && h.Path == service.Pattern { if h.Method == service.Method && h.Path == service.Pattern {
@ -49,13 +49,13 @@ func (s Handler) resolve(w http.ResponseWriter, r *http.Request) {
} }
} }
// fail on no matching handler // no handler found
if handler == nil { if handler == nil {
handleError(api.ErrUncallableService, w, r) newResponse().WithError(api.ErrUncallableService).ServeHTTP(w, r)
return return
} }
// build context with builtin data // add info into context
c := r.Context() c := r.Context()
c = context.WithValue(c, ctx.Request, r) c = context.WithValue(c, ctx.Request, r)
c = context.WithValue(c, ctx.Response, w) c = context.WithValue(c, ctx.Response, w)
@ -63,63 +63,55 @@ func (s Handler) resolve(w http.ResponseWriter, r *http.Request) {
// create http handler // create http handler
var h http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var h http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// should not happen
auth := api.GetAuth(r.Context()) auth := api.GetAuth(r.Context())
if auth == nil { if auth == nil {
handleError(api.ErrPermission, w, r) newResponse().WithError(api.ErrPermission).ServeHTTP(w, r)
return return
} }
// reject non granted requests // reject non granted requests
if !auth.Granted() { if !auth.Granted() {
handleError(api.ErrPermission, w, r) newResponse().WithError(api.ErrPermission).ServeHTTP(w, r)
return return
} }
// use context defined in the request // execute the service handler
s.handle(r.Context(), input, handler, service, w, r) s.handle(r.Context(), input, handler, service, w, r)
}) })
// run middlewares the handler // run contextual middlewares
for _, mw := range s.ctxMiddlewares { for _, mw := range s.ctxMiddlewares {
h = mw(h) h = mw(h)
} }
// serve using the context with values // serve using the pre-filled context
h.ServeHTTP(w, r.WithContext(c)) h.ServeHTTP(w, r.WithContext(c))
} }
// handle the service request with the associated handler func and respond using
// the handler func output
func (s *Handler) handle(c context.Context, input *reqdata.T, handler *apiHandler, service *config.Service, w http.ResponseWriter, r *http.Request) { func (s *Handler) handle(c context.Context, input *reqdata.T, handler *apiHandler, service *config.Service, w http.ResponseWriter, r *http.Request) {
// pass execution to the handler // pass execution to the handler function
var outData, outErr = handler.dyn.Handle(c, input.Data) var outData, outErr = handler.dyn.Handle(c, input.Data)
// build response from returned arguments // build response from output arguments
var res = api.EmptyResponse().WithError(outErr) var res = newResponse().WithError(outErr)
for key, value := range outData { for key, value := range outData {
// find original name from 'rename' field // find original name from 'rename' field
for name, param := range service.Output { for name, param := range service.Output {
if param.Rename == key { if param.Rename == key {
res.SetData(name, value) res.SetValue(name, value)
} }
} }
} }
// 7. apply headers // write response and close request
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
for key, values := range res.Headers {
for _, value := range values {
w.Header().Add(key, value)
}
}
res.ServeHTTP(w, r) res.ServeHTTP(w, r)
} }
func handleError(err api.Err, w http.ResponseWriter, r *http.Request) {
var response = api.EmptyResponse().WithError(err)
response.ServeHTTP(w, r)
}
func extractInput(service *config.Service, req http.Request) (*reqdata.T, error) { func extractInput(service *config.Service, req http.Request) (*reqdata.T, error) {
var dataset = reqdata.New(service) var dataset = reqdata.New(service)

62
response.go Normal file
View File

@ -0,0 +1,62 @@
package aicra
import (
"encoding/json"
"net/http"
"github.com/xdrm-io/aicra/api"
)
// response for an service call
type response struct {
Data map[string]interface{}
Status int
Headers http.Header
err api.Err
}
// newResponse creates an empty response.
func newResponse() *response {
return &response{
Status: http.StatusOK,
Data: make(map[string]interface{}),
err: api.ErrFailure,
Headers: make(http.Header),
}
}
// WithError sets the response error
func (res *response) WithError(err api.Err) *response {
res.err = err
return res
}
// SetValue sets a response value
func (res *response) SetValue(name string, value interface{}) {
res.Data[name] = value
}
// MarshalJSON generates the JSON representation of the response
//
// implements json.Marshaler
func (res *response) MarshalJSON() ([]byte, error) {
fmt := make(map[string]interface{})
for k, v := range res.Data {
fmt[k] = v
}
fmt["error"] = res.err
return json.Marshal(fmt)
}
// ServeHTTP writes the response representation back to the http.ResponseWriter
//
// implements http.Handler
func (res *response) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
w.WriteHeader(res.err.Status)
encoded, err := json.Marshal(res)
if err != nil {
return err
}
w.Write(encoded)
return nil
}