diff --git a/encode/encode.go b/encode/encode.go new file mode 100644 index 0000000..11caabb --- /dev/null +++ b/encode/encode.go @@ -0,0 +1,10 @@ +package encode + +import "net/http" + +//Encoder for api http endpoints +type Encoder interface { + //Encode an interface{} to a data format. + //Can only be called once in a request because it sets data format specific http headers + Encode(w http.ResponseWriter, v interface{}) error +} diff --git a/encode/json.go b/encode/json.go new file mode 100644 index 0000000..6dd0255 --- /dev/null +++ b/encode/json.go @@ -0,0 +1,18 @@ +package encode + +import ( + "encoding/json" + "net/http" +) + +type jsonEncoder struct{} + +//NewJSONEncoder for JSON API endpoints +func NewJSONEncoder() Encoder { + return &jsonEncoder{} +} + +func (e *jsonEncoder) Encode(w http.ResponseWriter, v interface{}) error { + defer w.Header().Set("Content-Type", "application/json; charset=utf-8") + return json.NewEncoder(w).Encode(v) +} diff --git a/transport/endpoint.go b/transport/endpoint.go new file mode 100644 index 0000000..64395ab --- /dev/null +++ b/transport/endpoint.go @@ -0,0 +1,126 @@ +package transport + +import ( + "context" + "net/http" + + "github.com/TheMysteriousVincent/apiexperiments/encode" + "github.com/TheMysteriousVincent/libs/problem" + "go.uber.org/zap" +) + +//HandlerFunc of the main service functions +type HandlerFunc func(ctx context.Context, r *http.Request) (interface{}, error) + +//MiddlewareFunc of middlewares surrounding the main service +type MiddlewareFunc func(ctx context.Context, r *http.Request) error + +//Endpoint of an service +type Endpoint interface { + //PutMiddlewareBefore the handler. + //PutMiddlewareBefore can be called multiple as every middleware is appended one after another + PutMiddlewareBefore(mw MiddlewareFunc) Endpoint + //PutMiddlewareAfter the handler. + //PutMiddlewareAfter can be called multiple as every middleware is appended one after another + PutMiddlewareAfter(mw MiddlewareFunc) Endpoint + //HandlerFunc for the http endpoint + HandlerFunc(*zap.Logger) http.HandlerFunc +} + +type endpoint struct { + before MiddlewareFunc + after MiddlewareFunc + hndl HandlerFunc + enc encode.Encoder +} + +//NewEndpoint of an http service +func NewEndpoint(e encode.Encoder, hndl HandlerFunc) Endpoint { + return &endpoint{ + hndl: hndl, + enc: e, + } +} + +func (e *endpoint) PutMiddlewareBefore(mw MiddlewareFunc) Endpoint { + if e.before == nil { + e.before = mw + } else { + e.before = func(ctx context.Context, r *http.Request) error { + if err := e.before(ctx, r); err != nil { + return err + } + + return mw(ctx, r) + } + } + + return e +} + +func (e *endpoint) PutMiddlewareAfter(mw MiddlewareFunc) Endpoint { + if e.after == nil { + e.after = mw + } else { + e.after = func(ctx context.Context, r *http.Request) error { + if err := e.after(ctx, r); err != nil { + return err + } + + return mw(ctx, r) + } + } + + return e +} + +func (e *endpoint) HandlerFunc(l *zap.Logger) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + if err := e.before(ctx, r); err != nil { + e.encodeError(w, err, l) + return + } + + v, err := e.hndl(ctx, r) + if err != nil { + e.encodeError(w, err, l) + return + } + + if err := e.after(ctx, r); err != nil { + e.encodeError(w, err, l) + return + } + + e.enc.Encode(w, v) + } +} + +func (e *endpoint) encodeError(w http.ResponseWriter, err error, l *zap.Logger) { + if err := e.enc.Encode(w, e.finalizeError(err)); err != nil { + w.WriteHeader(http.StatusInternalServerError) + l.Error( + "encoding failed", + zap.Error(err), + ) + } +} + +func (e *endpoint) finalizeError(err error) problem.Problem { + p, ok := err.(problem.Problem) + if !ok { + p = problem.New( + http.StatusText(http.StatusInternalServerError), + err.Error(), + http.StatusInternalServerError, + ) + } + + if p.Status == 0 { + p.Status = http.StatusInternalServerError + } + + return p +}