diff --git a/packages/apidom-ns-openapi-2/README.md b/packages/apidom-ns-openapi-2/README.md index 215ae5fdf..b31a3dc00 100644 --- a/packages/apidom-ns-openapi-2/README.md +++ b/packages/apidom-ns-openapi-2/README.md @@ -185,7 +185,7 @@ const swaggerElement = SwaggerElement.refract(apiDOM.result, { Only fully implemented specification objects should be checked here. - [ ] [Swagger Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-swagger-object) -- [ ] [Info Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-info-object) +- [x] [Info Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-info-object) - [x] [Contact Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-contact-object) - [x] [License Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-license-object) - [ ] [Paths Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-paths-object) diff --git a/packages/apidom-ns-openapi-2/src/elements/Info.ts b/packages/apidom-ns-openapi-2/src/elements/Info.ts new file mode 100644 index 000000000..975b50ab9 --- /dev/null +++ b/packages/apidom-ns-openapi-2/src/elements/Info.ts @@ -0,0 +1,62 @@ +import { StringElement, ObjectElement, Attributes, Meta } from '@swagger-api/apidom-core'; + +import ContactElement from './Contact'; +import LicenseElement from './License'; + +class Info extends ObjectElement { + constructor(content?: Record, meta?: Meta, attributes?: Attributes) { + super(content, meta, attributes); + this.element = 'info'; + this.classes.push('info'); + } + + get title(): StringElement | undefined { + return this.get('title'); + } + + set title(title: StringElement | undefined) { + this.set('title', title); + } + + get description(): StringElement | undefined { + return this.get('description'); + } + + set description(description: StringElement | undefined) { + this.set('description', description); + } + + get termsOfService(): StringElement | undefined { + return this.get('termsOfService'); + } + + set termsOfService(tos: StringElement | undefined) { + this.set('termsOfService', tos); + } + + get contact(): ContactElement | undefined { + return this.get('contact'); + } + + set contact(contactElement: ContactElement | undefined) { + this.set('contact', contactElement); + } + + get license(): LicenseElement | undefined { + return this.get('license'); + } + + set license(licenseElement: LicenseElement | undefined) { + this.set('license', licenseElement); + } + + get version(): StringElement | undefined { + return this.get('version'); + } + + set version(version: StringElement | undefined) { + this.set('version', version); + } +} + +export default Info; diff --git a/packages/apidom-ns-openapi-2/src/index.ts b/packages/apidom-ns-openapi-2/src/index.ts index 40a9478e6..22f25487b 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 { + isInfoElement, isLicenseElement, isContactElement, isExternalDocumentationElement, @@ -34,6 +35,7 @@ export { keyMap, getNodeType } from './traversal/visitor'; // OpenAPI 2.0 elements export { + InfoElement, LicenseElement, ContactElement, ExternalDocumentationElement, diff --git a/packages/apidom-ns-openapi-2/src/namespace.ts b/packages/apidom-ns-openapi-2/src/namespace.ts index 16cacfc4b..f28776e8c 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 InfoElement from './elements/Info'; import LicenseElement from './elements/License'; import ContactElement from './elements/Contact'; import ExternalDocumentation from './elements/ExternalDocumentation'; @@ -13,6 +14,7 @@ const openApi2 = { namespace: (options: NamespacePluginOptions) => { const { base } = options; + base.register('info', InfoElement); base.register('license', LicenseElement); base.register('contact', ContactElement); base.register('externalDocumentation', ExternalDocumentation); diff --git a/packages/apidom-ns-openapi-2/src/predicates.ts b/packages/apidom-ns-openapi-2/src/predicates.ts index 8909c4747..08e78ddc2 100644 --- a/packages/apidom-ns-openapi-2/src/predicates.ts +++ b/packages/apidom-ns-openapi-2/src/predicates.ts @@ -1,5 +1,6 @@ import { createPredicate } from '@swagger-api/apidom-core'; +import InfoElement from './elements/Info'; import LicenseElement from './elements/License'; import ContactElement from './elements/Contact'; import ExternalDocumentation from './elements/ExternalDocumentation'; @@ -9,6 +10,16 @@ import SecuritySchemeElement from './elements/SecurityScheme'; import SecurityRequirementElement from './elements/SecurityRequirement'; import ScopesElement from './elements/Scopes'; +export const isInfoElement = createPredicate( + ({ hasBasicElementProps, isElementType, primitiveEq }) => { + return (element: any) => + element instanceof InfoElement || + (hasBasicElementProps(element) && + isElementType('info', element) && + primitiveEq('object', element)); + }, +); + export const isLicenseElement = 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 abf1cf912..bae195c77 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 InfoElement from '../elements/Info'; import LicenseElement from '../elements/License'; import ContactElement from '../elements/Contact'; import ExternalDocumentationElement from '../elements/ExternalDocumentation'; @@ -9,6 +10,7 @@ import SecurityRequirementElement from '../elements/SecurityRequirement'; import { createRefractor } from './index'; // register refractors specific to element types +InfoElement.refract = createRefractor(['visitors', 'document', 'objects', 'Info', '$visitor']); LicenseElement.refract = createRefractor([ 'visitors', 'document', @@ -55,6 +57,7 @@ SecurityRequirementElement.refract = createRefractor([ ]); export { + InfoElement, LicenseElement, ContactElement, ExternalDocumentationElement, diff --git a/packages/apidom-ns-openapi-2/src/refractor/specification.ts b/packages/apidom-ns-openapi-2/src/refractor/specification.ts index a5e00053b..270c66cac 100644 --- a/packages/apidom-ns-openapi-2/src/refractor/specification.ts +++ b/packages/apidom-ns-openapi-2/src/refractor/specification.ts @@ -1,4 +1,6 @@ import FallbackVisitor from './visitors/FallbackVisitor'; +import InfoVisitor from './visitors/open-api-2/info'; +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'; @@ -23,6 +25,21 @@ const specification = { value: FallbackVisitor, document: { objects: { + Info: { + $visitor: InfoVisitor, + fixedFields: { + title: FallbackVisitor, + description: FallbackVisitor, + termsOfService: FallbackVisitor, + contact: { + $ref: '#/visitors/document/objects/Contact', + }, + license: { + $ref: '#/visitors/document/objects/License', + }, + version: InfoVersionVisitor, + }, + }, License: { $visitor: LicenseVisitor, fixedFields: { diff --git a/packages/apidom-ns-openapi-2/src/refractor/visitors/open-api-2/info/VersionVisitor.ts b/packages/apidom-ns-openapi-2/src/refractor/visitors/open-api-2/info/VersionVisitor.ts new file mode 100644 index 000000000..3cb5f633a --- /dev/null +++ b/packages/apidom-ns-openapi-2/src/refractor/visitors/open-api-2/info/VersionVisitor.ts @@ -0,0 +1,18 @@ +import stampit from 'stampit'; +import { StringElement, BREAK, cloneDeep } from '@swagger-api/apidom-core'; + +import FallbackVisitor from '../../FallbackVisitor'; + +const VersionVisitor = stampit(FallbackVisitor, { + methods: { + StringElement(stringElement: StringElement) { + this.element = cloneDeep(stringElement); + this.element.classes.push('api-version'); + this.element.classes.push('version'); + + return BREAK; + }, + }, +}); + +export default VersionVisitor; diff --git a/packages/apidom-ns-openapi-2/src/refractor/visitors/open-api-2/info/index.ts b/packages/apidom-ns-openapi-2/src/refractor/visitors/open-api-2/info/index.ts new file mode 100644 index 000000000..2fe1403ab --- /dev/null +++ b/packages/apidom-ns-openapi-2/src/refractor/visitors/open-api-2/info/index.ts @@ -0,0 +1,18 @@ +import stampit from 'stampit'; +import { always } from 'ramda'; + +import InfoElement from '../../../../elements/Info'; +import FallbackVisitor from '../../FallbackVisitor'; +import FixedFieldsVisitor from '../../generics/FixedFieldsVisitor'; + +const InfoVisitor = stampit(FixedFieldsVisitor, FallbackVisitor, { + props: { + specPath: always(['document', 'objects', 'Info']), + canSupportSpecificationExtensions: true, + }, + init() { + this.element = new InfoElement(); + }, +}); + +export default InfoVisitor; diff --git a/packages/apidom-ns-openapi-2/src/traversal/visitor.ts b/packages/apidom-ns-openapi-2/src/traversal/visitor.ts index 5e2b16602..d7ead7ad2 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 = { + InfoElement: ['content'], LicenseElement: ['content'], ContactElement: ['content'], ExternalDocumentationElement: ['content'], diff --git a/packages/apidom-ns-openapi-2/test/predicates.ts b/packages/apidom-ns-openapi-2/test/predicates.ts index 04405212a..904b43b7f 100644 --- a/packages/apidom-ns-openapi-2/test/predicates.ts +++ b/packages/apidom-ns-openapi-2/test/predicates.ts @@ -1,6 +1,7 @@ import { assert } from 'chai'; import { + InfoElement, LicenseElement, ContactElement, ExternalDocumentationElement, @@ -9,6 +10,7 @@ import { SecuritySchemeElement, ScopesElement, SecurityRequirementElement, + isInfoElement, isLicenseElement, isContactElement, isExternalDocumentationElement, @@ -20,6 +22,63 @@ import { } from '../src'; describe('predicates', function () { + context('isInfoElement', function () { + context('given InfoElement instance value', function () { + specify('should return true', function () { + const element = new InfoElement(); + + assert.isTrue(isInfoElement(element)); + }); + }); + + context('given subtype instance value', function () { + specify('should return true', function () { + // eslint-disable-next-line @typescript-eslint/naming-convention + class InfoSubElement extends InfoElement {} + + assert.isTrue(isInfoElement(new InfoSubElement())); + }); + }); + + context('given non InfoSubElement instance value', function () { + specify('should return false', function () { + assert.isFalse(isInfoElement(1)); + assert.isFalse(isInfoElement(null)); + assert.isFalse(isInfoElement(undefined)); + assert.isFalse(isInfoElement({})); + assert.isFalse(isInfoElement([])); + assert.isFalse(isInfoElement('string')); + }); + }); + + specify('should support duck-typing', function () { + const infoElementDuck = { + _storedElement: 'info', + _content: [], + primitive() { + return 'object'; + }, + get element() { + return this._storedElement; + }, + }; + + const infoElementSwan = { + _storedElement: undefined, + _content: undefined, + primitive() { + return 'swan'; + }, + get length() { + return 0; + }, + }; + + assert.isTrue(isInfoElement(infoElementDuck)); + assert.isFalse(isInfoElement(infoElementSwan)); + }); + }); + context('isLicenseElement', function () { context('given LicenseElement instance value', function () { specify('should return true', function () { diff --git a/packages/apidom-ns-openapi-2/test/refractor/elements/Info/__snapshots__/index.ts.snap b/packages/apidom-ns-openapi-2/test/refractor/elements/Info/__snapshots__/index.ts.snap new file mode 100644 index 000000000..5723171b7 --- /dev/null +++ b/packages/apidom-ns-openapi-2/test/refractor/elements/Info/__snapshots__/index.ts.snap @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`refractor elements InfoElement should refract to semantic ApiDOM tree 1`] = ` +(InfoElement + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (StringElement)) + (MemberElement + (StringElement) + (ContactElement)) + (MemberElement + (StringElement) + (LicenseElement)) + (MemberElement + (StringElement) + (StringElement))) +`; diff --git a/packages/apidom-ns-openapi-2/test/refractor/elements/Info/index.ts b/packages/apidom-ns-openapi-2/test/refractor/elements/Info/index.ts new file mode 100644 index 000000000..cbe65af0e --- /dev/null +++ b/packages/apidom-ns-openapi-2/test/refractor/elements/Info/index.ts @@ -0,0 +1,23 @@ +import { expect } from 'chai'; +import { sexprs } from '@swagger-api/apidom-core'; + +import { InfoElement } from '../../../../src'; + +describe('refractor', function () { + context('elements', function () { + context('InfoElement', function () { + specify('should refract to semantic ApiDOM tree', function () { + const infoElement = InfoElement.refract({ + title: 'Sample Pet Store App', + description: 'This is a sample server for a pet store.', + termsOfService: 'https://example.com/terms/', + contact: {}, + license: {}, + version: '1.0.1', + }); + + expect(sexprs(infoElement)).toMatchSnapshot(); + }); + }); + }); +});