Skip to content

Commit bf41aec

Browse files
committed
Fix: Preserve extensions (like x-order) when converting OpenAPI v2 to v3
This commit fixes an issue where schema property-level extensions such as x-order were being lost during OpenAPI v2 to v3 conversion. The problem was in the ToV3SchemaRef function where schema.Extensions was being assigned to v3Schema.Extensions instead of schema.Value.Extensions. This meant that only SchemaRef-level extensions were preserved, but not the actual schema property extensions. Changes: - Modified ToV3SchemaRef to properly copy schema.Value.Extensions - Added comprehensive tests to verify extension preservation - Ensured both schema-level and property-level extensions are maintained - Verified nested properties also preserve their extensions Fixes #1091
1 parent e00a340 commit bf41aec

File tree

2 files changed

+206
-2
lines changed

2 files changed

+206
-2
lines changed

openapi2conv/issue1091_test.go

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
package openapi2conv
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
9+
"github.com/getkin/kin-openapi/openapi2"
10+
)
11+
12+
func TestIssue1091_PropertyExtensions(t *testing.T) {
13+
// Create a v2 schema with x-order extensions on properties
14+
v2SchemaJSON := `{
15+
"type": "object",
16+
"properties": {
17+
"field1": {
18+
"type": "string",
19+
"x-order": 1
20+
},
21+
"field2": {
22+
"type": "integer",
23+
"x-order": 2
24+
},
25+
"field3": {
26+
"type": "object",
27+
"properties": {
28+
"nestedField": {
29+
"type": "string",
30+
"x-order": 10
31+
}
32+
},
33+
"x-order": 3
34+
}
35+
}
36+
}`
37+
38+
var v2Schema openapi2.Schema
39+
err := json.Unmarshal([]byte(v2SchemaJSON), &v2Schema)
40+
require.NoError(t, err)
41+
42+
v2SchemaRef := &openapi2.SchemaRef{
43+
Value: &v2Schema,
44+
}
45+
46+
// Convert to v3
47+
v3SchemaRef := ToV3SchemaRef(v2SchemaRef)
48+
49+
// Verify that the conversion was successful
50+
require.NotNil(t, v3SchemaRef)
51+
require.NotNil(t, v3SchemaRef.Value)
52+
require.NotNil(t, v3SchemaRef.Value.Properties)
53+
54+
// Verify that extensions are preserved on properties
55+
field1 := v3SchemaRef.Value.Properties["field1"]
56+
require.NotNil(t, field1.Value)
57+
require.NotNil(t, field1.Value.Extensions)
58+
require.Equal(t, float64(1), field1.Value.Extensions["x-order"])
59+
60+
field2 := v3SchemaRef.Value.Properties["field2"]
61+
require.NotNil(t, field2.Value)
62+
require.NotNil(t, field2.Value.Extensions)
63+
require.Equal(t, float64(2), field2.Value.Extensions["x-order"])
64+
65+
// Verify nested properties also preserve extensions
66+
field3 := v3SchemaRef.Value.Properties["field3"]
67+
require.NotNil(t, field3.Value)
68+
require.NotNil(t, field3.Value.Extensions)
69+
require.Equal(t, float64(3), field3.Value.Extensions["x-order"])
70+
71+
nestedField := field3.Value.Properties["nestedField"]
72+
require.NotNil(t, nestedField.Value)
73+
require.NotNil(t, nestedField.Value.Extensions)
74+
require.Equal(t, float64(10), nestedField.Value.Extensions["x-order"])
75+
}
76+
77+
func TestIssue1091_SchemaLevelExtensions(t *testing.T) {
78+
// Create a v2 schema with schema-level extensions
79+
v2SchemaJSON := `{
80+
"type": "object",
81+
"x-schema-level": "test-value",
82+
"properties": {
83+
"field1": {
84+
"type": "string"
85+
}
86+
}
87+
}`
88+
89+
var v2Schema openapi2.Schema
90+
err := json.Unmarshal([]byte(v2SchemaJSON), &v2Schema)
91+
require.NoError(t, err)
92+
93+
v2SchemaRef := &openapi2.SchemaRef{
94+
Value: &v2Schema,
95+
Extensions: map[string]interface{}{"x-ref-level": "ref-value"},
96+
}
97+
98+
// Convert to v3
99+
v3SchemaRef := ToV3SchemaRef(v2SchemaRef)
100+
101+
// Verify that the conversion was successful
102+
require.NotNil(t, v3SchemaRef)
103+
require.NotNil(t, v3SchemaRef.Value)
104+
105+
// Verify that schema-level extensions are preserved
106+
require.NotNil(t, v3SchemaRef.Value.Extensions)
107+
require.Equal(t, "test-value", v3SchemaRef.Value.Extensions["x-schema-level"])
108+
109+
// Verify that ref-level extensions are preserved
110+
require.NotNil(t, v3SchemaRef.Extensions)
111+
require.Equal(t, "ref-value", v3SchemaRef.Extensions["x-ref-level"])
112+
}
113+
114+
func TestIssue1091_CompleteV2ToV3Conversion(t *testing.T) {
115+
// Create a complete v2 spec with extensions in definitions
116+
v2SpecJSON := `{
117+
"swagger": "2.0",
118+
"info": {
119+
"title": "Test API",
120+
"version": "1.0.0"
121+
},
122+
"host": "api.example.com",
123+
"basePath": "/v1",
124+
"schemes": ["https"],
125+
"definitions": {
126+
"User": {
127+
"type": "object",
128+
"x-model-type": "entity",
129+
"properties": {
130+
"id": {
131+
"type": "integer",
132+
"x-order": 1,
133+
"x-primary-key": true
134+
},
135+
"name": {
136+
"type": "string",
137+
"x-order": 2
138+
},
139+
"email": {
140+
"type": "string",
141+
"x-order": 3,
142+
"x-sensitive": true
143+
}
144+
}
145+
}
146+
},
147+
"paths": {
148+
"/users": {
149+
"get": {
150+
"responses": {
151+
"200": {
152+
"description": "List of users",
153+
"schema": {
154+
"type": "array",
155+
"items": {
156+
"$ref": "#/definitions/User"
157+
}
158+
}
159+
}
160+
}
161+
}
162+
}
163+
}
164+
}`
165+
166+
var v2Doc openapi2.T
167+
err := json.Unmarshal([]byte(v2SpecJSON), &v2Doc)
168+
require.NoError(t, err)
169+
170+
// Convert to v3
171+
v3Doc, err := ToV3(&v2Doc)
172+
require.NoError(t, err)
173+
174+
// Verify that User schema has extensions preserved
175+
userSchema := v3Doc.Components.Schemas["User"]
176+
require.NotNil(t, userSchema)
177+
require.NotNil(t, userSchema.Value)
178+
require.NotNil(t, userSchema.Value.Extensions)
179+
require.Equal(t, "entity", userSchema.Value.Extensions["x-model-type"])
180+
181+
// Verify that property-level extensions are preserved
182+
idProp := userSchema.Value.Properties["id"]
183+
require.NotNil(t, idProp.Value)
184+
require.NotNil(t, idProp.Value.Extensions)
185+
require.Equal(t, float64(1), idProp.Value.Extensions["x-order"])
186+
require.Equal(t, true, idProp.Value.Extensions["x-primary-key"])
187+
188+
nameProp := userSchema.Value.Properties["name"]
189+
require.NotNil(t, nameProp.Value)
190+
require.NotNil(t, nameProp.Value.Extensions)
191+
require.Equal(t, float64(2), nameProp.Value.Extensions["x-order"])
192+
193+
emailProp := userSchema.Value.Properties["email"]
194+
require.NotNil(t, emailProp.Value)
195+
require.NotNil(t, emailProp.Value.Extensions)
196+
require.Equal(t, float64(3), emailProp.Value.Extensions["x-order"])
197+
require.Equal(t, true, emailProp.Value.Extensions["x-sensitive"])
198+
}

openapi2conv/openapi2_conv.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -483,8 +483,14 @@ func ToV3SchemaRef(schema *openapi2.SchemaRef) *openapi3.SchemaRef {
483483
}
484484
}
485485

486+
// Copy schema.Value.Extensions to avoid modifying the original
487+
schemaExtensions := make(map[string]interface{})
488+
for k, v := range schema.Value.Extensions {
489+
schemaExtensions[k] = v
490+
}
491+
486492
v3Schema := &openapi3.Schema{
487-
Extensions: schema.Extensions,
493+
Extensions: schemaExtensions,
488494
Type: schema.Value.Type,
489495
Title: schema.Value.Title,
490496
Format: schema.Value.Format,
@@ -535,7 +541,7 @@ func ToV3SchemaRef(schema *openapi2.SchemaRef) *openapi3.SchemaRef {
535541
for i, v := range schema.Value.AllOf {
536542
v3Schema.AllOf[i] = ToV3SchemaRef(v)
537543
}
538-
if val, ok := schema.Value.Extensions["x-nullable"]; ok {
544+
if val, ok := v3Schema.Extensions["x-nullable"]; ok {
539545
if nullable, valid := val.(bool); valid {
540546
v3Schema.Nullable = nullable
541547
delete(v3Schema.Extensions, "x-nullable")

0 commit comments

Comments
 (0)