Skip to content

Commit 236504d

Browse files
committed
feat(parser): wrap errors with object location
1 parent 93ad16b commit 236504d

10 files changed

Lines changed: 105 additions & 22 deletions

openapi/parser/errors.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package parser
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/go-faster/errors"
7+
8+
ogenjson "github.com/ogen-go/ogen/json"
9+
)
10+
11+
var _ interface {
12+
error
13+
errors.Wrapper
14+
errors.Formatter
15+
} = (*LocationError)(nil)
16+
17+
// LocationError is a wrapper for an error that has a location.
18+
type LocationError struct {
19+
loc ogenjson.Location
20+
err error
21+
}
22+
23+
// Unwrap implements errors.Wrapper.
24+
func (e *LocationError) Unwrap() error {
25+
return e.err
26+
}
27+
28+
// FormatError implements errors.Formatter.
29+
func (e *LocationError) FormatError(p errors.Printer) (next error) {
30+
p.Printf("at %s", e.loc)
31+
return e.err
32+
}
33+
34+
// Error implements error.
35+
func (e *LocationError) Error() string {
36+
return fmt.Sprintf("at %s: %s", e.loc, e.err)
37+
}
38+
39+
func (p *parser) wrapLocation(l ogenjson.Locatable, err error) error {
40+
if err == nil {
41+
return nil
42+
}
43+
loc, ok := l.Location()
44+
if !ok {
45+
return err
46+
}
47+
return &LocationError{
48+
loc: loc,
49+
err: err,
50+
}
51+
}

openapi/parser/parse_example.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"github.com/ogen-go/ogen/openapi"
88
)
99

10-
func (p *parser) parseExample(e *ogen.Example, ctx *resolveCtx) (*openapi.Example, error) {
10+
func (p *parser) parseExample(e *ogen.Example, ctx *resolveCtx) (_ *openapi.Example, rerr error) {
1111
if e == nil {
1212
return nil, nil
1313
}
@@ -19,6 +19,9 @@ func (p *parser) parseExample(e *ogen.Example, ctx *resolveCtx) (*openapi.Exampl
1919
}
2020
return ex, nil
2121
}
22+
defer func() {
23+
rerr = p.wrapLocation(e, rerr)
24+
}()
2225

2326
return &openapi.Example{
2427
Summary: e.Summary,

openapi/parser/parse_header.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func (p *parser) parseHeaders(headers map[string]*ogen.Header, ctx *resolveCtx)
2323
return result, nil
2424
}
2525

26-
func (p *parser) parseHeader(name string, header *ogen.Header, ctx *resolveCtx) (*openapi.Header, error) {
26+
func (p *parser) parseHeader(name string, header *ogen.Header, ctx *resolveCtx) (_ *openapi.Header, rerr error) {
2727
if header == nil {
2828
return nil, errors.New("header object is empty or null")
2929
}
@@ -34,6 +34,9 @@ func (p *parser) parseHeader(name string, header *ogen.Header, ctx *resolveCtx)
3434
}
3535
return parsed, nil
3636
}
37+
defer func() {
38+
rerr = p.wrapLocation(header, rerr)
39+
}()
3740

3841
if header.In != "" {
3942
return nil, errors.Errorf(`"in" MUST NOT be specified, got %q`, header.In)

openapi/parser/parse_mediatype.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,11 @@ func (p *parser) parseParameterContent(content map[string]ogen.Media, ctx *resol
5656
panic("unreachable")
5757
}
5858

59-
func (p *parser) parseMediaType(m ogen.Media, ctx *resolveCtx) (*openapi.MediaType, error) {
59+
func (p *parser) parseMediaType(m ogen.Media, ctx *resolveCtx) (_ *openapi.MediaType, rerr error) {
60+
defer func() {
61+
rerr = p.wrapLocation(&m, rerr)
62+
}()
63+
6064
s, err := p.parseSchema(m.Schema, ctx)
6165
if err != nil {
6266
return nil, errors.Wrap(err, "schema")

openapi/parser/parse_parameter.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func validateParameter(name string, locatedIn openapi.ParameterLocation, param *
7373
return nil
7474
}
7575

76-
func (p *parser) parseParameter(param *ogen.Parameter, ctx *resolveCtx) (*openapi.Parameter, error) {
76+
func (p *parser) parseParameter(param *ogen.Parameter, ctx *resolveCtx) (_ *openapi.Parameter, rerr error) {
7777
if param == nil {
7878
return nil, errors.New("parameter object is empty or null")
7979
}
@@ -84,6 +84,9 @@ func (p *parser) parseParameter(param *ogen.Parameter, ctx *resolveCtx) (*openap
8484
}
8585
return parsed, nil
8686
}
87+
defer func() {
88+
rerr = p.wrapLocation(param, rerr)
89+
}()
8790

8891
types := map[string]openapi.ParameterLocation{
8992
"query": openapi.LocationQuery,

openapi/parser/parse_request_body.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"github.com/ogen-go/ogen/openapi"
88
)
99

10-
func (p *parser) parseRequestBody(body *ogen.RequestBody, ctx *resolveCtx) (*openapi.RequestBody, error) {
10+
func (p *parser) parseRequestBody(body *ogen.RequestBody, ctx *resolveCtx) (_ *openapi.RequestBody, rerr error) {
1111
if body == nil {
1212
return nil, errors.New("requestBody object is empty or null")
1313
}
@@ -19,6 +19,10 @@ func (p *parser) parseRequestBody(body *ogen.RequestBody, ctx *resolveCtx) (*ope
1919

2020
return reqBody, nil
2121
}
22+
defer func() {
23+
rerr = p.wrapLocation(body, rerr)
24+
}()
25+
2226
if len(body.Content) < 1 {
2327
// See https://github.com/OAI/OpenAPI-Specification/discussions/2875.
2428
return nil, errors.New("content must have at least one entry")

openapi/parser/parse_response.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func (p *parser) parseResponses(responses ogen.Responses) (_ map[string]*openapi
3838
return result, nil
3939
}
4040

41-
func (p *parser) parseResponse(resp *ogen.Response, ctx *resolveCtx) (*openapi.Response, error) {
41+
func (p *parser) parseResponse(resp *ogen.Response, ctx *resolveCtx) (_ *openapi.Response, rerr error) {
4242
if resp == nil {
4343
return nil, errors.New("response object is empty or null")
4444
}
@@ -50,6 +50,9 @@ func (p *parser) parseResponse(resp *ogen.Response, ctx *resolveCtx) (*openapi.R
5050

5151
return resp, nil
5252
}
53+
defer func() {
54+
rerr = p.wrapLocation(resp, rerr)
55+
}()
5356

5457
content, err := p.parseContent(resp.Content, ctx)
5558
if err != nil {

openapi/parser/parse_schema.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ import (
1010
"github.com/ogen-go/ogen/jsonschema"
1111
)
1212

13-
func (p *parser) parseSchema(schema *ogen.Schema, ctx *resolveCtx) (*jsonschema.Schema, error) {
13+
func (p *parser) parseSchema(schema *ogen.Schema, ctx *resolveCtx) (_ *jsonschema.Schema, rerr error) {
14+
if schema != nil {
15+
defer func() {
16+
rerr = p.wrapLocation(schema, rerr)
17+
}()
18+
}
1419
s := schema.ToJSONSchema()
1520
if loc := ctx.lastLoc(); s != nil && s.Ref != "" && loc != "" {
1621
base, err := url.Parse(loc)

openapi/parser/parse_security.go

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,34 @@ import (
1111
)
1212

1313
func (p *parser) parseSecurityScheme(
14-
s *ogen.SecurityScheme,
14+
scheme *ogen.SecurityScheme,
1515
scopes []string,
1616
ctx *resolveCtx,
17-
) (*ogen.SecurityScheme, error) {
18-
if s == nil {
17+
) (_ *ogen.SecurityScheme, rerr error) {
18+
if scheme == nil {
1919
return nil, errors.New("securityScheme is empty or null")
2020
}
2121

22-
if ref := s.Ref; ref != "" {
22+
if ref := scheme.Ref; ref != "" {
2323
sch, err := p.resolveSecurityScheme(ref, ctx)
2424
if err != nil {
2525
return nil, errors.Wrap(err, "resolve security schema")
2626
}
2727
return sch, nil
2828
}
29+
defer func() {
30+
rerr = p.wrapLocation(scheme, rerr)
31+
}()
2932

3033
if err := func() error {
31-
switch s.Type {
34+
switch scheme.Type {
3235
case "apiKey":
33-
switch s.In {
36+
switch scheme.In {
3437
case "query", "header", "cookie":
3538
default:
36-
return errors.Errorf(`invalid "in": %q`, s.In)
39+
return errors.Errorf(`invalid "in": %q`, scheme.In)
3740
}
38-
if s.Name == "" {
41+
if scheme.Name == "" {
3942
return errors.New(`"name" is required and MUST be a non-empty string`)
4043
}
4144
return nil
@@ -45,7 +48,7 @@ func (p *parser) parseSecurityScheme(
4548
// Probably such validation is too strict.
4649

4750
// Values from https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml.
48-
switch strings.ToLower(s.Scheme) {
51+
switch strings.ToLower(scheme.Scheme) {
4952
case "basic",
5053
"bearer",
5154
"digest",
@@ -57,26 +60,26 @@ func (p *parser) parseSecurityScheme(
5760
"scram-sha-256",
5861
"vapid":
5962
default:
60-
return errors.Errorf(`invalid "scheme": %q`, s.Scheme)
63+
return errors.Errorf(`invalid "scheme": %q`, scheme.Scheme)
6164
}
6265
return nil
6366
case "mutualTLS":
6467
return nil
6568
case "oauth2":
66-
return validateOAuthFlows(scopes, s.Flows)
69+
return validateOAuthFlows(scopes, scheme.Flows)
6770
case "openIdConnect":
68-
if _, err := url.ParseRequestURI(s.OpenIDConnectURL); err != nil {
71+
if _, err := url.ParseRequestURI(scheme.OpenIDConnectURL); err != nil {
6972
return errors.Wrap(err, `"openIdConnectUrl" MUST be in the form of a URL`)
7073
}
7174
return nil
7275
default:
73-
return errors.Errorf("unknown security scheme type %q", s.Type)
76+
return errors.Errorf("unknown security scheme type %q", scheme.Type)
7477
}
7578
}(); err != nil {
76-
return nil, errors.Wrap(err, s.Type)
79+
return nil, errors.Wrap(err, scheme.Type)
7780
}
7881

79-
return s, nil
82+
return scheme, nil
8083
}
8184

8285
func validateOAuthFlows(scopes []string, flows *ogen.OAuthFlows) error {

openapi/parser/parser.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ func (p *parser) parseOps() error {
130130
}
131131

132132
func (p *parser) parseOp(path, httpMethod string, spec ogen.Operation, itemParams []*openapi.Parameter) (_ *openapi.Operation, err error) {
133+
defer func() {
134+
err = p.wrapLocation(&spec, err)
135+
}()
136+
133137
op := &openapi.Operation{
134138
OperationID: spec.OperationID,
135139
Summary: spec.Summary,

0 commit comments

Comments
 (0)