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
5 changes: 5 additions & 0 deletions internal/test/openapi31/openapi31.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions internal/test/openapi31/openapi31_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,15 @@ func TestPetExampleComments(t *testing.T) {
require.Contains(t, fields, "Lives")
assert.Contains(t, fields["Lives"], "Examples: 9",
"Lives field should surface the integer example as a doc fragment")

// `nickname` used the singular `example` keyword, which stays valid in
// 3.1 and is what most 3.1 specs write. Reading only the plural array
// dropped every such example from the generated comments.
require.Contains(t, fields, "Nickname")
assert.Contains(t, fields["Nickname"], "The pet's nickname.",
"Nickname field should preserve the original description")
assert.Contains(t, fields["Nickname"], "Example: Whisk",
"Nickname field should fall back to the singular example under 3.1")
}

// ----------------------------------------------------------------------------
Expand Down
8 changes: 7 additions & 1 deletion internal/test/openapi31/spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,9 @@ components:
# openapi31_polish: examples -> doc comments; const -> typed enum
# ----------------------------------------------------------------------
# `examples` (plural array) on object properties must surface in the
# generated field's Go doc comment.
# generated field's Go doc comment. 3.1 keeps the singular `example`
# keyword as a valid annotation, and specs written against 3.1 use it
# heavily, so `nickname` locks in that it renders too.
Pet:
type: object
required: [name, lives]
Expand All @@ -145,6 +147,10 @@ components:
examples:
- "Whiskers"
- "Rex"
nickname:
type: string
description: The pet's nickname.
example: "Whisk"
lives:
type: integer
examples:
Expand Down
23 changes: 13 additions & 10 deletions pkg/codegen/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -848,13 +848,19 @@ const (

// describeWithExamples folds a schema's example data into its
// description string for use in generated Go doc comments. Version-aware:
// in 3.0 it reads schema.Example (singular); in 3.1 it reads
// schema.Examples (plural array). Cross-version misuse is documented as
// invalid input -- this helper does not look at the off-version field.
// in 3.0 it reads schema.Example (singular); in 3.1 it prefers
// schema.Examples (the JSON Schema plural array) and falls back to
// schema.Example.
//
// The output appends `Examples: <v1>, <v2>, ...` (or `Example: <v>` in
// 3.0) on a new paragraph after any existing description text. Non-
// string values are JSON-encoded so structured examples render
// The fallback matters because 3.1 keeps the singular `example` keyword
// as a valid annotation, and specs written against 3.1 use it heavily.
// kin-openapi parses it into schema.Example regardless of document
// version, so without the fallback every such example is silently
// dropped from the generated comments.
//
// The output appends `Examples: <v1>, <v2>, ...` (or `Example: <v>` for
// a single value) on a new paragraph after any existing description
// text. Non-string values are JSON-encoded so structured examples render
// readably.
//
// Precondition: globalState.is31 must be set (see schemaIsNullable
Expand All @@ -865,10 +871,7 @@ func describeWithExamples(description string, schema *openapi3.Schema) string {
}
var values []any
label := "Example"
if globalState.is31 {
if len(schema.Examples) == 0 {
return description
}
if globalState.is31 && len(schema.Examples) > 0 {
values = schema.Examples
label = "Examples"
} else {
Expand Down
72 changes: 72 additions & 0 deletions pkg/codegen/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,78 @@ func TestOapiSchemaToGoType_NullType(t *testing.T) {
assert.True(t, out.DefineViaAlias)
}

func TestDescribeWithExamples(t *testing.T) {
for _, tc := range []struct {
name string
is31 bool
description string
schema *openapi3.Schema
want string
}{
{
name: "nil schema keeps description",
want: "",
},
{
name: "3.0 singular example",
description: "The widget name.",
schema: &openapi3.Schema{Example: "hammer"},
want: "The widget name.\n\nExample: hammer",
},
{
name: "3.0 singular example with no description",
schema: &openapi3.Schema{Example: "hammer"},
want: "Example: hammer",
},
{
name: "3.0 ignores plural examples",
description: "The widget name.",
schema: &openapi3.Schema{Examples: []any{"hammer"}},
want: "The widget name.",
},
{
name: "3.1 plural examples",
is31: true,
description: "The widget name.",
schema: &openapi3.Schema{Examples: []any{"hammer", "wrench"}},
want: "The widget name.\n\nExamples: hammer, wrench",
},
{
name: "3.1 falls back to singular example",
is31: true,
description: "The widget name.",
schema: &openapi3.Schema{Example: "hammer"},
want: "The widget name.\n\nExample: hammer",
},
{
name: "3.1 prefers plural examples over singular",
is31: true,
schema: &openapi3.Schema{Example: "ignored", Examples: []any{"hammer"}},
want: "Examples: hammer",
},
{
name: "3.1 with neither keeps description",
is31: true,
description: "The widget name.",
schema: &openapi3.Schema{},
want: "The widget name.",
},
{
name: "structured example is JSON encoded",
schema: &openapi3.Schema{Example: map[string]any{"k": "v"}},
want: `Example: {"k":"v"}`,
},
} {
t.Run(tc.name, func(t *testing.T) {
old := globalState.is31
defer func() { globalState.is31 = old }()
globalState.is31 = tc.is31

assert.Equal(t, tc.want, describeWithExamples(tc.description, tc.schema))
})
}
}

// constScalarFamily is the type-inference core behind a no-outer-type
// enum-via-oneOf: it maps a branch's `const` to the OpenAPI scalar family it
// belongs to, and to the integer format wide enough to hold it. kin-openapi
Expand Down