Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func TestMain(m *testing.M) {
func TestApplication_LoadCode(t *testing.T) {
sctx := loadClassificationPkgsCtx(t)
require.NotNil(t, sctx)
require.Len(t, sctx.app.Models, 39)
require.Len(t, sctx.app.Models, 45)
require.Len(t, sctx.app.Meta, 1)
require.Len(t, sctx.app.Routes, 7)
require.Empty(t, sctx.app.Operations)
Expand Down
49 changes: 49 additions & 0 deletions fixtures/goparsing/classification/models/nomodel.go
Original file line number Diff line number Diff line change
Expand Up @@ -799,3 +799,52 @@ type NamedMapOfStoreOrderSlices GenericMap[string, GenericSlice[StoreOrder]]
//
// End of models related to named types with type arguments
//

// SomeStringSlice is a named slice type with swagger:type array.
// swagger:type array
type SomeStringSlice []string

// swagger:model namedWithArrayType
type NamedWithArrayType struct {
// Tags for this item.
Tags SomeStringSlice `json:"tags"`
}

// SomeFixedArray is a named fixed-length array type with swagger:type array.
// swagger:type array
type SomeFixedArray [5]string

// swagger:model namedWithFixedArrayType
type NamedWithFixedArrayType struct {
// Labels for this item.
Labels SomeFixedArray `json:"labels"`
}

// SomeStructWithBadType is a struct type with an unsupported swagger:type value.
// The unsupported value should be ignored and the type should fall through to $ref.
//
// swagger:type badvalue
// swagger:model someStructWithBadType
type SomeStructWithBadType struct {
Name string `json:"name"`
}

// swagger:model namedWithBadStructType
type NamedWithBadStructType struct {
// The nested struct with an unsupported swagger:type.
Nested SomeStructWithBadType `json:"nested"`
}

// SomeObjectType is a struct with swagger:type object.
// swagger:type object
// swagger:model someObjectType
type SomeObjectType struct {
Key string `json:"key"`
Value string `json:"value"`
}

// swagger:model namedWithObjectStructType
type NamedWithObjectStructType struct {
// Headers for this request.
Headers SomeObjectType `json:"headers"`
}
40 changes: 33 additions & 7 deletions schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -429,10 +429,15 @@ func (s *schemaBuilder) buildNamedType(titpe *types.Named, tgt swaggerTypable) e
cmt = new(ast.CommentGroup)
}

if typeName, ok := typeName(cmt); ok {
_ = swaggerSchemaForType(typeName, tgt)

return nil
if tn, ok := typeName(cmt); ok {
if err := swaggerSchemaForType(tn, tgt); err == nil {
return nil
}
// For unsupported swagger:type values (e.g., "array"), fall through
// to underlying type resolution so the full schema (including items
// for slices) is properly built. Build directly from the underlying
// type to bypass the named-type $ref creation.
return s.buildFromType(titpe.Underlying(), tgt)
}

if s.decl.Spec.Assign.IsValid() {
Expand Down Expand Up @@ -559,9 +564,12 @@ func (s *schemaBuilder) buildNamedStruct(tio *types.TypeName, cmt *ast.CommentGr
return nil
}

if typeName, ok := typeName(cmt); ok {
_ = swaggerSchemaForType(typeName, tgt)
return nil
if tn, ok := typeName(cmt); ok {
if err := swaggerSchemaForType(tn, tgt); err == nil {
return nil
}
// For unsupported swagger:type values, fall through to makeRef
// rather than silently returning an empty schema.
}

return s.makeRef(decl, tgt)
Expand All @@ -583,6 +591,14 @@ func (s *schemaBuilder) buildNamedArray(tio *types.TypeName, cmt *ast.CommentGro
tgt.Items().Typed("string", sfnm)
return nil
}
// When swagger:type is set to an unsupported value (e.g., "array"),
// skip the $ref and inline the array schema with proper items type.
if tn, ok := typeName(cmt); ok {
if err := swaggerSchemaForType(tn, tgt); err != nil {
return s.buildFromType(elem, tgt.Items())
}
return nil
}
if decl, ok := s.ctx.FindModel(tio.Pkg().Path(), tio.Name()); ok {
return s.makeRef(decl, tgt)
}
Expand All @@ -600,6 +616,16 @@ func (s *schemaBuilder) buildNamedSlice(tio *types.TypeName, cmt *ast.CommentGro
tgt.Items().Typed("string", sfnm)
return nil
}
// When swagger:type is set to an unsupported value (e.g., "array"),
// skip the $ref and inline the slice schema with proper items type.
// This preserves the field's description that would be lost with $ref.
if tn, ok := typeName(cmt); ok {
if err := swaggerSchemaForType(tn, tgt); err != nil {
// Unsupported type name (e.g., "array") — build inline from element type.
return s.buildFromType(elem, tgt.Items())
}
return nil
}
if decl, ok := s.ctx.FindModel(tio.Pkg().Path(), tio.Name()); ok {
return s.makeRef(decl, tgt)
}
Expand Down
77 changes: 77 additions & 0 deletions schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2582,3 +2582,80 @@ func TestSetEnumDoesNotPanic(t *testing.T) {

require.NoError(t, err)
}

func TestSwaggerTypeNamedArray(t *testing.T) {
sctx := loadClassificationPkgsCtx(t)
decl := getClassificationModel(sctx, "NamedWithArrayType")
require.NotNil(t, decl)
prs := &schemaBuilder{
ctx: sctx,
decl: decl,
}
models := make(map[string]spec.Schema)
require.NoError(t, prs.Build(models))
schema := models["namedWithArrayType"]

// swagger:type array on a named []string type should produce
// an inlined array with string items, not a $ref.
assertArrayProperty(t, &schema, "string", "tags", "", "Tags")
}

func TestSwaggerTypeNamedFixedArray(t *testing.T) {
sctx := loadClassificationPkgsCtx(t)
decl := getClassificationModel(sctx, "NamedWithFixedArrayType")
require.NotNil(t, decl)
prs := &schemaBuilder{
ctx: sctx,
decl: decl,
}
models := make(map[string]spec.Schema)
require.NoError(t, prs.Build(models))
schema := models["namedWithFixedArrayType"]

// swagger:type array on a named [5]string type should produce
// an inlined array with string items via buildNamedArray.
assertArrayProperty(t, &schema, "string", "labels", "", "Labels")
}

func TestSwaggerTypeBadValueOnStruct(t *testing.T) {
sctx := loadClassificationPkgsCtx(t)
decl := getClassificationModel(sctx, "NamedWithBadStructType")
require.NotNil(t, decl)
prs := &schemaBuilder{
ctx: sctx,
decl: decl,
}
models := make(map[string]spec.Schema)
require.NoError(t, prs.Build(models))
schema := models["namedWithBadStructType"]

// swagger:type with an unsupported value on a struct should not
// produce an empty schema — it should either inline the struct
// or create a $ref. The key assertion is that the property exists
// and is not empty (i.e., the error was not silently swallowed).
prop := schema.Properties["nested"]
hasType := len(prop.Type) > 0
hasRef := prop.Ref.String() != ""
hasProps := len(prop.Properties) > 0
assert.TrueT(t, hasType || hasRef || hasProps,
"expected nested property to have type, $ref, or properties — not an empty schema")
}

func TestSwaggerTypeObjectOnStruct(t *testing.T) {
sctx := loadClassificationPkgsCtx(t)
decl := getClassificationModel(sctx, "NamedWithObjectStructType")
require.NotNil(t, decl)
prs := &schemaBuilder{
ctx: sctx,
decl: decl,
}
models := make(map[string]spec.Schema)
require.NoError(t, prs.Build(models))
schema := models["namedWithObjectStructType"]

// swagger:type object on a struct should inline as type:object,
// preserving the field's description.
prop := schema.Properties["headers"]
assert.TrueT(t, prop.Type.Contains("object"))
assert.Empty(t, prop.Ref.String(), "should not have $ref when swagger:type object is set")
}
Loading