Skip to content

Commit

Permalink
feat(ns-openapi-2): add support for Reference Object
Browse files Browse the repository at this point in the history
Refs #3097
  • Loading branch information
char0n committed Oct 13, 2023
1 parent 715c4d0 commit 5dbb737
Show file tree
Hide file tree
Showing 13 changed files with 186 additions and 1 deletion.
2 changes: 1 addition & 1 deletion packages/apidom-ns-openapi-2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
19 changes: 19 additions & 0 deletions packages/apidom-ns-openapi-2/src/elements/Reference.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { StringElement, ObjectElement, Attributes, Meta } from '@swagger-api/apidom-core';

class Reference extends ObjectElement {
constructor(content?: Record<string, unknown>, 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;
2 changes: 2 additions & 0 deletions packages/apidom-ns-openapi-2/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export {
isExampleElement,
isHeaderElement,
isTagElement,
isReferenceElement,
isXmlElement,
isSecurityDefinitionsElement,
isSecuritySchemeElement,
Expand All @@ -51,6 +52,7 @@ export {
ExampleElement,
HeaderElement,
TagElement,
ReferenceElement,
XmlElement,
SecurityDefinitionsElement,
SecuritySchemeElement,
Expand Down
2 changes: 2 additions & 0 deletions packages/apidom-ns-openapi-2/src/namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);
Expand Down
11 changes: 11 additions & 0 deletions packages/apidom-ns-openapi-2/src/predicates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) =>
Expand Down
9 changes: 9 additions & 0 deletions packages/apidom-ns-openapi-2/src/refractor/registration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -97,6 +105,7 @@ export {
ExampleElement,
HeaderElement,
TagElement,
ReferenceElement,
XmlElement,
SecurityDefinitionsElement,
SecuritySchemeElement,
Expand Down
8 changes: 8 additions & 0 deletions packages/apidom-ns-openapi-2/src/refractor/specification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -170,6 +172,12 @@ const specification = {
},
},
},
Reference: {
$visitor: ReferenceVisitor,
fixedFields: {
$ref: Reference$RefVisitor,
},
},
Schema: {
$visitor: SchemaVisitor,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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;
1 change: 1 addition & 0 deletions packages/apidom-ns-openapi-2/src/traversal/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const keyMap = {
HeadersElement: ['content'],
HeaderElement: ['content'],
TagElement: ['content'],
ReferenceElement: ['content'],
XmlElement: ['content'],
SecurityDefinitionsElement: ['content'],
SecuritySchemeElement: ['content'],
Expand Down
58 changes: 58 additions & 0 deletions packages/apidom-ns-openapi-2/test/predicates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ExampleElement,
HeaderElement,
TagElement,
ReferenceElement,
XmlElement,
SecurityDefinitionsElement,
SecuritySchemeElement,
Expand All @@ -26,6 +27,7 @@ import {
isExampleElement,
isHeaderElement,
isTagElement,
isReferenceElement,
isXmlElement,
isSecurityDefinitionsElement,
isSecuritySchemeElement,
Expand Down Expand Up @@ -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 () {
Expand Down
Original file line number Diff line number Diff line change
@@ -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)))
`;
Original file line number Diff line number Diff line change
@@ -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();
});
});
});
});

0 comments on commit 5dbb737

Please sign in to comment.