Skip to content

Commit

Permalink
feat(ns-openapi-2): add support for Scopes Object (#3226)
Browse files Browse the repository at this point in the history
Refs #3097
  • Loading branch information
char0n authored Oct 5, 2023
1 parent dab4908 commit 4226eab
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 5 deletions.
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 @@ -208,7 +208,7 @@ Only fully implemented specification objects should be checked here.
- [ ] [Responses Definitions Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-responses-definitions-object)
- [ ] [Security Definitions Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-security-definitions-object)
- [ ] [Security Scheme Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-security-scheme-object)
- [ ] [Scopes Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-scopes-object)
- [x] [Scopes Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-scopes-object)
- [x] [Security Requirement Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-security-requirement-object)
- [ ] [Specification extensions](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-specification-extensions)

10 changes: 10 additions & 0 deletions packages/apidom-ns-openapi-2/src/elements/ScopesElement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ObjectElement, Attributes, Meta } from '@swagger-api/apidom-core';

class Scopes extends ObjectElement {
constructor(content?: Record<string, unknown>, meta?: Meta, attributes?: Attributes) {
super(content, meta, attributes);
this.element = 'scopes';
}
}

export default Scopes;
3 changes: 2 additions & 1 deletion packages/apidom-ns-openapi-2/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ export { default } from './namespace';
export { default as refract, createRefractor } from './refractor';
export { default as specificationObj } from './refractor/specification';

export { isSecurityRequirementElement } from './predicates';
export { isScopesElement, isSecurityRequirementElement } from './predicates';

export { keyMap, getNodeType } from './traversal/visitor';

// OpenAPI 2.0 elements
export { ScopesElement } from './refractor/registration';
export { SecurityRequirementElement } from './refractor/registration';
// NCE types
2 changes: 2 additions & 0 deletions packages/apidom-ns-openapi-2/src/namespace.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { NamespacePluginOptions } from '@swagger-api/apidom-core';

import ScopesElement from './elements/ScopesElement';
import SecurityRequirementElement from './elements/SecurityRequirement';

const openApi2 = {
namespace: (options: NamespacePluginOptions) => {
const { base } = options;

base.register('scopes', ScopesElement);
base.register('securityRequirement', SecurityRequirementElement);

return base;
Expand Down
12 changes: 11 additions & 1 deletion packages/apidom-ns-openapi-2/src/predicates.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import { createPredicate } from '@swagger-api/apidom-core';

import SecurityRequirementElement from './elements/SecurityRequirement';
import ScopesElement from './elements/ScopesElement';

export const isScopesElement = createPredicate(
({ hasBasicElementProps, isElementType, primitiveEq }) => {
return (element: any) =>
element instanceof ScopesElement ||
(hasBasicElementProps(element) &&
isElementType('scopes', element) &&
primitiveEq('object', element));
},
);

// eslint-disable-next-line import/prefer-default-export
export const isSecurityRequirementElement = createPredicate(
({ hasBasicElementProps, isElementType, primitiveEq }) => {
return (element: any) =>
Expand Down
5 changes: 3 additions & 2 deletions packages/apidom-ns-openapi-2/src/refractor/registration.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import ScopesElement from '../elements/ScopesElement';
import SecurityRequirementElement from '../elements/SecurityRequirement';
import { createRefractor } from './index';

// register refractors specific to element types
ScopesElement.refract = createRefractor(['visitors', 'document', 'objects', 'Scopes', '$visitor']);
SecurityRequirementElement.refract = createRefractor([
'visitors',
'document',
Expand All @@ -10,5 +12,4 @@ SecurityRequirementElement.refract = createRefractor([
'$visitor',
]);

// eslint-disable-next-line import/prefer-default-export
export { SecurityRequirementElement };
export { ScopesElement, SecurityRequirementElement };
4 changes: 4 additions & 0 deletions packages/apidom-ns-openapi-2/src/refractor/specification.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import FallbackVisitor from './visitors/FallbackVisitor';
import ScopesVisitor from './visitors/open-api-2/scopes';
import SecurityRequirementVisitor from './visitors/open-api-2/security-requirement';
import SpecificationExtensionVisitor from './visitors/SpecificationExtensionVisitor';

Expand All @@ -19,6 +20,9 @@ const specification = {
SecurityRequirement: {
$visitor: SecurityRequirementVisitor,
},
Scopes: {
$visitor: ScopesVisitor,
},
},
extension: {
$visitor: SpecificationExtensionVisitor,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import stampit from 'stampit';
import { always } from 'ramda';

import ScopesElement from '../../../../elements/ScopesElement';
import MapVisitor from '../../generics/MapVisitor';
import FallbackVisitor from '../../FallbackVisitor';

const ScopesVisitor = stampit(MapVisitor, FallbackVisitor, {
props: {
specPath: always(['value']),
canSupportSpecificationExtensions: true,
},
init() {
this.element = new ScopesElement();
},
});

export default ScopesVisitor;
124 changes: 124 additions & 0 deletions packages/apidom-ns-openapi-2/test/predicates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { assert } from 'chai';

import {
ScopesElement,
SecurityRequirementElement,
isScopesElement,
isSecurityRequirementElement,
} from '../src';

describe('predicates', function () {
context('isScopesElement', function () {
context('given ScopesElement instance value', function () {
specify('should return true', function () {
const element = new ScopesElement();

assert.isTrue(isScopesElement(element));
});
});

context('given subtype instance value', function () {
specify('should return true', function () {
// eslint-disable-next-line @typescript-eslint/naming-convention
class ScopesSubElement extends ScopesElement {}

assert.isTrue(isScopesElement(new ScopesSubElement()));
});
});

context('given non ScopesSubElement instance value', function () {
specify('should return false', function () {
assert.isFalse(isScopesElement(1));
assert.isFalse(isScopesElement(null));
assert.isFalse(isScopesElement(undefined));
assert.isFalse(isScopesElement({}));
assert.isFalse(isScopesElement([]));
assert.isFalse(isScopesElement('string'));
});
});

specify('should support duck-typing', function () {
const scopesElementDuck = {
_storedElement: 'scopes',
_content: [],
primitive() {
return 'object';
},
get element() {
return this._storedElement;
},
};

const scopesElementSwan = {
_storedElement: undefined,
_content: undefined,
primitive() {
return 'swan';
},
get length() {
return 0;
},
};

assert.isTrue(isScopesElement(scopesElementDuck));
assert.isFalse(isScopesElement(scopesElementSwan));
});
});

context('isSecurityRequirementElement', function () {
context('given SecurityRequirementElement instance value', function () {
specify('should return true', function () {
const element = new SecurityRequirementElement();

assert.isTrue(isSecurityRequirementElement(element));
});
});

context('given subtype instance value', function () {
specify('should return true', function () {
// eslint-disable-next-line @typescript-eslint/naming-convention
class SecurityRequirementSubElement extends SecurityRequirementElement {}

assert.isTrue(isSecurityRequirementElement(new SecurityRequirementSubElement()));
});
});

context('given non SecurityRequirementSubElement instance value', function () {
specify('should return false', function () {
assert.isFalse(isSecurityRequirementElement(1));
assert.isFalse(isSecurityRequirementElement(null));
assert.isFalse(isSecurityRequirementElement(undefined));
assert.isFalse(isSecurityRequirementElement({}));
assert.isFalse(isSecurityRequirementElement([]));
assert.isFalse(isSecurityRequirementElement('string'));
});
});

specify('should support duck-typing', function () {
const securityRequirementElementDuck = {
_storedElement: 'securityRequirement',
_content: [],
primitive() {
return 'object';
},
get element() {
return this._storedElement;
},
};

const securityRequirementElementSwan = {
_storedElement: undefined,
_content: undefined,
primitive() {
return 'swan';
},
get length() {
return 0;
},
};

assert.isTrue(isSecurityRequirementElement(securityRequirementElementDuck));
assert.isFalse(isSecurityRequirementElement(securityRequirementElementSwan));
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`refractor elements ScopesElement should refract to semantic ApiDOM tree 1`] = `
(ScopesElement
(MemberElement
(StringElement)
(StringElement))
(MemberElement
(StringElement)
(StringElement))
(MemberElement
(StringElement)
(StringElement)))
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { expect, assert } from 'chai';
import { sexprs, includesClasses } from '@swagger-api/apidom-core';

import { ScopesElement } from '../../../../src';

describe('refractor', function () {
context('elements', function () {
context('ScopesElement', function () {
specify('should refract to semantic ApiDOM tree', function () {
const scopesElement = ScopesElement.refract({
'write:pets': 'modify pets in your account',
'read:pets': 'read your pets',
'x-extension': 'extension',
});

expect(sexprs(scopesElement)).toMatchSnapshot();
});

specify('should support specification extensions', function () {
const scopesElement = ScopesElement.refract({
'write:pets': 'modify pets in your account',
'read:pets': 'read your pets',
'x-extension': 'extension',
}) as ScopesElement;

assert.isFalse(
includesClasses(['specification-extension'], scopesElement.getMember('write:pets')),
);
assert.isTrue(
includesClasses(['specification-extension'], scopesElement.getMember('x-extension')),
);
});
});
});
});

0 comments on commit 4226eab

Please sign in to comment.