| | import httpStatusCodes from 'http-status-code' |
| | import { get, isPlainObject } from 'lodash-es' |
| | import { parseTemplate } from 'url-template' |
| | import mergeAllOf from 'json-schema-merge-allof' |
| |
|
| | import { renderContent } from './render-content' |
| | import getCodeSamples from './create-rest-examples' |
| | import operationSchema from './operation-schema' |
| | import { validateJson } from '@/tests/lib/validate-json-schema' |
| | import { getBodyParams } from './get-body-params' |
| |
|
| | export default class Operation { |
| | |
| | #operation: any |
| | serverUrl: string |
| | verb: string |
| | requestPath: string |
| | title: string |
| | category: string |
| | subcategory: string |
| | |
| | parameters: any[] |
| | |
| | bodyParameters: any[] |
| | descriptionHTML?: string |
| | |
| | codeExamples?: any[] |
| | |
| | statusCodes?: any[] |
| | previews?: any[] |
| | |
| | progAccess?: any |
| |
|
| | |
| | constructor(verb: string, requestPath: string, operation: any, globalServers?: any[]) { |
| | this.#operation = operation |
| | |
| | |
| | |
| | this.serverUrl = operation.servers ? operation.servers[0].url : globalServers?.[0]?.url |
| |
|
| | const serverVariables = operation.servers |
| | ? operation.servers[0].variables |
| | : globalServers?.[0]?.variables |
| | if (serverVariables) { |
| | |
| | const templateVariables: Record<string, any> = {} |
| | for (const key of Object.keys(serverVariables)) { |
| | templateVariables[key] = serverVariables[key].default |
| | } |
| | this.serverUrl = parseTemplate(this.serverUrl).expand(templateVariables) |
| | } |
| |
|
| | this.serverUrl = this.serverUrl.replace('http:', 'http(s):') |
| |
|
| | |
| | |
| | this.#operation.serverUrl = this.serverUrl |
| | this.#operation.requestPath = requestPath |
| | this.#operation.verb = verb |
| |
|
| | this.verb = verb |
| | this.requestPath = requestPath |
| | this.title = operation.summary |
| | this.category = operation['x-github'].category |
| | this.subcategory = operation['x-github'].subcategory |
| | this.parameters = operation.parameters || [] |
| | this.bodyParameters = [] |
| | return this |
| | } |
| |
|
| | |
| | async process(progAccessData: any): Promise<void> { |
| | await Promise.all([ |
| | this.renderCodeExamples(), |
| | this.renderDescription(), |
| | this.renderStatusCodes(), |
| | this.renderParameterDescriptions(), |
| | this.renderBodyParameterDescriptions(), |
| | this.renderPreviewNotes(), |
| | this.programmaticAccess(progAccessData), |
| | ]) |
| |
|
| | const { isValid, errors } = validateJson(operationSchema, this) |
| | if (!isValid) { |
| | console.error(JSON.stringify(errors, null, 2)) |
| | throw new Error('Invalid OpenAPI operation found') |
| | } |
| | } |
| |
|
| | async renderDescription(): Promise<this> { |
| | try { |
| | this.descriptionHTML = await renderContent(this.#operation.description) |
| | return this |
| | } catch (error) { |
| | console.error(error) |
| | throw new Error(`Error rendering description for ${this.verb} ${this.requestPath}`) |
| | } |
| | } |
| |
|
| | async renderCodeExamples(): Promise<any[]> { |
| | const codeExamples = await getCodeSamples(this.#operation) |
| | try { |
| | this.codeExamples = await Promise.all( |
| | |
| | codeExamples.map(async (codeExample: any) => { |
| | codeExample.response.description = await renderContent(codeExample.response.description) |
| | return codeExample |
| | }), |
| | ) |
| | return this.codeExamples |
| | } catch (error) { |
| | console.error(error) |
| | throw new Error(`Error generating code examples for ${this.verb} ${this.requestPath}`) |
| | } |
| | } |
| |
|
| | async renderStatusCodes(): Promise<void> { |
| | const responses = this.#operation.responses |
| | const responseKeys = Object.keys(responses) |
| | if (responseKeys.length === 0) return |
| |
|
| | try { |
| | this.statusCodes = await Promise.all( |
| | responseKeys.map(async (responseCode) => { |
| | const response = responses[responseCode] |
| | const httpStatusCode = responseCode |
| | const httpStatusMessage = httpStatusCodes.getMessage(Number(responseCode), 'HTTP/2') |
| | |
| | |
| | |
| | const responseDescription = |
| | !response.description || response.description?.toLowerCase() === 'response' |
| | ? await renderContent(httpStatusMessage) |
| | : await renderContent(response.description) |
| |
|
| | return { |
| | httpStatusCode, |
| | description: responseDescription, |
| | } |
| | }), |
| | ) |
| | } catch (error) { |
| | console.error(error) |
| | throw new Error(`Error rendering status codes for ${this.verb} ${this.requestPath}`) |
| | } |
| | } |
| |
|
| | async renderParameterDescriptions(): Promise<any[]> { |
| | try { |
| | return Promise.all( |
| | this.parameters.map(async (param) => { |
| | param.description = await renderContent(param.description) |
| | return param |
| | }), |
| | ) |
| | } catch (error) { |
| | console.error(error) |
| | throw new Error(`Error rendering parameter descriptions for ${this.verb} ${this.requestPath}`) |
| | } |
| | } |
| |
|
| | async renderBodyParameterDescriptions(): Promise<void> { |
| | if (!this.#operation.requestBody) return |
| |
|
| | |
| | |
| | |
| | const contentType = Object.keys(this.#operation.requestBody.content)[0] |
| | const schema = get(this.#operation, `requestBody.content.${contentType}.schema`, {}) |
| | |
| | const mergedAllofSchema = mergeAllOf(schema) |
| | try { |
| | this.bodyParameters = isPlainObject(schema) |
| | ? await getBodyParams(mergedAllofSchema as any, true) |
| | : [] |
| | } catch (error) { |
| | console.error(error) |
| | throw new Error( |
| | `Error rendering body parameter descriptions for ${this.verb} ${this.requestPath}`, |
| | ) |
| | } |
| | } |
| |
|
| | async renderPreviewNotes(): Promise<void> { |
| | const previews = get(this.#operation, 'x-github.previews', []) |
| | try { |
| | this.previews = await Promise.all( |
| | |
| | previews.map(async (preview: any) => { |
| | const note = preview.note |
| | |
| | .replace(/```\n\n\n/gm, '```\n') |
| | .replace(/```\n\n/gm, '```\n') |
| | .replace(/\n\n\n```/gm, '\n```') |
| | .replace(/\n\n```/gm, '\n```') |
| |
|
| | |
| | |
| | .replace(/\n`application/, '\n```\napplication') |
| | .replace(/json`$/, 'json\n```') |
| | return await renderContent(note) |
| | }), |
| | ) |
| | } catch (error) { |
| | console.error(error) |
| | throw new Error(`Error rendering preview notes for ${this.verb} ${this.requestPath}`) |
| | } |
| | } |
| |
|
| | |
| | programmaticAccess(progAccessData: any): void { |
| | this.progAccess = progAccessData[this.#operation.operationId] |
| | } |
| | } |
| |
|