Skip to content

Commit

Permalink
feat(parser): wrap errors with object location
Browse files Browse the repository at this point in the history
  • Loading branch information
tdakkota committed Jul 5, 2022
1 parent 93ad16b commit 236504d
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 22 deletions.
51 changes: 51 additions & 0 deletions openapi/parser/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package parser

import (
"fmt"

"github.com/go-faster/errors"

ogenjson "github.com/ogen-go/ogen/json"
)

var _ interface {
error
errors.Wrapper
errors.Formatter
} = (*LocationError)(nil)

// LocationError is a wrapper for an error that has a location.
type LocationError struct {
loc ogenjson.Location
err error
}

// Unwrap implements errors.Wrapper.
func (e *LocationError) Unwrap() error {
return e.err
}

// FormatError implements errors.Formatter.
func (e *LocationError) FormatError(p errors.Printer) (next error) {
p.Printf("at %s", e.loc)
return e.err
}

// Error implements error.
func (e *LocationError) Error() string {
return fmt.Sprintf("at %s: %s", e.loc, e.err)
}

func (p *parser) wrapLocation(l ogenjson.Locatable, err error) error {
if err == nil {
return nil
}
loc, ok := l.Location()
if !ok {
return err
}
return &LocationError{
loc: loc,
err: err,
}
}
5 changes: 4 additions & 1 deletion openapi/parser/parse_example.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/ogen-go/ogen/openapi"
)

func (p *parser) parseExample(e *ogen.Example, ctx *resolveCtx) (*openapi.Example, error) {
func (p *parser) parseExample(e *ogen.Example, ctx *resolveCtx) (_ *openapi.Example, rerr error) {
if e == nil {
return nil, nil
}
Expand All @@ -19,6 +19,9 @@ func (p *parser) parseExample(e *ogen.Example, ctx *resolveCtx) (*openapi.Exampl
}
return ex, nil
}
defer func() {
rerr = p.wrapLocation(e, rerr)
}()

return &openapi.Example{
Summary: e.Summary,
Expand Down
5 changes: 4 additions & 1 deletion openapi/parser/parse_header.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func (p *parser) parseHeaders(headers map[string]*ogen.Header, ctx *resolveCtx)
return result, nil
}

func (p *parser) parseHeader(name string, header *ogen.Header, ctx *resolveCtx) (*openapi.Header, error) {
func (p *parser) parseHeader(name string, header *ogen.Header, ctx *resolveCtx) (_ *openapi.Header, rerr error) {
if header == nil {
return nil, errors.New("header object is empty or null")
}
Expand All @@ -34,6 +34,9 @@ func (p *parser) parseHeader(name string, header *ogen.Header, ctx *resolveCtx)
}
return parsed, nil
}
defer func() {
rerr = p.wrapLocation(header, rerr)
}()

if header.In != "" {
return nil, errors.Errorf(`"in" MUST NOT be specified, got %q`, header.In)
Expand Down
6 changes: 5 additions & 1 deletion openapi/parser/parse_mediatype.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ func (p *parser) parseParameterContent(content map[string]ogen.Media, ctx *resol
panic("unreachable")
}

func (p *parser) parseMediaType(m ogen.Media, ctx *resolveCtx) (*openapi.MediaType, error) {
func (p *parser) parseMediaType(m ogen.Media, ctx *resolveCtx) (_ *openapi.MediaType, rerr error) {
defer func() {
rerr = p.wrapLocation(&m, rerr)
}()

s, err := p.parseSchema(m.Schema, ctx)
if err != nil {
return nil, errors.Wrap(err, "schema")
Expand Down
5 changes: 4 additions & 1 deletion openapi/parser/parse_parameter.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func validateParameter(name string, locatedIn openapi.ParameterLocation, param *
return nil
}

func (p *parser) parseParameter(param *ogen.Parameter, ctx *resolveCtx) (*openapi.Parameter, error) {
func (p *parser) parseParameter(param *ogen.Parameter, ctx *resolveCtx) (_ *openapi.Parameter, rerr error) {
if param == nil {
return nil, errors.New("parameter object is empty or null")
}
Expand All @@ -84,6 +84,9 @@ func (p *parser) parseParameter(param *ogen.Parameter, ctx *resolveCtx) (*openap
}
return parsed, nil
}
defer func() {
rerr = p.wrapLocation(param, rerr)
}()

types := map[string]openapi.ParameterLocation{
"query": openapi.LocationQuery,
Expand Down
6 changes: 5 additions & 1 deletion openapi/parser/parse_request_body.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/ogen-go/ogen/openapi"
)

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

return reqBody, nil
}
defer func() {
rerr = p.wrapLocation(body, rerr)
}()

if len(body.Content) < 1 {
// See https://github.com/OAI/OpenAPI-Specification/discussions/2875.
return nil, errors.New("content must have at least one entry")
Expand Down
5 changes: 4 additions & 1 deletion openapi/parser/parse_response.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func (p *parser) parseResponses(responses ogen.Responses) (_ map[string]*openapi
return result, nil
}

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

return resp, nil
}
defer func() {
rerr = p.wrapLocation(resp, rerr)
}()

content, err := p.parseContent(resp.Content, ctx)
if err != nil {
Expand Down
7 changes: 6 additions & 1 deletion openapi/parser/parse_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ import (
"github.com/ogen-go/ogen/jsonschema"
)

func (p *parser) parseSchema(schema *ogen.Schema, ctx *resolveCtx) (*jsonschema.Schema, error) {
func (p *parser) parseSchema(schema *ogen.Schema, ctx *resolveCtx) (_ *jsonschema.Schema, rerr error) {
if schema != nil {
defer func() {
rerr = p.wrapLocation(schema, rerr)
}()
}
s := schema.ToJSONSchema()
if loc := ctx.lastLoc(); s != nil && s.Ref != "" && loc != "" {
base, err := url.Parse(loc)
Expand Down
33 changes: 18 additions & 15 deletions openapi/parser/parse_security.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,34 @@ import (
)

func (p *parser) parseSecurityScheme(
s *ogen.SecurityScheme,
scheme *ogen.SecurityScheme,
scopes []string,
ctx *resolveCtx,
) (*ogen.SecurityScheme, error) {
if s == nil {
) (_ *ogen.SecurityScheme, rerr error) {
if scheme == nil {
return nil, errors.New("securityScheme is empty or null")
}

if ref := s.Ref; ref != "" {
if ref := scheme.Ref; ref != "" {
sch, err := p.resolveSecurityScheme(ref, ctx)
if err != nil {
return nil, errors.Wrap(err, "resolve security schema")
}
return sch, nil
}
defer func() {
rerr = p.wrapLocation(scheme, rerr)
}()

if err := func() error {
switch s.Type {
switch scheme.Type {
case "apiKey":
switch s.In {
switch scheme.In {
case "query", "header", "cookie":
default:
return errors.Errorf(`invalid "in": %q`, s.In)
return errors.Errorf(`invalid "in": %q`, scheme.In)
}
if s.Name == "" {
if scheme.Name == "" {
return errors.New(`"name" is required and MUST be a non-empty string`)
}
return nil
Expand All @@ -45,7 +48,7 @@ func (p *parser) parseSecurityScheme(
// Probably such validation is too strict.

// Values from https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml.
switch strings.ToLower(s.Scheme) {
switch strings.ToLower(scheme.Scheme) {
case "basic",
"bearer",
"digest",
Expand All @@ -57,26 +60,26 @@ func (p *parser) parseSecurityScheme(
"scram-sha-256",
"vapid":
default:
return errors.Errorf(`invalid "scheme": %q`, s.Scheme)
return errors.Errorf(`invalid "scheme": %q`, scheme.Scheme)
}
return nil
case "mutualTLS":
return nil
case "oauth2":
return validateOAuthFlows(scopes, s.Flows)
return validateOAuthFlows(scopes, scheme.Flows)
case "openIdConnect":
if _, err := url.ParseRequestURI(s.OpenIDConnectURL); err != nil {
if _, err := url.ParseRequestURI(scheme.OpenIDConnectURL); err != nil {
return errors.Wrap(err, `"openIdConnectUrl" MUST be in the form of a URL`)
}
return nil
default:
return errors.Errorf("unknown security scheme type %q", s.Type)
return errors.Errorf("unknown security scheme type %q", scheme.Type)
}
}(); err != nil {
return nil, errors.Wrap(err, s.Type)
return nil, errors.Wrap(err, scheme.Type)
}

return s, nil
return scheme, nil
}

func validateOAuthFlows(scopes []string, flows *ogen.OAuthFlows) error {
Expand Down
4 changes: 4 additions & 0 deletions openapi/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ func (p *parser) parseOps() error {
}

func (p *parser) parseOp(path, httpMethod string, spec ogen.Operation, itemParams []*openapi.Parameter) (_ *openapi.Operation, err error) {
defer func() {
err = p.wrapLocation(&spec, err)
}()

op := &openapi.Operation{
OperationID: spec.OperationID,
Summary: spec.Summary,
Expand Down

0 comments on commit 236504d

Please sign in to comment.