-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Security Solution] Implement OpenAPI specs merger utility (#188110)
**Addresses:** #186356 ## Summary This PR adds OpenAPI spec files merger utility (programmatic API). It provides a similar functionality as `npx @redocly/cli join` does and takes into account [discussion results](#183019 (comment)) - provides a simple way to produce a single Kibana OpenAPI bundle - extends `requestBody` and `responses` MIME types with a version parameters `Elastic-Api-Version=<version>` to avoid different API endpoint versions conflicts - has flexibility to adjust Kibana needs The utility is exposed from `kbn-openapi-bundler` package. ## Details **OpenAPI merger** is a tool for merging multiple OpenAPI specification files. It's useful to merge already processed specification files to produce a result bundle. **OpenAPI bundler** uses the merger under the hood to merge bundled OpenAPI specification files. Exposed externally merger is a wrapper of the bundler's merger but extended with an ability to parse JSON files and forced to produce a single result file. It is able to read OpenAPI spec files defined in JSON and YAML formats. The result file is always written in YAML format where every `requestBody` and response in `responses` extended with document's `info.version` value added as a MIME type parameter `Elastic-Api-Version=<version>`. Currently package supports only programmatic API. As the next step you need to create a JavaScript script file like below ```ts require('../../src/setup_node_env'); const { resolve } = require('path'); const { merge } = require('@kbn/openapi-bundler'); const { REPO_ROOT } = require('@kbn/repo-info'); (async () => { await merge({ sourceGlobs: [ `${REPO_ROOT}/my/path/to/spec1.json`, `${REPO_ROOT}/my/path/to/spec2.yml`, `${REPO_ROOT}/my/path/to/spec3.yaml`, ], outputFilePath: `${REPO_ROOT}/oas_docs/bundle.yaml`, mergedSpecInfo: { title: 'My merged OpenAPI specs', version: '1.0.0', }, }); })(); ``` Finally you should be able to run OpenAPI merger via ```bash node ./path/to/the/script.js ``` or it could be added to `package.json` and run via `yarn`. After running the script it will log different information and write a merged OpenAPI specification to a the provided path. ## Caveats Items below don't look critical at the moment and can be addressed later on. - It doesn't support merging of specs having different OpenAPI versions (Kibana's OpenAPI specs use version `3.0.x` but we should keep an eye on that) - It doesn't support top level `$ref` for - Path item - Request body - Responses
- Loading branch information
Showing
50 changed files
with
1,590 additions
and
214 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
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 |
---|---|---|
|
@@ -7,3 +7,4 @@ | |
*/ | ||
|
||
export * from './src/openapi_bundler'; | ||
export * from './src/openapi_merger'; |
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
82 changes: 82 additions & 0 deletions
82
packages/kbn-openapi-bundler/src/bundler/merge_documents/enrich_with_version_mime_param.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,82 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0 and the Server Side Public License, v 1; you may not use this file except | ||
* in compliance with, at your election, the Elastic License 2.0 or the Server | ||
* Side Public License, v 1. | ||
*/ | ||
|
||
import { OpenAPIV3 } from 'openapi-types'; | ||
import { ResolvedDocument } from '../ref_resolver/resolved_document'; | ||
import { isRefNode } from '../process_document'; | ||
import { getOasDocumentVersion } from '../../utils/get_oas_document_version'; | ||
import { KNOWN_HTTP_METHODS } from './http_methods'; | ||
|
||
export function enrichWithVersionMimeParam(resolvedDocuments: ResolvedDocument[]): void { | ||
for (const resolvedDocument of resolvedDocuments) { | ||
const version = getOasDocumentVersion(resolvedDocument); | ||
const paths = resolvedDocument.document.paths as OpenAPIV3.PathsObject; | ||
|
||
for (const path of Object.keys(paths ?? {})) { | ||
const pathItemObj = paths[path]; | ||
|
||
for (const httpVerb of KNOWN_HTTP_METHODS) { | ||
const operationObj = pathItemObj?.[httpVerb]; | ||
|
||
if (operationObj?.requestBody && !isRefNode(operationObj.requestBody)) { | ||
const requestBodyContent = operationObj.requestBody.content; | ||
|
||
enrichContentWithVersion(requestBodyContent, version); | ||
} | ||
|
||
enrichCollection(operationObj?.responses ?? {}, version); | ||
} | ||
} | ||
|
||
if (resolvedDocument.document.components) { | ||
const components = resolvedDocument.document.components as OpenAPIV3.ComponentsObject; | ||
|
||
if (components.requestBodies) { | ||
enrichCollection(components.requestBodies, version); | ||
} | ||
|
||
if (components.responses) { | ||
enrichCollection(components.responses, version); | ||
} | ||
} | ||
} | ||
} | ||
|
||
function enrichCollection( | ||
collection: Record< | ||
string, | ||
{ content?: Record<string, OpenAPIV3.MediaTypeObject> } | OpenAPIV3.ReferenceObject | ||
>, | ||
version: string | ||
) { | ||
for (const name of Object.keys(collection)) { | ||
const obj = collection[name]; | ||
|
||
if (!obj || isRefNode(obj) || !obj.content) { | ||
continue; | ||
} | ||
|
||
enrichContentWithVersion(obj.content, version); | ||
} | ||
} | ||
|
||
function enrichContentWithVersion( | ||
content: Record<string, OpenAPIV3.MediaTypeObject>, | ||
version: string | ||
): void { | ||
for (const mimeType of Object.keys(content)) { | ||
if (mimeType.includes('; Elastic-Api-Version=')) { | ||
continue; | ||
} | ||
|
||
const mimeTypeWithVersionParam = `${mimeType}; Elastic-Api-Version=${version}`; | ||
|
||
content[mimeTypeWithVersionParam] = content[mimeType]; | ||
delete content[mimeType]; | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
packages/kbn-openapi-bundler/src/bundler/merge_documents/http_methods.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,20 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0 and the Server Side Public License, v 1; you may not use this file except | ||
* in compliance with, at your election, the Elastic License 2.0 or the Server | ||
* Side Public License, v 1. | ||
*/ | ||
|
||
import { OpenAPIV3 } from 'openapi-types'; | ||
|
||
export const KNOWN_HTTP_METHODS = [ | ||
OpenAPIV3.HttpMethods.HEAD, | ||
OpenAPIV3.HttpMethods.GET, | ||
OpenAPIV3.HttpMethods.POST, | ||
OpenAPIV3.HttpMethods.PATCH, | ||
OpenAPIV3.HttpMethods.PUT, | ||
OpenAPIV3.HttpMethods.OPTIONS, | ||
OpenAPIV3.HttpMethods.DELETE, | ||
OpenAPIV3.HttpMethods.TRACE, | ||
] as const; |
39 changes: 39 additions & 0 deletions
39
packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_arrays.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,39 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0 and the Server Side Public License, v 1; you may not use this file except | ||
* in compliance with, at your election, the Elastic License 2.0 or the Server | ||
* Side Public License, v 1. | ||
*/ | ||
|
||
/** | ||
* Merges source arrays by merging array items and omitting duplicates. | ||
* Duplicates checked by exacts match. | ||
*/ | ||
export function mergeArrays<T>(sources: Array<readonly T[]>): T[] { | ||
const merged: T[] = []; | ||
const seen = new Set<string>(); | ||
|
||
for (const itemsSource of sources) { | ||
for (const item of itemsSource) { | ||
const searchableItem = toString(item); | ||
|
||
if (seen.has(searchableItem)) { | ||
continue; | ||
} | ||
|
||
merged.push(item); | ||
seen.add(searchableItem); | ||
} | ||
} | ||
|
||
return merged; | ||
} | ||
|
||
function toString(value: unknown): string { | ||
try { | ||
return JSON.stringify(value); | ||
} catch { | ||
throw new Error('Unable to merge arrays - encountered value is not serializable'); | ||
} | ||
} |
Oops, something went wrong.