diff --git a/apidom/packages/apidom-reference/src/resolve/strategies/openapi-3-1/visitor.ts b/apidom/packages/apidom-reference/src/resolve/strategies/openapi-3-1/visitor.ts index 925e8118a1..027b4b4275 100644 --- a/apidom/packages/apidom-reference/src/resolve/strategies/openapi-3-1/visitor.ts +++ b/apidom/packages/apidom-reference/src/resolve/strategies/openapi-3-1/visitor.ts @@ -10,6 +10,7 @@ import { ReferenceElement, PathItemElement, LinkElement, + ExampleElement, SchemaElement, isReferenceElementExternal, isSchemaElementExternal, @@ -164,6 +165,32 @@ const OpenApi3_1ResolveVisitor = stampit({ return undefined; }, + ExampleElement(exampleElement: ExampleElement) { + // ignore ExampleElement without externalValue field + if (!isStringElement(exampleElement.externalValue)) { + return undefined; + } + + // ignore resolving ExampleElement externalValue + if (!this.options.resolve.external && isStringElement(exampleElement.externalValue)) { + return undefined; + } + + // value and externalValue fields are mutually exclusive + if (exampleElement.hasKey('value') && isStringElement(exampleElement.externalValue)) { + throw new Error('ExampleElement value and externalValue fields are mutually exclusive.'); + } + + const uri = exampleElement.externalValue.toValue(); + const baseURI = this.toBaseURI(uri); + + if (!has(baseURI, this.crawlingMap)) { + this.crawlingMap[baseURI] = this.toReference(uri); + } + + return undefined; + }, + SchemaElement(schemaElement: SchemaElement) { /** * Skip traversal for already visited schemas and all their child schemas. diff --git a/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-binary/favicon.ico b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-binary/favicon.ico new file mode 100644 index 0000000000..4ffa222883 Binary files /dev/null and b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-binary/favicon.ico differ diff --git a/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-binary/root.json b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-binary/root.json new file mode 100644 index 0000000000..8179437c62 --- /dev/null +++ b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-binary/root.json @@ -0,0 +1,11 @@ +{ + "openapi": "3.1.0", + "components": { + "examples": { + "example1": { + "description": "example1 description", + "externalValue": "./favicon.ico" + } + } + } +} diff --git a/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-ignore-external/ex.json b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-ignore-external/ex.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-ignore-external/ex.json @@ -0,0 +1 @@ +{} diff --git a/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-ignore-external/root.json b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-ignore-external/root.json new file mode 100644 index 0000000000..2c2d58d639 --- /dev/null +++ b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-ignore-external/root.json @@ -0,0 +1,11 @@ +{ + "openapi": "3.1.0", + "components": { + "examples": { + "example1": { + "description": "example1 description", + "externalValue": "./ex.json" + } + } + } +} diff --git a/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-json/ex.json b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-json/ex.json new file mode 100644 index 0000000000..6fb8855f8a --- /dev/null +++ b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-json/ex.json @@ -0,0 +1,4 @@ +{ + "prop1": "value1", + "prop2": "value2" +} diff --git a/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-json/root.json b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-json/root.json new file mode 100644 index 0000000000..2c2d58d639 --- /dev/null +++ b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-json/root.json @@ -0,0 +1,11 @@ +{ + "openapi": "3.1.0", + "components": { + "examples": { + "example1": { + "description": "example1 description", + "externalValue": "./ex.json" + } + } + } +} diff --git a/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-pointer/ex.json b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-pointer/ex.json new file mode 100644 index 0000000000..6fb8855f8a --- /dev/null +++ b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-pointer/ex.json @@ -0,0 +1,4 @@ +{ + "prop1": "value1", + "prop2": "value2" +} diff --git a/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-pointer/root.json b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-pointer/root.json new file mode 100644 index 0000000000..e5e5883e48 --- /dev/null +++ b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-pointer/root.json @@ -0,0 +1,11 @@ +{ + "openapi": "3.1.0", + "components": { + "examples": { + "example1": { + "description": "example1 description", + "externalValue": "./ex.json#/json/pointer" + } + } + } +} diff --git a/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-text/ex.csv b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-text/ex.csv new file mode 100644 index 0000000000..afd194d0e1 --- /dev/null +++ b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-text/ex.csv @@ -0,0 +1 @@ +val1;val2;val3 diff --git a/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-text/root.json b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-text/root.json new file mode 100644 index 0000000000..fb347a7767 --- /dev/null +++ b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-text/root.json @@ -0,0 +1,11 @@ +{ + "openapi": "3.1.0", + "components": { + "examples": { + "example1": { + "description": "example1 description", + "externalValue": "./ex.csv" + } + } + } +} diff --git a/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-unresolvable/root.json b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-unresolvable/root.json new file mode 100644 index 0000000000..2c2d58d639 --- /dev/null +++ b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-unresolvable/root.json @@ -0,0 +1,11 @@ +{ + "openapi": "3.1.0", + "components": { + "examples": { + "example1": { + "description": "example1 description", + "externalValue": "./ex.json" + } + } + } +} diff --git a/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-value-both-defined/ex.json b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-value-both-defined/ex.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-value-both-defined/ex.json @@ -0,0 +1 @@ +{} diff --git a/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-value-both-defined/root.json b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-value-both-defined/root.json new file mode 100644 index 0000000000..a63d5e606f --- /dev/null +++ b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-value-both-defined/root.json @@ -0,0 +1,12 @@ +{ + "openapi": "3.1.0", + "components": { + "examples": { + "example1": { + "description": "example1 description", + "value": "sample value", + "externalValue": "./ex.json" + } + } + } +} diff --git a/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-yaml/ex.yaml b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-yaml/ex.yaml new file mode 100644 index 0000000000..95d4e79f9c --- /dev/null +++ b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-yaml/ex.yaml @@ -0,0 +1,3 @@ +--- +prop1: value1 +prop2: value2 diff --git a/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-yaml/root.json b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-yaml/root.json new file mode 100644 index 0000000000..54aa18bb2f --- /dev/null +++ b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/fixtures/external-value-yaml/root.json @@ -0,0 +1,11 @@ +{ + "openapi": "3.1.0", + "components": { + "examples": { + "example1": { + "description": "example1 description", + "externalValue": "./ex.yaml" + } + } + } +} diff --git a/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/index.ts b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/index.ts new file mode 100644 index 0000000000..5fcaf5c044 --- /dev/null +++ b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/example-object/index.ts @@ -0,0 +1,134 @@ +import path from 'path'; +import { assert } from 'chai'; + +import { resolve } from '../../../../../src'; +import { ResolverError } from '../../../../../src/util/errors'; + +const rootFixturePath = path.join(__dirname, 'fixtures'); + +describe('resolve', function () { + context('strategies', function () { + context('openapi-3-1', function () { + context('Example Object', function () { + context('given externalValue field', function () { + context('and pointing to a JSON file', function () { + const fixturePath = path.join(rootFixturePath, 'external-value-json'); + + specify('should resolve', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const refSet = await resolve(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + }); + + assert.strictEqual(refSet.size, 2); + }); + }); + + context('and pointing to a JSON file and having JSON Pointer', function () { + const fixturePath = path.join(rootFixturePath, 'external-value-pointer'); + + specify('should resolve', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const refSet = await resolve(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + }); + + assert.strictEqual(refSet.size, 2); + }); + }); + + context('and pointing to a YAML file', function () { + const fixturePath = path.join(rootFixturePath, 'external-value-yaml'); + + specify('should resolve', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const refSet = await resolve(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + }); + + assert.strictEqual(refSet.size, 2); + }); + }); + + context('and pointing to a text file', function () { + const fixturePath = path.join(rootFixturePath, 'external-value-text'); + + specify('should resolve', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const refSet = await resolve(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + }); + + assert.strictEqual(refSet.size, 2); + }); + }); + + context('and pointing to a binary file', function () { + const fixturePath = path.join(rootFixturePath, 'external-value-binary'); + + specify('should resolve', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const refSet = await resolve(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + }); + + assert.strictEqual(refSet.size, 2); + }); + }); + + context('and with unresolvable URI', function () { + const fixturePath = path.join(rootFixturePath, 'external-value-unresolvable'); + + specify('should throw error', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + + try { + await resolve(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + }); + assert.fail('should throw ResolverError'); + } catch (e) { + assert.instanceOf(e, ResolverError); + } + }); + }); + + context('with external resolution disabled', function () { + const fixturePath = path.join(rootFixturePath, 'external-value-ignore-external'); + + specify('should not resolve', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const refSet = await resolve(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + resolve: { external: false }, + }); + + assert.strictEqual(refSet.size, 1); + }); + }); + + context('given both value and externalValue fields are defined', function () { + const fixturePath = path.join(rootFixturePath, 'external-value-value-both-defined'); + + specify('should throw error', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + + try { + await resolve(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + }); + assert.fail('should throw ResolverError'); + } catch (e) { + assert.strictEqual( + e.cause.cause.message, + 'ExampleElement value and externalValue fields are mutually exclusive.', + ); + assert.instanceOf(e, ResolverError); + } + }); + }); + }); + }); + }); + }); +});