-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ns-openapi-3-1): make servers refractor plugin idempotent (#4152)
Refs #4134
- Loading branch information
Showing
5 changed files
with
241 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import type { Namespace } from '@swagger-api/apidom-core'; | ||
import { Element } from '@swagger-api/apidom-core'; | ||
import { | ||
PathItemServersElement, | ||
OperationServersElement, | ||
|
@@ -9,7 +9,8 @@ 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'; | ||
import type { Toolbox } from '../toolbox'; | ||
import NormalizeStorage from './normalize-header-examples/NormalizeStorage'; | ||
|
||
/** | ||
* Override of Server Objects. | ||
|
@@ -24,36 +25,60 @@ import type { Predicates } from '../toolbox'; | |
* If an alternative server object is specified at the Operation Object level, it will override PathItem.servers and OpenAPI.servers respectively. | ||
*/ | ||
|
||
interface PluginOptions { | ||
storageField?: string; | ||
} | ||
|
||
/* eslint-disable no-param-reassign */ | ||
const plugin = | ||
() => | ||
({ predicates, namespace }: { predicates: Predicates; namespace: Namespace }) => { | ||
({ storageField = 'x-normalized' }: PluginOptions = {}) => | ||
(toolbox: Toolbox) => { | ||
const { namespace, ancestorLineageToJSONPointer, predicates } = toolbox; | ||
let storage: NormalizeStorage | undefined; | ||
|
||
return { | ||
visitor: { | ||
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); | ||
} | ||
OpenApi3_1Element: { | ||
enter(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); | ||
} | ||
storage = new NormalizeStorage(openapiElement, storageField, 'servers'); | ||
}, | ||
leave() { | ||
storage = undefined; | ||
}, | ||
}, | ||
PathItemElement( | ||
pathItemElement: PathItemElement, | ||
key: any, | ||
parent: any, | ||
path: any, | ||
ancestors: any[], | ||
key: string | number, | ||
parent: Element | undefined, | ||
path: (string | number)[], | ||
ancestors: [Element | Element[]], | ||
) { | ||
// skip visiting this Path Item | ||
if (ancestors.some(predicates.isComponentsElement)) return; | ||
if (!ancestors.some(predicates.isOpenApi3_1Element)) return; | ||
|
||
const pathItemJSONPointer = ancestorLineageToJSONPointer([ | ||
...ancestors, | ||
parent!, | ||
pathItemElement, | ||
]); | ||
|
||
// skip visiting this Path Item Object if it's already normalized | ||
if (storage!.includes(pathItemJSONPointer)) { | ||
return; | ||
} | ||
|
||
const parentOpenapiElement = ancestors.find(predicates.isOpenApi3_1Element); | ||
const isServersUndefined = typeof pathItemElement.servers === 'undefined'; | ||
const isServersArrayElement = predicates.isArrayElement(pathItemElement.servers); | ||
|
@@ -71,19 +96,31 @@ const plugin = | |
pathItemElement.servers!.push(server); | ||
}); | ||
} | ||
storage!.append(pathItemJSONPointer); | ||
} | ||
}, | ||
OperationElement( | ||
operationElement: OperationElement, | ||
key: any, | ||
parent: any, | ||
path: any, | ||
ancestors: any[], | ||
key: string | number, | ||
parent: Element | undefined, | ||
path: (string | number)[], | ||
ancestors: [Element | Element[]], | ||
) { | ||
// skip visiting this Operation | ||
if (ancestors.some(predicates.isComponentsElement)) return; | ||
if (!ancestors.some(predicates.isOpenApi3_1Element)) return; | ||
|
||
const operationJSONPointer = ancestorLineageToJSONPointer([ | ||
...ancestors, | ||
parent!, | ||
operationElement, | ||
]); | ||
|
||
// skip visiting this Operation Object if it's already normalized | ||
if (storage!.includes(operationJSONPointer)) { | ||
return; | ||
} | ||
|
||
// @TODO([email protected]): can be replaced by Array.prototype.findLast in future | ||
const parentPathItemElement = [...ancestors].reverse().find(predicates.isPathItemElement); | ||
const isServersUndefined = typeof operationElement.servers === 'undefined'; | ||
|
@@ -102,6 +139,7 @@ const plugin = | |
operationElement.servers!.push(server); | ||
}); | ||
} | ||
storage!.append(operationJSONPointer); | ||
} | ||
}, | ||
}, | ||
|
18 changes: 18 additions & 0 deletions
18
...ns-openapi-3-1/test/refractor/plugins/normalize-servers/__snapshots__/idempotence.ts.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`refractor plugins normalize-servers should have idempotent characteristics 1`] = ` | ||
Object { | ||
openapi: 3.1.0, | ||
paths: Object { | ||
/: Object { | ||
get: Object {}, | ||
}, | ||
}, | ||
servers: Array [ | ||
Object { | ||
description: production server, | ||
url: https://example.com/, | ||
}, | ||
], | ||
} | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
packages/apidom-ns-openapi-3-1/test/refractor/plugins/normalize-servers/idempotence.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { expect } from 'chai'; | ||
import dedent from 'dedent'; | ||
import { toValue, dispatchRefractorPlugins } from '@swagger-api/apidom-core'; | ||
import { parse } from '@swagger-api/apidom-parser-adapter-yaml-1-2'; | ||
|
||
import { | ||
createToolbox, | ||
OpenApi3_1Element, | ||
refractorPluginNormalizeServers, | ||
keyMap, | ||
getNodeType, | ||
} from '../../../../src'; | ||
|
||
describe('refractor', function () { | ||
context('plugins', function () { | ||
context('normalize-servers', function () { | ||
specify('should have idempotent characteristics', async function () { | ||
const yamlDefinition = dedent` | ||
openapi: 3.1.0 | ||
servers: | ||
- url: https://example.com/ | ||
description: production server | ||
paths: | ||
/: | ||
get: {} | ||
`; | ||
const apiDOM = await parse(yamlDefinition); | ||
const openApiElement = OpenApi3_1Element.refract(apiDOM.result) as OpenApi3_1Element; | ||
const options = { | ||
toolboxCreator: createToolbox, | ||
visitorOptions: { keyMap, nodeTypeGetter: getNodeType }, | ||
}; | ||
|
||
dispatchRefractorPlugins(openApiElement, [refractorPluginNormalizeServers()], options); | ||
dispatchRefractorPlugins(openApiElement, [refractorPluginNormalizeServers()], options); | ||
dispatchRefractorPlugins(openApiElement, [refractorPluginNormalizeServers()], options); | ||
|
||
expect(toValue(apiDOM.result)).toMatchSnapshot(); | ||
}); | ||
}); | ||
}); | ||
}); |
58 changes: 58 additions & 0 deletions
58
packages/apidom-ns-openapi-3-1/test/refractor/plugins/normalize-servers/storage-fields.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { assert } from 'chai'; | ||
import dedent from 'dedent'; | ||
import { toValue } from '@swagger-api/apidom-core'; | ||
import { parse } from '@swagger-api/apidom-parser-adapter-yaml-1-2'; | ||
|
||
import { OpenApi3_1Element, refractorPluginNormalizeServers } from '../../../../src'; | ||
|
||
describe('refractor', function () { | ||
context('plugins', function () { | ||
context('normalize-servers', function () { | ||
specify('should use sub-field to store normalized scopes', async function () { | ||
const yamlDefinition = dedent` | ||
openapi: 3.1.0 | ||
servers: | ||
- url: https://example.com/ | ||
description: production server | ||
paths: | ||
/: | ||
get: {} | ||
`; | ||
const apiDOM = await parse(yamlDefinition); | ||
const openApiElement = OpenApi3_1Element.refract(apiDOM.result, { | ||
plugins: [refractorPluginNormalizeServers()], | ||
}) as OpenApi3_1Element; | ||
|
||
assert.deepEqual(toValue(openApiElement.get('x-normalized')), { | ||
servers: ['/paths/~1', '/paths/~1/get'], | ||
}); | ||
}); | ||
|
||
context('given custom storage field', function () { | ||
specify('should use custom storage field to store normalized scopes', async function () { | ||
const yamlDefinition = dedent` | ||
openapi: 3.1.0 | ||
servers: | ||
- url: https://example.com/ | ||
description: production server | ||
paths: | ||
/: | ||
get: {} | ||
`; | ||
const apiDOM = await parse(yamlDefinition); | ||
const openApiElement = OpenApi3_1Element.refract(apiDOM.result, { | ||
plugins: [ | ||
refractorPluginNormalizeServers({ | ||
storageField: '$$normalized', | ||
}), | ||
], | ||
}) as OpenApi3_1Element; | ||
|
||
assert.deepEqual(toValue(openApiElement.get('$$normalized')), { | ||
servers: ['/paths/~1', '/paths/~1/get'], | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |