From e1e42696b18dc7132d0c62bdea7868e89a68c57c Mon Sep 17 00:00:00 2001 From: Piotr Icikowski Date: Sat, 9 Jul 2022 11:34:04 +0200 Subject: [PATCH] Separate route handler building logic --- application/config/types.go | 19 ++++ application/config/types_test.go | 67 ++++++++++++ application/service/builder.go | 164 ++++++++++++---------------- application/service/builder_test.go | 2 +- 4 files changed, 157 insertions(+), 95 deletions(-) diff --git a/application/config/types.go b/application/config/types.go index e3449ac..2738051 100644 --- a/application/config/types.go +++ b/application/config/types.go @@ -28,6 +28,25 @@ type Route struct { Default *Response `json:"default,omitempty" yaml:"default,omitempty"` } +// GetResponseForMethod return the preferred response for given HTTP method +func (r *Route) GetResponseForMethod(method string) *Response { + methodsMap := map[string]*Response{ + http.MethodGet: r.GET, + http.MethodPost: r.POST, + http.MethodPut: r.PUT, + http.MethodPatch: r.PATCH, + http.MethodDelete: r.DELETE, + } + + if response, ok := methodsMap[method]; ok && response != nil { + return response + } else if r.Default != nil { + return r.Default + } + + return nil +} + type configuration struct { routes map[string]Route mutex sync.Mutex diff --git a/application/config/types_test.go b/application/config/types_test.go index 1937580..6a3626b 100644 --- a/application/config/types_test.go +++ b/application/config/types_test.go @@ -101,3 +101,70 @@ func TestRouteMarshalZerologObject(t *testing.T) { }, }, contents, "log contains unexpected Route properties") } + +func TestGetResponseForMethod(t *testing.T) { + emptyResponse := &Response{} + fullRoute := Route{ + GET: emptyResponse, + PATCH: emptyResponse, + POST: emptyResponse, + PUT: emptyResponse, + DELETE: emptyResponse, + } + strippedRoute := Route{ + GET: emptyResponse, + Default: emptyResponse, + } + singleMethodRoute := Route{ + GET: emptyResponse, + } + + tests := map[string]struct { + examinedRoute Route + method string + expectedResponse *Response + }{ + "get GET response with match": { + examinedRoute: fullRoute, + method: http.MethodGet, + expectedResponse: emptyResponse, + }, + "get POST response with match": { + examinedRoute: fullRoute, + method: http.MethodPost, + expectedResponse: emptyResponse, + }, + "get PUT response with match": { + examinedRoute: fullRoute, + method: http.MethodPatch, + expectedResponse: emptyResponse, + }, + "get PATCH response with match": { + examinedRoute: fullRoute, + method: http.MethodPut, + expectedResponse: emptyResponse, + }, + "get DELETE response with match": { + examinedRoute: fullRoute, + method: http.MethodDelete, + expectedResponse: emptyResponse, + }, + "get POST response with default fallback": { + examinedRoute: strippedRoute, + method: http.MethodPost, + expectedResponse: emptyResponse, + }, + "get POST response with no default fallback": { + examinedRoute: singleMethodRoute, + method: http.MethodPost, + expectedResponse: nil, + }, + } + + for name, tc := range tests { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + require.Equal(t, tc.expectedResponse, tc.examinedRoute.GetResponseForMethod(tc.method)) + }) + } +} diff --git a/application/service/builder.go b/application/service/builder.go index 4672f68..4c1d453 100644 --- a/application/service/builder.go +++ b/application/service/builder.go @@ -11,8 +11,77 @@ import ( "icikowski.pl/gpts/common" "icikowski.pl/gpts/config" "icikowski.pl/gpts/health" + "icikowski.pl/gpts/utils" ) +func getHandlerForRoute(path string, route config.Route, log zerolog.Logger) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + innerLog := log.With(). + Dict( + "request", + zerolog.Dict(). + Str("remote", r.RemoteAddr). + Str("path", r.URL.Path). + Str("method", r.Method), + ). + Dict( + "endpoint", + zerolog.Dict(). + Str("path", path). + Str("type", "user"), + ). + Logger() + + var ( + status *int + contentType *string + content *string + headers *map[string]string + ) + + if response := route.GetResponseForMethod(r.Method); response != nil { + status = response.Status + contentType = response.ContentType + content = response.Content + headers = response.Headers + } else { + status = utils.PointerTo(http.StatusMethodNotAllowed) + } + + var finalContent []byte + + if headers != nil { + for headerName, headerValue := range *headers { + w.Header().Set(headerName, headerValue) + } + } + if status == nil { + status = utils.PointerTo(http.StatusOK) + } + if contentType == nil { + contentType = utils.PointerTo("text/plain") + } + if content == nil { + content = new(string) + } + if strings.HasPrefix(strings.TrimSpace(*content), "base64,") { + var err error + finalContent, err = base64.StdEncoding.DecodeString(strings.TrimPrefix(strings.TrimSpace(*content), "base64,")) + if err != nil { + innerLog.Warn().Err(err).Msg("cannot decode base64 content") + *status = http.StatusInternalServerError + } + } else { + finalContent = []byte(*content) + } + + w.Header().Set(common.HeaderContentType, *contentType) + w.WriteHeader(*status) + _, _ = w.Write(finalContent) + innerLog.Info().Msg("request served") + } +} + // PrepareServer prepares, configures and runs test service server func PrepareServer(log zerolog.Logger, port int) *http.Server { log.Info().Msg("preparing test service's router & server") @@ -37,100 +106,7 @@ func PrepareServer(log zerolog.Logger, port int) *http.Server { Object("route", route). Msg("preparing handler") - var handler = func(w http.ResponseWriter, r *http.Request) { - innerLog := log.With(). - Dict( - "request", - zerolog.Dict(). - Str("remote", r.RemoteAddr). - Str("path", r.URL.Path). - Str("method", r.Method), - ). - Dict( - "endpoint", - zerolog.Dict(). - Str("path", path). - Str("type", "user"), - ). - Logger() - - var ( - status *int - contentType *string - content *string - headers *map[string]string - ) - - switch { - case r.Method == http.MethodGet && route.GET != nil: - status = route.GET.Status - contentType = route.GET.ContentType - content = route.GET.Content - headers = route.GET.Headers - case r.Method == http.MethodPost && route.POST != nil: - status = route.POST.Status - contentType = route.POST.ContentType - content = route.POST.Content - headers = route.POST.Headers - case r.Method == http.MethodPut && route.PUT != nil: - status = route.PUT.Status - contentType = route.PUT.ContentType - content = route.PUT.Content - headers = route.PUT.Headers - case r.Method == http.MethodPatch && route.PATCH != nil: - status = route.PATCH.Status - contentType = route.PATCH.ContentType - content = route.PATCH.Content - headers = route.PATCH.Headers - case r.Method == http.MethodDelete && route.DELETE != nil: - status = route.DELETE.Status - contentType = route.DELETE.ContentType - content = route.DELETE.Content - headers = route.DELETE.Headers - case route.Default != nil: - status = route.Default.Status - contentType = route.Default.ContentType - content = route.Default.Content - headers = route.Default.Headers - default: - status = new(int) - *status = http.StatusServiceUnavailable - } - - var finalContent []byte - - if headers != nil { - for headerName, headerValue := range *headers { - w.Header().Set(headerName, headerValue) - } - } - if status == nil { - status = new(int) - *status = 200 - } - if contentType == nil { - contentType = new(string) - *contentType = "text/plain" - } - if content == nil { - content = new(string) - } - if strings.HasPrefix(strings.TrimSpace(*content), "base64,") { - var err error - finalContent, err = base64.StdEncoding.DecodeString(strings.TrimPrefix(strings.TrimSpace(*content), "base64,")) - if err != nil { - innerLog.Warn().Err(err).Msg("cannot decode base64 content") - *status = http.StatusInternalServerError - } - } else { - finalContent = []byte(*content) - } - - w.Header().Set(common.HeaderContentType, *contentType) - w.WriteHeader(*status) - _, _ = w.Write(finalContent) - innerLog.Info().Msg("request served") - } + handler := getHandlerForRoute(path, route, log) if route.AllowSubpaths { r.PathPrefix(path).HandlerFunc(handler) diff --git a/application/service/builder_test.go b/application/service/builder_test.go index 619b903..a6233db 100644 --- a/application/service/builder_test.go +++ b/application/service/builder_test.go @@ -34,7 +34,7 @@ func TestPrepareServer(t *testing.T) { "get route with no details": { method: http.MethodGet, path: "/no-details", - expectedStatus: http.StatusServiceUnavailable, + expectedStatus: http.StatusMethodNotAllowed, }, "get route with base64-encoded body": { method: http.MethodGet,