From 7486e7003938dad73cdec34f9c3969221d08fb7e Mon Sep 17 00:00:00 2001 From: Andriy Zaleskyy Date: Fri, 27 Aug 2021 11:16:15 +0300 Subject: [PATCH] feat: Webhooks option to reverse readOnly/writeOnly properties #1720 --- demo/openapi.yaml | 4 +-- demo/playground/hmr-playground.tsx | 7 ++++- demo/playground/index.html | 2 +- src/components/Callbacks/CallbackDetails.tsx | 17 ++++++++--- .../Callbacks/CallbackOperation.tsx | 17 +++++++++-- src/components/Callbacks/CallbacksList.tsx | 11 +++++-- src/components/Operation/Operation.tsx | 22 ++++++++++---- src/components/Parameters/Parameters.tsx | 19 ++++++++---- src/components/Responses/Response.tsx | 18 +++++++++--- src/components/Responses/ResponseDetails.tsx | 14 ++++++--- src/components/Responses/ResponsesList.tsx | 13 +++++---- src/services/RedocNormalizedOptions.ts | 9 +++++- src/services/models/Field.ts | 4 ++- src/services/models/MediaContent.ts | 4 ++- src/services/models/MediaType.ts | 13 +++++---- src/services/models/Operation.ts | 26 +++++++++++++++-- src/services/models/RequestBody.ts | 4 ++- src/services/models/Response.ts | 6 ++-- src/services/models/Schema.ts | 29 ++++++++++++------- 19 files changed, 178 insertions(+), 61 deletions(-) diff --git a/demo/openapi.yaml b/demo/openapi.yaml index 74c16b70f2..c570cc6080 100644 --- a/demo/openapi.yaml +++ b/demo/openapi.yaml @@ -1193,7 +1193,7 @@ x-webhooks: summary: New pet description: Information about a new pet in the systems operationId: newPet - tags: + tags: - pet requestBody: content: @@ -1202,4 +1202,4 @@ x-webhooks: $ref: "#/components/schemas/Pet" responses: "200": - description: Return a 200 status to indicate that the data was received successfully \ No newline at end of file + description: Return a 200 status to indicate that the data was received successfully diff --git a/demo/playground/hmr-playground.tsx b/demo/playground/hmr-playground.tsx index efd843abcd..f378ca23e5 100644 --- a/demo/playground/hmr-playground.tsx +++ b/demo/playground/hmr-playground.tsx @@ -11,6 +11,11 @@ const userUrl = window.location.search.match(/url=(.*)$/); const specUrl = (userUrl && userUrl[1]) || (swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml'); -const options: RedocRawOptions = { nativeScrollbars: false, maxDisplayedEnumValues: 3 }; +const options: RedocRawOptions = { + nativeScrollbars: false, + maxDisplayedEnumValues: 3, + reverseEventsReadOnlyProps: true, + reverseEventsWriteOnlyProps: true, +}; render(, document.getElementById('example')); diff --git a/demo/playground/index.html b/demo/playground/index.html index d322ee3e29..7a3a5092f1 100644 --- a/demo/playground/index.html +++ b/demo/playground/index.html @@ -22,4 +22,4 @@ - \ No newline at end of file + diff --git a/src/components/Callbacks/CallbackDetails.tsx b/src/components/Callbacks/CallbackDetails.tsx index ce003c5612..086a623417 100644 --- a/src/components/Callbacks/CallbackDetails.tsx +++ b/src/components/Callbacks/CallbackDetails.tsx @@ -1,7 +1,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; -import { OperationModel } from '../../services/models'; +import { OperationModel, ReverseEventsRWOProps } from '../../services/models'; import styled from '../../styled-components'; import { Endpoint } from '../Endpoint/Endpoint'; import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation'; @@ -14,12 +14,13 @@ import { CallbackDetailsWrap } from './styled.elements'; export interface CallbackDetailsProps { operation: OperationModel; + reverseEventsReadWriteOnly?: ReverseEventsRWOProps; } @observer export class CallbackDetails extends React.Component { render() { - const { operation } = this.props; + const { operation, reverseEventsReadWriteOnly } = this.props; const { description, externalDocs } = operation; const hasDescription = !!(description || externalDocs); @@ -34,8 +35,16 @@ export class CallbackDetails extends React.Component { - - + + ); } diff --git a/src/components/Callbacks/CallbackOperation.tsx b/src/components/Callbacks/CallbackOperation.tsx index f811cdb583..4e40ee2b10 100644 --- a/src/components/Callbacks/CallbackOperation.tsx +++ b/src/components/Callbacks/CallbackOperation.tsx @@ -1,18 +1,24 @@ import { observer } from 'mobx-react'; import * as React from 'react'; -import { OperationModel } from '../../services/models'; +import { OperationModel, ReverseEventsRWOProps } from '../../services/models'; import { StyledCallbackTitle } from './styled.elements'; import { CallbackDetails } from './CallbackDetails'; +export interface CallbackOperationProps { + callbackOperation: OperationModel; + reverseEventsReadWriteOnly?: ReverseEventsRWOProps; +} + @observer -export class CallbackOperation extends React.Component<{ callbackOperation: OperationModel }> { +export class CallbackOperation extends React.Component { toggle = () => { this.props.callbackOperation.toggle(); }; render() { const { name, expanded, httpVerb, deprecated } = this.props.callbackOperation; + const { reverseEventsReadWriteOnly } = this.props; return ( <> @@ -23,7 +29,12 @@ export class CallbackOperation extends React.Component<{ callbackOperation: Oper httpVerb={httpVerb} deprecated={deprecated} /> - {expanded && } + {expanded && + + } ); } diff --git a/src/components/Callbacks/CallbacksList.tsx b/src/components/Callbacks/CallbacksList.tsx index 8ef40654c3..0dae2c5bba 100644 --- a/src/components/Callbacks/CallbacksList.tsx +++ b/src/components/Callbacks/CallbacksList.tsx @@ -1,16 +1,17 @@ import * as React from 'react'; -import { CallbackModel } from '../../services/models'; +import { CallbackModel, ReverseEventsRWOProps } from '../../services/models'; import styled from '../../styled-components'; import { CallbackOperation } from './CallbackOperation'; export interface CallbacksListProps { callbacks: CallbackModel[]; + reverseEventsReadWriteOnly?: ReverseEventsRWOProps; } export class CallbacksList extends React.PureComponent { render() { - const { callbacks } = this.props; + const { callbacks, reverseEventsReadWriteOnly } = this.props; if (!callbacks || callbacks.length === 0) { return null; @@ -22,7 +23,11 @@ export class CallbacksList extends React.PureComponent { {callbacks.map(callback => { return callback.operations.map((operation, index) => { return ( - + ); }); })} diff --git a/src/components/Operation/Operation.tsx b/src/components/Operation/Operation.tsx index 57776cf0d7..00f7c83b21 100644 --- a/src/components/Operation/Operation.tsx +++ b/src/components/Operation/Operation.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { Badge, DarkRightPanel, H2, MiddlePanel, Row } from '../../common-elements'; import { ShareLink } from '../../common-elements/linkify'; -import { OperationModel } from '../../services/models'; +import { OperationModel, ReverseEventsRWOProps } from '../../services/models'; import styled from '../../styled-components'; import { CallbacksList } from '../Callbacks'; import { CallbackSamples } from '../CallbackSamples/CallbackSamples'; @@ -30,14 +30,14 @@ const Description = styled.div` export interface OperationProps { operation: OperationModel; + reverseEventsReadWriteOnly?: ReverseEventsRWOProps; } @observer export class Operation extends React.Component { render() { const { operation } = this.props; - - const { name: summary, description, deprecated, externalDocs, isWebhook } = operation; + const { name: summary, description, deprecated, externalDocs, isWebhook, reverseEventsReadWriteOnly = {} } = operation; const hasDescription = !!(description || externalDocs); return ( @@ -61,9 +61,19 @@ export class Operation extends React.Component { )} - - - + + + {!options.pathInMiddlePanel && !isWebhook && } diff --git a/src/components/Parameters/Parameters.tsx b/src/components/Parameters/Parameters.tsx index 17ab9c484d..dfbd653d00 100644 --- a/src/components/Parameters/Parameters.tsx +++ b/src/components/Parameters/Parameters.tsx @@ -4,7 +4,7 @@ import { ParametersGroup } from './ParametersGroup'; import { UnderlinedHeader } from '../../common-elements'; -import { MediaContentModel } from '../../services'; +import { MediaContentModel, ReverseEventsRWOProps } from '../../services'; import { FieldModel, RequestBodyModel } from '../../services/models'; import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch'; import { Schema } from '../Schema'; @@ -21,6 +21,7 @@ function safePush(obj, prop, item) { export interface ParametersProps { parameters?: FieldModel[]; body?: RequestBodyModel; + reverseEventsReadWriteOnly?: ReverseEventsRWOProps; } const PARAM_PLACES = ['path', 'query', 'cookie', 'header']; @@ -35,7 +36,7 @@ export class Parameters extends React.PureComponent { } render() { - const { body, parameters = [] } = this.props; + const { body, parameters = [], reverseEventsReadWriteOnly } = this.props; if (body === undefined && parameters === undefined) { return null; } @@ -53,7 +54,13 @@ export class Parameters extends React.PureComponent { {paramsPlaces.map(place => ( ))} - {bodyContent && } + {bodyContent && + + } ); } @@ -67,15 +74,17 @@ function DropdownWithinHeader(props) { ); } -export function BodyContent(props: { content: MediaContentModel; description?: string }): JSX.Element { +export function BodyContent(props: { content: MediaContentModel; description?: string, reverseEventsReadWriteOnly?: ReverseEventsRWOProps }): JSX.Element { const { content, description } = props; + const { reverseEventsReadOnlyProps, reverseEventsWriteOnlyProps } = props.reverseEventsReadWriteOnly || {}; + const skipReadOnly = !reverseEventsReadOnlyProps; return ( {({ schema }) => { return ( <> {description !== undefined && } - + ); }} diff --git a/src/components/Responses/Response.tsx b/src/components/Responses/Response.tsx index 58e0ae32e2..0691c67a91 100644 --- a/src/components/Responses/Response.tsx +++ b/src/components/Responses/Response.tsx @@ -1,18 +1,25 @@ import { observer } from 'mobx-react'; import * as React from 'react'; -import { ResponseModel } from '../../services/models'; +import { ResponseModel, ReverseEventsRWOProps } from '../../services/models'; import { ResponseDetails } from './ResponseDetails'; import { ResponseDetailsWrap, StyledResponseTitle } from './styled.elements'; +export interface ResponseViewProps { + response: ResponseModel; + reverseEventsReadWriteOnly?: ReverseEventsRWOProps; +} + @observer -export class ResponseView extends React.Component<{ response: ResponseModel }> { +export class ResponseView extends React.Component { toggle = () => { this.props.response.toggle(); }; render() { - const { headers, type, summary, description, code, expanded, content } = this.props.response; + const { + reverseEventsReadWriteOnly, response: { headers, type, summary, description, code, expanded, content } + } = this.props; const mimes = content === undefined ? [] : content.mediaTypes.filter(mime => mime.schema !== undefined); @@ -30,7 +37,10 @@ export class ResponseView extends React.Component<{ response: ResponseModel }> { /> {expanded && !empty && ( - + )} diff --git a/src/components/Responses/ResponseDetails.tsx b/src/components/Responses/ResponseDetails.tsx index 821fc2da99..1b7712ddc6 100644 --- a/src/components/Responses/ResponseDetails.tsx +++ b/src/components/Responses/ResponseDetails.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { ResponseModel } from '../../services/models'; +import { ResponseModel, ReverseEventsRWOProps } from '../../services/models'; import { UnderlinedHeader } from '../../common-elements'; import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel'; @@ -10,16 +10,22 @@ import { Schema } from '../Schema'; import { Markdown } from '../Markdown/Markdown'; import { ResponseHeaders } from './ResponseHeaders'; -export class ResponseDetails extends React.PureComponent<{ response: ResponseModel }> { +export interface ResponseDetailsProps { + response: ResponseModel; + reverseEventsReadWriteOnly?: ReverseEventsRWOProps; +} + +export class ResponseDetails extends React.PureComponent { render() { - const { description, headers, content } = this.props.response; + const { reverseEventsReadWriteOnly = {}, response: { description, headers, content } } = this.props; + const skipWriteOnly = !reverseEventsReadWriteOnly.reverseEventsWriteOnlyProps; return ( <> {description && } {({ schema }) => { - return ; + return ; }} diff --git a/src/components/Responses/ResponsesList.tsx b/src/components/Responses/ResponsesList.tsx index e578a1cf81..f7c4b362ec 100644 --- a/src/components/Responses/ResponsesList.tsx +++ b/src/components/Responses/ResponsesList.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { l } from '../../services/Labels'; -import { ResponseModel } from '../../services/models'; +import { ResponseModel, ReverseEventsRWOProps } from '../../services/models'; import styled from '../../styled-components'; import { ResponseView } from './Response'; @@ -15,11 +15,12 @@ const ResponsesHeader = styled.h3` export interface ResponseListProps { responses: ResponseModel[]; isCallback?: boolean; + reverseEventsReadWriteOnly?: ReverseEventsRWOProps; } export class ResponsesList extends React.PureComponent { render() { - const { responses, isCallback } = this.props; + const { responses, isCallback, reverseEventsReadWriteOnly } = this.props; if (!responses || responses.length === 0) { return null; @@ -28,9 +29,11 @@ export class ResponsesList extends React.PureComponent { return (
{isCallback ? l('callbackResponses') : l('responses')} - {responses.map(response => { - return ; - })} + {responses.map(response => )}
); } diff --git a/src/services/RedocNormalizedOptions.ts b/src/services/RedocNormalizedOptions.ts index e310a9adcd..83dc2baa46 100644 --- a/src/services/RedocNormalizedOptions.ts +++ b/src/services/RedocNormalizedOptions.ts @@ -42,6 +42,8 @@ export interface RedocRawOptions { maxDisplayedEnumValues?: number; ignoreNamedSchemas?: string[] | string; hideSchemaPattern?: boolean; + reverseEventsReadOnlyProps?: boolean; + reverseEventsWriteOnlyProps?: boolean; } export function argValueToBoolean(val?: string | boolean, defaultValue?: boolean): boolean { @@ -49,7 +51,7 @@ export function argValueToBoolean(val?: string | boolean, defaultValue?: boolean return defaultValue || false; } if (typeof val === 'string') { - return val === 'false' ? false : true; + return val !== 'false'; } return val; } @@ -197,6 +199,9 @@ export class RedocNormalizedOptions { ignoreNamedSchemas: Set; hideSchemaPattern: boolean; + reverseEventsReadOnlyProps: boolean; + reverseEventsWriteOnlyProps: boolean; + constructor(raw: RedocRawOptions, defaults: RedocRawOptions = {}) { raw = { ...defaults, ...raw }; const hook = raw.theme && raw.theme.extensionsHook; @@ -257,5 +262,7 @@ export class RedocNormalizedOptions { : raw.ignoreNamedSchemas?.split(',').map((s) => s.trim()); this.ignoreNamedSchemas = new Set(ignoreNamedSchemas); this.hideSchemaPattern = argValueToBoolean(raw.hideSchemaPattern); + this.reverseEventsReadOnlyProps = argValueToBoolean(raw.reverseEventsReadOnlyProps); + this.reverseEventsWriteOnlyProps = argValueToBoolean(raw.reverseEventsWriteOnlyProps); } } diff --git a/src/services/models/Field.ts b/src/services/models/Field.ts index ef031ab894..6626f6edbc 100644 --- a/src/services/models/Field.ts +++ b/src/services/models/Field.ts @@ -13,6 +13,7 @@ import { OpenAPIParser } from '../OpenAPIParser'; import { SchemaModel } from './Schema'; import { ExampleModel } from './Example'; import { mapValues } from '../../utils/helpers'; +import { ReverseEventsRWOProps } from './Operation'; const DEFAULT_SERIALIZATION: Record< OpenAPIParameterLocation, @@ -64,6 +65,7 @@ export class FieldModel { infoOrRef: Referenced & { name?: string; kind?: string }, pointer: string, options: RedocNormalizedOptions, + reverseEventsReadWriteOnly?: ReverseEventsRWOProps, ) { makeObservable(this); @@ -80,7 +82,7 @@ export class FieldModel { fieldSchema = info.content[serializationMime] && info.content[serializationMime].schema; } - this.schema = new SchemaModel(parser, fieldSchema || {}, pointer, options); + this.schema = new SchemaModel(parser, fieldSchema || {}, pointer, options, false, reverseEventsReadWriteOnly); this.description = info.description === undefined ? this.schema.description || '' : info.description; this.example = info.example || this.schema.example; diff --git a/src/services/models/MediaContent.ts b/src/services/models/MediaContent.ts index e993da91e5..a6dfa1fc51 100644 --- a/src/services/models/MediaContent.ts +++ b/src/services/models/MediaContent.ts @@ -6,6 +6,7 @@ import { MediaTypeModel } from './MediaType'; import { mergeSimilarMediaTypes } from '../../utils'; import { OpenAPIParser } from '../OpenAPIParser'; import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; +import { ReverseEventsRWOProps } from './Operation'; /** * MediaContent model ready to be sued by React components @@ -25,6 +26,7 @@ export class MediaContentModel { info: Record, public isRequestType: boolean, options: RedocNormalizedOptions, + reverseEventsReadWriteOnly?: ReverseEventsRWOProps, ) { makeObservable(this); @@ -35,7 +37,7 @@ export class MediaContentModel { const mime = info[name]; // reset deref cache just in case something is left there parser.resetVisited(); - return new MediaTypeModel(parser, name, isRequestType, mime, options); + return new MediaTypeModel(parser, name, isRequestType, mime, options, reverseEventsReadWriteOnly); }); } diff --git a/src/services/models/MediaType.ts b/src/services/models/MediaType.ts index 9283fbaecb..096de6310c 100644 --- a/src/services/models/MediaType.ts +++ b/src/services/models/MediaType.ts @@ -7,6 +7,7 @@ import { SchemaModel } from './Schema'; import { isJsonLike, mapValues } from '../../utils'; import { OpenAPIParser } from '../OpenAPIParser'; import { ExampleModel } from './Example'; +import { ReverseEventsRWOProps } from './Operation'; export class MediaTypeModel { examples?: { [name: string]: ExampleModel }; @@ -24,10 +25,11 @@ export class MediaTypeModel { isRequestType: boolean, info: OpenAPIMediaType, options: RedocNormalizedOptions, + reverseEventsReadWriteOnly: ReverseEventsRWOProps = {}, ) { this.name = name; this.isRequestType = isRequestType; - this.schema = info.schema && new SchemaModel(parser, info.schema, '', options); + this.schema = info.schema && new SchemaModel(parser, info.schema, '', options, false, reverseEventsReadWriteOnly); this.onlyRequiredInSamples = options.onlyRequiredInSamples; if (info.examples !== undefined) { this.examples = mapValues( @@ -44,15 +46,16 @@ export class MediaTypeModel { ), }; } else if (isJsonLike(name)) { - this.generateExample(parser, info); + this.generateExample(parser, info, reverseEventsReadWriteOnly); } } - generateExample(parser: OpenAPIParser, info: OpenAPIMediaType) { + generateExample(parser: OpenAPIParser, info: OpenAPIMediaType, reverseEventsReadWriteOnly: ReverseEventsRWOProps) { + const { reverseEventsReadOnlyProps, reverseEventsWriteOnlyProps } = reverseEventsReadWriteOnly; const samplerOptions = { - skipReadOnly: this.isRequestType, + skipReadOnly: reverseEventsReadOnlyProps ? !this.isRequestType : this.isRequestType, + skipWriteOnly: reverseEventsWriteOnlyProps ? this.isRequestType : !this.isRequestType, skipNonRequired: this.isRequestType && this.onlyRequiredInSamples, - skipWriteOnly: !this.isRequestType, maxSampleDepth: 10, }; if (this.schema && this.schema.oneOf) { diff --git a/src/services/models/Operation.ts b/src/services/models/Operation.ts index d00461a136..5ae29a7a22 100644 --- a/src/services/models/Operation.ts +++ b/src/services/models/Operation.ts @@ -33,6 +33,11 @@ export interface XPayloadSample { source: string; } +export interface ReverseEventsRWOProps { + reverseEventsReadOnlyProps?: boolean; + reverseEventsWriteOnlyProps?: boolean; +} + export function isPayloadSample( sample: XPayloadSample | OpenAPIXCodeSample, ): sample is XPayloadSample { @@ -76,6 +81,7 @@ export class OperationModel implements IMenuItem { extensions: Record; isCallback: boolean; isWebhook: boolean; + reverseEventsReadWriteOnly?: ReverseEventsRWOProps; constructor( private parser: OpenAPIParser, @@ -100,6 +106,11 @@ export class OperationModel implements IMenuItem { this.isCallback = isCallback; this.isWebhook = !!operationSpec.isWebhook; + this.reverseEventsReadWriteOnly = (this.isCallback || this.isWebhook) ? { + reverseEventsReadOnlyProps: this.options.reverseEventsReadOnlyProps, + reverseEventsWriteOnlyProps: this.options.reverseEventsWriteOnlyProps, + } : {}; + this.name = getOperationSummary(operationSpec); if (this.isCallback) { @@ -172,7 +183,11 @@ export class OperationModel implements IMenuItem { get requestBody() { return ( this.operationSpec.requestBody && - new RequestBodyModel(this.parser, this.operationSpec.requestBody, this.options) + new RequestBodyModel(this.parser, + this.operationSpec.requestBody, + this.options, + this.reverseEventsReadWriteOnly, + ) ); } @@ -212,7 +227,13 @@ export class OperationModel implements IMenuItem { this.operationSpec.pathParameters, this.operationSpec.parameters, // TODO: fix pointer - ).map((paramOrRef) => new FieldModel(this.parser, paramOrRef, this.pointer, this.options)); + ).map((paramOrRef) => new FieldModel( + this.parser, + paramOrRef, + this.pointer, + this.options, + this.reverseEventsReadWriteOnly, + )); if (this.options.sortPropsAlphabetically) { return sortByField(_parameters, 'name'); @@ -246,6 +267,7 @@ export class OperationModel implements IMenuItem { hasSuccessResponses, this.operationSpec.responses[code], this.options, + this.reverseEventsReadWriteOnly, ); }); } diff --git a/src/services/models/RequestBody.ts b/src/services/models/RequestBody.ts index f3b45959cb..4295eee4d3 100644 --- a/src/services/models/RequestBody.ts +++ b/src/services/models/RequestBody.ts @@ -3,6 +3,7 @@ import { OpenAPIRequestBody, Referenced } from '../../types'; import { OpenAPIParser } from '../OpenAPIParser'; import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; import { MediaContentModel } from './MediaContent'; +import { ReverseEventsRWOProps } from './Operation'; export class RequestBodyModel { description: string; @@ -13,13 +14,14 @@ export class RequestBodyModel { parser: OpenAPIParser, infoOrRef: Referenced, options: RedocNormalizedOptions, + reverseEventsReadWriteOnly?: ReverseEventsRWOProps, ) { const info = parser.deref(infoOrRef); this.description = info.description || ''; this.required = !!info.required; parser.exitRef(infoOrRef); if (info.content !== undefined) { - this.content = new MediaContentModel(parser, info.content, true, options); + this.content = new MediaContentModel(parser, info.content, true, options, reverseEventsReadWriteOnly); } } } diff --git a/src/services/models/Response.ts b/src/services/models/Response.ts index f50ee0f1ca..7504d8b141 100644 --- a/src/services/models/Response.ts +++ b/src/services/models/Response.ts @@ -7,6 +7,7 @@ import { OpenAPIParser } from '../OpenAPIParser'; import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; import { FieldModel } from './Field'; import { MediaContentModel } from './MediaContent'; +import { ReverseEventsRWOProps } from './Operation'; export class ResponseModel { @observable @@ -25,6 +26,7 @@ export class ResponseModel { defaultAsError: boolean, infoOrRef: Referenced, options: RedocNormalizedOptions, + reverseEventsReadWriteOnly?: ReverseEventsRWOProps, ) { makeObservable(this); @@ -34,7 +36,7 @@ export class ResponseModel { parser.exitRef(infoOrRef); this.code = code; if (info.content !== undefined) { - this.content = new MediaContentModel(parser, info.content, false, options); + this.content = new MediaContentModel(parser, info.content, false, options, reverseEventsReadWriteOnly); } if (info['x-summary'] !== undefined) { @@ -51,7 +53,7 @@ export class ResponseModel { if (headers !== undefined) { this.headers = Object.keys(headers).map(name => { const header = headers[name]; - return new FieldModel(parser, { ...header, name }, '', options); + return new FieldModel(parser, { ...header, name }, '', options, reverseEventsReadWriteOnly); }); } } diff --git a/src/services/models/Schema.ts b/src/services/models/Schema.ts index d79d738ba4..6f0f59cafe 100644 --- a/src/services/models/Schema.ts +++ b/src/services/models/Schema.ts @@ -6,7 +6,7 @@ import { OpenAPIParser } from '../OpenAPIParser'; import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; import { FieldModel } from './Field'; -import { MergedOpenAPISchema } from '../'; +import { MergedOpenAPISchema, ReverseEventsRWOProps } from '../'; import { detectType, extractExtensions, @@ -74,6 +74,7 @@ export class SchemaModel { pointer: string, private options: RedocNormalizedOptions, isChild: boolean = false, + reverseEventsReadWriteOnly: ReverseEventsRWOProps = {}, ) { makeObservable(this); @@ -81,7 +82,7 @@ export class SchemaModel { this.rawSchema = parser.deref(schemaOrRef, false, true); this.schema = parser.mergeAllOf(this.rawSchema, this.pointer, isChild); - this.init(parser, isChild); + this.init(parser, isChild, reverseEventsReadWriteOnly); parser.exitRef(schemaOrRef); parser.exitParents(this.schema); @@ -104,7 +105,7 @@ export class SchemaModel { return this.type === type || (Array.isArray(this.type) && this.type.includes(type)); } - init(parser: OpenAPIParser, isChild: boolean) { + init(parser: OpenAPIParser, isChild: boolean, reverseEventsReadWriteOnly: ReverseEventsRWOProps) { const schema = this.schema; this.isCircular = schema['x-circular-ref']; @@ -146,7 +147,7 @@ export class SchemaModel { } if (!isChild && getDiscriminator(schema) !== undefined) { - this.initDiscriminator(schema, parser); + this.initDiscriminator(schema, parser, reverseEventsReadWriteOnly); return; } else if ( isChild && @@ -158,7 +159,7 @@ export class SchemaModel { } if (schema.oneOf !== undefined) { - this.initOneOf(schema.oneOf, parser); + this.initOneOf(schema.oneOf, parser, reverseEventsReadWriteOnly); this.oneOfType = 'One of'; if (schema.anyOf !== undefined) { console.warn( @@ -169,15 +170,17 @@ export class SchemaModel { } if (schema.anyOf !== undefined) { - this.initOneOf(schema.anyOf, parser); + this.initOneOf(schema.anyOf, parser, reverseEventsReadWriteOnly); this.oneOfType = 'Any of'; return; } if (this.hasType('object')) { - this.fields = buildFields(parser, schema, this.pointer, this.options); + this.fields = buildFields(parser, schema, this.pointer, this.options, reverseEventsReadWriteOnly); } else if (this.hasType('array') && schema.items) { - this.items = new SchemaModel(parser, schema.items, this.pointer + '/items', this.options); + this.items = new SchemaModel( + parser, schema.items, this.pointer + '/items', this.options, false, reverseEventsReadWriteOnly + ); this.displayType = pluralizeType(this.items.displayType); this.displayFormat = this.items.format; this.typePrefix = this.items.typePrefix + l('arrayOf'); @@ -201,7 +204,7 @@ export class SchemaModel { } } - private initOneOf(oneOf: OpenAPISchema[], parser: OpenAPIParser) { + private initOneOf(oneOf: OpenAPISchema[], parser: OpenAPIParser, reverseEventsReadWriteOnly: ReverseEventsRWOProps) { this.oneOf = oneOf!.map((variant, idx) => { const derefVariant = parser.deref(variant, false, true); @@ -224,6 +227,8 @@ export class SchemaModel { } as OpenAPISchema, this.pointer + '/oneOf/' + idx, this.options, + false, + reverseEventsReadWriteOnly, ); parser.exitRef(variant); @@ -257,6 +262,7 @@ export class SchemaModel { parentRefs?: string[]; }, parser: OpenAPIParser, + reverseEventsReadWriteOnly, ) { const discriminator = getDiscriminator(schema)!; this.discriminatorProp = discriminator.propertyName; @@ -344,7 +350,7 @@ export class SchemaModel { } this.oneOf = refs.map(({ $ref, name }) => { - const innerSchema = new SchemaModel(parser, parser.byRef($ref)!, $ref, this.options, true); + const innerSchema = new SchemaModel(parser, parser.byRef($ref)!, $ref, this.options, true, reverseEventsReadWriteOnly); innerSchema.title = name; return innerSchema; }); @@ -356,6 +362,7 @@ function buildFields( schema: OpenAPISchema, $ref: string, options: RedocNormalizedOptions, + reverseEventsReadWriteOnly: ReverseEventsRWOProps, ): FieldModel[] { const props = schema.properties || {}; const additionalProps = schema.additionalProperties; @@ -385,6 +392,7 @@ function buildFields( }, $ref + '/properties/' + fieldName, options, + reverseEventsReadWriteOnly, ); }); @@ -411,6 +419,7 @@ function buildFields( }, $ref + '/additionalProperties', options, + reverseEventsReadWriteOnly, ), ); }