Skip to content

Commit

Permalink
fix(ns-openapi-3-1): fix normalize-servers refractor plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
char0n committed Nov 3, 2023
1 parent 8d58555 commit dc3a27c
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 62 deletions.
1 change: 1 addition & 0 deletions packages/apidom-ns-openapi-3-0/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export {
isServerElement,
isServerVariableElement,
isMediaTypeElement,
isServersElement,
} from './predicates';

export {
Expand Down
13 changes: 13 additions & 0 deletions packages/apidom-ns-openapi-3-0/src/predicates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import SecurityRequirementElement from './elements/SecurityRequirement';
import ServerElement from './elements/Server';
import ServerVariableElement from './elements/ServerVariable';
import MediaTypeElement from './elements/MediaType';
// NCE types
import ServersElement from './elements/nces/Servers';

export const isCallbackElement = createPredicate(
({ hasBasicElementProps, isElementType, primitiveEq }) => {
Expand Down Expand Up @@ -324,3 +326,14 @@ export const isMediaTypeElement = createPredicate(
primitiveEq('object', element));
},
);

export const isServersElement = createPredicate(
({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => {
return (element: unknown): element is ServersElement =>
element instanceof ServersElement ||
(hasBasicElementProps(element) &&
isElementType('array', element) &&
primitiveEq('array', element) &&
hasClass('servers', element));
},
);
1 change: 1 addition & 0 deletions packages/apidom-ns-openapi-3-1/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export {
isResponseLikeElement,
isOpenApiExtension,
isServerLikeElement,
isServersElement,
} from '@swagger-api/apidom-ns-openapi-3-0';

export { keyMap, getNodeType } from './traversal/visitor';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { last } from 'ramda';
import type { Namespace } from '@swagger-api/apidom-core';
import {
PathItemServersElement,
OperationServersElement,
ServersElement,
} from '@swagger-api/apidom-ns-openapi-3-0';

import OpenApi3_1Element from '../../elements/OpenApi3-1';
import PathItemElement from '../../elements/PathItem';
import ServerElement from '../../elements/Server';
import OperationElement from '../../elements/Operation';
import { Predicates } from '../toolbox';
import type OpenApi3_1Element from '../../elements/OpenApi3-1';
import type PathItemElement from '../../elements/PathItem';
import type ServerElement from '../../elements/Server';
import type OperationElement from '../../elements/Operation';
import type { Predicates } from '../toolbox';

/**
* Override of Server Objects.
Expand All @@ -26,67 +27,81 @@ import { Predicates } from '../toolbox';
/* eslint-disable no-param-reassign */
const plugin =
() =>
({ predicates }: { predicates: Predicates }) => {
let openAPIServers: ServerElement[] | undefined;
const pathItemServers: (ServerElement[] | undefined)[] = [];

({ predicates, namespace }: { predicates: Predicates; namespace: Namespace }) => {
return {
visitor: {
OpenApi3_1Element: {
enter(openapiElement: OpenApi3_1Element) {
if (predicates.isArrayElement(openapiElement.servers)) {
openAPIServers = openapiElement.servers?.content as ServerElement[];
}
},
leave() {
openAPIServers = undefined;
},
OpenApi3_1Element(openapiElement: OpenApi3_1Element) {
const isServersUndefined = typeof openapiElement.servers === 'undefined';
const isServersArrayElement = predicates.isArrayElement(openapiElement.servers);
const isServersEmpty = isServersArrayElement && openapiElement.servers!.length === 0;
// @ts-ignore
const defaultServer = namespace.elements.Server.refract({ url: '/' });

if (isServersUndefined || !isServersArrayElement) {
openapiElement.servers = new ServersElement([defaultServer]);
} else if (isServersArrayElement && isServersEmpty) {
openapiElement.servers!.push(defaultServer);
}
},
PathItemElement: {
enter(
pathItemElement: PathItemElement,
key: any,
parent: any,
path: any,
ancestors: any[],
) {
// skip visiting this Path Item
if (ancestors.some(predicates.isComponentsElement)) {
return;
}
PathItemElement(
pathItemElement: PathItemElement,
key: any,
parent: any,
path: any,
ancestors: any[],
) {
// skip visiting this Path Item
if (ancestors.some(predicates.isComponentsElement)) return;
if (!ancestors.some(predicates.isOpenApi3_1Element)) return;

// duplicate OpenAPI.servers into this Path Item object
if (
typeof pathItemElement.servers === 'undefined' &&
typeof openAPIServers !== 'undefined'
) {
pathItemElement.servers = new PathItemServersElement(openAPIServers);
}
const parentOpenapiElement = ancestors.find(predicates.isOpenApi3_1Element);
const isServersUndefined = typeof pathItemElement.servers === 'undefined';
const isServersArrayElement = predicates.isArrayElement(pathItemElement.servers);
const isServersEmpty = isServersArrayElement && pathItemElement.servers!.length === 0;

// duplicate OpenAPI.servers into this Path Item object
if (predicates.isOpenApi3_1Element(parentOpenapiElement)) {
const openapiServersContent = parentOpenapiElement.servers?.content;
const openapiServers = (openapiServersContent ?? []) as ServerElement[];

// prepare Server Objects for child Operation Objects
const { servers } = pathItemElement;
if (typeof servers !== 'undefined' && predicates.isArrayElement(servers)) {
pathItemServers.push([...servers.content] as ServerElement[]);
} else {
pathItemServers.push(undefined);
if (isServersUndefined || !isServersArrayElement) {
pathItemElement.servers = new PathItemServersElement(openapiServers);
} else if (isServersArrayElement && isServersEmpty) {
openapiServers.forEach((server) => {
pathItemElement.servers!.push(server);
});
}
},
leave() {
pathItemServers.pop();
},
}
},
OperationElement: {
enter(operationElement: OperationElement) {
const parentPathItemServers = last(pathItemServers);
OperationElement(
operationElement: OperationElement,
key: any,
parent: any,
path: any,
ancestors: any[],
) {
// skip visiting this Operation
if (ancestors.some(predicates.isComponentsElement)) return;
if (!ancestors.some(predicates.isOpenApi3_1Element)) return;

// no Server Objects defined in parents
if (typeof parentPathItemServers === 'undefined') return;
// Server Objects are defined for this Operation Object
if (predicates.isArrayElement(operationElement.servers)) return;
const parentPathItemElement = ancestors.findLast(predicates.isPathItemElement);
const isServersUndefined = typeof operationElement.servers === 'undefined';
const isServersArrayElement = predicates.isArrayElement(operationElement.servers);
const isServersEmpty = isServersArrayElement && operationElement.servers!.length === 0;

// duplicate parent PathItem.servers into this Operation object
operationElement.servers = new OperationServersElement(parentPathItemServers);
},
if (predicates.isPathItemElement(parentPathItemElement)) {
const pathItemServersContent = parentPathItemElement.servers?.content;
const pathItemServers = (pathItemServersContent ?? []) as ServerElement[];

if (isServersUndefined || !isServersArrayElement) {
// duplicate parent PathItem.servers into this Operation object
operationElement.servers = new OperationServersElement(pathItemServers);
} else if (isServersArrayElement && isServersEmpty) {
pathItemServers.forEach((server) => {
operationElement.servers!.push(server);
});
}
}
},
},
};
Expand Down
3 changes: 3 additions & 0 deletions packages/apidom-ns-openapi-3-1/src/refractor/toolbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
isObjectElement,
includesClasses,
} from '@swagger-api/apidom-core';
import { isServersElement } from '@swagger-api/apidom-ns-openapi-3-0';

import * as openApi3_1Predicates from '../predicates';
import openApi3_1Namespace from '../namespace';
Expand All @@ -13,6 +14,7 @@ export type Predicates = typeof openApi3_1Predicates & {
isStringElement: typeof isStringElement;
isArrayElement: typeof isArrayElement;
isObjectElement: typeof isObjectElement;
isServersElement: typeof isServersElement;
includesClasses: typeof includesClasses;
};

Expand All @@ -23,6 +25,7 @@ const createToolbox = () => {
isStringElement,
isArrayElement,
isObjectElement,
isServersElement,
includesClasses,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -521,10 +521,17 @@ exports[`refractor plugins normalize-servers given PathItem.servers defined shou
(StringElement))
(MemberElement
(StringElement)
(StringElement))))))))))))
(StringElement)))))))))))
(MemberElement
(StringElement)
(ArrayElement
(ServerElement
(MemberElement
(StringElement)
(StringElement))))))
`;

exports[`refractor plugins normalize-servers given no servers field is defined should not duplicate any Server Objects 1`] = `
exports[`refractor plugins normalize-servers given no servers field is defined should create default Server Object and duplicate 1`] = `
(OpenApi3_1Element
(MemberElement
(StringElement)
Expand All @@ -537,5 +544,26 @@ exports[`refractor plugins normalize-servers given no servers field is defined s
(PathItemElement
(MemberElement
(StringElement)
(OperationElement)))))))
(OperationElement
(MemberElement
(StringElement)
(ArrayElement
(ServerElement
(MemberElement
(StringElement)
(StringElement)))))))
(MemberElement
(StringElement)
(ArrayElement
(ServerElement
(MemberElement
(StringElement)
(StringElement)))))))))
(MemberElement
(StringElement)
(ArrayElement
(ServerElement
(MemberElement
(StringElement)
(StringElement))))))
`;
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ describe('refractor', function () {
});

context('given no servers field is defined', function () {
specify('should not duplicate any Server Objects', async function () {
specify('should create default Server Object and duplicate', async function () {
const yamlDefinition = dedent`
openapi: 3.1.0
paths:
Expand Down

0 comments on commit dc3a27c

Please sign in to comment.