From 5d8b8c0a0366c0d5e2a7f75eea2bbd7ab3a7798b Mon Sep 17 00:00:00 2001 From: Davide Bianchi Date: Wed, 27 Jan 2021 00:29:57 +0100 Subject: [PATCH] feat: add custom documentation path --- integration_test.go | 2 +- main.go | 58 ++++++++++++++++----- main_test.go | 122 +++++++++++++++++++++++++++++++++++++++++--- route_test.go | 2 +- 4 files changed, 161 insertions(+), 23 deletions(-) diff --git a/integration_test.go b/integration_test.go index eb903e1..85e1657 100644 --- a/integration_test.go +++ b/integration_test.go @@ -34,7 +34,7 @@ func TestIntegration(t *testing.T) { t.Run("and generate swagger", func(t *testing.T) { w := httptest.NewRecorder() - r := httptest.NewRequest(http.MethodGet, JSONDocumentationPath, nil) + r := httptest.NewRequest(http.MethodGet, DefaultJSONDocumentationPath, nil) muxRouter.ServeHTTP(w, r) diff --git a/main.go b/main.go index 20ba306..8aeef7d 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "strings" "github.com/getkin/kin-openapi/openapi3" "github.com/ghodss/yaml" @@ -19,24 +20,30 @@ var ( ) const ( - // JSONDocumentationPath is the path of the swagger documentation in json format. - JSONDocumentationPath = "/documentation/json" - // YAMLDocumentationPath is the path of the swagger documentation in yaml format. - YAMLDocumentationPath = "/documentation/yaml" - defaultOpenapiVersion = "3.0.0" + // DefaultJSONDocumentationPath is the path of the swagger documentation in json format. + DefaultJSONDocumentationPath = "/documentation/json" + // DefaultYAMLDocumentationPath is the path of the swagger documentation in yaml format. + DefaultYAMLDocumentationPath = "/documentation/yaml" + defaultOpenapiVersion = "3.0.0" ) // Router handle the gorilla mux router and the swagger schema type Router struct { - router *mux.Router - swaggerSchema *openapi3.Swagger - context context.Context + router *mux.Router + swaggerSchema *openapi3.Swagger + context context.Context + jsonDocumentationPath string + yamlDocumentationPath string } // Options to be passed to create the new router and swagger type Options struct { Context context.Context Openapi *openapi3.Swagger + // JSONDocumentationPath is the path exposed by json endpoint. Default to /documentation/json. + JSONDocumentationPath string + // YAMLDocumentationPath is the path exposed by yaml endpoint. Default to /documentation/yaml. + YAMLDocumentationPath string } // NewRouter generate new router with swagger. Default to OpenAPI 3.0.0 @@ -51,10 +58,28 @@ func NewRouter(router *mux.Router, options Options) (*Router, error) { ctx = context.Background() } + yamlDocumentationPath := DefaultYAMLDocumentationPath + if options.YAMLDocumentationPath != "" { + if err := isValidDocumentationPath(options.YAMLDocumentationPath); err != nil { + return nil, err + } + yamlDocumentationPath = options.YAMLDocumentationPath + } + + jsonDocumentationPath := DefaultJSONDocumentationPath + if options.JSONDocumentationPath != "" { + if err := isValidDocumentationPath(options.JSONDocumentationPath); err != nil { + return nil, err + } + jsonDocumentationPath = options.JSONDocumentationPath + } + return &Router{ - router: router, - swaggerSchema: swagger, - context: ctx, + router: router, + swaggerSchema: swagger, + context: ctx, + yamlDocumentationPath: yamlDocumentationPath, + jsonDocumentationPath: jsonDocumentationPath, }, nil } @@ -93,7 +118,7 @@ func (r Router) GenerateAndExposeSwagger() error { if err != nil { return fmt.Errorf("%w json marshal: %s", ErrGenerateSwagger, err) } - r.router.HandleFunc(JSONDocumentationPath, func(w http.ResponseWriter, req *http.Request) { + r.router.HandleFunc(r.jsonDocumentationPath, func(w http.ResponseWriter, req *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write(jsonSwagger) @@ -103,7 +128,7 @@ func (r Router) GenerateAndExposeSwagger() error { if err != nil { return fmt.Errorf("%w yaml marshal: %s", ErrGenerateSwagger, err) } - r.router.HandleFunc(YAMLDocumentationPath, func(w http.ResponseWriter, req *http.Request) { + r.router.HandleFunc(r.yamlDocumentationPath, func(w http.ResponseWriter, req *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) w.Write(yamlSwagger) @@ -111,3 +136,10 @@ func (r Router) GenerateAndExposeSwagger() error { return nil } + +func isValidDocumentationPath(path string) error { + if !strings.HasPrefix(path, "/") { + return fmt.Errorf("invalid path %s. Path should start with '/'", path) + } + return nil +} diff --git a/main_test.go b/main_test.go index 7299ad5..70a190d 100644 --- a/main_test.go +++ b/main_test.go @@ -40,9 +40,11 @@ func TestNewRouter(t *testing.T) { require.NoError(t, err) require.Equal(t, &Router{ - context: context.Background(), - router: mRouter, - swaggerSchema: openapi, + context: context.Background(), + router: mRouter, + swaggerSchema: openapi, + jsonDocumentationPath: DefaultJSONDocumentationPath, + yamlDocumentationPath: DefaultYAMLDocumentationPath, }, r) }) @@ -56,11 +58,61 @@ func TestNewRouter(t *testing.T) { require.NoError(t, err) require.Equal(t, &Router{ - context: ctx, - router: mRouter, - swaggerSchema: openapi, + context: ctx, + router: mRouter, + swaggerSchema: openapi, + jsonDocumentationPath: DefaultJSONDocumentationPath, + yamlDocumentationPath: DefaultYAMLDocumentationPath, }, r) }) + + t.Run("ok - with custom docs paths", func(t *testing.T) { + type key struct{} + ctx := context.WithValue(context.Background(), key{}, "value") + r, err := NewRouter(mRouter, Options{ + Openapi: openapi, + Context: ctx, + JSONDocumentationPath: "/json/path", + YAMLDocumentationPath: "/yaml/path", + }) + + require.NoError(t, err) + require.Equal(t, &Router{ + context: ctx, + router: mRouter, + swaggerSchema: openapi, + jsonDocumentationPath: "/json/path", + yamlDocumentationPath: "/yaml/path", + }, r) + }) + + t.Run("ko - json documentation path does not start with /", func(t *testing.T) { + type key struct{} + ctx := context.WithValue(context.Background(), key{}, "value") + r, err := NewRouter(mRouter, Options{ + Openapi: openapi, + Context: ctx, + JSONDocumentationPath: "json/path", + YAMLDocumentationPath: "/yaml/path", + }) + + require.EqualError(t, err, "invalid path json/path. Path should start with '/'") + require.Nil(t, r) + }) + + t.Run("ko - yaml documentation path does not start with /", func(t *testing.T) { + type key struct{} + ctx := context.WithValue(context.Background(), key{}, "value") + r, err := NewRouter(mRouter, Options{ + Openapi: openapi, + Context: ctx, + JSONDocumentationPath: "/json/path", + YAMLDocumentationPath: "yaml/path", + }) + + require.EqualError(t, err, "invalid path yaml/path. Path should start with '/'") + require.Nil(t, r) + }) } func TestGenerateValidSwagger(t *testing.T) { @@ -166,7 +218,34 @@ func TestGenerateAndExposeSwagger(t *testing.T) { require.NoError(t, err) w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, JSONDocumentationPath, nil) + req := httptest.NewRequest(http.MethodGet, DefaultJSONDocumentationPath, nil) + mRouter.ServeHTTP(w, req) + + require.Equal(t, http.StatusOK, w.Result().StatusCode) + require.True(t, strings.Contains(w.Result().Header.Get("content-type"), "application/json")) + + body := readBody(t, w.Result().Body) + actual, err := ioutil.ReadFile("testdata/users_employees.json") + require.NoError(t, err) + require.JSONEq(t, string(actual), body) + }) + + t.Run("correctly expose json documentation from loaded swagger file - custom path", func(t *testing.T) { + mRouter := mux.NewRouter() + + swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromFile("testdata/users_employees.json") + require.NoError(t, err) + + router, err := NewRouter(mRouter, Options{ + Openapi: swagger, + JSONDocumentationPath: "/custom/path", + }) + + err = router.GenerateAndExposeSwagger() + require.NoError(t, err) + + w := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/custom/path", nil) mRouter.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Result().StatusCode) @@ -192,7 +271,34 @@ func TestGenerateAndExposeSwagger(t *testing.T) { require.NoError(t, err) w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, YAMLDocumentationPath, nil) + req := httptest.NewRequest(http.MethodGet, DefaultYAMLDocumentationPath, nil) + mRouter.ServeHTTP(w, req) + + require.Equal(t, http.StatusOK, w.Result().StatusCode) + require.True(t, strings.Contains(w.Result().Header.Get("content-type"), "text/plain")) + + body := readBody(t, w.Result().Body) + expected, err := ioutil.ReadFile("testdata/users_employees.yaml") + require.NoError(t, err) + require.YAMLEq(t, string(expected), body, string(body)) + }) + + t.Run("correctly expose yaml documentation from loaded swagger file - custom path", func(t *testing.T) { + mRouter := mux.NewRouter() + + swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromFile("testdata/users_employees.json") + require.NoError(t, err) + + router, err := NewRouter(mRouter, Options{ + Openapi: swagger, + YAMLDocumentationPath: "/custom/path", + }) + + err = router.GenerateAndExposeSwagger() + require.NoError(t, err) + + w := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/custom/path", nil) mRouter.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Result().StatusCode) diff --git a/route_test.go b/route_test.go index 2c82738..594f719 100644 --- a/route_test.go +++ b/route_test.go @@ -386,7 +386,7 @@ func TestAddRoutes(t *testing.T) { t.Run("and generate swagger documentation in json", func(t *testing.T) { w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, JSONDocumentationPath, nil) + req := httptest.NewRequest(http.MethodGet, DefaultJSONDocumentationPath, nil) r.ServeHTTP(w, req)