Skip to content

Commit 2baea3d

Browse files
authored
openapi2conv: preserve x-fields when converting from v2 to v3 (#1092)
* 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 * fix: remove the unnecessary copy loop Signed-off-by: saltbo <[email protected]> --------- Signed-off-by: saltbo <[email protected]>
1 parent 59b018c commit 2baea3d

File tree

2 files changed

+200
-2
lines changed

2 files changed

+200
-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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,7 @@ func ToV3SchemaRef(schema *openapi2.SchemaRef) *openapi3.SchemaRef {
484484
}
485485

486486
v3Schema := &openapi3.Schema{
487-
Extensions: schema.Extensions,
487+
Extensions: schema.Value.Extensions,
488488
Type: schema.Value.Type,
489489
Title: schema.Value.Title,
490490
Format: schema.Value.Format,
@@ -535,7 +535,7 @@ func ToV3SchemaRef(schema *openapi2.SchemaRef) *openapi3.SchemaRef {
535535
for i, v := range schema.Value.AllOf {
536536
v3Schema.AllOf[i] = ToV3SchemaRef(v)
537537
}
538-
if val, ok := schema.Value.Extensions["x-nullable"]; ok {
538+
if val, ok := v3Schema.Extensions["x-nullable"]; ok {
539539
if nullable, valid := val.(bool); valid {
540540
v3Schema.Nullable = nullable
541541
delete(v3Schema.Extensions, "x-nullable")

0 commit comments

Comments
 (0)