Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
25 changes: 25 additions & 0 deletions .github/docs/openapi3.txt
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,31 @@ type DuplicateParameterError struct {

func (e *DuplicateParameterError) Error() string

type DuplicateRequiredFieldError struct {
// Field is the field name listed more than once in `required`.
Field string
// Origin is the source location of the offending schema when the
// document was loaded with Loader.IncludeOrigin = true.
Origin *Origin
}
DuplicateRequiredFieldError clusters "duplicate field in required" failures.
The elements of a schema's `required` array MUST be unique (JSON Schema
2020-12 §6.5.3 for OpenAPI 3.1, draft-04 for OpenAPI 3.0).

func (e *DuplicateRequiredFieldError) Error() string

type DuplicateTagError struct {
// Name is the tag name that appears more than once.
Name string
// Origin is the source location of the offending tag when the document
// was loaded with Loader.IncludeOrigin = true.
Origin *Origin
}
DuplicateTagError clusters "more than one tag has name X" failures. Each tag
name in the document-root `tags` list MUST be unique (OpenAPI Object).

func (e *DuplicateTagError) Error() string

type DynamicAnchorFieldFor31Plus struct{ ValidationError }

func (e *DynamicAnchorFieldFor31Plus) As(target any) bool
Expand Down
12 changes: 12 additions & 0 deletions openapi3/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -1406,6 +1406,18 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) ([]*Schema,
return stack, newSchemaReadOnlyWriteOnlyExclusive(schema.Origin)
}

// The elements of `required` MUST be unique (JSON Schema 2020-12 §6.5.3
// for OAS 3.1, draft-04 for OAS 3.0).
if len(schema.Required) > 1 {
seen := make(map[string]struct{}, len(schema.Required))
for _, name := range schema.Required {
if _, dup := seen[name]; dup {
return stack, &DuplicateRequiredFieldError{Field: name, Origin: schema.Origin}
}
seen[name] = struct{}{}
}
}

// Reject fields that only exist in OAS 3.1 / JSON Schema 2020-12 when the
// document is OAS 3.0. Fields explicitly allowed via AllowExtraSiblingFields
// are skipped (opt-in escape hatch for 3.0 docs that reference external
Expand Down
13 changes: 13 additions & 0 deletions openapi3/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,20 @@ func (tags Tags) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
me := newErrCollector(ctx)

// Each tag name in the list MUST be unique (OpenAPI Object). Empty names
// are left to per-tag validation, not flagged as duplicates here.
seen := make(map[string]struct{}, len(tags))

for _, v := range tags {
if v.Name != "" {
if _, dup := seen[v.Name]; dup {
if err := me.emit(&DuplicateTagError{Name: v.Name, Origin: v.Origin}); err != nil {
return err
}
} else {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this else here no?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

seen[v.Name] = struct{}{}
}
}
wrap := func(e error) error { return &TagValidationError{Name: v.Name, Cause: e} }
if err := me.emitWrapped(wrap, v.Validate(ctx)); err != nil {
return err
Expand Down
29 changes: 29 additions & 0 deletions openapi3/validation_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,35 @@ func (e *DuplicateParameterError) Error() string {
return fmt.Sprintf("more than one %q parameter has name %q", e.In, e.Name)
}

// DuplicateRequiredFieldError clusters "duplicate field in required" failures.
// The elements of a schema's `required` array MUST be unique (JSON Schema
// 2020-12 §6.5.3 for OpenAPI 3.1, draft-04 for OpenAPI 3.0).
type DuplicateRequiredFieldError struct {
// Field is the field name listed more than once in `required`.
Field string
// Origin is the source location of the offending schema when the
// document was loaded with Loader.IncludeOrigin = true.
Origin *Origin
}

func (e *DuplicateRequiredFieldError) Error() string {
return fmt.Sprintf("duplicate field %q in required", e.Field)
}

// DuplicateTagError clusters "more than one tag has name X" failures. Each tag
// name in the document-root `tags` list MUST be unique (OpenAPI Object).
type DuplicateTagError struct {
// Name is the tag name that appears more than once.
Name string
// Origin is the source location of the offending tag when the document
// was loaded with Loader.IncludeOrigin = true.
Origin *Origin
}

func (e *DuplicateTagError) Error() string {
return fmt.Sprintf("more than one tag has name %q", e.Name)
}

// InvalidSerializationMethodError clusters "serialization method with
// style=X and explode=Y is not supported by Z" failures. Fires for
// invalid (style, explode) combinations on encodings, parameters,
Expand Down
40 changes: 40 additions & 0 deletions openapi3/validation_error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2127,6 +2127,46 @@ func TestValidationError_SchemaCombinatorElementValidationError_NoStutter(t *tes
require.Equal(t, "invalid oneOf element: invalid allOf element: boom", mixed.Error())
}

func TestValidationError_DuplicateRequiredFieldError(t *testing.T) {
doc := loadDocFromYAML(t, `
openapi: 3.0.3
info: { title: t, version: "1" }
paths: {}
components:
schemas:
Bad:
type: object
required: [id, id]
properties:
id: { type: string }
`)
err := doc.Validate(context.Background())
require.Error(t, err)

var dup *openapi3.DuplicateRequiredFieldError
require.True(t, errors.As(err, &dup))
require.Equal(t, "id", dup.Field)
require.Contains(t, dup.Error(), `duplicate field "id" in required`)
}

func TestValidationError_DuplicateTagError(t *testing.T) {
doc := loadDocFromYAML(t, `
openapi: 3.0.3
info: { title: t, version: "1" }
paths: {}
tags:
- name: pet
- name: pet
`)
err := doc.Validate(context.Background())
require.Error(t, err)

var dup *openapi3.DuplicateTagError
require.True(t, errors.As(err, &dup))
require.Equal(t, "pet", dup.Name)
require.Contains(t, dup.Error(), `more than one tag has name "pet"`)
}

func TestValidationError_TagValidationError(t *testing.T) {
doc := loadDocFromYAML(t, `
openapi: 3.0.3
Expand Down
Loading