fix(goctl/swagger): correct $ref placement in array definitions when useDefinitions is enabled (#5199)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kevwan <1918356+kevwan@users.noreply.github.com>
This commit is contained in:
Copilot
2025-10-02 22:11:18 +08:00
committed by GitHub
parent fa85c84af3
commit ce6d0e3ea7
2 changed files with 145 additions and 5 deletions

View File

@@ -70,15 +70,40 @@ func propertiesFromType(ctx Context, tp apiSpec.Type) (spec.SchemaProperties, []
switch sampleTypeFromGoType(ctx, member.Type) {
case swaggerTypeArray:
schema.Items = itemsFromGoType(ctx, member.Type)
// Special handling for arrays with useDefinitions
if ctx.UseDefinitions {
// For arrays, check if the array element (not the array itself) contains a struct
if arrayType, ok := member.Type.(apiSpec.ArrayType); ok {
if structName, containsStruct := containsStruct(arrayType.Value); containsStruct {
// Set the $ref inside the items, not at the schema level
schema.Items = &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Ref: spec.MustCreateRef(getRefName(structName)),
},
},
}
}
}
}
case swaggerTypeObject:
p, r := propertiesFromType(ctx, member.Type)
schema.Properties = p
schema.Required = r
}
if ctx.UseDefinitions {
structName, containsStruct := containsStruct(member.Type)
if containsStruct {
schema.SchemaProps.Ref = spec.MustCreateRef(getRefName(structName))
// For objects with useDefinitions, set $ref at schema level
if ctx.UseDefinitions {
structName, containsStruct := containsStruct(member.Type)
if containsStruct {
schema.SchemaProps.Ref = spec.MustCreateRef(getRefName(structName))
}
}
default:
// For non-array, non-object types, apply useDefinitions logic
if ctx.UseDefinitions {
structName, containsStruct := containsStruct(member.Type)
if containsStruct {
schema.SchemaProps.Ref = spec.MustCreateRef(getRefName(structName))
}
}
}

View File

@@ -3,6 +3,7 @@ package swagger
import (
"testing"
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
"github.com/stretchr/testify/assert"
)
@@ -23,3 +24,117 @@ func Test_pathVariable2SwaggerVariable(t *testing.T) {
assert.Equal(t, tc.expected, result)
}
}
func TestArrayDefinitionsBug(t *testing.T) {
// Test case for the bug where array of structs with useDefinitions
// generates incorrect swagger JSON structure
// Context with useDefinitions enabled
ctx := Context{
UseDefinitions: true,
}
// Create a test struct containing an array of structs
testStruct := spec.DefineStruct{
RawName: "TestStruct",
Members: []spec.Member{
{
Name: "ArrayField",
Type: spec.ArrayType{
Value: spec.DefineStruct{
RawName: "ItemStruct",
Members: []spec.Member{
{
Name: "ItemName",
Type: spec.PrimitiveType{RawName: "string"},
Tag: `json:"itemName"`,
},
},
},
},
Tag: `json:"arrayField"`,
},
},
}
// Get properties from the struct
properties, _ := propertiesFromType(ctx, testStruct)
// Check that we have the array field
assert.Contains(t, properties, "arrayField")
arrayField := properties["arrayField"]
// Verify the array field has correct structure
assert.Equal(t, "array", arrayField.Type[0])
// Check that we have items
assert.NotNil(t, arrayField.Items, "Array should have items defined")
assert.NotNil(t, arrayField.Items.Schema, "Array items should have schema")
// The FIX: $ref should be inside items, not at schema level
hasRef := arrayField.Ref.String() != ""
assert.False(t, hasRef, "Schema level should NOT have $ref")
// The $ref should be in the items
hasItemsRef := arrayField.Items.Schema.Ref.String() != ""
assert.True(t, hasItemsRef, "Items should have $ref")
assert.Equal(t, "#/definitions/ItemStruct", arrayField.Items.Schema.Ref.String())
// Verify there are no other properties in the items when using $ref
assert.Nil(t, arrayField.Items.Schema.Properties, "Items with $ref should not have properties")
assert.Empty(t, arrayField.Items.Schema.Required, "Items with $ref should not have required")
assert.Empty(t, arrayField.Items.Schema.Type, "Items with $ref should not have type")
}
func TestArrayWithoutDefinitions(t *testing.T) {
// Test that arrays work correctly when useDefinitions is false
ctx := Context{
UseDefinitions: false, // This is the default
}
// Create the same test struct
testStruct := spec.DefineStruct{
RawName: "TestStruct",
Members: []spec.Member{
{
Name: "ArrayField",
Type: spec.ArrayType{
Value: spec.DefineStruct{
RawName: "ItemStruct",
Members: []spec.Member{
{
Name: "ItemName",
Type: spec.PrimitiveType{RawName: "string"},
Tag: `json:"itemName"`,
},
},
},
},
Tag: `json:"arrayField"`,
},
},
}
properties, _ := propertiesFromType(ctx, testStruct)
assert.Contains(t, properties, "arrayField")
arrayField := properties["arrayField"]
// Should be array type
assert.Equal(t, "array", arrayField.Type[0])
// Should have items with full schema, no $ref
assert.NotNil(t, arrayField.Items)
assert.NotNil(t, arrayField.Items.Schema)
// Should NOT have $ref at schema level
assert.Empty(t, arrayField.Ref.String(), "Schema should not have $ref when useDefinitions is false")
// Should NOT have $ref in items either
assert.Empty(t, arrayField.Items.Schema.Ref.String(), "Items should not have $ref when useDefinitions is false")
// Should have full schema properties in items
assert.Equal(t, "object", arrayField.Items.Schema.Type[0])
assert.Contains(t, arrayField.Items.Schema.Properties, "itemName")
assert.Equal(t, []string{"itemName"}, arrayField.Items.Schema.Required)
}