-
Notifications
You must be signed in to change notification settings - Fork 74
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
THREESCALE-3927 Update Swagger to Open API 3.0 (#3103)
Co-authored-by: Joan Lledó <jlledo@redhat.com> Co-authored-by: Daria Mayorova <mayorova@users.noreply.github.com> Co-authored-by: josemigallas <josemigallas@gmail.com>
- Loading branch information
1 parent
b9471ca
commit a115df9
Showing
33 changed files
with
14,874 additions
and
12,893 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
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,24 @@ | ||
// We can define the 3scale plugins here and export the modified bundle | ||
import 'swagger-ui/dist/swagger-ui.css' | ||
|
||
import { renderSwaggerUI } from 'ActiveDocs/ThreeScaleApiDocs' | ||
|
||
import 'ActiveDocs/swagger-ui-3-provider-patch.scss' | ||
|
||
const renderActiveDocs = async () => { | ||
const containerId = 'api-containers' | ||
const container = document.getElementById(containerId) | ||
|
||
if (!container) { | ||
console.error(`The target element with ID ${containerId} was not found`) | ||
return | ||
} | ||
|
||
const { baseUrl = '', apiDocsPath = '', apiDocsAccountDataPath = '' } = container.dataset | ||
|
||
await renderSwaggerUI(container, apiDocsPath, baseUrl, apiDocsAccountDataPath) | ||
} | ||
|
||
document.addEventListener('DOMContentLoaded', () => { | ||
renderActiveDocs().catch(error => { console.error(error) }) | ||
}) |
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,27 +1,37 @@ | ||
import SwaggerUI from 'swagger-ui' | ||
import 'swagger-ui/dist/swagger-ui.css' | ||
|
||
import { autocompleteInterceptor } from 'ActiveDocs/OAS3Autocomplete' | ||
import { autocompleteRequestInterceptor } from 'ActiveDocs/OAS3Autocomplete' | ||
|
||
import type { AccountDataResponse } from 'Types/SwaggerTypes' | ||
|
||
import 'ActiveDocs/swagger-ui-3-provider-patch.scss' | ||
import { fetchData } from '../src/utilities/fetchData' | ||
|
||
document.addEventListener('DOMContentLoaded', () => { | ||
const renderActiveDocs = async () => { | ||
const containerId = 'swagger-ui-container' | ||
const DATA_URL = 'p/admin/api_docs/account_data.json' | ||
const container = document.getElementById(containerId) | ||
|
||
if (!container) { | ||
throw new Error('The target ID was not found: ' + containerId) | ||
console.error(`Element with ID ${containerId} not found`) | ||
return | ||
} | ||
const { url = '', baseUrl = '', serviceEndpoint = '' } = container.dataset | ||
const accountDataUrl = `${baseUrl}${DATA_URL}` | ||
|
||
const responseInterceptor: SwaggerUI.SwaggerUIOptions['responseInterceptor'] = (response) => autocompleteInterceptor(response, accountDataUrl, serviceEndpoint, url) | ||
const accountData: AccountDataResponse = await fetchData<AccountDataResponse>(accountDataUrl) | ||
|
||
const requestInterceptor: SwaggerUI.SwaggerUIOptions['requestInterceptor'] = (request) => autocompleteRequestInterceptor(request, accountData, serviceEndpoint) | ||
|
||
SwaggerUI({ | ||
url, | ||
// eslint-disable-next-line @typescript-eslint/naming-convention -- SwaggerUI API | ||
dom_id: `#${containerId}`, | ||
responseInterceptor | ||
requestInterceptor | ||
}) | ||
} | ||
|
||
document.addEventListener('DOMContentLoaded', () => { | ||
renderActiveDocs().catch(error => { console.error(error) }) | ||
}) |
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 |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import SwaggerUI from 'swagger-ui' | ||
// this is how SwaggerUI imports this function https://github.com/swagger-api/swagger-ui/pull/6208 | ||
import { execute } from 'swagger-client/es/execute' | ||
|
||
import { fetchData } from 'utilities/fetchData' | ||
import { safeFromJsonString } from 'utilities/json-utils' | ||
import { autocompleteRequestInterceptor } from 'ActiveDocs/OAS3Autocomplete' | ||
|
||
import type { AccountDataResponse, ApiDocsServices, BackendApiReportBody, BackendApiTransaction, BodyValue, BodyValueObject, FormData } from 'Types/SwaggerTypes' | ||
import type { ExecuteData } from 'swagger-client/es/execute' | ||
import type { SwaggerUIPlugin } from 'swagger-ui' | ||
|
||
const getApiSpecUrl = (baseUrl: string, specPath: string): string => { | ||
return `${baseUrl.replace(/\/$/, '')}${specPath}` | ||
} | ||
|
||
const appendSwaggerDiv = (container: HTMLElement, id: string): void => { | ||
const div = document.createElement('div') | ||
div.setAttribute('class', 'api-docs-wrap') | ||
div.setAttribute('id', id) | ||
|
||
container.appendChild(div) | ||
} | ||
|
||
/** | ||
* A recursive function that traverses the tree of the multi-level object `data` | ||
* and for every leaf (i.e. value of primitive type) adds the value to `formData` single-level object, | ||
* with the key that is the `path` to that leaf, e.g. 'paramName[nestedArray][0][arrayProp]' | ||
* @param formData single-level object used as accumulator | ||
* @param data current node of the object | ||
* @param parentKey part of the formData key inherited from the upper level | ||
*/ | ||
const buildFormData = (formData: FormData, data: BodyValue, parentKey?: string) => { | ||
if (data && typeof data === 'object') { | ||
const dataObject = data as BodyValueObject | ||
Object.keys(dataObject).forEach((key: string) => { | ||
buildFormData(formData, dataObject[key], parentKey ? `${parentKey}[${key}]` : key) | ||
}) | ||
} else { | ||
if (parentKey) { | ||
formData[parentKey] = data ? data : '' | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Transforms an object into form data representation. Does not URL-encode, because it will be done by | ||
* swagger-client itself | ||
* Returns an empty object if the argument is not an object | ||
* Example: | ||
* { | ||
* a_string: 'hello', | ||
* an_array: [ | ||
* { first: 1 }, | ||
* { second: 1, extra_param: 'with whitespace'} | ||
* ] | ||
* } | ||
* => | ||
* { | ||
* a_string: 'hello', | ||
* 'an_array[0][first]': '1', | ||
* 'an_array[1][second]': '1', | ||
* 'an_array[1][extra_param]': 'with whitespace' | ||
* } | ||
* @param object | ||
*/ | ||
export const objectToFormData = (object: BodyValueObject): FormData => { | ||
if (typeof object !== 'object' || Array.isArray(object)) { | ||
return {} | ||
} | ||
const formData: FormData = {} | ||
buildFormData(formData, object) | ||
return formData | ||
} | ||
|
||
/** | ||
* Transforms the request body of the Service Management API Report, as | ||
* SwaggerUI (or rather swagger-js) does not serialize arrays of objects properly | ||
* The hack is to process the request body in two steps: | ||
* 1. normalize the 'transactions' array, as its elements may be either an object (if the value is taken | ||
* from the example in the spec), or as a serialized JSON (if the field is changed manually) | ||
* 2. "flatten" the objects by transforming them into form-data structure with the entries like | ||
* 'transactions[0][app_id]': 'example' | ||
* 'transactions[0][usage][hits]': 1 | ||
* @param body BackendApiReportBody | ||
*/ | ||
export const transformReportRequestBody = (body: BackendApiReportBody): FormData => { | ||
if (Array.isArray(body.transactions)) { | ||
body.transactions = body.transactions.reduce((acc: BackendApiTransaction[], transaction) => { | ||
let value = undefined | ||
if (typeof transaction === 'object') { | ||
value = transaction | ||
} else { | ||
value = safeFromJsonString<BackendApiTransaction>(transaction) | ||
} | ||
if (value) { | ||
acc.push(value) | ||
} | ||
return acc | ||
}, []) | ||
} | ||
return objectToFormData(body) | ||
} | ||
|
||
const RequestBodyTransformerPlugin: SwaggerUIPlugin = () => { | ||
return { | ||
fn: { | ||
execute: (req: ExecuteData): unknown => { | ||
if (req.contextUrl.includes('api_docs/services/service_management_api.json') | ||
&& req.operationId === 'report' | ||
&& req.requestBody) { | ||
req.requestBody = transformReportRequestBody(req.requestBody as BackendApiReportBody) | ||
} | ||
|
||
return execute(req) | ||
} | ||
} | ||
} | ||
} | ||
|
||
export const renderSwaggerUI = async (container: HTMLElement, apiDocsPath: string, baseUrl: string, accountDataUrl: string): Promise<void> => { | ||
const apiSpecs: ApiDocsServices = await fetchData<ApiDocsServices>(apiDocsPath) | ||
|
||
const accountData: AccountDataResponse = await fetchData<AccountDataResponse>(accountDataUrl) | ||
|
||
apiSpecs.apis.forEach( api => { | ||
const domId = api.system_name.replace(/_/g, '-') | ||
const url = getApiSpecUrl(baseUrl, api.path) | ||
appendSwaggerDiv(container, domId) | ||
SwaggerUI({ | ||
url, | ||
// eslint-disable-next-line @typescript-eslint/naming-convention -- Swagger UI | ||
dom_id: `#${domId}`, | ||
requestInterceptor: (request) => autocompleteRequestInterceptor(request, accountData, ''), | ||
tryItOutEnabled: true, | ||
plugins: [ | ||
RequestBodyTransformerPlugin | ||
] | ||
}) | ||
}) | ||
} |
Oops, something went wrong.