Skip to content

Commit

Permalink
refactor(jsonpointer): add a way to set root spec URL
Browse files Browse the repository at this point in the history
  • Loading branch information
tdakkota committed Oct 28, 2022
1 parent 347fc29 commit 89d1eac
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 73 deletions.
31 changes: 19 additions & 12 deletions internal/jsonpointer/resolve_ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ type ResolveCtx struct {
// "#/definitions/SchemaProperty" should be resolved against "https://example.com/schema".
locstack []string
files []location.File
// root is root location.
root *url.URL
// Store references to detect infinite recursive references.
refs map[RefKey]struct{}
depthLimit int
Expand All @@ -49,13 +51,15 @@ const DefaultDepthLimit = 1000

// DefaultCtx creates new ResolveCtx with default depth limit.
func DefaultCtx() *ResolveCtx {
return NewResolveCtx(DefaultDepthLimit)
return NewResolveCtx(nil, DefaultDepthLimit)
}

// NewResolveCtx creates new ResolveCtx.
func NewResolveCtx(depthLimit int) *ResolveCtx {
func NewResolveCtx(root *url.URL, depthLimit int) *ResolveCtx {
return &ResolveCtx{
locstack: nil,
files: nil,
root: root,
refs: map[RefKey]struct{}{},
depthLimit: depthLimit,
}
Expand All @@ -64,8 +68,11 @@ func NewResolveCtx(depthLimit int) *ResolveCtx {
// Key creates new reference key.
func (r *ResolveCtx) Key(ref string) (key RefKey, _ error) {
parser := url.Parse
if loc := r.LastLoc(); loc != "" {
base, err := url.Parse(loc)
if r.root != nil {
parser = r.root.Parse
}
if s := r.locstack; len(s) > 0 {
base, err := url.Parse(s[len(s)-1])
if err != nil {
return key, err
}
Expand All @@ -74,15 +81,19 @@ func (r *ResolveCtx) Key(ref string) (key RefKey, _ error) {
if err != nil {
return nil, err
}
u.Path = strings.TrimPrefix(u.Path, "/")
return u, nil
}
} else if strings.HasPrefix(ref, "#") {
return RefKey{
Ref: ref,
}, nil
}

u, err := parser(ref)
if err != nil {
return RefKey{}, err
}
u.Path = strings.TrimPrefix(u.Path, "/")
key.FromURL(u)
return key, nil
}
Expand Down Expand Up @@ -114,13 +125,9 @@ func (r *ResolveCtx) Delete(key RefKey) {
}
}

// LastLoc returns last location from stack.
func (r *ResolveCtx) LastLoc() string {
s := r.locstack
if len(s) == 0 {
return ""
}
return s[len(s)-1]
// IsRoot returns true if location stack is empty.
func (r *ResolveCtx) IsRoot() bool {
return len(r.locstack) == 0
}

// File returns last file from stack.
Expand Down
2 changes: 1 addition & 1 deletion jsonschema/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func (p *Parser) getResolver(loc string) (r resolver, rerr error) {

raw, err := p.external.Get(context.TODO(), loc)
if err != nil {
return r, errors.Wrap(err, "get")
return r, errors.Wrapf(err, "get %q", loc)
}

file := location.NewFile(loc, loc, raw)
Expand Down
2 changes: 1 addition & 1 deletion jsonschema/resolve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func TestLimitDepth(t *testing.T) {
parser := NewParser(Settings{
Resolver: root,
})
_, err := parser.Resolve("#/components/schemas/Schema1", jsonpointer.NewResolveCtx(tt.limit))
_, err := parser.Resolve("#/components/schemas/Schema1", jsonpointer.NewResolveCtx(nil, tt.limit))
tt.checker(t, err, "limit: %d", tt.limit)
}
}
10 changes: 10 additions & 0 deletions openapi/parser/_testdata/remotes/api/response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"description": "User info",
"content": {
"application/json": {
"schema": {
"$ref": "../schemas.json#/definitions/User"
}
}
}
}
18 changes: 18 additions & 0 deletions openapi/parser/_testdata/remotes/api/spec.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"openapi": "3.0.3",
"info": {
"title": "title",
"version": "v0.1.0"
},
"paths": {
"/get": {
"get": {
"responses": {
"200": {
"$ref": "response.json#"
}
}
}
}
}
}
17 changes: 17 additions & 0 deletions openapi/parser/_testdata/remotes/schemas.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"definitions": {
"User": {
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"age": {
"type": "integer",
"minimum": 0
}
}
}
}
}
11 changes: 5 additions & 6 deletions openapi/parser/parse_components.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"github.com/go-faster/errors"

"github.com/ogen-go/ogen"
"github.com/ogen-go/ogen/internal/jsonpointer"
"github.com/ogen-go/ogen/internal/location"
"github.com/ogen-go/ogen/jsonschema"
"github.com/ogen-go/ogen/openapi"
Expand Down Expand Up @@ -114,7 +113,7 @@ func (p *parser) parseComponents(c *ogen.Components) (_ *openapi.Components, rer

for name := range c.Schemas {
ref := "#/components/schemas/" + name
s, err := p.schemaParser.Resolve(ref, jsonpointer.NewResolveCtx(p.depthLimit))
s, err := p.schemaParser.Resolve(ref, p.resolveCtx())
if err != nil {
return nil, wrapErr("schemas", name, err)
}
Expand All @@ -124,7 +123,7 @@ func (p *parser) parseComponents(c *ogen.Components) (_ *openapi.Components, rer

for name := range c.Responses {
ref := "#/components/responses/" + name
r, err := p.resolveResponse(ref, jsonpointer.NewResolveCtx(p.depthLimit))
r, err := p.resolveResponse(ref, p.resolveCtx())
if err != nil {
return nil, wrapErr("responses", name, err)
}
Expand All @@ -134,7 +133,7 @@ func (p *parser) parseComponents(c *ogen.Components) (_ *openapi.Components, rer

for name := range c.Parameters {
ref := "#/components/parameters/" + name
pp, err := p.resolveParameter(ref, jsonpointer.NewResolveCtx(p.depthLimit))
pp, err := p.resolveParameter(ref, p.resolveCtx())
if err != nil {
return nil, wrapErr("parameters", name, err)
}
Expand All @@ -144,7 +143,7 @@ func (p *parser) parseComponents(c *ogen.Components) (_ *openapi.Components, rer

for name := range c.Examples {
ref := "#/components/examples/" + name
ex, err := p.resolveExample(ref, jsonpointer.NewResolveCtx(p.depthLimit))
ex, err := p.resolveExample(ref, p.resolveCtx())
if err != nil {
return nil, wrapErr("examples", name, err)
}
Expand All @@ -154,7 +153,7 @@ func (p *parser) parseComponents(c *ogen.Components) (_ *openapi.Components, rer

for name := range c.RequestBodies {
ref := "#/components/requestBodies/" + name
b, err := p.resolveRequestBody(ref, jsonpointer.NewResolveCtx(p.depthLimit))
b, err := p.resolveRequestBody(ref, p.resolveCtx())
if err != nil {
return nil, wrapErr("requestBodies", name, err)
}
Expand Down
2 changes: 1 addition & 1 deletion openapi/parser/parse_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func (p *parser) parseWebhooks(webhooks map[string]*ogen.PathItem) (r []openapi.
}
var (
locator = p.rootLoc.Field("webhooks")
ctx = jsonpointer.NewResolveCtx(p.depthLimit)
ctx = p.resolveCtx()
)
defer func() {
rerr = p.wrapLocation(ctx.File(), locator, rerr)
Expand Down
13 changes: 8 additions & 5 deletions openapi/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
package parser

import (
"net/url"

"github.com/go-faster/errors"
"golang.org/x/exp/maps"

Expand Down Expand Up @@ -47,6 +49,7 @@ type parser struct {
operationIDs map[string]struct{}

external jsonschema.ExternalResolver
rootURL *url.URL
schemas map[string]resolver
depthLimit int
file location.File // optional, used for error messages
Expand All @@ -63,9 +66,7 @@ func Parse(spec *ogen.Spec, s Settings) (_ *openapi.API, rerr error) {

s.setDefaults()
p := &parser{
spec: spec,
operations: nil,
operationIDs: map[string]struct{}{},
spec: spec,
refs: struct {
requestBodies map[refKey]*openapi.RequestBody
responses map[refKey]*openapi.Response
Expand All @@ -84,7 +85,9 @@ func Parse(spec *ogen.Spec, s Settings) (_ *openapi.API, rerr error) {
pathItems: map[refKey]pathItem{},
},
securitySchemes: maps.Clone(spec.Components.SecuritySchemes),
operationIDs: map[string]struct{}{},
external: s.External,
rootURL: s.RootURL,
schemas: map[string]resolver{
"": {
node: spec.Raw,
Expand Down Expand Up @@ -125,7 +128,7 @@ func Parse(spec *ogen.Spec, s Settings) (_ *openapi.API, rerr error) {
return nil, errors.Wrap(err, "parse path items")
}

servers, err := p.parseServers(p.spec.Servers, jsonpointer.NewResolveCtx(p.depthLimit))
servers, err := p.parseServers(p.spec.Servers, p.resolveCtx())
if err != nil {
return nil, errors.Wrap(err, "parse servers")
}
Expand Down Expand Up @@ -175,7 +178,7 @@ func (p *parser) parsePathItems() error {
return p.wrapLocation(p.file, pathsLoc.Key(path), err)
}

ops, err := p.parsePathItem(path, item, jsonpointer.NewResolveCtx(p.depthLimit))
ops, err := p.parsePathItem(path, item, p.resolveCtx())
if err != nil {
err := errors.Wrapf(err, "path %q", path)
return p.wrapLocation(p.file, pathsLoc.Field(path), err)
Expand Down
8 changes: 6 additions & 2 deletions openapi/parser/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func (p *parser) getResolver(loc string) (r resolver, rerr error) {

raw, err := p.external.Get(context.TODO(), loc)
if err != nil {
return r, errors.Wrap(err, "get")
return r, errors.Wrapf(err, "get %q", loc)
}

file := location.NewFile(loc, loc, raw)
Expand Down Expand Up @@ -93,7 +93,7 @@ func resolveComponent[Raw, Target any](

file := p.file
var raw Raw
if key.Loc == "" && ctx.LastLoc() == "" {
if key.Loc == "" && ctx.IsRoot() {
name := strings.TrimPrefix(ref, cr.prefix)
c, found := cr.components[name]
if found {
Expand Down Expand Up @@ -130,6 +130,10 @@ func resolveComponent[Raw, Target any](
return r, false, nil
}

func (p *parser) resolveCtx() *jsonpointer.ResolveCtx {
return jsonpointer.NewResolveCtx(p.rootURL, p.depthLimit)
}

func (p *parser) resolveRequestBody(ref string, ctx *jsonpointer.ResolveCtx) (*openapi.RequestBody, error) {
const prefix = "#/components/requestBodies/"
c, cached, err := resolveComponent(p, componentResolve[*ogen.RequestBody, *openapi.RequestBody]{
Expand Down
Loading

0 comments on commit 89d1eac

Please sign in to comment.