diff --git a/.changeset/little-pears-sniff.md b/.changeset/little-pears-sniff.md new file mode 100644 index 000000000..335f6e493 --- /dev/null +++ b/.changeset/little-pears-sniff.md @@ -0,0 +1,6 @@ +--- +"@redocly/openapi-core": patch +"@redocly/cli": patch +--- + +Fixed an issue where the bundle command did not resolve links in `externalValue`. diff --git a/__tests__/bundle/bundle-external-value/external-value.json b/__tests__/bundle/bundle-external-value/external-value.json new file mode 100644 index 000000000..b5b7bd90b --- /dev/null +++ b/__tests__/bundle/bundle-external-value/external-value.json @@ -0,0 +1,4 @@ +{ + "foo": "bar", + "key": "value" +} diff --git a/__tests__/bundle/bundle-external-value/redocly.yaml b/__tests__/bundle/bundle-external-value/redocly.yaml new file mode 100644 index 000000000..4eb5e2747 --- /dev/null +++ b/__tests__/bundle/bundle-external-value/redocly.yaml @@ -0,0 +1,8 @@ +apis: + first: + root: ./test-success.yaml + second: + root: ./test-wrong-examples.yaml + +extends: + - recommended diff --git a/__tests__/bundle/bundle-external-value/snapshot.js b/__tests__/bundle/bundle-external-value/snapshot.js new file mode 100644 index 000000000..d34f50ee1 --- /dev/null +++ b/__tests__/bundle/bundle-external-value/snapshot.js @@ -0,0 +1,59 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`E2E bundle bundle-external-value 1`] = ` +openapi: 3.1.0 +info: + version: 1.0.0 + title: Example.com + termsOfService: https://example.com/terms/ + contact: + email: contact@example.com + url: http://example.com/contact + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + description: OpenAPI description with external example +security: [] +paths: + /: + post: + summary: Test request externalValue with relative reference in examples + requestBody: + content: + application/xml: + schema: + type: object + examples: + test-resolved: + summary: Example should resolved to value + value: + foo: bar + key: value + test-no-resolved: + summary: Example shouldn't be resolved to value + value: + type: object + externalValue: ./external-value.json +components: {} + +bundling ./test-success.yaml... +📦 Created a bundle for ./test-success.yaml at stdout ms. +bundling ./test-wrong-examples.yaml... +[1] test-wrong-examples.yaml:27:17 at #/paths/~1/post/requestBody/content/application~1xml/examples/test-wrong-ref + +Can't resolve $ref: ENOENT: no such file or directory './__tests__/bundle/bundle-external-value/external-value-bad-path.json' + +25 | examples: +26 | test-wrong-ref: +27 | summary: Example shouldn't resolved to value + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +28 | externalValue: './external-value-bad-path.json' + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +29 | + +Error was generated by the bundler rule. + + +❌ Errors encountered while bundling ./test-wrong-examples.yaml: bundle not created (use --force to ignore errors). + +`; diff --git a/__tests__/bundle/bundle-external-value/test-success.yaml b/__tests__/bundle/bundle-external-value/test-success.yaml new file mode 100644 index 000000000..63a523628 --- /dev/null +++ b/__tests__/bundle/bundle-external-value/test-success.yaml @@ -0,0 +1,33 @@ +openapi: 3.1.0 +security: [] +info: + version: 1.0.0 + title: Example.com + termsOfService: https://example.com/terms/ + contact: + email: contact@example.com + url: http://example.com/contact + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + description: OpenAPI description with external example +components: {} + +paths: + /: + post: + summary: Test request externalValue with relative reference in examples + requestBody: + content: + application/xml: + schema: + type: object + examples: + test-resolved: + summary: Example should resolved to value + externalValue: './external-value.json' + test-no-resolved: + summary: Example shouldn't be resolved to value + value: + type: object + externalValue: './external-value.json' diff --git a/__tests__/bundle/bundle-external-value/test-wrong-examples.yaml b/__tests__/bundle/bundle-external-value/test-wrong-examples.yaml new file mode 100644 index 000000000..b6a355eaa --- /dev/null +++ b/__tests__/bundle/bundle-external-value/test-wrong-examples.yaml @@ -0,0 +1,28 @@ +openapi: 3.1.0 +security: [] +info: + version: 1.0.0 + title: Example.com + termsOfService: https://example.com/terms/ + contact: + email: contact@example.com + url: http://example.com/contact + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + description: OpenAPI description with external example +components: {} + +paths: + /: + post: + summary: Test request externalValue with relative reference in examples + requestBody: + content: + application/xml: + schema: + type: object + examples: + test-wrong-ref: + summary: Example shouldn't resolved to value + externalValue: './external-value-bad-path.json' diff --git a/packages/core/src/bundle.ts b/packages/core/src/bundle.ts index be58ab1d1..970df3f92 100755 --- a/packages/core/src/bundle.ts +++ b/packages/core/src/bundle.ts @@ -379,6 +379,25 @@ function makeBundleVisitor( } }, }, + Example: { + leave(node: any, ctx: UserContext) { + if (node.externalValue && node.value === undefined) { + const resolved = ctx.resolve({ $ref: node.externalValue }); + + if (!resolved.location || resolved.node === undefined) { + reportUnresolvedRef(resolved, ctx.report, ctx.location); + return; + } + + if (keepUrlRefs && isAbsoluteUrl(node.externalValue)) { + return; + } + + node.value = ctx.resolve({ $ref: node.externalValue }).node; + delete node.externalValue; + } + }, + }, Root: { enter(root: any, ctx: any) { rootLocation = ctx.location; diff --git a/packages/core/src/resolve.ts b/packages/core/src/resolve.ts index fd55c85c0..1ca83a58b 100644 --- a/packages/core/src/resolve.ts +++ b/packages/core/src/resolve.ts @@ -335,6 +335,28 @@ export async function resolveDocument(opts: { }); resolvePromises.push(promise); } + + // handle example.externalValue as reference + if (node.externalValue) { + const promise = followRef( + rootNodeDocument, + { $ref: node.externalValue }, + { + prev: null, + node, + } + ).then((resolvedRef) => { + if (resolvedRef.resolved) { + resolveRefsInParallel( + resolvedRef.node, + resolvedRef.document, + resolvedRef.nodePointer!, + type + ); + } + }); + resolvePromises.push(promise); + } } async function followRef( diff --git a/packages/core/src/rules/oas3/__tests__/fixtures/external-value.yaml b/packages/core/src/rules/oas3/__tests__/fixtures/external-value.yaml new file mode 100644 index 000000000..e70d46b91 --- /dev/null +++ b/packages/core/src/rules/oas3/__tests__/fixtures/external-value.yaml @@ -0,0 +1,2 @@ +a: foo +b: 100 diff --git a/packages/core/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts b/packages/core/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts index fd09a7feb..1020916bf 100644 --- a/packages/core/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts +++ b/packages/core/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts @@ -553,7 +553,7 @@ describe('no-invalid-media-type-examples', () => { type: number examples: first: - externalValue: "https://example.com/example.json" + externalValue: ./fixtures/external-value.yaml `, 'foobar.yaml' );