From 9c0b4f5d1c06bcb769b828306053acb26d963b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Gorej?= Date: Fri, 6 Oct 2023 14:45:18 +0200 Subject: [PATCH] feat(ns-openapi-2): add support for XML Object (#3234) Refs #3097 --- packages/apidom-ns-openapi-2/README.md | 2 +- .../apidom-ns-openapi-2/src/elements/Xml.ts | 56 ++++++++++++++++++ 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/xml/index.ts | 18 ++++++ .../src/traversal/visitor.ts | 1 + .../apidom-ns-openapi-2/test/predicates.ts | 59 +++++++++++++++++++ .../elements/Xml/__snapshots__/index.ts.snap | 20 +++++++ .../test/refractor/elements/Xml/index.ts | 34 +++++++++++ 12 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 packages/apidom-ns-openapi-2/src/elements/Xml.ts create mode 100644 packages/apidom-ns-openapi-2/src/refractor/visitors/open-api-2/xml/index.ts create mode 100644 packages/apidom-ns-openapi-2/test/refractor/elements/Xml/__snapshots__/index.ts.snap create mode 100644 packages/apidom-ns-openapi-2/test/refractor/elements/Xml/index.ts diff --git a/packages/apidom-ns-openapi-2/README.md b/packages/apidom-ns-openapi-2/README.md index 56bb2dd1f..2c8d3f78e 100644 --- a/packages/apidom-ns-openapi-2/README.md +++ b/packages/apidom-ns-openapi-2/README.md @@ -202,7 +202,7 @@ Only fully implemented specification objects should be checked here. - [ ] [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) -- [ ] [XML Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-xml-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) - [ ] [Parameters Definitions Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-paramters-definitions-object) - [ ] [Responses Definitions Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-responses-definitions-object) diff --git a/packages/apidom-ns-openapi-2/src/elements/Xml.ts b/packages/apidom-ns-openapi-2/src/elements/Xml.ts new file mode 100644 index 000000000..267bd2eef --- /dev/null +++ b/packages/apidom-ns-openapi-2/src/elements/Xml.ts @@ -0,0 +1,56 @@ +import { + StringElement, + ObjectElement, + BooleanElement, + Attributes, + Meta, +} from '@swagger-api/apidom-core'; + +class Xml extends ObjectElement { + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.element = 'xml'; + } + + get name(): StringElement | undefined { + return this.get('name'); + } + + set name(name: StringElement | undefined) { + this.set('name', name); + } + + get namespace(): StringElement | undefined { + return this.get('namespace'); + } + + set namespace(namespace: StringElement | undefined) { + this.set('namespace', namespace); + } + + get prefix(): StringElement | undefined { + return this.get('prefix'); + } + + set prefix(prefix: StringElement | undefined) { + this.set('prefix', prefix); + } + + get attribute(): BooleanElement | undefined { + return this.get('attribute'); + } + + set attribute(attribute: BooleanElement | undefined) { + this.set('attribute', attribute); + } + + get wrapped(): BooleanElement | undefined { + return this.get('wrapped'); + } + + set wrapped(wrapped: BooleanElement | undefined) { + this.set('wrapped', wrapped); + } +} + +export default Xml; diff --git a/packages/apidom-ns-openapi-2/src/index.ts b/packages/apidom-ns-openapi-2/src/index.ts index 88c653d78..17b8373d2 100644 --- a/packages/apidom-ns-openapi-2/src/index.ts +++ b/packages/apidom-ns-openapi-2/src/index.ts @@ -20,6 +20,7 @@ export { default as refract, createRefractor } from './refractor'; export { default as specificationObj } from './refractor/specification'; export { + isXmlElement, isSecurityDefinitionsElement, isSecuritySchemeElement, isScopesElement, @@ -30,6 +31,7 @@ export { keyMap, getNodeType } from './traversal/visitor'; // OpenAPI 2.0 elements export { + XmlElement, SecurityDefinitionsElement, SecuritySchemeElement, ScopesElement, diff --git a/packages/apidom-ns-openapi-2/src/namespace.ts b/packages/apidom-ns-openapi-2/src/namespace.ts index ab6e6d19b..57d1a2884 100644 --- a/packages/apidom-ns-openapi-2/src/namespace.ts +++ b/packages/apidom-ns-openapi-2/src/namespace.ts @@ -1,5 +1,6 @@ import { NamespacePluginOptions } from '@swagger-api/apidom-core'; +import XmlElement from './elements/Xml'; import SecurityDefinitionsElement from './elements/SecurityDefinitions'; import SecuritySchemeElement from './elements/SecurityScheme'; import ScopesElement from './elements/Scopes'; @@ -9,6 +10,7 @@ const openApi2 = { namespace: (options: NamespacePluginOptions) => { const { base } = options; + base.register('xml', XmlElement); base.register('securityDefinitions', SecurityDefinitionsElement); base.register('securityScheme', SecuritySchemeElement); base.register('scopes', ScopesElement); diff --git a/packages/apidom-ns-openapi-2/src/predicates.ts b/packages/apidom-ns-openapi-2/src/predicates.ts index 9d18f6d19..536be42db 100644 --- a/packages/apidom-ns-openapi-2/src/predicates.ts +++ b/packages/apidom-ns-openapi-2/src/predicates.ts @@ -1,10 +1,21 @@ import { createPredicate } from '@swagger-api/apidom-core'; +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 isXmlElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq }) => { + return (element: any) => + element instanceof XmlElement || + (hasBasicElementProps(element) && + isElementType('xml', element) && + primitiveEq('object', element)); + }, +); + export const isSecurityDefinitionsElement = 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 6957d8f71..9ffe5b1e3 100644 --- a/packages/apidom-ns-openapi-2/src/refractor/registration.ts +++ b/packages/apidom-ns-openapi-2/src/refractor/registration.ts @@ -1,3 +1,4 @@ +import XmlElement from '../elements/Xml'; import SecurityDefinitionsElement from '../elements/SecurityDefinitions'; import SecuritySchemeElement from '../elements/SecurityScheme'; import ScopesElement from '../elements/Scopes'; @@ -5,6 +6,7 @@ import SecurityRequirementElement from '../elements/SecurityRequirement'; import { createRefractor } from './index'; // register refractors specific to element types +XmlElement.refract = createRefractor(['visitors', 'document', 'objects', 'XML', '$visitor']); SecurityDefinitionsElement.refract = createRefractor([ 'visitors', 'document', @@ -29,6 +31,7 @@ SecurityRequirementElement.refract = createRefractor([ ]); export { + XmlElement, SecurityDefinitionsElement, SecuritySchemeElement, ScopesElement, diff --git a/packages/apidom-ns-openapi-2/src/refractor/specification.ts b/packages/apidom-ns-openapi-2/src/refractor/specification.ts index 8eeb1e642..772e2ed55 100644 --- a/packages/apidom-ns-openapi-2/src/refractor/specification.ts +++ b/packages/apidom-ns-openapi-2/src/refractor/specification.ts @@ -1,4 +1,5 @@ import FallbackVisitor from './visitors/FallbackVisitor'; +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'; import ScopesVisitor from './visitors/open-api-2/scopes'; @@ -19,6 +20,16 @@ const specification = { value: FallbackVisitor, document: { objects: { + XML: { + $visitor: XmlVisitor, + fixedFields: { + name: FallbackVisitor, + namespace: FallbackVisitor, + prefix: FallbackVisitor, + attribute: FallbackVisitor, + wrapped: FallbackVisitor, + }, + }, SecurityDefinitions: { $visitor: SecurityDefinitionsVisitor, }, diff --git a/packages/apidom-ns-openapi-2/src/refractor/visitors/open-api-2/xml/index.ts b/packages/apidom-ns-openapi-2/src/refractor/visitors/open-api-2/xml/index.ts new file mode 100644 index 000000000..728f24819 --- /dev/null +++ b/packages/apidom-ns-openapi-2/src/refractor/visitors/open-api-2/xml/index.ts @@ -0,0 +1,18 @@ +import stampit from 'stampit'; +import { always } from 'ramda'; + +import XmlElement from '../../../../elements/Xml'; +import FallbackVisitor from '../../FallbackVisitor'; +import FixedFieldsVisitor from '../../generics/FixedFieldsVisitor'; + +const XmlVisitor = stampit(FixedFieldsVisitor, FallbackVisitor, { + props: { + specPath: always(['document', 'objects', 'XML']), + canSupportSpecificationExtensions: true, + }, + init() { + this.element = new XmlElement(); + }, +}); + +export default XmlVisitor; diff --git a/packages/apidom-ns-openapi-2/src/traversal/visitor.ts b/packages/apidom-ns-openapi-2/src/traversal/visitor.ts index a4cd12a56..33895b84e 100644 --- a/packages/apidom-ns-openapi-2/src/traversal/visitor.ts +++ b/packages/apidom-ns-openapi-2/src/traversal/visitor.ts @@ -19,6 +19,7 @@ export const getNodeType = (element: T): string | undefined = */ export const keyMap = { + XmlElement: ['content'], SecurityDefinitionsElement: ['content'], SecuritySchemeElement: ['content'], ScopesElement: ['content'], diff --git a/packages/apidom-ns-openapi-2/test/predicates.ts b/packages/apidom-ns-openapi-2/test/predicates.ts index 629856780..42ba4c6db 100644 --- a/packages/apidom-ns-openapi-2/test/predicates.ts +++ b/packages/apidom-ns-openapi-2/test/predicates.ts @@ -1,10 +1,12 @@ import { assert } from 'chai'; import { + XmlElement, SecurityDefinitionsElement, SecuritySchemeElement, ScopesElement, SecurityRequirementElement, + isXmlElement, isSecurityDefinitionsElement, isSecuritySchemeElement, isScopesElement, @@ -12,6 +14,63 @@ import { } from '../src'; describe('predicates', function () { + context('isXmlElement', function () { + context('given XmlElement instance value', function () { + specify('should return true', function () { + const element = new XmlElement(); + + assert.isTrue(isXmlElement(element)); + }); + }); + + context('given subtype instance value', function () { + specify('should return true', function () { + // eslint-disable-next-line @typescript-eslint/naming-convention + class XmlSubElement extends XmlElement {} + + assert.isTrue(isXmlElement(new XmlSubElement())); + }); + }); + + context('given non XmlSubElement instance value', function () { + specify('should return false', function () { + assert.isFalse(isXmlElement(1)); + assert.isFalse(isXmlElement(null)); + assert.isFalse(isXmlElement(undefined)); + assert.isFalse(isXmlElement({})); + assert.isFalse(isXmlElement([])); + assert.isFalse(isXmlElement('string')); + }); + }); + + specify('should support duck-typing', function () { + const xmlElementDuck = { + _storedElement: 'xml', + _content: [], + primitive() { + return 'object'; + }, + get element() { + return this._storedElement; + }, + }; + + const xmlElementSwan = { + _storedElement: undefined, + _content: undefined, + primitive() { + return 'swan'; + }, + get length() { + return 0; + }, + }; + + assert.isTrue(isXmlElement(xmlElementDuck)); + assert.isFalse(isXmlElement(xmlElementSwan)); + }); + }); + context('isSecurityDefinitionsElement', function () { context('given SecurityDefinitionsElement instance value', function () { specify('should return true', function () { diff --git a/packages/apidom-ns-openapi-2/test/refractor/elements/Xml/__snapshots__/index.ts.snap b/packages/apidom-ns-openapi-2/test/refractor/elements/Xml/__snapshots__/index.ts.snap new file mode 100644 index 000000000..c5bf4b14c --- /dev/null +++ b/packages/apidom-ns-openapi-2/test/refractor/elements/Xml/__snapshots__/index.ts.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor elements XmlElement should refract to semantic ApiDOM tree 1`] = ` +(XmlElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (BooleanElement)) + (MemberElement + (StringElement) + (BooleanElement))) +`; diff --git a/packages/apidom-ns-openapi-2/test/refractor/elements/Xml/index.ts b/packages/apidom-ns-openapi-2/test/refractor/elements/Xml/index.ts new file mode 100644 index 000000000..ba84f5553 --- /dev/null +++ b/packages/apidom-ns-openapi-2/test/refractor/elements/Xml/index.ts @@ -0,0 +1,34 @@ +import { expect, assert } from 'chai'; +import { sexprs, includesClasses } from '@swagger-api/apidom-core'; + +import { XmlElement } from '../../../../src'; + +describe('refractor', function () { + context('elements', function () { + context('XmlElement', function () { + specify('should refract to semantic ApiDOM tree', function () { + const xmlElement = XmlElement.refract({ + name: 'animal', + namespace: 'http://swagger.io/schema/sample', + prefix: 'sample', + attribute: true, + wrapped: false, + }); + + expect(sexprs(xmlElement)).toMatchSnapshot(); + }); + + specify('should support specification extensions', function () { + const xmlElement = XmlElement.refract({ + name: 'animal', + 'x-extension': 'extension', + }) as XmlElement; + + assert.isFalse(includesClasses(['specification-extension'], xmlElement.getMember('name'))); + assert.isTrue( + includesClasses(['specification-extension'], xmlElement.getMember('x-extension')), + ); + }); + }); + }); +});