Skip to content

Commit ce6d0e3

Browse files
Copilotkevwan
andauthored
fix(goctl/swagger): correct $ref placement in array definitions when useDefinitions is enabled (#5199)
Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: kevwan <[email protected]>
1 parent fa85c84 commit ce6d0e3

File tree

2 files changed

+145
-5
lines changed

2 files changed

+145
-5
lines changed

tools/goctl/api/swagger/properties.go

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,40 @@ func propertiesFromType(ctx Context, tp apiSpec.Type) (spec.SchemaProperties, []
7070
switch sampleTypeFromGoType(ctx, member.Type) {
7171
case swaggerTypeArray:
7272
schema.Items = itemsFromGoType(ctx, member.Type)
73+
// Special handling for arrays with useDefinitions
74+
if ctx.UseDefinitions {
75+
// For arrays, check if the array element (not the array itself) contains a struct
76+
if arrayType, ok := member.Type.(apiSpec.ArrayType); ok {
77+
if structName, containsStruct := containsStruct(arrayType.Value); containsStruct {
78+
// Set the $ref inside the items, not at the schema level
79+
schema.Items = &spec.SchemaOrArray{
80+
Schema: &spec.Schema{
81+
SchemaProps: spec.SchemaProps{
82+
Ref: spec.MustCreateRef(getRefName(structName)),
83+
},
84+
},
85+
}
86+
}
87+
}
88+
}
7389
case swaggerTypeObject:
7490
p, r := propertiesFromType(ctx, member.Type)
7591
schema.Properties = p
7692
schema.Required = r
77-
}
78-
if ctx.UseDefinitions {
79-
structName, containsStruct := containsStruct(member.Type)
80-
if containsStruct {
81-
schema.SchemaProps.Ref = spec.MustCreateRef(getRefName(structName))
93+
// For objects with useDefinitions, set $ref at schema level
94+
if ctx.UseDefinitions {
95+
structName, containsStruct := containsStruct(member.Type)
96+
if containsStruct {
97+
schema.SchemaProps.Ref = spec.MustCreateRef(getRefName(structName))
98+
}
99+
}
100+
default:
101+
// For non-array, non-object types, apply useDefinitions logic
102+
if ctx.UseDefinitions {
103+
structName, containsStruct := containsStruct(member.Type)
104+
if containsStruct {
105+
schema.SchemaProps.Ref = spec.MustCreateRef(getRefName(structName))
106+
}
82107
}
83108
}
84109

tools/goctl/api/swagger/swagger_test.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package swagger
33
import (
44
"testing"
55

6+
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
67
"github.com/stretchr/testify/assert"
78
)
89

@@ -23,3 +24,117 @@ func Test_pathVariable2SwaggerVariable(t *testing.T) {
2324
assert.Equal(t, tc.expected, result)
2425
}
2526
}
27+
28+
func TestArrayDefinitionsBug(t *testing.T) {
29+
// Test case for the bug where array of structs with useDefinitions
30+
// generates incorrect swagger JSON structure
31+
32+
// Context with useDefinitions enabled
33+
ctx := Context{
34+
UseDefinitions: true,
35+
}
36+
37+
// Create a test struct containing an array of structs
38+
testStruct := spec.DefineStruct{
39+
RawName: "TestStruct",
40+
Members: []spec.Member{
41+
{
42+
Name: "ArrayField",
43+
Type: spec.ArrayType{
44+
Value: spec.DefineStruct{
45+
RawName: "ItemStruct",
46+
Members: []spec.Member{
47+
{
48+
Name: "ItemName",
49+
Type: spec.PrimitiveType{RawName: "string"},
50+
Tag: `json:"itemName"`,
51+
},
52+
},
53+
},
54+
},
55+
Tag: `json:"arrayField"`,
56+
},
57+
},
58+
}
59+
60+
// Get properties from the struct
61+
properties, _ := propertiesFromType(ctx, testStruct)
62+
63+
// Check that we have the array field
64+
assert.Contains(t, properties, "arrayField")
65+
arrayField := properties["arrayField"]
66+
67+
// Verify the array field has correct structure
68+
assert.Equal(t, "array", arrayField.Type[0])
69+
70+
// Check that we have items
71+
assert.NotNil(t, arrayField.Items, "Array should have items defined")
72+
assert.NotNil(t, arrayField.Items.Schema, "Array items should have schema")
73+
74+
// The FIX: $ref should be inside items, not at schema level
75+
hasRef := arrayField.Ref.String() != ""
76+
assert.False(t, hasRef, "Schema level should NOT have $ref")
77+
78+
// The $ref should be in the items
79+
hasItemsRef := arrayField.Items.Schema.Ref.String() != ""
80+
assert.True(t, hasItemsRef, "Items should have $ref")
81+
assert.Equal(t, "#/definitions/ItemStruct", arrayField.Items.Schema.Ref.String())
82+
83+
// Verify there are no other properties in the items when using $ref
84+
assert.Nil(t, arrayField.Items.Schema.Properties, "Items with $ref should not have properties")
85+
assert.Empty(t, arrayField.Items.Schema.Required, "Items with $ref should not have required")
86+
assert.Empty(t, arrayField.Items.Schema.Type, "Items with $ref should not have type")
87+
}
88+
89+
func TestArrayWithoutDefinitions(t *testing.T) {
90+
// Test that arrays work correctly when useDefinitions is false
91+
ctx := Context{
92+
UseDefinitions: false, // This is the default
93+
}
94+
95+
// Create the same test struct
96+
testStruct := spec.DefineStruct{
97+
RawName: "TestStruct",
98+
Members: []spec.Member{
99+
{
100+
Name: "ArrayField",
101+
Type: spec.ArrayType{
102+
Value: spec.DefineStruct{
103+
RawName: "ItemStruct",
104+
Members: []spec.Member{
105+
{
106+
Name: "ItemName",
107+
Type: spec.PrimitiveType{RawName: "string"},
108+
Tag: `json:"itemName"`,
109+
},
110+
},
111+
},
112+
},
113+
Tag: `json:"arrayField"`,
114+
},
115+
},
116+
}
117+
118+
properties, _ := propertiesFromType(ctx, testStruct)
119+
120+
assert.Contains(t, properties, "arrayField")
121+
arrayField := properties["arrayField"]
122+
123+
// Should be array type
124+
assert.Equal(t, "array", arrayField.Type[0])
125+
126+
// Should have items with full schema, no $ref
127+
assert.NotNil(t, arrayField.Items)
128+
assert.NotNil(t, arrayField.Items.Schema)
129+
130+
// Should NOT have $ref at schema level
131+
assert.Empty(t, arrayField.Ref.String(), "Schema should not have $ref when useDefinitions is false")
132+
133+
// Should NOT have $ref in items either
134+
assert.Empty(t, arrayField.Items.Schema.Ref.String(), "Items should not have $ref when useDefinitions is false")
135+
136+
// Should have full schema properties in items
137+
assert.Equal(t, "object", arrayField.Items.Schema.Type[0])
138+
assert.Contains(t, arrayField.Items.Schema.Properties, "itemName")
139+
assert.Equal(t, []string{"itemName"}, arrayField.Items.Schema.Required)
140+
}

0 commit comments

Comments
 (0)