From 36c6f8b159a1fcc456167c105afbf6ce6174ca7c Mon Sep 17 00:00:00 2001 From: Davide Bianchi Date: Mon, 17 Apr 2023 10:20:59 +0200 Subject: [PATCH] feat: add new fields to definition struct --- CHANGELOG.md | 10 +++ operation.go | 9 +++ route.go | 31 +++++++++- route_test.go | 61 ++++++++++++++++++- testdata/extension.json | 21 +++++++ testdata/security.json | 26 ++++++++ .../users-deprecated-with-description.json | 21 +++++++ 7 files changed, 175 insertions(+), 4 deletions(-) create mode 100644 testdata/extension.json create mode 100644 testdata/security.json create mode 100644 testdata/users-deprecated-with-description.json diff --git a/CHANGELOG.md b/CHANGELOG.md index e2dd464..2bad2a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- add new fields to Definition: + - Security + - Tags + - Summary + - Description + - Deprecated + - Extensions + ### Updated - update kin-openapi to 0.114.0: this change removes components from exposed oas (if not manually set). In this update of kin-openapi, there is a breaking change with the T struct, which now accepts component as pointer. diff --git a/operation.go b/operation.go index e3e6d5e..23743a5 100644 --- a/operation.go +++ b/operation.go @@ -37,3 +37,12 @@ func (o *Operation) AddResponse(status int, response *openapi3.Response) { } o.Operation.AddResponse(status, response) } + +func (o *Operation) addSecurityRequirements(securityRequirements SecurityRequirements) { + if securityRequirements != nil && o.Security == nil { + o.Security = openapi3.NewSecurityRequirements() + } + for _, securityRequirement := range securityRequirements { + o.Security.With(openapi3.SecurityRequirement(securityRequirement)) + } +} diff --git a/route.go b/route.go index b9b076d..e426f66 100644 --- a/route.go +++ b/route.go @@ -71,15 +71,42 @@ type ContentValue struct { Description string } +type SecurityRequirements []SecurityRequirement +type SecurityRequirement map[string][]string + // Definitions of the route. +// To see how to use, refer to https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md type Definitions struct { + // Specification extensions https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specification-extensions + Extensions map[string]interface{} + // Optional field for documentation Tags []string + Summary string + Description string + Deprecated bool + + // PathParams contains the path parameters. If empty is autocompleted from the path PathParams ParameterValue Querystring ParameterValue Headers ParameterValue Cookies ParameterValue RequestBody *ContentValue Responses map[int]ContentValue + + Security SecurityRequirements +} + +func newOperationFromDefinition(schema Definitions) Operation { + operation := NewOperation() + operation.Responses = make(openapi3.Responses) + operation.Tags = schema.Tags + operation.Extensions = schema.Extensions + operation.addSecurityRequirements(schema.Security) + operation.Description = schema.Description + operation.Summary = schema.Summary + operation.Deprecated = schema.Deprecated + + return operation } const ( @@ -91,9 +118,7 @@ const ( // AddRoute add a route with json schema inferred by passed schema. func (r Router[HandlerFunc, Route]) AddRoute(method string, path string, handler HandlerFunc, schema Definitions) (Route, error) { - operation := NewOperation() - operation.Responses = make(openapi3.Responses) - operation.Tags = schema.Tags + operation := newOperationFromDefinition(schema) err := r.resolveRequestBodySchema(schema.RequestBody, operation) if err != nil { diff --git a/route_test.go b/route_test.go index 74ecb36..50d3d18 100644 --- a/route_test.go +++ b/route_test.go @@ -20,7 +20,6 @@ const formDataType = "multipart/form-data" type TestRouter = Router[gorilla.HandlerFunc, gorilla.Route] func TestAddRoutes(t *testing.T) { - type User struct { Name string `json:"name" jsonschema:"title=The user name,required" jsonschema_extras:"example=Jane"` PhoneNumber int `json:"phone" jsonschema:"title=mobile number of user"` @@ -435,6 +434,66 @@ func TestAddRoutes(t *testing.T) { testPath: "/users", fixturesPath: "testdata/tags.json", }, + { + name: "schema with security", + routes: func(t *testing.T, router *TestRouter) { + _, err := router.AddRoute(http.MethodGet, "/users", okHandler, Definitions{ + Security: SecurityRequirements{ + SecurityRequirement{ + "api_key": []string{}, + "auth": []string{ + "resource.write", + }, + }, + }, + }) + require.NoError(t, err) + }, + testPath: "/users", + fixturesPath: "testdata/security.json", + }, + { + name: "schema with extension", + routes: func(t *testing.T, router *TestRouter) { + _, err := router.AddRoute(http.MethodGet, "/users", okHandler, Definitions{ + Extensions: map[string]interface{}{ + "x-extension-field": map[string]string{ + "foo": "bar", + }, + }, + }) + require.NoError(t, err) + }, + testPath: "/users", + fixturesPath: "testdata/extension.json", + }, + { + name: "invalid extension - not starts with x-", + routes: func(t *testing.T, router *TestRouter) { + _, err := router.AddRoute(http.MethodGet, "/", okHandler, Definitions{ + Extensions: map[string]interface{}{ + "extension-field": map[string]string{ + "foo": "bar", + }, + }, + }) + require.EqualError(t, err, "extra sibling fields: [extension-field]") + }, + fixturesPath: "testdata/empty.json", + }, + { + name: "schema with summary, description, deprecated and operationID", + routes: func(t *testing.T, router *TestRouter) { + _, err := router.AddRoute(http.MethodGet, "/users", okHandler, Definitions{ + Summary: "small description", + Description: "this is the long route description", + Deprecated: true, + }) + require.NoError(t, err) + }, + testPath: "/users", + fixturesPath: "testdata/users-deprecated-with-description.json", + }, } for _, test := range tests { diff --git a/testdata/extension.json b/testdata/extension.json new file mode 100644 index 0000000..13208c1 --- /dev/null +++ b/testdata/extension.json @@ -0,0 +1,21 @@ +{ + "info": { + "title": "test swagger title", + "version": "test swagger version" + }, + "openapi": "3.0.0", + "paths": { + "/users": { + "get": { + "responses": { + "default": { + "description": "" + } + }, + "x-extension-field": { + "foo": "bar" + } + } + } + } +} diff --git a/testdata/security.json b/testdata/security.json new file mode 100644 index 0000000..d2a0d45 --- /dev/null +++ b/testdata/security.json @@ -0,0 +1,26 @@ +{ + "info": { + "title": "test swagger title", + "version": "test swagger version" + }, + "openapi": "3.0.0", + "paths": { + "/users": { + "get": { + "responses": { + "default": { + "description": "" + } + }, + "security": [ + { + "api_key": [], + "auth": [ + "resource.write" + ] + } + ] + } + } + } +} diff --git a/testdata/users-deprecated-with-description.json b/testdata/users-deprecated-with-description.json new file mode 100644 index 0000000..b72af79 --- /dev/null +++ b/testdata/users-deprecated-with-description.json @@ -0,0 +1,21 @@ +{ + "info": { + "title": "test swagger title", + "version": "test swagger version" + }, + "openapi": "3.0.0", + "paths": { + "/users": { + "get": { + "deprecated": true, + "description": "this is the long route description", + "responses": { + "default": { + "description": "" + } + }, + "summary": "small description" + } + } + } +}