Skip to content

Commit 8381bfc

Browse files
openapi3: type the remaining bare-error validation sites (#1187)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d29b5c0 commit 8381bfc

34 files changed

Lines changed: 2293 additions & 150 deletions

.github/docs/openapi3.txt

Lines changed: 403 additions & 4 deletions
Large diffs are not rendered by default.

openapi3/callback.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func (callback *Callback) Validate(ctx context.Context, opts ...ValidationOption
4545
}
4646
}
4747

48-
return validateExtensions(ctx, callback.Extensions)
48+
return validateExtensions(ctx, callback.Extensions, callback.Origin)
4949
}
5050

5151
// UnmarshalJSON sets Callbacks to a copy of data.

openapi3/components.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ func (components *Components) Validate(ctx context.Context, opts ...ValidationOp
115115
validateMap := func(label string, names []string, validate func(k string) error) error {
116116
for _, k := range names {
117117
if idErr := ValidateIdentifier(k); idErr != nil {
118-
if err := me.emit(fmt.Errorf("%s %q: %w", label, k, idErr)); err != nil {
118+
if err := me.emit(&ComponentValidationError{Section: label, Name: k, Cause: idErr}); err != nil {
119119
return err
120120
}
121121
// Skip validating the component's value when its name is
@@ -125,7 +125,7 @@ func (components *Components) Validate(ctx context.Context, opts ...ValidationOp
125125
// per component bounded to a single, actionable finding.
126126
continue
127127
}
128-
wrap := func(e error) error { return fmt.Errorf("%s %q: %w", label, k, e) }
128+
wrap := func(e error) error { return &ComponentValidationError{Section: label, Name: k, Cause: e} }
129129
if err := me.emitWrapped(wrap, validate(k)); err != nil {
130130
return err
131131
}
@@ -187,7 +187,7 @@ func (components *Components) Validate(ctx context.Context, opts ...ValidationOp
187187
return err
188188
}
189189

190-
return me.finalize(validateExtensions(ctx, components.Extensions))
190+
return me.finalize(validateExtensions(ctx, components.Extensions, components.Origin))
191191
}
192192

193193
var _ jsonpointer.JSONPointable = (*Schemas)(nil)

openapi3/contact.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,5 @@ func (contact *Contact) UnmarshalJSON(data []byte) error {
6565
func (contact *Contact) Validate(ctx context.Context, opts ...ValidationOption) error {
6666
ctx = WithValidationOptions(ctx, opts...)
6767

68-
return validateExtensions(ctx, contact.Extensions)
68+
return validateExtensions(ctx, contact.Extensions, contact.Origin)
6969
}

openapi3/discriminator.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,5 @@ func (discriminator *Discriminator) UnmarshalJSON(data []byte) error {
7272
func (discriminator *Discriminator) Validate(ctx context.Context, opts ...ValidationOption) error {
7373
ctx = WithValidationOptions(ctx, opts...)
7474

75-
return validateExtensions(ctx, discriminator.Extensions)
75+
return validateExtensions(ctx, discriminator.Extensions, discriminator.Origin)
7676
}

openapi3/encoding.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package openapi3
33
import (
44
"context"
55
"encoding/json"
6-
"fmt"
76
"maps"
87
)
98

@@ -145,8 +144,8 @@ func (encoding *Encoding) Validate(ctx context.Context, opts ...ValidationOption
145144
sm.Style == SerializationPipeDelimited && !sm.Explode,
146145
sm.Style == SerializationDeepObject && sm.Explode:
147146
default:
148-
return fmt.Errorf("serialization method with style=%q and explode=%v is not supported by media type", sm.Style, sm.Explode)
147+
return newInvalidSerializationMethod("media type", sm.Style, sm.Explode, encoding.Origin)
149148
}
150149

151-
return validateExtensions(ctx, encoding.Extensions)
150+
return validateExtensions(ctx, encoding.Extensions, encoding.Origin)
152151
}

openapi3/example.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func (example *Example) Validate(ctx context.Context, opts ...ValidationOption)
8080
return newExampleValueOrExternalValueRequired(example.Origin)
8181
}
8282

83-
return validateExtensions(ctx, example.Extensions)
83+
return validateExtensions(ctx, example.Extensions, example.Origin)
8484
}
8585

8686
// UnmarshalJSON sets Examples to a copy of data.

openapi3/extension.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@ package openapi3
22

33
import (
44
"context"
5-
"fmt"
65
"strings"
76
)
87

9-
func validateExtensions(ctx context.Context, extensions map[string]any) error { // FIXME: newtype + Validate(...)
8+
// validateExtensions reports any non-`x-` keys in the given extensions
9+
// map that are not explicitly allowed by the validation context. The
10+
// origin argument is attached to the resulting ExtraSiblingFieldsError
11+
// so callers can pin the finding to the parent object that carries the
12+
// unknown keys; pass nil when the parent has no Origin (the loader was
13+
// run with IncludeOrigin = false, or the parent was constructed
14+
// programmatically without an Origin set).
15+
func validateExtensions(ctx context.Context, extensions map[string]any, origin *Origin) error { // FIXME: newtype + Validate(...)
1016
allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
1117

1218
var unknowns []string
@@ -23,7 +29,7 @@ func validateExtensions(ctx context.Context, extensions map[string]any) error {
2329
}
2430

2531
if len(unknowns) != 0 {
26-
return fmt.Errorf("extra sibling fields: %+v", unknowns)
32+
return newExtraSiblingFields(unknowns, origin)
2733
}
2834

2935
return nil

openapi3/external_docs.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package openapi3
33
import (
44
"context"
55
"encoding/json"
6-
"fmt"
76
"maps"
87
"net/url"
98
)
@@ -68,10 +67,10 @@ func (e *ExternalDocs) Validate(ctx context.Context, opts ...ValidationOption) e
6867
}
6968
}
7069
if _, err := url.Parse(e.URL); err != nil {
71-
if err := me.emit(fmt.Errorf("url is incorrect: %w", err)); err != nil {
70+
if err := me.emit(&ExternalDocsURLValidationError{Cause: err}); err != nil {
7271
return err
7372
}
7473
}
7574

76-
return me.finalize(validateExtensions(ctx, e.Extensions))
75+
return me.finalize(validateExtensions(ctx, e.Extensions, e.Origin))
7776
}

openapi3/header.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package openapi3
22

33
import (
44
"context"
5-
"fmt"
65

76
"github.com/go-openapi/jsonpointer"
87
)
@@ -67,28 +66,28 @@ func (header *Header) Validate(ctx context.Context, opts ...ValidationOption) er
6766
if smSupported := false ||
6867
sm.Style == SerializationSimple && !sm.Explode ||
6968
sm.Style == SerializationSimple && sm.Explode; !smSupported {
70-
e := fmt.Errorf("serialization method with style=%q and explode=%v is not supported by a header parameter", sm.Style, sm.Explode)
71-
return fmt.Errorf("header schema is invalid: %w", e)
69+
e := newInvalidSerializationMethod("header", sm.Style, sm.Explode, header.Origin)
70+
return &HeaderFieldValidationError{Field: "schema", Cause: e}
7271
}
7372

7473
if (header.Schema == nil) == (len(header.Content) == 0) {
75-
return fmt.Errorf("header schema is invalid: %w",
76-
newHeaderContentSchemaExactlyOne(header, header.Origin))
74+
return &HeaderFieldValidationError{Field: "schema",
75+
Cause: newHeaderContentSchemaExactlyOne(header, header.Origin)}
7776
}
7877
if schema := header.Schema; schema != nil {
7978
if err := schema.Validate(ctx); err != nil {
80-
return fmt.Errorf("header schema is invalid: %w", err)
79+
return &HeaderFieldValidationError{Field: "schema", Cause: err}
8180
}
8281
}
8382

8483
if content := header.Content; content != nil {
8584
if len(content) > 1 {
86-
return fmt.Errorf("header content is invalid: %w",
87-
newHeaderContentSingleEntry(header.Origin))
85+
return &HeaderFieldValidationError{Field: "content",
86+
Cause: newHeaderContentSingleEntry(header.Origin)}
8887
}
8988

9089
if err := content.Validate(ctx); err != nil {
91-
return fmt.Errorf("header content is invalid: %w", err)
90+
return &HeaderFieldValidationError{Field: "content", Cause: err}
9291
}
9392
}
9493
return nil

0 commit comments

Comments
 (0)