Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ns-openapi-2): add support for Security Requirement Object #3225

Merged
merged 1 commit into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -209,6 +209,6 @@ Only fully implemented specification objects should be checked here.
- [ ] [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)
- [ ] [Security Requirement Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#user-content-security-requirement-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/SecurityRequirement.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 SecurityRequirement extends ObjectElement {
constructor(content?: Record<string, unknown>, meta?: Meta, attributes?: Attributes) {
super(content, meta, attributes);
this.element = 'securityRequirement';
}
}

export default SecurityRequirement;
29 changes: 28 additions & 1 deletion packages/apidom-ns-openapi-2/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,28 @@
export {};
export {
isRefElement,
isLinkElement as isLinkPrimitiveElement,
isMemberElement,
isObjectElement,
isArrayElement,
isBooleanElement,
isNullElement,
isElement,
isNumberElement,
isStringElement,
} from '@swagger-api/apidom-core';

export { default as mediaTypes, OpenAPIMediaTypes } from './media-types';

// eslint-disable-next-line no-restricted-exports
export { default } from './namespace';

export { default as refract, createRefractor } from './refractor';
export { default as specificationObj } from './refractor/specification';

export { isSecurityRequirementElement } from './predicates';

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

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

type Format = 'generic' | 'json' | 'yaml';

export class OpenAPIMediaTypes extends MediaTypes<string> {
filterByFormat(format: Format = 'generic') {
const effectiveFormat = format === 'generic' ? 'openapi;version' : format;
return this.filter((mediaType) => mediaType.includes(effectiveFormat));
}

findBy(version = '2.0', format: Format = 'generic') {
const search =
format === 'generic'
? `vnd.oai.openapi;version=${version}`
: `vnd.oai.openapi+${format};version=${version}`;
const found = this.find((mediaType) => mediaType.includes(search));

return found || this.unknownMediaType;
}

latest(format: Format = 'generic') {
return last(this.filterByFormat(format)) as string;
}
}

const mediaTypes = new OpenAPIMediaTypes(
'application/vnd.oai.openapi;version=2.0',
'application/vnd.oai.openapi+json;version=2.0',
'application/vnd.oai.openapi+yaml;version=2.0',
);

export default mediaTypes;
15 changes: 15 additions & 0 deletions packages/apidom-ns-openapi-2/src/namespace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { NamespacePluginOptions } from '@swagger-api/apidom-core';

import SecurityRequirementElement from './elements/SecurityRequirement';

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

base.register('securityRequirement', SecurityRequirementElement);

return base;
},
};

export default openApi2;
14 changes: 14 additions & 0 deletions packages/apidom-ns-openapi-2/src/predicates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createPredicate } from '@swagger-api/apidom-core';

import SecurityRequirementElement from './elements/SecurityRequirement';

// eslint-disable-next-line import/prefer-default-export
export const isSecurityRequirementElement = createPredicate(
({ hasBasicElementProps, isElementType, primitiveEq }) => {
return (element: any) =>
element instanceof SecurityRequirementElement ||
(hasBasicElementProps(element) &&
isElementType('securityRequirement', element) &&
primitiveEq('object', element));
},
);
44 changes: 44 additions & 0 deletions packages/apidom-ns-openapi-2/src/refractor/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { invokeArgs } from 'ramda-adjunct';
import {
visit,
Element,
dereference,
refract as baseRefract,
dispatchRefractorPlugins,
} from '@swagger-api/apidom-core';

import specification from './specification';
import { keyMap, getNodeType } from '../traversal/visitor';
import createToolbox from './toolbox';

const refract = <T extends Element>(
value: any,
{ specPath = ['visitors', 'document', 'objects', 'Swagger', '$visitor'], plugins = [] } = {},
): T => {
const element = baseRefract(value);
const resolvedSpec = dereference(specification);

/**
* This is where generic ApiDOM becomes semantic (namespace applied).
* We don't allow consumers to hook into this translation.
* Though we allow consumers to define their onw plugins on already transformed ApiDOM.
*/
const rootVisitor = invokeArgs(specPath, [], resolvedSpec);
// @ts-ignore
visit(element, rootVisitor, { state: { specObj: resolvedSpec } });

/**
* Running plugins visitors means extra single traversal === performance hit.
*/
return dispatchRefractorPlugins(rootVisitor.element, plugins, {
toolboxCreator: createToolbox,
visitorOptions: { keyMap, nodeTypeGetter: getNodeType },
});
};

export const createRefractor =
(specPath: string[]) =>
(value: any, options = {}) =>
refract(value, { specPath, ...options });

export default refract;
8 changes: 8 additions & 0 deletions packages/apidom-ns-openapi-2/src/refractor/predicates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { startsWith } from 'ramda';
import { MemberElement, isStringElement, toValue } from '@swagger-api/apidom-core';

// eslint-disable-next-line import/prefer-default-export
export const isSwaggerExtension = (element: MemberElement): boolean => {
// @ts-ignore
return isStringElement(element.key) && startsWith('x-', toValue(element.key));
};
14 changes: 14 additions & 0 deletions packages/apidom-ns-openapi-2/src/refractor/registration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import SecurityRequirementElement from '../elements/SecurityRequirement';
import { createRefractor } from './index';

// register refractors specific to element types
SecurityRequirementElement.refract = createRefractor([
'visitors',
'document',
'objects',
'SecurityRequirement',
'$visitor',
]);

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

/**
* Specification object allows us to have complete control over visitors
* when traversing the ApiDOM.
* Specification also allows us to create amended refractors from
* existing ones by manipulating it.
*
* Note: Specification object allows to use absolute internal JSON pointers.
*/

const specification = {
visitors: {
value: FallbackVisitor,
document: {
objects: {
SecurityRequirement: {
$visitor: SecurityRequirementVisitor,
},
},
extension: {
$visitor: SpecificationExtensionVisitor,
},
},
},
};

export default specification;
14 changes: 14 additions & 0 deletions packages/apidom-ns-openapi-2/src/refractor/toolbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createNamespace, isStringElement } from '@swagger-api/apidom-core';

import * as openApi2Predicates from '../predicates';
import * as refractorPredicates from './predicates';
import openApi2Namespace from '../namespace';

const createToolbox = () => {
const namespace = createNamespace(openApi2Namespace);
const predicates = { ...refractorPredicates, ...openApi2Predicates, isStringElement };

return { predicates, namespace };
};

export default createToolbox;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import stampit from 'stampit';
import { Element, BREAK, cloneDeep } from '@swagger-api/apidom-core';

import Visitor from './Visitor';

/**
* This visitor is responsible for falling back to current traversed element
* Given OpenApi3_0Visitor expects ObjectElement to be traversed. If
* different Element is provided FallBackVisitor is responsible to assigning
* this Element as current element.
*/
const FallbackVisitor = stampit(Visitor, {
methods: {
enter(element: Element) {
this.element = cloneDeep(element);
return BREAK;
},
},
});

export default FallbackVisitor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import stampit from 'stampit';
import { MemberElement, BREAK, cloneDeep } from '@swagger-api/apidom-core';

import SpecificationVisitor from './SpecificationVisitor';

const SpecificationExtensionVisitor = stampit(SpecificationVisitor, {
methods: {
MemberElement(memberElement: MemberElement) {
this.element = cloneDeep(memberElement);
this.element.classes.push('specification-extension');

return BREAK;
},
},
});

export default SpecificationExtensionVisitor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import stampit from 'stampit';
import { pathSatisfies, path, pick, pipe, keys } from 'ramda';
import { isFunction, isUndefined } from 'ramda-adjunct';
import { visit, cloneDeep } from '@swagger-api/apidom-core';

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

/**
* This is a base Type for every visitor that does
* internal look-ups to retrieve other child visitors.
*/
const SpecificationVisitor = stampit(Visitor, {
props: {
passingOptionsNames: ['specObj', 'openApiGenericElement', 'openApiSemanticElement'],
specObj: null,
openApiGenericElement: null,
openApiSemanticElement: null,
},
init({
// @ts-ignore
specObj = this.specObj,
// @ts-ignore
openApiGenericElement = this.openApiGenericElement,
// @ts-ignore
openApiSemanticElement = this.openApiSemanticElement,
}) {
this.specObj = specObj;
this.openApiGenericElement = openApiGenericElement;
this.openApiSemanticElement = openApiSemanticElement;
},
methods: {
retrievePassingOptions() {
return pick(this.passingOptionsNames, this);
},

retrieveFixedFields(specPath) {
return pipe(path(['visitors', ...specPath, 'fixedFields']), keys)(this.specObj);
},

retrieveVisitor(specPath) {
if (pathSatisfies(isFunction, ['visitors', ...specPath], this.specObj)) {
return path(['visitors', ...specPath], this.specObj);
}

return path(['visitors', ...specPath, '$visitor'], this.specObj);
},

retrieveVisitorInstance(specPath, options = {}) {
const passingOpts = this.retrievePassingOptions();

return this.retrieveVisitor(specPath)({ ...passingOpts, ...options });
},

toRefractedElement(specPath: string[], element, options = {}) {
/**
* This is `Visitor shortcut`: mechanism for short circuiting the traversal and replacing
* it by basic node cloning.
*
* Visiting the element is equivalent to cloning it if the prototype of a visitor
* is the same as the prototype of FallbackVisitor. If that's the case, we can avoid
* bootstrapping the traversal cycle for fields that don't require any special visiting.
*/
const visitor = this.retrieveVisitorInstance(specPath, options);
const visitorPrototype = Object.getPrototypeOf(visitor);

if (isUndefined(this.fallbackVisitorPrototype)) {
this.fallbackVisitorPrototype = Object.getPrototypeOf(
this.retrieveVisitorInstance(['value']),
);
}
if (this.fallbackVisitorPrototype === visitorPrototype) {
return cloneDeep(element);
}

// standard processing continues
visit(element, visitor, { keyMap, nodeTypeGetter: getNodeType, ...options });
return visitor.element;
},
},
});

export default SpecificationVisitor;
19 changes: 19 additions & 0 deletions packages/apidom-ns-openapi-2/src/refractor/visitors/Visitor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import stampit from 'stampit';
import { hasElementSourceMap } from '@swagger-api/apidom-core';

const Visitor = stampit({
props: {
element: null,
},
// @ts-ignore
methods: {
copyMetaAndAttributes(from, to) {
// copy sourcemaps
if (hasElementSourceMap(from)) {
to.meta.set('sourceMap', from.meta.get('sourceMap'));
}
},
},
});

export default Visitor;
Loading