diff --git a/apidom/packages/apidom-ns-openapi-3-1/src/index.ts b/apidom/packages/apidom-ns-openapi-3-1/src/index.ts index 2716ccd345..3562bbbe3d 100644 --- a/apidom/packages/apidom-ns-openapi-3-1/src/index.ts +++ b/apidom/packages/apidom-ns-openapi-3-1/src/index.ts @@ -28,6 +28,7 @@ export { isOperationElement, isParameterElement, isPathItemElement, + isPathItemElementExternal, isPathsElement, isReferenceElement, isReferenceElementExternal, diff --git a/apidom/packages/apidom-ns-openapi-3-1/src/predicates.ts b/apidom/packages/apidom-ns-openapi-3-1/src/predicates.ts index a7c5929513..50da8cfdda 100644 --- a/apidom/packages/apidom-ns-openapi-3-1/src/predicates.ts +++ b/apidom/packages/apidom-ns-openapi-3-1/src/predicates.ts @@ -183,6 +183,19 @@ export const isPathItemElement = createPredicate( }, ); +export const isPathItemElementExternal = (element: any): element is PathItemElement => { + if (!isPathItemElement(element)) { + return false; + } + if (!isStringElement(element.$ref)) { + return false; + } + + const value = element.$ref.toValue(); + + return isNonEmptyString(value) && !startsWith('#', value); +}; + export const isPathsElement = createPredicate( ({ hasBasicElementProps, isElementType, primitiveEq }) => { const isElementTypePaths = isElementType('paths'); diff --git a/apidom/packages/apidom-ns-openapi-3-1/src/refractor/predicates.ts b/apidom/packages/apidom-ns-openapi-3-1/src/refractor/predicates.ts index cd403afb88..d806971e16 100644 --- a/apidom/packages/apidom-ns-openapi-3-1/src/refractor/predicates.ts +++ b/apidom/packages/apidom-ns-openapi-3-1/src/refractor/predicates.ts @@ -1,5 +1,5 @@ import { MemberElement, isStringElement, isObjectElement, Element } from 'apidom'; -import { startsWith, all } from 'ramda'; +import { startsWith } from 'ramda'; export const isOpenApi3_1LikeElement = (element: T): boolean => { // @ts-ignore @@ -12,20 +12,8 @@ export const isParameterLikeElement = (element: T): boolean = }; export const isReferenceLikeElement = (element: T): boolean => { - const isAllowedProperty = (property: string): boolean => { - // @ts-ignore - return ['$ref', 'description', 'summary'].includes(property); - }; - - return ( - isObjectElement(element) && - // @ts-ignore - element.hasKey('$ref') && - // @ts-ignore - element.keys.length <= 3 && - // @ts-ignore - all(isAllowedProperty)(element.keys) - ); + // @ts-ignore + return isObjectElement(element) && element.hasKey('$ref'); }; export const isRequestBodyLikeElement = (element: T): boolean => { diff --git a/apidom/packages/apidom-ns-openapi-3-1/test/refractor/elements/Components/__snapshots__/index.ts.snap b/apidom/packages/apidom-ns-openapi-3-1/test/refractor/elements/Components/__snapshots__/index.ts.snap index 44660a3124..d398391746 100644 --- a/apidom/packages/apidom-ns-openapi-3-1/test/refractor/elements/Components/__snapshots__/index.ts.snap +++ b/apidom/packages/apidom-ns-openapi-3-1/test/refractor/elements/Components/__snapshots__/index.ts.snap @@ -115,5 +115,14 @@ exports[`refractor elements ComponentsElement should refract to semantic ApiDOM (ReferenceElement (MemberElement (StringElement) - (StringElement))))))) + (StringElement)))) + (MemberElement + (StringElement) + (ReferenceElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (ObjectElement))))))) `; diff --git a/apidom/packages/apidom-ns-openapi-3-1/test/refractor/elements/Components/index.ts b/apidom/packages/apidom-ns-openapi-3-1/test/refractor/elements/Components/index.ts index e65e7ca992..0665d32208 100644 --- a/apidom/packages/apidom-ns-openapi-3-1/test/refractor/elements/Components/index.ts +++ b/apidom/packages/apidom-ns-openapi-3-1/test/refractor/elements/Components/index.ts @@ -46,6 +46,10 @@ describe('refractor', function () { pathItems: { PathItem1: {}, PathItem2: { $ref: '#/components/pathsItems/PathItem1' }, + PathItem3: { + $ref: '#/components/pathsItems/PathItem1', + get: {}, + }, }, }); diff --git a/apidom/packages/apidom-reference/src/dereference/strategies/openapi-3-1/visitor.ts b/apidom/packages/apidom-reference/src/dereference/strategies/openapi-3-1/visitor.ts index 4cad4c0818..60a1464ad1 100644 --- a/apidom/packages/apidom-reference/src/dereference/strategies/openapi-3-1/visitor.ts +++ b/apidom/packages/apidom-reference/src/dereference/strategies/openapi-3-1/visitor.ts @@ -7,8 +7,10 @@ import { isReferenceLikeElement, keyMap, ReferenceElement, + PathItemElement, SchemaElement, isReferenceElementExternal, + isPathItemElementExternal, isSchemaElementExternal, } from 'apidom-ns-openapi-3-1'; @@ -161,6 +163,81 @@ const OpenApi3_1DereferenceVisitor = stampit({ return fragment; }, + async PathItemElement(pathItemElement: PathItemElement) { + // ignore PathItemElement without $ref field + if (!isStringElement(pathItemElement.$ref)) { + return undefined; + } + + // ignore resolving external Reference Objects + if (!this.options.resolve.external && isPathItemElementExternal(pathItemElement)) { + return undefined; + } + + // @ts-ignore + const reference = await this.toReference(pathItemElement.$ref.toValue()); + + this.indirections.push(pathItemElement); + + const jsonPointer = uriToPointer(pathItemElement.$ref.toValue()); + + // possibly non-semantic fragment + let referencedElement = jsonPointerEvaluate(jsonPointer, reference.value.result); + + // applying semantics to a fragment + if (isPrimitiveElement(referencedElement)) { + referencedElement = PathItemElement.refract(referencedElement); + } + + // detect direct or indirect reference + if (this.indirections.includes(referencedElement)) { + throw new Error('Recursive JSON Pointer detected'); + } + + // detect maximum depth of dereferencing + if (this.indirections.length > this.options.dereference.maxDepth) { + throw new MaximumDereferenceDepthError( + `Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`, + ); + } + + // dive deep into the fragment + const visitor: any = OpenApi3_1DereferenceVisitor({ + reference, + namespace: this.namespace, + indirections: [...this.indirections], + options: this.options, + }); + referencedElement = await visitAsync(referencedElement, visitor, { + keyMap, + nodeTypeGetter: getNodeType, + }); + + this.indirections.pop(); + + // merge fields from referenced Path Item with referencing one + const mergedResult = new PathItemElement( + // @ts-ignore + [...referencedElement.content], + referencedElement.meta.clone(), + referencedElement.attributes.clone(), + ); + // existing keywords from referencing PathItemElement overrides ones from referenced schema + pathItemElement.forEach((value: Element, key: Element, item: Element) => { + mergedResult.remove(key.toValue()); + mergedResult.content.push(item); + }); + mergedResult.remove('$ref'); + + // annotate referencing element with info about original referenced element + mergedResult.setMetaProperty('ref-fields', { + $ref: pathItemElement.$ref?.toValue(), + }); + + // transclude referencing element with merged referenced element + return mergedResult; + }, + async SchemaElement(referencingElement: SchemaElement) { /** * Skip traversal for already visited schemas and all their child schemas. diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/additional-fields/dereferenced.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/additional-fields/dereferenced.json new file mode 100644 index 0000000000..3d0e6b4ed1 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/additional-fields/dereferenced.json @@ -0,0 +1,17 @@ +[ + { + "openapi": "3.1.0", + "paths": { + "/path1": { + "summary": "path1 item summary", + "description": "path item description", + "get": {} + }, + "/path2": { + "summary": "path2 item summary", + "description": "path item description", + "get": {} + } + } + } +] diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/additional-fields/root.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/additional-fields/root.json new file mode 100644 index 0000000000..a49cbf0e5f --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/additional-fields/root.json @@ -0,0 +1,14 @@ +{ + "openapi": "3.1.0", + "paths": { + "/path1": { + "$ref": "#/paths/~1path2", + "summary": "path1 item summary" + }, + "/path2": { + "summary": "path2 item summary", + "description": "path item description", + "get": {} + } + } +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/direct-external-circular/ex.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/direct-external-circular/ex.json new file mode 100644 index 0000000000..4d55709987 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/direct-external-circular/ex.json @@ -0,0 +1,3 @@ +{ + "$ref": "./root.json#/paths/~1path1" +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/direct-external-circular/root.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/direct-external-circular/root.json new file mode 100644 index 0000000000..7850ce66b6 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/direct-external-circular/root.json @@ -0,0 +1,8 @@ +{ + "openapi": "3.1.0", + "paths": { + "/path1": { + "$ref": "./ex.json" + } + } +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/direct-internal-circular/root.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/direct-internal-circular/root.json new file mode 100644 index 0000000000..c727e272f0 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/direct-internal-circular/root.json @@ -0,0 +1,11 @@ +{ + "openapi": "3.1.0", + "paths": { + "/path1": { + "$ref": "#/paths/~1path2" + }, + "/path2": { + "$ref": "#/paths/~1path1" + } + } +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/external-indirections/dereferenced.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/external-indirections/dereferenced.json new file mode 100644 index 0000000000..8f02538ef6 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/external-indirections/dereferenced.json @@ -0,0 +1,12 @@ +[ + { + "openapi": "3.1.0", + "paths": { + "/path1": { + "summary": "path item summary", + "description": "path item description", + "get": {} + } + } + } +] diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/external-indirections/ex1.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/external-indirections/ex1.json new file mode 100644 index 0000000000..d7b3e3be55 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/external-indirections/ex1.json @@ -0,0 +1,3 @@ +{ + "$ref": "./ex2.json#/~1path3" +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/external-indirections/ex2.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/external-indirections/ex2.json new file mode 100644 index 0000000000..e6d9558407 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/external-indirections/ex2.json @@ -0,0 +1,7 @@ +{ + "/path3": { + "summary": "path item summary", + "description": "path item description", + "get": {} + } +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/external-indirections/root.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/external-indirections/root.json new file mode 100644 index 0000000000..5557004334 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/external-indirections/root.json @@ -0,0 +1,8 @@ +{ + "openapi": "3.1.0", + "paths": { + "/path1": { + "$ref": "./ex1.json" + } + } +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/external-only/dereferenced.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/external-only/dereferenced.json new file mode 100644 index 0000000000..8f02538ef6 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/external-only/dereferenced.json @@ -0,0 +1,12 @@ +[ + { + "openapi": "3.1.0", + "paths": { + "/path1": { + "summary": "path item summary", + "description": "path item description", + "get": {} + } + } + } +] diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/external-only/ex.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/external-only/ex.json new file mode 100644 index 0000000000..da09224581 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/external-only/ex.json @@ -0,0 +1,7 @@ +{ + "/path2": { + "summary": "path item summary", + "description": "path item description", + "get": {} + } +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/external-only/root.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/external-only/root.json new file mode 100644 index 0000000000..5843b01245 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/external-only/root.json @@ -0,0 +1,8 @@ +{ + "openapi": "3.1.0", + "paths": { + "/path1": { + "$ref": "./ex.json#/~1path2" + } + } +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/ignore-external/dereferenced.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/ignore-external/dereferenced.json new file mode 100644 index 0000000000..dde8e5a6a8 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/ignore-external/dereferenced.json @@ -0,0 +1,10 @@ +[ + { + "openapi": "3.1.0", + "paths": { + "/path1": { + "$ref": "./ex.json#/~1path2" + } + } + } +] diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/ignore-external/ex.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/ignore-external/ex.json new file mode 100644 index 0000000000..da09224581 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/ignore-external/ex.json @@ -0,0 +1,7 @@ +{ + "/path2": { + "summary": "path item summary", + "description": "path item description", + "get": {} + } +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/ignore-external/root.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/ignore-external/root.json new file mode 100644 index 0000000000..5843b01245 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/ignore-external/root.json @@ -0,0 +1,8 @@ +{ + "openapi": "3.1.0", + "paths": { + "/path1": { + "$ref": "./ex.json#/~1path2" + } + } +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/indirect-external-circular/ex1.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/indirect-external-circular/ex1.json new file mode 100644 index 0000000000..8e49d4768f --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/indirect-external-circular/ex1.json @@ -0,0 +1,3 @@ +{ + "$ref": "./ex2.json" +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/indirect-external-circular/ex2.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/indirect-external-circular/ex2.json new file mode 100644 index 0000000000..4d55709987 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/indirect-external-circular/ex2.json @@ -0,0 +1,3 @@ +{ + "$ref": "./root.json#/paths/~1path1" +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/indirect-external-circular/root.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/indirect-external-circular/root.json new file mode 100644 index 0000000000..5557004334 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/indirect-external-circular/root.json @@ -0,0 +1,8 @@ +{ + "openapi": "3.1.0", + "paths": { + "/path1": { + "$ref": "./ex1.json" + } + } +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/indirect-internal-circular/root.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/indirect-internal-circular/root.json new file mode 100644 index 0000000000..16c0928aa1 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/indirect-internal-circular/root.json @@ -0,0 +1,14 @@ +{ + "openapi": "3.1.0", + "paths": { + "/path1": { + "$ref": "#/paths/~1path2" + }, + "/path2": { + "$ref": "#/paths/~1path3" + }, + "/path3": { + "$ref": "#/paths/~1path1" + } + } +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/internal-external/dereferenced.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/internal-external/dereferenced.json new file mode 100644 index 0000000000..a84c3aa600 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/internal-external/dereferenced.json @@ -0,0 +1,22 @@ +[ + { + "openapi": "3.1.0", + "paths": { + "/path1": { + "summary": "path item summary", + "description": "path item description", + "get": {} + }, + "/path3": { + "summary": "path item summary", + "description": "path item description", + "get": {} + }, + "/path4": { + "summary": "path item summary", + "description": "path item description", + "get": {} + } + } + } +] diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/internal-external/ex.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/internal-external/ex.json new file mode 100644 index 0000000000..da09224581 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/internal-external/ex.json @@ -0,0 +1,7 @@ +{ + "/path2": { + "summary": "path item summary", + "description": "path item description", + "get": {} + } +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/internal-external/root.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/internal-external/root.json new file mode 100644 index 0000000000..71d688c0be --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/internal-external/root.json @@ -0,0 +1,16 @@ +{ + "openapi": "3.1.0", + "paths": { + "/path1": { + "$ref": "./ex.json#/~1path2" + }, + "/path3": { + "$ref": "#/paths/~1path4" + }, + "/path4": { + "summary": "path item summary", + "description": "path item description", + "get": {} + } + } +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/internal-indirections/dereferenced.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/internal-indirections/dereferenced.json new file mode 100644 index 0000000000..417a402ed3 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/internal-indirections/dereferenced.json @@ -0,0 +1,22 @@ +[ + { + "openapi": "3.1.0", + "paths": { + "/path1": { + "summary": "path item summary", + "description": "path item description", + "get": {} + }, + "/path2": { + "summary": "path item summary", + "description": "path item description", + "get": {} + }, + "/path3": { + "summary": "path item summary", + "description": "path item description", + "get": {} + } + } + } +] diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/internal-indirections/root.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/internal-indirections/root.json new file mode 100644 index 0000000000..bf154cb3e5 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/internal-indirections/root.json @@ -0,0 +1,16 @@ +{ + "openapi": "3.1.0", + "paths": { + "/path1": { + "$ref": "#/paths/~1path2" + }, + "/path2": { + "$ref": "#/paths/~1path3" + }, + "/path3": { + "summary": "path item summary", + "description": "path item description", + "get": {} + } + } +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/internal-only/dereferenced.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/internal-only/dereferenced.json new file mode 100644 index 0000000000..f45de7e5c3 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/internal-only/dereferenced.json @@ -0,0 +1,17 @@ +[ + { + "openapi": "3.1.0", + "paths": { + "/path1": { + "summary": "path item summary", + "description": "path item description", + "get": {} + }, + "/path2": { + "summary": "path item summary", + "description": "path item description", + "get": {} + } + } + } +] diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/internal-only/root.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/internal-only/root.json new file mode 100644 index 0000000000..12fe95541e --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/internal-only/root.json @@ -0,0 +1,13 @@ +{ + "openapi": "3.1.0", + "paths": { + "/path1": { + "$ref": "#/paths/~1path2" + }, + "/path2": { + "summary": "path item summary", + "description": "path item description", + "get": {} + } + } +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/invalid-pointer/root.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/invalid-pointer/root.json new file mode 100644 index 0000000000..8d4df81c5d --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/invalid-pointer/root.json @@ -0,0 +1,13 @@ +{ + "openapi": "3.1.0", + "paths": { + "/path1": { + "$ref": "invalid-pointer" + }, + "/path2": { + "summary": "path item summary", + "description": "path item description", + "get": {} + } + } +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/max-depth/ex1.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/max-depth/ex1.json new file mode 100644 index 0000000000..d7b3e3be55 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/max-depth/ex1.json @@ -0,0 +1,3 @@ +{ + "$ref": "./ex2.json#/~1path3" +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/max-depth/ex2.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/max-depth/ex2.json new file mode 100644 index 0000000000..e6d9558407 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/max-depth/ex2.json @@ -0,0 +1,7 @@ +{ + "/path3": { + "summary": "path item summary", + "description": "path item description", + "get": {} + } +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/max-depth/root.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/max-depth/root.json new file mode 100644 index 0000000000..5557004334 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/max-depth/root.json @@ -0,0 +1,8 @@ +{ + "openapi": "3.1.0", + "paths": { + "/path1": { + "$ref": "./ex1.json" + } + } +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/unresolvable-path-item/root.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/unresolvable-path-item/root.json new file mode 100644 index 0000000000..1f7c6dcf9b --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/fixtures/unresolvable-path-item/root.json @@ -0,0 +1,13 @@ +{ + "openapi": "3.1.0", + "paths": { + "/path1": { + "$ref": "#/paths/invalid-pointer" + }, + "/path2": { + "summary": "path item summary", + "description": "path item description", + "get": {} + } + } +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/index.ts b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/index.ts index d8cc19d1c7..dbefffe72c 100644 --- a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/index.ts +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/path-item-object/index.ts @@ -4,6 +4,7 @@ import { toValue } from 'apidom'; import { loadJsonFile } from '../../../../helpers'; import { dereference } from '../../../../../src'; +import { DereferenceError, MaximumDereferenceDepthError } from '../../../../../src/util/errors'; const rootFixturePath = path.join(__dirname, 'fixtures'); @@ -52,6 +53,229 @@ describe('dereference', function () { assert.deepEqual(toValue(actual), expected); }); }); + + context('given Path Item Object $ref field', function () { + context('given $ref field pointing internally only', function () { + const fixturePath = path.join(rootFixturePath, 'internal-only'); + + specify('should dereference', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const actual = await dereference(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + }); + const expected = loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + + assert.deepEqual(toValue(actual), expected); + }); + }); + + context('given $ref field pointing externally only', function () { + const fixturePath = path.join(rootFixturePath, 'external-only'); + + specify('should dereference', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const actual = await dereference(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + }); + const expected = loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + + assert.deepEqual(toValue(actual), expected); + }); + }); + + context('given $ref field pointing internally and externally', function () { + const fixturePath = path.join(rootFixturePath, 'internal-external'); + + specify('should dereference', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const actual = await dereference(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + }); + const expected = loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + + assert.deepEqual(toValue(actual), expected); + }); + }); + + context('given $ref field + additional fields', function () { + const fixturePath = path.join(rootFixturePath, 'additional-fields'); + + specify('should dereference', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const actual = await dereference(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + }); + const expected = loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + + assert.deepEqual(toValue(actual), expected); + }); + }); + + context('given external resolution disabled', function () { + const fixturePath = path.join(rootFixturePath, 'ignore-external'); + + specify('should dereference', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const actual = await dereference(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + resolve: { external: false }, + }); + const expected = loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + + assert.deepEqual(toValue(actual), expected); + }); + }); + + context('given $ref field pointing to internal indirection', function () { + const fixturePath = path.join(rootFixturePath, 'internal-indirections'); + + specify('should dereference', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const actual = await dereference(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + }); + const expected = loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + + assert.deepEqual(toValue(actual), expected); + }); + }); + + context('given $ref field pointing to external indirections', function () { + const fixturePath = path.join(rootFixturePath, 'external-indirections'); + + specify('should dereference', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const actual = await dereference(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + }); + const expected = loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + + assert.deepEqual(toValue(actual), expected); + }); + }); + + context('given $ref field with invalid JSON Pointer', function () { + const fixturePath = path.join(rootFixturePath, 'invalid-pointer'); + + specify('should throw error', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + + try { + await dereference(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + }); + assert.fail('should throw DereferenceError'); + } catch (e) { + assert.instanceOf(e, DereferenceError); + } + }); + }); + + context('given $ref field and maxDepth of dereference', function () { + const fixturePath = path.join(rootFixturePath, 'max-depth'); + + specify('should throw error', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + + try { + await dereference(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + dereference: { maxDepth: 1 }, + }); + assert.fail('should throw MaximumDereferenceDepthError'); + } catch (error) { + assert.instanceOf(error, DereferenceError); + assert.instanceOf(error.cause.cause, MaximumDereferenceDepthError); + assert.match(error.cause.cause.message, /fixtures\/max-depth\/ex1.json"$/); + } + }); + }); + + context('given $ref field with unresolvable JSON Pointer', function () { + const fixturePath = path.join(rootFixturePath, 'unresolvable-path-item'); + + specify('should throw error', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + + try { + await dereference(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + }); + assert.fail('should throw DereferenceError'); + } catch (e) { + assert.instanceOf(e, DereferenceError); + } + }); + }); + + context('given $ref field with with direct circular internal reference', function () { + const fixturePath = path.join(rootFixturePath, 'direct-internal-circular'); + + specify('should throw error', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + + try { + await dereference(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + }); + assert.fail('should throw DereferenceError'); + } catch (e) { + assert.instanceOf(e, DereferenceError); + } + }); + }); + + context('given $ref field with with indirect circular internal reference', function () { + const fixturePath = path.join(rootFixturePath, 'indirect-internal-circular'); + + specify('should throw error', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + + try { + await dereference(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + }); + assert.fail('should throw DereferenceError'); + } catch (e) { + assert.instanceOf(e, DereferenceError); + } + }); + }); + + context('given $ref field with with direct circular external reference', function () { + const fixturePath = path.join(rootFixturePath, 'direct-external-circular'); + + specify('should throw error', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + + try { + await dereference(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + }); + assert.fail('should throw DereferenceError'); + } catch (e) { + assert.instanceOf(e, DereferenceError); + } + }); + }); + + context('given $ref field with with indirect circular external reference', function () { + const fixturePath = path.join(rootFixturePath, 'indirect-external-circular'); + + specify('should throw error', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + + try { + await dereference(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + }); + assert.fail('should throw DereferenceError'); + } catch (e) { + assert.instanceOf(e, DereferenceError); + } + }); + }); + }); }); }); }); diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-ignored-props/dereferenced.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-fields/dereferenced.json similarity index 100% rename from apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-ignored-props/dereferenced.json rename to apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-fields/dereferenced.json diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-ignored-props/ex.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-fields/ex.json similarity index 100% rename from apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-ignored-props/ex.json rename to apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-fields/ex.json diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-props/root.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-fields/root.json similarity index 100% rename from apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-props/root.json rename to apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-fields/root.json diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-props/dereferenced.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-ignored-fields/dereferenced.json similarity index 100% rename from apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-props/dereferenced.json rename to apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-ignored-fields/dereferenced.json diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-props/ex.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-ignored-fields/ex.json similarity index 100% rename from apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-props/ex.json rename to apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-ignored-fields/ex.json diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-ignored-props/root.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-ignored-fields/root.json similarity index 100% rename from apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-ignored-props/root.json rename to apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/additional-ignored-fields/root.json diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/invalid-pointer/root.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/invalid-pointer/root.json index 716be399be..afde294f76 100644 --- a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/invalid-pointer/root.json +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/invalid-pointer/root.json @@ -3,7 +3,7 @@ "components": { "parameters": { "userId": { - "$ref": "#/components/parameters/invalid-pointer", + "$ref": "invalid-pointer", "description": "override" }, "userIdRef": { diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/index.ts b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/index.ts index 6a0b09e452..867e0ba840 100644 --- a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/index.ts +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/index.ts @@ -115,8 +115,8 @@ describe('dereference', function () { }); }); - context('given Reference Objects with additional props', function () { - const fixturePath = path.join(rootFixturePath, 'additional-props'); + context('given Reference Objects with additional fields', function () { + const fixturePath = path.join(rootFixturePath, 'additional-fields'); specify('should dereference', async function () { const rootFilePath = path.join(fixturePath, 'root.json'); @@ -129,8 +129,8 @@ describe('dereference', function () { }); }); - context('given Reference Objects with additional ignored props', function () { - const fixturePath = path.join(rootFixturePath, 'additional-ignored-props'); + context('given Reference Objects with additional ignored fields', function () { + const fixturePath = path.join(rootFixturePath, 'additional-ignored-fields'); specify('should dereference', async function () { const rootFilePath = path.join(fixturePath, 'root.json'); @@ -167,6 +167,7 @@ describe('dereference', function () { await dereference(rootFilePath, { parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, }); + assert.fail('should throw DereferenceError'); } catch (e) { assert.instanceOf(e, DereferenceError); } @@ -182,6 +183,7 @@ describe('dereference', function () { await dereference(rootFilePath, { parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, }); + assert.fail('should throw DereferenceError'); } catch (e) { assert.instanceOf(e, DereferenceError); } @@ -197,6 +199,7 @@ describe('dereference', function () { await dereference(rootFilePath, { parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, }); + assert.fail('should throw DereferenceError'); } catch (e) { assert.instanceOf(e, DereferenceError); } @@ -212,6 +215,7 @@ describe('dereference', function () { await dereference(rootFilePath, { parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, }); + assert.fail('should throw DereferenceError'); } catch (e) { assert.instanceOf(e, DereferenceError); } @@ -227,6 +231,7 @@ describe('dereference', function () { await dereference(rootFilePath, { parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, }); + assert.fail('should throw DereferenceError'); } catch (e) { assert.instanceOf(e, DereferenceError); } @@ -242,6 +247,7 @@ describe('dereference', function () { await dereference(rootFilePath, { parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, }); + assert.fail('should throw DereferenceError'); } catch (e) { assert.instanceOf(e, DereferenceError); }