Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions encode/encode.go
Original file line number Diff line number Diff line change
@@ -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
}
18 changes: 18 additions & 0 deletions encode/json.go
Original file line number Diff line number Diff line change
@@ -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)
}
126 changes: 126 additions & 0 deletions transport/endpoint.go
Original file line number Diff line number Diff line change
@@ -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
}