Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds request parser, updated router logging and structure #4

Merged
merged 1 commit into from
Oct 13, 2021
Merged
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
1 change: 0 additions & 1 deletion Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ WORKDIR /app
COPY . .

# Setup go mod
RUN [ ! -f ./go.mod ] && go mod init main
RUN go mod tidy

# Development requires fresh
Expand Down
1 change: 0 additions & 1 deletion Dockerfile.scratch
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ WORKDIR /app
COPY . .

# Setup go mod
# RUN [ ! -f ./go.mod ] && go mod init main
RUN go mod tidy
RUN GOOS=linux CGO_ENABLED=0 go build -ldflags="-w -s" -o api .

Expand Down
4 changes: 0 additions & 4 deletions cmd/oas.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ type OasCmd struct {
}

func (v *OasCmd) Execute(args []string) error {
fmt.Println("oascmd")
fmt.Printf("%#v\n", v)
fmt.Printf("%#v\n", Application)

router := router.NewRouter(Application.Name, Application.Description, Application.Version)

oasModel := exporter.ToOasModel(router.OpenAPI)
Expand Down
4 changes: 2 additions & 2 deletions endpoint/docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/charmixer/golang-api-template/app"

"github.com/charmixer/golang-api-template/endpoint"
"github.com/charmixer/golang-api-template/middleware"
// "github.com/charmixer/golang-api-template/middleware"

"github.com/rs/zerolog/log"
)
Expand Down Expand Up @@ -144,7 +144,7 @@ func NewGetDocsEndpoint() (endpoint.EndpointHandler) {
}),

endpoint.WithMiddleware(
middleware.WithRequestParser(&ep.Request),

),
)

Expand Down
9 changes: 1 addition & 8 deletions endpoint/docs/openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,8 @@ var (
}
)

type Test struct {
Testfield string `validate:"iscolor"`
}
type GetOpenapiRequest struct {
Format string `json:"format" oas-query:"format" validate:"iscolor" oas-desc:"Format returned by the endpoint, eg. json"`
Test Test `oas-query:"fordasat" validate:"iscolor" oas-desc:"Format returned by the endpoint, eg. json"`
Format string `json:"format" oas-query:"format" oas-desc:"Format returned by the endpoint, eg. json"`
}

// https://golang.org/doc/effective_go#embedding
Expand Down Expand Up @@ -84,9 +80,6 @@ func NewGetOpenapiEndpoint() (endpoint.EndpointHandler) {
}),

endpoint.WithMiddleware(
middleware.WithRequestParser(&ep.Request),
middleware.WithRequestValidation(&ep.Request/*, &ep.BadRequest*/),

middleware.WithResponseWriter(&ep.responseType, &ep.Response),
),
)
Expand Down
25 changes: 25 additions & 0 deletions endpoint/errors/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package errors

/*
Codes we care about

200 OK
400 BadRequest
401 Unauthorized - Authentication denied
403 Forbidden - Authorization denined
404 Not Found
500 Internal Server Error
503 Service Unavailable
*/

type FieldError struct {
Path string `json:"path"`
Err string `json:"err"`
}

type HttpClientErrorResponse struct {
StatusCode int `json:"status_code"`
Method string `json:"method"`
Url string `json:"url"`
Errors []FieldError `json:"errors"`
}
3 changes: 0 additions & 3 deletions endpoint/health/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,6 @@ func NewGetHealthEndpoint() (endpoint.EndpointHandler) {
}),

endpoint.WithMiddleware(
middleware.WithRequestParser(&ep.Request),
middleware.WithRequestValidation(&ep.Request),

middleware.WithResponseValidation(&ep.Response),
middleware.WithJsonResponseWriter(&ep.Response),
),
Expand Down
4 changes: 2 additions & 2 deletions endpoint/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"net/http"

"github.com/charmixer/golang-api-template/endpoint"
"github.com/charmixer/golang-api-template/middleware"
// "github.com/charmixer/golang-api-template/middleware"

"github.com/charmixer/oas/api"
)
Expand Down Expand Up @@ -54,7 +54,7 @@ func NewGetMetricsEndpoint() (endpoint.EndpointHandler) {
}),

endpoint.WithMiddleware(
middleware.WithRequestParser(&ep.Request),

),
)

Expand Down
31 changes: 31 additions & 0 deletions middleware/request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package middleware

import (
"net/http"

"encoding/json"

"go.opentelemetry.io/otel"
)

// TODO move this to better place
func WithJsonRequestParser(request interface{}) MiddlewareHandler {
return func (next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
tr := otel.Tracer("request")
ctx, span := tr.Start(ctx, "middleware.request-parser")
defer span.End()

// Try to decode the request body into the struct. If there is an error,
// respond to the client with the error message and a 400 status code.
err := json.NewDecoder(r.Body).Decode(&request)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

next.ServeHTTP(w, r)
})
}
}
88 changes: 53 additions & 35 deletions middleware/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,16 @@ import (
"go.opentelemetry.io/otel"
)

func WithJsonResponseWriter(response interface{}) MiddlewareHandler {
func contains(s []int, e int) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}

func WithJsonResponseWriter(response interface{}, status ...int) MiddlewareHandler {
return func (next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
Expand All @@ -20,19 +29,22 @@ func WithJsonResponseWriter(response interface{}) MiddlewareHandler {

next.ServeHTTP(w, r)

ctx, span = tr.Start(ctx, "write response")
defer span.End()
// Only write if written statuscode is in given list
if contains(status, w.(*responseWriter).Status) {
ctx, span = tr.Start(ctx, "write response")
defer span.End()

d, err := json.Marshal(response)
if err != nil {
panic(err) // TODO FIXME
d, err := json.Marshal(response)
if err != nil {
panic(err) // TODO FIXME
}
w.Write(d)
}
w.Write(d)
})
}
}

func WithYamlResponseWriter(response interface{}) MiddlewareHandler {
func WithYamlResponseWriter(response interface{}, status ...int) MiddlewareHandler {
return func (next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
Expand All @@ -42,19 +54,22 @@ func WithYamlResponseWriter(response interface{}) MiddlewareHandler {

next.ServeHTTP(w, r)

ctx, span = tr.Start(ctx, "write response")
defer span.End()
// Only write if written statuscode is in given list
if contains(status, w.(*responseWriter).Status) {
ctx, span = tr.Start(ctx, "write response")
defer span.End()

d, err := yaml.Marshal(response)
if err != nil {
panic(err) // TODO FIXME
d, err := yaml.Marshal(response)
if err != nil {
panic(err) // TODO FIXME
}
w.Write(d)
}
w.Write(d)
})
}
}

func WithResponseWriter(tp *string, response interface{}) MiddlewareHandler {
func WithResponseWriter(tp *string, response interface{}, status ...int) MiddlewareHandler {
return func (next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
Expand All @@ -64,26 +79,29 @@ func WithResponseWriter(tp *string, response interface{}) MiddlewareHandler {

next.ServeHTTP(w, r)

ctx, span = tr.Start(ctx, "write response")
defer span.End()

switch (*tp) {
case "json":
d, err := json.Marshal(response)
if err != nil {
panic(err) // TODO FIXME
}
w.Write(d)
break;
case "yaml":
d, err := yaml.Marshal(response)
if err != nil {
panic(err) // TODO FIXME
}
w.Write(d)
break;
default:
panic(fmt.Sprintf("Unknown response type given, %s", tp))
// Only write if written statuscode is in given list
if contains(status, w.(*responseWriter).Status) {
ctx, span = tr.Start(ctx, "write response")
defer span.End()

switch (*tp) {
case "json":
d, err := json.Marshal(response)
if err != nil {
panic(err) // TODO FIXME
}
w.Write(d)
break;
case "yaml":
d, err := yaml.Marshal(response)
if err != nil {
panic(err) // TODO FIXME
}
w.Write(d)
break;
default:
panic(fmt.Sprintf("Unknown response type given, %s", *tp))
}
}

})
Expand Down
48 changes: 4 additions & 44 deletions middleware/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
"github.com/go-playground/locales/en"
ut "github.com/go-playground/universal-translator"
en_translations "github.com/go-playground/validator/v10/translations/en"

ep_errors "github.com/charmixer/golang-api-template/endpoint/errors"
)

var (
Expand Down Expand Up @@ -46,30 +48,6 @@ func init() {
en_translations.RegisterDefaultTranslations(validate, trans)
}

/*
Codes we care about

200 OK,
400 BadRequest
401 Unauthorized - Authentication denied,
403 Forbidden - Authorization denined,
404 Not Found
500 Internal Server Error,
503 Service Unavailable
*/

type FieldError struct {
Path string `json:"path"`
Err string `json:"err"`
}

type HttpClientErrorResponse struct {
StatusCode int `json:"status_code"`
Method string `json:"method"`
Url string `json:"url"`
Errors []FieldError `json:"errors"`
}

func WithRequestValidation(request interface{}) MiddlewareHandler{
return func (next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand All @@ -86,15 +64,15 @@ func WithRequestValidation(request interface{}) MiddlewareHandler{
return
}

response := HttpClientErrorResponse{
response := ep_errors.HttpClientErrorResponse{
StatusCode: http.StatusBadRequest,
Method: r.Method,
Url: fmt.Sprintf("%s%s", r.Host, r.URL.RequestURI()),
// Body: ...
}

for _, verr := range err.(validator.ValidationErrors) {
e := FieldError{
e := ep_errors.FieldError{
Path: verr.Field(),
Err: verr.Translate(trans),
}
Expand Down Expand Up @@ -163,21 +141,3 @@ func WithResponseValidation(response interface{}) MiddlewareHandler {
})
}
}





// TODO move this to better place
func WithRequestParser(request interface{}) MiddlewareHandler {
return func (next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
tr := otel.Tracer("request")
ctx, span := tr.Start(ctx, "middleware.request-parser")
defer span.End()

next.ServeHTTP(w, r)
})
}
}
14 changes: 8 additions & 6 deletions router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,9 @@ import (
"github.com/charmixer/oas/api"

"github.com/julienschmidt/httprouter"
)

/*type Route interface {
http.Handler
Specification() api.Path
}*/
"github.com/rs/zerolog/log"
)

type Router struct {
httprouter.Router
Expand All @@ -28,7 +25,12 @@ type Router struct {
}

func (r *Router) NewRoute(method string, uri string, ep endpoint.EndpointHandler, handlers ...middleware.MiddlewareHandler) {
r.OpenAPI.NewEndpoint(method, uri, ep.Specification())
log.Debug().
Str("method", method).
Str("endpoint", uri).
Msg("Setting up endpoint")

r.OpenAPI.NewEndpoint(method, uri, ep.Specification())

middlewareHandlers := append(handlers, ep.Middleware()...)
r.Handler(method, uri, middleware.New(ep.(http.Handler), middlewareHandlers...))
Expand Down