Skip to content

Commit d5e00c2

Browse files
FrancisLennon17francis-lennon-whs
andauthored
Swagger2 Extensions (#225)
Co-authored-by: Francis Lennon <francis.lennon@whitehatsec.com>
1 parent 5217252 commit d5e00c2

3 files changed

Lines changed: 166 additions & 39 deletions

File tree

openapi2/openapi2.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import (
1111
"fmt"
1212
"net/http"
1313

14+
"github.com/getkin/kin-openapi/jsoninfo"
1415
"github.com/getkin/kin-openapi/openapi3"
1516
)
1617

1718
type Swagger struct {
19+
openapi3.ExtensionProps
1820
Info openapi3.Info `json:"info"`
1921
ExternalDocs *openapi3.ExternalDocs `json:"externalDocs,omitempty"`
2022
Schemes []string `json:"schemes,omitempty"`
@@ -29,6 +31,14 @@ type Swagger struct {
2931
Tags openapi3.Tags `json:"tags,omitempty"`
3032
}
3133

34+
func (swagger *Swagger) MarshalJSON() ([]byte, error) {
35+
return jsoninfo.MarshalStrictStruct(swagger)
36+
}
37+
38+
func (swagger *Swagger) UnmarshalJSON(data []byte) error {
39+
return jsoninfo.UnmarshalStrictStruct(data, swagger)
40+
}
41+
3242
func (swagger *Swagger) AddOperation(path string, method string, operation *Operation) {
3343
paths := swagger.Paths
3444
if paths == nil {
@@ -44,6 +54,7 @@ func (swagger *Swagger) AddOperation(path string, method string, operation *Oper
4454
}
4555

4656
type PathItem struct {
57+
openapi3.ExtensionProps
4758
Ref string `json:"$ref,omitempty"`
4859
Delete *Operation `json:"delete,omitempty"`
4960
Get *Operation `json:"get,omitempty"`
@@ -55,6 +66,14 @@ type PathItem struct {
5566
Parameters Parameters `json:"parameters,omitempty"`
5667
}
5768

69+
func (pathItem *PathItem) MarshalJSON() ([]byte, error) {
70+
return jsoninfo.MarshalStrictStruct(pathItem)
71+
}
72+
73+
func (pathItem *PathItem) UnmarshalJSON(data []byte) error {
74+
return jsoninfo.UnmarshalStrictStruct(data, pathItem)
75+
}
76+
5877
func (pathItem *PathItem) Operations() map[string]*Operation {
5978
operations := make(map[string]*Operation, 8)
6079
if v := pathItem.Delete; v != nil {
@@ -124,6 +143,7 @@ func (pathItem *PathItem) SetOperation(method string, operation *Operation) {
124143
}
125144

126145
type Operation struct {
146+
openapi3.ExtensionProps
127147
Summary string `json:"summary,omitempty"`
128148
Description string `json:"description,omitempty"`
129149
ExternalDocs *openapi3.ExternalDocs `json:"externalDocs,omitempty"`
@@ -136,9 +156,18 @@ type Operation struct {
136156
Security *SecurityRequirements `json:"security,omitempty"`
137157
}
138158

159+
func (operation *Operation) MarshalJSON() ([]byte, error) {
160+
return jsoninfo.MarshalStrictStruct(operation)
161+
}
162+
163+
func (operation *Operation) UnmarshalJSON(data []byte) error {
164+
return jsoninfo.UnmarshalStrictStruct(data, operation)
165+
}
166+
139167
type Parameters []*Parameter
140168

141169
type Parameter struct {
170+
openapi3.ExtensionProps
142171
Ref string `json:"$ref,omitempty"`
143172
In string `json:"in,omitempty"`
144173
Name string `json:"name,omitempty"`
@@ -162,14 +191,31 @@ type Parameter struct {
162191
Default interface{} `json:"default,omitempty"`
163192
}
164193

194+
func (parameter *Parameter) MarshalJSON() ([]byte, error) {
195+
return jsoninfo.MarshalStrictStruct(parameter)
196+
}
197+
198+
func (parameter *Parameter) UnmarshalJSON(data []byte) error {
199+
return jsoninfo.UnmarshalStrictStruct(data, parameter)
200+
}
201+
165202
type Response struct {
203+
openapi3.ExtensionProps
166204
Ref string `json:"$ref,omitempty"`
167205
Description string `json:"description,omitempty"`
168206
Schema *openapi3.SchemaRef `json:"schema,omitempty"`
169207
Headers map[string]*Header `json:"headers,omitempty"`
170208
Examples map[string]interface{} `json:"examples,omitempty"`
171209
}
172210

211+
func (response *Response) MarshalJSON() ([]byte, error) {
212+
return jsoninfo.MarshalStrictStruct(response)
213+
}
214+
215+
func (response *Response) UnmarshalJSON(data []byte) error {
216+
return jsoninfo.UnmarshalStrictStruct(data, response)
217+
}
218+
173219
type Header struct {
174220
Ref string `json:"$ref,omitempty"`
175221
Description string `json:"description,omitempty"`
@@ -179,6 +225,7 @@ type Header struct {
179225
type SecurityRequirements []map[string][]string
180226

181227
type SecurityScheme struct {
228+
openapi3.ExtensionProps
182229
Ref string `json:"$ref,omitempty"`
183230
Description string `json:"description,omitempty"`
184231
Type string `json:"type,omitempty"`
@@ -190,3 +237,11 @@ type SecurityScheme struct {
190237
Scopes map[string]string `json:"scopes,omitempty"`
191238
Tags openapi3.Tags `json:"tags,omitempty"`
192239
}
240+
241+
func (securityScheme *SecurityScheme) MarshalJSON() ([]byte, error) {
242+
return jsoninfo.MarshalStrictStruct(securityScheme)
243+
}
244+
245+
func (securityScheme *SecurityScheme) UnmarshalJSON(data []byte) error {
246+
return jsoninfo.UnmarshalStrictStruct(data, securityScheme)
247+
}

openapi2conv/openapi2_conv.go

Lines changed: 93 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@ import (
1313

1414
// ToV3Swagger converts an OpenAPIv2 spec to an OpenAPIv3 spec
1515
func ToV3Swagger(swagger *openapi2.Swagger) (*openapi3.Swagger, error) {
16+
stripNonCustomExtensions(swagger.Extensions)
17+
1618
result := &openapi3.Swagger{
17-
OpenAPI: "3.0.2",
18-
Info: &swagger.Info,
19-
Components: openapi3.Components{},
20-
Tags: swagger.Tags,
19+
OpenAPI: "3.0.2",
20+
Info: &swagger.Info,
21+
Components: openapi3.Components{},
22+
Tags: swagger.Tags,
23+
ExtensionProps: swagger.ExtensionProps,
2124
}
2225
host := swagger.Host
2326
if len(host) > 0 {
@@ -93,7 +96,10 @@ func ToV3Swagger(swagger *openapi2.Swagger) (*openapi3.Swagger, error) {
9396
}
9497

9598
func ToV3PathItem(swagger *openapi2.Swagger, pathItem *openapi2.PathItem) (*openapi3.PathItem, error) {
96-
result := &openapi3.PathItem{}
99+
stripNonCustomExtensions(pathItem.Extensions)
100+
result := &openapi3.PathItem{
101+
ExtensionProps: pathItem.ExtensionProps,
102+
}
97103
for method, operation := range pathItem.Operations() {
98104
resultOperation, err := ToV3Operation(swagger, pathItem, operation)
99105
if err != nil {
@@ -118,11 +124,13 @@ func ToV3Operation(swagger *openapi2.Swagger, pathItem *openapi2.PathItem, opera
118124
if operation == nil {
119125
return nil, nil
120126
}
127+
stripNonCustomExtensions(operation.Extensions)
121128
result := &openapi3.Operation{
122-
OperationID: operation.OperationID,
123-
Summary: operation.Summary,
124-
Description: operation.Description,
125-
Tags: operation.Tags,
129+
OperationID: operation.OperationID,
130+
Summary: operation.Summary,
131+
Description: operation.Description,
132+
Tags: operation.Tags,
133+
ExtensionProps: operation.ExtensionProps,
126134
}
127135
if v := operation.Security; v != nil {
128136
resultSecurity := ToV3SecurityRequirements(*v)
@@ -162,11 +170,13 @@ func ToV3Parameter(parameter *openapi2.Parameter) (*openapi3.ParameterRef, *open
162170
Ref: ToV3Ref(ref),
163171
}, nil, nil
164172
}
173+
stripNonCustomExtensions(parameter.Extensions)
165174
in := parameter.In
166175
if in == "body" {
167176
result := &openapi3.RequestBody{
168-
Description: parameter.Description,
169-
Required: parameter.Required,
177+
Description: parameter.Description,
178+
Required: parameter.Required,
179+
ExtensionProps: parameter.ExtensionProps,
170180
}
171181
if schemaRef := parameter.Schema; schemaRef != nil {
172182
// Assume it's JSON
@@ -177,10 +187,11 @@ func ToV3Parameter(parameter *openapi2.Parameter) (*openapi3.ParameterRef, *open
177187
}, nil
178188
}
179189
result := &openapi3.Parameter{
180-
In: in,
181-
Name: parameter.Name,
182-
Description: parameter.Description,
183-
Required: parameter.Required,
190+
In: in,
191+
Name: parameter.Name,
192+
Description: parameter.Description,
193+
Required: parameter.Required,
194+
ExtensionProps: parameter.ExtensionProps,
184195
}
185196

186197
if parameter.Type != "" {
@@ -214,8 +225,10 @@ func ToV3Response(response *openapi2.Response) (*openapi3.ResponseRef, error) {
214225
Ref: ToV3Ref(ref),
215226
}, nil
216227
}
228+
stripNonCustomExtensions(response.Extensions)
217229
result := &openapi3.Response{
218-
Description: &response.Description,
230+
Description: &response.Description,
231+
ExtensionProps: response.ExtensionProps,
219232
}
220233
if schemaRef := response.Schema; schemaRef != nil {
221234
result.WithJSONSchemaRef(ToV3SchemaRef(schemaRef))
@@ -293,8 +306,10 @@ func ToV3SecurityScheme(securityScheme *openapi2.SecurityScheme) (*openapi3.Secu
293306
if securityScheme == nil {
294307
return nil, nil
295308
}
309+
stripNonCustomExtensions(securityScheme.Extensions)
296310
result := &openapi3.SecurityScheme{
297-
Description: securityScheme.Description,
311+
Description: securityScheme.Description,
312+
ExtensionProps: securityScheme.ExtensionProps,
298313
}
299314
switch securityScheme.Type {
300315
case "basic":
@@ -339,12 +354,16 @@ func FromV3Swagger(swagger *openapi3.Swagger) (*openapi2.Swagger, error) {
339354
if err != nil {
340355
return nil, err
341356
}
357+
stripNonCustomExtensions(swagger.Extensions)
358+
342359
result := &openapi2.Swagger{
343-
Info: *swagger.Info,
344-
Definitions: FromV3Schemas(swagger.Components.Schemas),
345-
Responses: resultResponses,
346-
Tags: swagger.Tags,
360+
Info: *swagger.Info,
361+
Definitions: FromV3Schemas(swagger.Components.Schemas),
362+
Responses: resultResponses,
363+
Tags: swagger.Tags,
364+
ExtensionProps: swagger.ExtensionProps,
347365
}
366+
348367
isHTTPS := false
349368
isHTTP := false
350369
servers := swagger.Servers
@@ -375,6 +394,8 @@ func FromV3Swagger(swagger *openapi3.Swagger) (*openapi2.Swagger, error) {
375394
continue
376395
}
377396
result.AddOperation(path, "GET", nil)
397+
stripNonCustomExtensions(pathItem.Extensions)
398+
addPathExtensions(result, path, pathItem.ExtensionProps)
378399
for method, operation := range pathItem.Operations() {
379400
if operation == nil {
380401
continue
@@ -457,7 +478,10 @@ func FromV3SecurityRequirements(requirements openapi3.SecurityRequirements) open
457478
}
458479

459480
func FromV3PathItem(swagger *openapi3.Swagger, pathItem *openapi3.PathItem) (*openapi2.PathItem, error) {
460-
result := &openapi2.PathItem{}
481+
stripNonCustomExtensions(pathItem.Extensions)
482+
result := &openapi2.PathItem{
483+
ExtensionProps: pathItem.ExtensionProps,
484+
}
461485
for method, operation := range pathItem.Operations() {
462486
r, err := FromV3Operation(swagger, operation)
463487
if err != nil {
@@ -493,11 +517,13 @@ func FromV3Operation(swagger *openapi3.Swagger, operation *openapi3.Operation) (
493517
if operation == nil {
494518
return nil, nil
495519
}
520+
stripNonCustomExtensions(operation.Extensions)
496521
result := &openapi2.Operation{
497-
OperationID: operation.OperationID,
498-
Summary: operation.Summary,
499-
Description: operation.Description,
500-
Tags: operation.Tags,
522+
OperationID: operation.OperationID,
523+
Summary: operation.Summary,
524+
Description: operation.Description,
525+
Tags: operation.Tags,
526+
ExtensionProps: operation.ExtensionProps,
501527
}
502528
if v := operation.Security; v != nil {
503529
resultSecurity := FromV3SecurityRequirements(*v)
@@ -542,11 +568,13 @@ func FromV3RequestBody(swagger *openapi3.Swagger, operation *openapi3.Operation,
542568
if name == "" {
543569
return nil, errors.New("Could not find a name for request body")
544570
}
571+
stripNonCustomExtensions(requestBody.Extensions)
545572
result := &openapi2.Parameter{
546-
In: "body",
547-
Name: name,
548-
Description: requestBody.Description,
549-
Required: requestBody.Required,
573+
In: "body",
574+
Name: name,
575+
Description: requestBody.Description,
576+
Required: requestBody.Required,
577+
ExtensionProps: requestBody.ExtensionProps,
550578
}
551579

552580
// Add JSON schema
@@ -567,11 +595,13 @@ func FromV3Parameter(ref *openapi3.ParameterRef) (*openapi2.Parameter, error) {
567595
if parameter == nil {
568596
return nil, nil
569597
}
598+
stripNonCustomExtensions(parameter.Extensions)
570599
result := &openapi2.Parameter{
571-
Description: parameter.Description,
572-
In: parameter.In,
573-
Name: parameter.Name,
574-
Required: parameter.Required,
600+
Description: parameter.Description,
601+
In: parameter.In,
602+
Name: parameter.Name,
603+
Required: parameter.Required,
604+
ExtensionProps: parameter.ExtensionProps,
575605
}
576606
if schemaRef := parameter.Schema; schemaRef != nil {
577607
schemaRef = FromV3SchemaRef(schemaRef)
@@ -621,8 +651,10 @@ func FromV3Response(ref *openapi3.ResponseRef) (*openapi2.Response, error) {
621651
if desc := response.Description; desc != nil {
622652
description = *desc
623653
}
654+
stripNonCustomExtensions(response.Extensions)
624655
result := &openapi2.Response{
625-
Description: description,
656+
Description: description,
657+
ExtensionProps: response.ExtensionProps,
626658
}
627659
if content := response.Content; content != nil {
628660
if ct := content["application/json"]; ct != nil {
@@ -637,9 +669,11 @@ func FromV3SecurityScheme(swagger *openapi3.Swagger, ref *openapi3.SecuritySchem
637669
if securityScheme == nil {
638670
return nil, nil
639671
}
672+
stripNonCustomExtensions(securityScheme.Extensions)
640673
result := &openapi2.SecurityScheme{
641-
Ref: FromV3Ref(ref.Ref),
642-
Description: securityScheme.Description,
674+
Ref: FromV3Ref(ref.Ref),
675+
Description: securityScheme.Description,
676+
ExtensionProps: securityScheme.ExtensionProps,
643677
}
644678
switch securityScheme.Type {
645679
case "http":
@@ -684,3 +718,25 @@ var attemptedBodyParameterNames = []string{
684718
"body",
685719
"requestBody",
686720
}
721+
722+
func stripNonCustomExtensions(extensions map[string]interface{}) {
723+
for extName, _ := range extensions {
724+
if !strings.HasPrefix(extName, "x-") {
725+
delete(extensions, extName)
726+
}
727+
}
728+
}
729+
730+
func addPathExtensions(swagger *openapi2.Swagger, path string, extensionProps openapi3.ExtensionProps) {
731+
paths := swagger.Paths
732+
if paths == nil {
733+
paths = make(map[string]*openapi2.PathItem, 8)
734+
swagger.Paths = paths
735+
}
736+
pathItem := paths[path]
737+
if pathItem == nil {
738+
pathItem = &openapi2.PathItem{}
739+
paths[path] = pathItem
740+
}
741+
pathItem.ExtensionProps = extensionProps
742+
}

0 commit comments

Comments
 (0)