mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-05-13 09:50:00 +08:00
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:
@@ -70,15 +70,40 @@ func propertiesFromType(ctx Context, tp apiSpec.Type) (spec.SchemaProperties, []
|
|||||||
switch sampleTypeFromGoType(ctx, member.Type) {
|
switch sampleTypeFromGoType(ctx, member.Type) {
|
||||||
case swaggerTypeArray:
|
case swaggerTypeArray:
|
||||||
schema.Items = itemsFromGoType(ctx, member.Type)
|
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:
|
case swaggerTypeObject:
|
||||||
p, r := propertiesFromType(ctx, member.Type)
|
p, r := propertiesFromType(ctx, member.Type)
|
||||||
schema.Properties = p
|
schema.Properties = p
|
||||||
schema.Required = r
|
schema.Required = r
|
||||||
}
|
// For objects with useDefinitions, set $ref at schema level
|
||||||
if ctx.UseDefinitions {
|
if ctx.UseDefinitions {
|
||||||
structName, containsStruct := containsStruct(member.Type)
|
structName, containsStruct := containsStruct(member.Type)
|
||||||
if containsStruct {
|
if containsStruct {
|
||||||
schema.SchemaProps.Ref = spec.MustCreateRef(getRefName(structName))
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package swagger
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,3 +24,117 @@ func Test_pathVariable2SwaggerVariable(t *testing.T) {
|
|||||||
assert.Equal(t, tc.expected, result)
|
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)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user