From 7adf6233a956bda44d3fb1aabcccb9645955ccf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Gorej?= Date: Tue, 10 Oct 2023 11:38:59 +0200 Subject: [PATCH] feat(ns-openapi-2): add support for Tag Object (#3246) Refs #3097 --- packages/apidom-ns-openapi-2/README.md | 2 +- .../apidom-ns-openapi-2/src/elements/Tag.ts | 36 +++++++++++ packages/apidom-ns-openapi-2/src/index.ts | 2 + packages/apidom-ns-openapi-2/src/namespace.ts | 2 + .../apidom-ns-openapi-2/src/predicates.ts | 11 ++++ .../src/refractor/registration.ts | 3 + .../src/refractor/specification.ts | 11 ++++ .../visitors/open-api-2/tag/index.ts | 18 ++++++ .../src/traversal/visitor.ts | 1 + .../apidom-ns-openapi-2/test/predicates.ts | 59 +++++++++++++++++++ .../elements/Tag/__snapshots__/index.ts.snap | 14 +++++ .../test/refractor/elements/Tag/index.ts | 20 +++++++ 12 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 packages/apidom-ns-openapi-2/src/elements/Tag.ts create mode 100644 packages/apidom-ns-openapi-2/src/refractor/visitors/open-api-2/tag/index.ts create mode 100644 packages/apidom-ns-openapi-2/test/refractor/elements/Tag/__snapshots__/index.ts.snap create mode 100644 packages/apidom-ns-openapi-2/test/refractor/elements/Tag/index.ts diff --git a/packages/apidom-ns-openapi-2/README.md b/packages/apidom-ns-openapi-2/README.md index b31a3dc004..5c0cd75754 100644 --- a/packages/apidom-ns-openapi-2/README.md +++ b/packages/apidom-ns-openapi-2/README.md @@ -199,7 +199,7 @@ Only fully implemented specification objects should be checked here. - [ ] [Headers Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-headers-object) - [ ] [Example Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-example-object) - [ ] [Header Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-header-object) -- [ ] [Tag Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-tag-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) - [ ] [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) diff --git a/packages/apidom-ns-openapi-2/src/elements/Tag.ts b/packages/apidom-ns-openapi-2/src/elements/Tag.ts new file mode 100644 index 0000000000..d44f7dbd91 --- /dev/null +++ b/packages/apidom-ns-openapi-2/src/elements/Tag.ts @@ -0,0 +1,36 @@ +import { ObjectElement, StringElement, Attributes, Meta } from '@swagger-api/apidom-core'; + +import ExternalDocumentationElement from './ExternalDocumentation'; + +class Tag extends ObjectElement { + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.element = 'tag'; + } + + get name(): StringElement | undefined { + return this.get('name'); + } + + set name(name: StringElement | undefined) { + this.set('name', name); + } + + get description(): StringElement | undefined { + return this.get('description'); + } + + set description(description: StringElement | undefined) { + this.set('description', description); + } + + get externalDocs(): ExternalDocumentationElement | undefined { + return this.get('externalDocs'); + } + + set externalDocs(externalDocs: ExternalDocumentationElement | undefined) { + this.set('externalDocs', externalDocs); + } +} + +export default Tag; diff --git a/packages/apidom-ns-openapi-2/src/index.ts b/packages/apidom-ns-openapi-2/src/index.ts index 22f25487bf..742ed918db 100644 --- a/packages/apidom-ns-openapi-2/src/index.ts +++ b/packages/apidom-ns-openapi-2/src/index.ts @@ -24,6 +24,7 @@ export { isLicenseElement, isContactElement, isExternalDocumentationElement, + isTagElement, isXmlElement, isSecurityDefinitionsElement, isSecuritySchemeElement, @@ -39,6 +40,7 @@ export { LicenseElement, ContactElement, ExternalDocumentationElement, + TagElement, XmlElement, SecurityDefinitionsElement, SecuritySchemeElement, diff --git a/packages/apidom-ns-openapi-2/src/namespace.ts b/packages/apidom-ns-openapi-2/src/namespace.ts index f28776e8c2..5079ab335c 100644 --- a/packages/apidom-ns-openapi-2/src/namespace.ts +++ b/packages/apidom-ns-openapi-2/src/namespace.ts @@ -4,6 +4,7 @@ import InfoElement from './elements/Info'; import LicenseElement from './elements/License'; import ContactElement from './elements/Contact'; import ExternalDocumentation from './elements/ExternalDocumentation'; +import TagElement from './elements/Tag'; import XmlElement from './elements/Xml'; import SecurityDefinitionsElement from './elements/SecurityDefinitions'; import SecuritySchemeElement from './elements/SecurityScheme'; @@ -18,6 +19,7 @@ const openApi2 = { base.register('license', LicenseElement); base.register('contact', ContactElement); base.register('externalDocumentation', ExternalDocumentation); + base.register('tag', TagElement); 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 08e78ddc23..b386a9df46 100644 --- a/packages/apidom-ns-openapi-2/src/predicates.ts +++ b/packages/apidom-ns-openapi-2/src/predicates.ts @@ -4,12 +4,23 @@ import InfoElement from './elements/Info'; import LicenseElement from './elements/License'; import ContactElement from './elements/Contact'; import ExternalDocumentation from './elements/ExternalDocumentation'; +import TagElement from './elements/Tag'; import XmlElement from './elements/Xml'; import SecurityDefinitionsElement from './elements/SecurityDefinitions'; import SecuritySchemeElement from './elements/SecurityScheme'; import SecurityRequirementElement from './elements/SecurityRequirement'; import ScopesElement from './elements/Scopes'; +export const isTagElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq }) => { + return (element: any) => + element instanceof TagElement || + (hasBasicElementProps(element) && + isElementType('tag', element) && + primitiveEq('object', element)); + }, +); + export const isInfoElement = createPredicate( ({ hasBasicElementProps, isElementType, primitiveEq }) => { return (element: any) => diff --git a/packages/apidom-ns-openapi-2/src/refractor/registration.ts b/packages/apidom-ns-openapi-2/src/refractor/registration.ts index bae195c775..50a4ac14f9 100644 --- a/packages/apidom-ns-openapi-2/src/refractor/registration.ts +++ b/packages/apidom-ns-openapi-2/src/refractor/registration.ts @@ -2,6 +2,7 @@ import InfoElement from '../elements/Info'; import LicenseElement from '../elements/License'; import ContactElement from '../elements/Contact'; import ExternalDocumentationElement from '../elements/ExternalDocumentation'; +import TagElement from '../elements/Tag'; import XmlElement from '../elements/Xml'; import SecurityDefinitionsElement from '../elements/SecurityDefinitions'; import SecuritySchemeElement from '../elements/SecurityScheme'; @@ -32,6 +33,7 @@ ExternalDocumentationElement.refract = createRefractor([ 'ExternalDocumentation', '$visitor', ]); +TagElement.refract = createRefractor(['visitors', 'document', 'objects', 'Tag', '$visitor']); XmlElement.refract = createRefractor(['visitors', 'document', 'objects', 'XML', '$visitor']); SecurityDefinitionsElement.refract = createRefractor([ 'visitors', @@ -61,6 +63,7 @@ export { LicenseElement, ContactElement, ExternalDocumentationElement, + TagElement, 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 270c66cac2..70d320418b 100644 --- a/packages/apidom-ns-openapi-2/src/refractor/specification.ts +++ b/packages/apidom-ns-openapi-2/src/refractor/specification.ts @@ -4,6 +4,7 @@ import InfoVersionVisitor from './visitors/open-api-2/info/VersionVisitor'; import LicenseVisitor from './visitors/open-api-2/license'; import ContactVisitor from './visitors/open-api-2/contact'; import ExternalDocumentationElement from './visitors/open-api-2/external-documentation'; +import TagVisitor from './visitors/open-api-2/tag'; import XmlVisitor from './visitors/open-api-2/xml'; import SecurityDefinitionsVisitor from './visitors/open-api-2/security-definitions'; import SecuritySchemeVisitor from './visitors/open-api-2/security-scheme'; @@ -62,6 +63,16 @@ const specification = { url: FallbackVisitor, }, }, + Tag: { + $visitor: TagVisitor, + fixedFields: { + name: FallbackVisitor, + description: FallbackVisitor, + externalDocs: { + $ref: '#/visitors/document/objects/ExternalDocumentation', + }, + }, + }, XML: { $visitor: XmlVisitor, fixedFields: { diff --git a/packages/apidom-ns-openapi-2/src/refractor/visitors/open-api-2/tag/index.ts b/packages/apidom-ns-openapi-2/src/refractor/visitors/open-api-2/tag/index.ts new file mode 100644 index 0000000000..62d26b7b82 --- /dev/null +++ b/packages/apidom-ns-openapi-2/src/refractor/visitors/open-api-2/tag/index.ts @@ -0,0 +1,18 @@ +import stampit from 'stampit'; +import { always } from 'ramda'; + +import TagElement from '../../../../elements/Tag'; +import FallbackVisitor from '../../FallbackVisitor'; +import FixedFieldsVisitor from '../../generics/FixedFieldsVisitor'; + +const TagVisitor = stampit(FixedFieldsVisitor, FallbackVisitor, { + props: { + specPath: always(['document', 'objects', 'Tag']), + canSupportSpecificationExtensions: true, + }, + init() { + this.element = new TagElement(); + }, +}); + +export default TagVisitor; diff --git a/packages/apidom-ns-openapi-2/src/traversal/visitor.ts b/packages/apidom-ns-openapi-2/src/traversal/visitor.ts index d7ead7ad2c..ed75a3f3a0 100644 --- a/packages/apidom-ns-openapi-2/src/traversal/visitor.ts +++ b/packages/apidom-ns-openapi-2/src/traversal/visitor.ts @@ -23,6 +23,7 @@ export const keyMap = { LicenseElement: ['content'], ContactElement: ['content'], ExternalDocumentationElement: ['content'], + TagElement: ['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 904b43b7f9..c1e22d9c07 100644 --- a/packages/apidom-ns-openapi-2/test/predicates.ts +++ b/packages/apidom-ns-openapi-2/test/predicates.ts @@ -5,6 +5,7 @@ import { LicenseElement, ContactElement, ExternalDocumentationElement, + TagElement, XmlElement, SecurityDefinitionsElement, SecuritySchemeElement, @@ -14,6 +15,7 @@ import { isLicenseElement, isContactElement, isExternalDocumentationElement, + isTagElement, isXmlElement, isSecurityDefinitionsElement, isSecuritySchemeElement, @@ -22,6 +24,63 @@ import { } from '../src'; describe('predicates', function () { + context('isTagElement', function () { + context('given TagElement instance value', function () { + specify('should return true', function () { + const element = new TagElement(); + + assert.isTrue(isTagElement(element)); + }); + }); + + context('given subtype instance value', function () { + specify('should return true', function () { + // eslint-disable-next-line @typescript-eslint/naming-convention + class TagSubElement extends TagElement {} + + assert.isTrue(isTagElement(new TagSubElement())); + }); + }); + + context('given non TagSubElement instance value', function () { + specify('should return false', function () { + assert.isFalse(isTagElement(1)); + assert.isFalse(isTagElement(null)); + assert.isFalse(isTagElement(undefined)); + assert.isFalse(isTagElement({})); + assert.isFalse(isTagElement([])); + assert.isFalse(isTagElement('string')); + }); + }); + + specify('should support duck-typing', function () { + const tagElementDuck = { + _storedElement: 'tag', + _content: [], + primitive() { + return 'object'; + }, + get element() { + return this._storedElement; + }, + }; + + const tagElementSwan = { + _storedElement: undefined, + _content: undefined, + primitive() { + return 'swan'; + }, + get length() { + return 0; + }, + }; + + assert.isTrue(isTagElement(tagElementDuck)); + assert.isFalse(isTagElement(tagElementSwan)); + }); + }); + context('isInfoElement', function () { context('given InfoElement instance value', function () { specify('should return true', function () { diff --git a/packages/apidom-ns-openapi-2/test/refractor/elements/Tag/__snapshots__/index.ts.snap b/packages/apidom-ns-openapi-2/test/refractor/elements/Tag/__snapshots__/index.ts.snap new file mode 100644 index 0000000000..f80b770934 --- /dev/null +++ b/packages/apidom-ns-openapi-2/test/refractor/elements/Tag/__snapshots__/index.ts.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor elements TagElement should refract to semantic ApiDOM tree 1`] = ` +(TagElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (ExternalDocumentationElement))) +`; diff --git a/packages/apidom-ns-openapi-2/test/refractor/elements/Tag/index.ts b/packages/apidom-ns-openapi-2/test/refractor/elements/Tag/index.ts new file mode 100644 index 0000000000..32af35e643 --- /dev/null +++ b/packages/apidom-ns-openapi-2/test/refractor/elements/Tag/index.ts @@ -0,0 +1,20 @@ +import { expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { TagElement } from '../../../../src'; + +describe('refractor', function () { + context('elements', function () { + context('TagElement', function () { + specify('should refract to semantic ApiDOM tree', function () { + const tagElement = TagElement.refract({ + name: 'tag-name', + description: 'tag-description', + externalDocs: {}, + }); + + expect(sexprs(tagElement)).toMatchSnapshot(); + }); + }); + }); +});