Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion internal/provider/libopenapi/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -660,9 +660,11 @@ func (c *converter) rebuild(p *base.SchemaProxy, ptr string) *base.Schema {
lp := p.GoLow()
ctx, node, idx := lp.GetContext(), lp.GetValueNode(), lp.GetIndex()
if isRef, _, _ := utils.IsNodeRefValue(node); isRef {
// A spec with a $ref libopenapi cannot find has no model, so the target is there.
node, idx, _, ctx = lowmodel.LocateRefNodeWithContext(ctx, node, idx)
}
if node == nil {
return nil
}

kept := keywords{c: c, node: utils.NodeAlias(node), ptr: ptr}.buildable()
ls := &lowbase.Schema{}
Expand Down
16 changes: 8 additions & 8 deletions internal/provider/libopenapi/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (

"github.com/pb33f/libopenapi"
"github.com/pb33f/libopenapi/datamodel"
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
lowv3 "github.com/pb33f/libopenapi/datamodel/low/v3"

"github.com/mockzilla/mockzilla-codegen/internal/diag"
"github.com/mockzilla/mockzilla-codegen/internal/provider"
Expand Down Expand Up @@ -66,7 +68,8 @@ func (p *Provider) Parse(ctx context.Context, data []byte, opts provider.ParseOp
return nil, nil, err
}

d, err := libopenapi.NewDocumentWithConfiguration(data, p.config())
cfg := p.config()
d, err := libopenapi.NewDocumentWithConfiguration(data, cfg)
if err != nil {
return nil, nil, fmt.Errorf("%w: %s: %w", provider.ErrParse, syntaxAt(opts.File, data, err), err)
}
Expand All @@ -75,14 +78,11 @@ func (p *Provider) Parse(ctx context.Context, data []byte, opts provider.ParseOp
return nil, nil, fmt.Errorf("%w: %s", err, opts.File)
}

model, buildErr := d.BuildV3Model()
if model == nil {
return nil, nil, fmt.Errorf("%w: %s: %w", provider.ErrParse, opts.File, buildErr)
}

// BuildV3Model drops the whole model for one ref it cannot follow, like one inside an example value.
low, buildErr := lowv3.CreateDocumentFromConfig(d.GetSpecInfo(), cfg)
c := newConverter(version, opts)
c.buildIssues(buildErr, model.Index.GetCircularReferences())
doc = c.document(&model.Model)
c.buildIssues(buildErr, low.Index.GetCircularReferences())
doc = c.document(v3.NewDocument(low))
return doc, c.diags.List(), nil
}

Expand Down
66 changes: 61 additions & 5 deletions internal/provider/libopenapi/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,6 @@ func TestParseErrors(t *testing.T) {
},
{name: "Not an OpenAPI document", src: "a: b\n", wantErr: provider.ErrParse},
{name: "Swagger 2.0", src: "swagger: '2.0'\ninfo: {title: t, version: '1'}\npaths: {}\n", wantErr: provider.ErrUnsupportedVersion},
{
name: "Ref to nothing",
src: minimalSpec + "components:\n schemas:\n A: {$ref: '#/components/schemas/Missing'}\n",
wantErr: provider.ErrParse,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand All @@ -100,6 +95,67 @@ func TestParseErrors(t *testing.T) {
}
}

func TestParseUnresolvedRefs(t *testing.T) {
t.Parallel()

file := diag.Origin{File: "spec.yaml"}
tests := []struct {
name string
src string
wantDiags []diag.Diagnostic
}{
{
name: "Ref inside an example value",
src: "openapi: 3.1.0\ninfo: {title: t, version: '1'}\n" +
"paths:\n /a:\n get:\n responses:\n '200':\n description: ok\n" +
" content:\n application/json:\n" +
" examples:\n many: {$ref: '#/components/examples/Many'}\n" +
"components:\n examples:\n One:\n value: {id: 1}\n" +
" Many:\n value: {$ref: '#/components/examples/One/value'}\n",
wantDiags: []diag.Diagnostic{{
Severity: diag.Warning,
Code: diag.CodeBuildIssue,
Origin: file,
Message: "cannot resolve reference `#/components/examples/One/value`, it's missing: $.components.examples['One'].value [18:15]",
}},
},
{
name: "Ref to nothing",
src: minimalSpec + "components:\n schemas:\n A: {$ref: '#/components/schemas/Missing'}\n",
wantDiags: []diag.Diagnostic{
{
Severity: diag.Warning,
Code: diag.CodeBuildIssue,
Origin: file,
Message: "cannot resolve reference `#/components/schemas/Missing`, it's missing: $.components.schemas['Missing'] [6:9]",
},
{
Severity: diag.Warning,
Code: diag.CodeBuildIssue,
Origin: file,
Message: "component `#/components/schemas/Missing` does not exist in the specification",
},
{
Severity: diag.Error,
Code: diag.CodeSchemaBuild,
Pointer: "/components/schemas/Missing",
Origin: diag.Origin{File: "spec.yaml", Line: 6, Col: 8},
Message: "schema cannot be built: build schema failed: reference cannot be found: '#/components/schemas/Missing'",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

_, diags, err := New().Parse(context.Background(), []byte(tt.src), provider.ParseOptions{File: "spec.yaml"})
require.NoError(t, err)
assert.Equal(t, tt.wantDiags, diags)
})
}
}

func TestSyntaxAt(t *testing.T) {
t.Parallel()

Expand Down
Loading