diff --git a/packages/apidom-ns-openapi-2/README.md b/packages/apidom-ns-openapi-2/README.md index 544cbef060..9129cf2bf0 100644 --- a/packages/apidom-ns-openapi-2/README.md +++ b/packages/apidom-ns-openapi-2/README.md @@ -200,7 +200,7 @@ Only fully implemented specification objects should be checked here. - [x] [Example Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-example-object) - [x] [Header Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-header-object) - [x] [Tag Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-tag-object) -- [ ] [Reference Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-reference-object) +- [x] [Reference Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-reference-object) - [ ] [Schema Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-schema-object) - [x] [XML Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-xml-object) - [ ] [Definitions Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-definitions-object) diff --git a/packages/apidom-ns-openapi-2/src/elements/Reference.ts b/packages/apidom-ns-openapi-2/src/elements/Reference.ts new file mode 100644 index 0000000000..74826b4196 --- /dev/null +++ b/packages/apidom-ns-openapi-2/src/elements/Reference.ts @@ -0,0 +1,19 @@ +import { StringElement, ObjectElement, Attributes, Meta } from '@swagger-api/apidom-core'; + +class Reference extends ObjectElement { + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.element = 'reference'; + this.classes.push('openapi-reference'); + } + + get $ref(): StringElement | undefined { + return this.get('$ref'); + } + + set $ref($ref: StringElement | undefined) { + this.set('$ref', $ref); + } +} + +export default Reference; diff --git a/packages/apidom-ns-openapi-2/src/index.ts b/packages/apidom-ns-openapi-2/src/index.ts index 707b832f93..82205fbf63 100644 --- a/packages/apidom-ns-openapi-2/src/index.ts +++ b/packages/apidom-ns-openapi-2/src/index.ts @@ -30,6 +30,7 @@ export { isExampleElement, isHeaderElement, isTagElement, + isReferenceElement, isXmlElement, isSecurityDefinitionsElement, isSecuritySchemeElement, @@ -51,6 +52,7 @@ export { ExampleElement, HeaderElement, TagElement, + ReferenceElement, XmlElement, SecurityDefinitionsElement, SecuritySchemeElement, diff --git a/packages/apidom-ns-openapi-2/src/namespace.ts b/packages/apidom-ns-openapi-2/src/namespace.ts index a164c1f5f9..9e27674cfb 100644 --- a/packages/apidom-ns-openapi-2/src/namespace.ts +++ b/packages/apidom-ns-openapi-2/src/namespace.ts @@ -11,6 +11,7 @@ import HeadersElement from './elements/Headers'; import HeaderElement from './elements/Header'; import TagElement from './elements/Tag'; import XmlElement from './elements/Xml'; +import ReferenceElement from './elements/Reference'; import SecurityDefinitionsElement from './elements/SecurityDefinitions'; import SecuritySchemeElement from './elements/SecurityScheme'; import ScopesElement from './elements/Scopes'; @@ -30,6 +31,7 @@ const openApi2 = { base.register('example', ExampleElement); base.register('header', HeaderElement); base.register('tag', TagElement); + base.register('reference', ReferenceElement); base.register('xml', XmlElement); base.register('securityDefinitions', SecurityDefinitionsElement); base.register('securityScheme', SecuritySchemeElement); diff --git a/packages/apidom-ns-openapi-2/src/predicates.ts b/packages/apidom-ns-openapi-2/src/predicates.ts index b5c61a317f..8e39b600f8 100644 --- a/packages/apidom-ns-openapi-2/src/predicates.ts +++ b/packages/apidom-ns-openapi-2/src/predicates.ts @@ -10,6 +10,7 @@ import ExampleElement from './elements/Example'; import HeadersElement from './elements/Headers'; import HeaderElement from './elements/Header'; import TagElement from './elements/Tag'; +import ReferenceElement from './elements/Reference'; import XmlElement from './elements/Xml'; import SecurityDefinitionsElement from './elements/SecurityDefinitions'; import SecuritySchemeElement from './elements/SecurityScheme'; @@ -115,6 +116,16 @@ export const isTagElement = createPredicate( }, ); +export const isReferenceElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq }) => { + return (element: unknown) => + element instanceof ReferenceElement || + (hasBasicElementProps(element) && + isElementType('reference', element) && + primitiveEq('object', element)); + }, +); + export const isXmlElement = createPredicate( ({ hasBasicElementProps, isElementType, primitiveEq }) => { return (element: unknown) => diff --git a/packages/apidom-ns-openapi-2/src/refractor/registration.ts b/packages/apidom-ns-openapi-2/src/refractor/registration.ts index ca738c0c27..4952752c0b 100644 --- a/packages/apidom-ns-openapi-2/src/refractor/registration.ts +++ b/packages/apidom-ns-openapi-2/src/refractor/registration.ts @@ -8,6 +8,7 @@ import HeadersElement from '../elements/Headers'; import ExampleElement from '../elements/Example'; import HeaderElement from '../elements/Header'; import TagElement from '../elements/Tag'; +import ReferenceElement from '../elements/Reference'; import XmlElement from '../elements/Xml'; import SecurityDefinitionsElement from '../elements/SecurityDefinitions'; import SecuritySchemeElement from '../elements/SecurityScheme'; @@ -62,6 +63,13 @@ ExampleElement.refract = createRefractor([ ]); HeaderElement.refract = createRefractor(['visitors', 'document', 'objects', 'Header', '$visitor']); TagElement.refract = createRefractor(['visitors', 'document', 'objects', 'Tag', '$visitor']); +ReferenceElement.refract = createRefractor([ + 'visitors', + 'document', + 'objects', + 'Reference', + '$visitor', +]); XmlElement.refract = createRefractor(['visitors', 'document', 'objects', 'XML', '$visitor']); SecurityDefinitionsElement.refract = createRefractor([ 'visitors', @@ -97,6 +105,7 @@ export { ExampleElement, HeaderElement, TagElement, + ReferenceElement, XmlElement, SecurityDefinitionsElement, SecuritySchemeElement, diff --git a/packages/apidom-ns-openapi-2/src/refractor/specification.ts b/packages/apidom-ns-openapi-2/src/refractor/specification.ts index 31a83943bc..eb5e118881 100644 --- a/packages/apidom-ns-openapi-2/src/refractor/specification.ts +++ b/packages/apidom-ns-openapi-2/src/refractor/specification.ts @@ -12,6 +12,8 @@ import HeadersVisitor from './visitors/open-api-2/headers'; import ExampleVisitor from './visitors/open-api-2/example'; import HeaderVisitor from './visitors/open-api-2/header'; import TagVisitor from './visitors/open-api-2/tag'; +import ReferenceVisitor from './visitors/open-api-2/reference'; +import Reference$RefVisitor from './visitors/open-api-2/reference/$RefVisitor'; import SchemaVisitor from './visitors/open-api-2/schema'; import XmlVisitor from './visitors/open-api-2/xml'; import SecurityDefinitionsVisitor from './visitors/open-api-2/security-definitions'; @@ -170,6 +172,12 @@ const specification = { }, }, }, + Reference: { + $visitor: ReferenceVisitor, + fixedFields: { + $ref: Reference$RefVisitor, + }, + }, Schema: { $visitor: SchemaVisitor, }, diff --git a/packages/apidom-ns-openapi-2/src/refractor/visitors/open-api-2/reference/$RefVisitor.ts b/packages/apidom-ns-openapi-2/src/refractor/visitors/open-api-2/reference/$RefVisitor.ts new file mode 100644 index 0000000000..b142b7a4ad --- /dev/null +++ b/packages/apidom-ns-openapi-2/src/refractor/visitors/open-api-2/reference/$RefVisitor.ts @@ -0,0 +1,17 @@ +import stampit from 'stampit'; +import { StringElement, BREAK, cloneDeep } from '@swagger-api/apidom-core'; + +import FallbackVisitor from '../../FallbackVisitor'; + +const $RefVisitor = stampit(FallbackVisitor, { + methods: { + StringElement(stringElement: StringElement) { + this.element = cloneDeep(stringElement); + this.element.classes.push('reference-value'); + + return BREAK; + }, + }, +}); + +export default $RefVisitor; diff --git a/packages/apidom-ns-openapi-2/src/refractor/visitors/open-api-2/reference/index.ts b/packages/apidom-ns-openapi-2/src/refractor/visitors/open-api-2/reference/index.ts new file mode 100644 index 0000000000..62d8e01e7b --- /dev/null +++ b/packages/apidom-ns-openapi-2/src/refractor/visitors/open-api-2/reference/index.ts @@ -0,0 +1,32 @@ +import stampit from 'stampit'; +import { always } from 'ramda'; +import { ObjectElement, isStringElement } from '@swagger-api/apidom-core'; + +import ReferenceElement from '../../../../elements/Reference'; +import FixedFieldsVisitor from '../../generics/FixedFieldsVisitor'; +import FallbackVisitor from '../../FallbackVisitor'; + +const ReferenceVisitor = stampit(FixedFieldsVisitor, FallbackVisitor, { + props: { + specPath: always(['document', 'objects', 'Reference']), + canSupportSpecificationExtensions: false, + }, + init() { + this.element = new ReferenceElement(); + }, + methods: { + ObjectElement(objectElement: ObjectElement) { + // @ts-ignore + const result = FixedFieldsVisitor.compose.methods.ObjectElement.call(this, objectElement); + + // mark this ReferenceElement with reference metadata + if (isStringElement(this.element.$ref)) { + this.element.classes.push('reference-element'); + } + + return result; + }, + }, +}); + +export default ReferenceVisitor; diff --git a/packages/apidom-ns-openapi-2/src/traversal/visitor.ts b/packages/apidom-ns-openapi-2/src/traversal/visitor.ts index 792f9ebe8e..75fd170945 100644 --- a/packages/apidom-ns-openapi-2/src/traversal/visitor.ts +++ b/packages/apidom-ns-openapi-2/src/traversal/visitor.ts @@ -29,6 +29,7 @@ export const keyMap = { HeadersElement: ['content'], HeaderElement: ['content'], TagElement: ['content'], + ReferenceElement: ['content'], XmlElement: ['content'], SecurityDefinitionsElement: ['content'], SecuritySchemeElement: ['content'], diff --git a/packages/apidom-ns-openapi-2/test/predicates.ts b/packages/apidom-ns-openapi-2/test/predicates.ts index c34bde280d..62eef951c7 100644 --- a/packages/apidom-ns-openapi-2/test/predicates.ts +++ b/packages/apidom-ns-openapi-2/test/predicates.ts @@ -11,6 +11,7 @@ import { ExampleElement, HeaderElement, TagElement, + ReferenceElement, XmlElement, SecurityDefinitionsElement, SecuritySchemeElement, @@ -26,6 +27,7 @@ import { isExampleElement, isHeaderElement, isTagElement, + isReferenceElement, isXmlElement, isSecurityDefinitionsElement, isSecuritySchemeElement, @@ -594,6 +596,62 @@ describe('predicates', function () { }); }); + context('isReferenceElement', function () { + context('given ReferenceElement instance value', function () { + specify('should return true', function () { + const element = new ReferenceElement(); + + assert.isTrue(isReferenceElement(element)); + }); + }); + + context('given subtype instance value', function () { + specify('should return true', function () { + class ReferenceSubElement extends ReferenceElement {} + + assert.isTrue(isReferenceElement(new ReferenceSubElement())); + }); + }); + + context('given non ReferenceSubElement instance value', function () { + specify('should return false', function () { + assert.isFalse(isReferenceElement(1)); + assert.isFalse(isReferenceElement(null)); + assert.isFalse(isReferenceElement(undefined)); + assert.isFalse(isReferenceElement({})); + assert.isFalse(isReferenceElement([])); + assert.isFalse(isReferenceElement('string')); + }); + }); + + specify('should support duck-typing', function () { + const referenceElementDuck = { + _storedElement: 'reference', + _content: [], + primitive() { + return 'object'; + }, + get element() { + return this._storedElement; + }, + }; + + const referenceElementSwan = { + _storedElement: undefined, + _content: undefined, + primitive() { + return 'swan'; + }, + get length() { + return 0; + }, + }; + + assert.isTrue(isReferenceElement(referenceElementDuck)); + assert.isFalse(isReferenceElement(referenceElementSwan)); + }); + }); + context('isXmlElement', function () { context('given XmlElement instance value', function () { specify('should return true', function () { diff --git a/packages/apidom-ns-openapi-2/test/refractor/elements/Reference/__snapshots__/index.ts.snap b/packages/apidom-ns-openapi-2/test/refractor/elements/Reference/__snapshots__/index.ts.snap new file mode 100644 index 0000000000..9c3b4fc610 --- /dev/null +++ b/packages/apidom-ns-openapi-2/test/refractor/elements/Reference/__snapshots__/index.ts.snap @@ -0,0 +1,8 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor elements ReferenceElement should refract to semantic ApiDOM tree 1`] = ` +(ReferenceElement + (MemberElement + (StringElement) + (StringElement))) +`; diff --git a/packages/apidom-ns-openapi-2/test/refractor/elements/Reference/index.ts b/packages/apidom-ns-openapi-2/test/refractor/elements/Reference/index.ts new file mode 100644 index 0000000000..e454e2dba1 --- /dev/null +++ b/packages/apidom-ns-openapi-2/test/refractor/elements/Reference/index.ts @@ -0,0 +1,18 @@ +import { expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { ReferenceElement } from '../../../../src'; + +describe('refractor', function () { + context('elements', function () { + context('ReferenceElement', function () { + specify('should refract to semantic ApiDOM tree', function () { + const referenceElement = ReferenceElement.refract({ + $ref: '#/path/to/somewhere', + }); + + expect(sexprs(referenceElement)).toMatchSnapshot(); + }); + }); + }); +});