From 7486e7003938dad73cdec34f9c3969221d08fb7e Mon Sep 17 00:00:00 2001 From: Andriy Zaleskyy Date: Fri, 27 Aug 2021 11:16:15 +0300 Subject: [PATCH 1/8] 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, ), ); } From c04e4035d5c800039b50bb8ec6c2a67d070160ff Mon Sep 17 00:00:00 2001 From: Andriy Zaleskyy Date: Fri, 27 Aug 2021 11:27:57 +0300 Subject: [PATCH 2/8] feat: add readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6c63e3e677..432a9d924d 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,7 @@ You can use all of the following options with standalone version on tag * `onlyRequiredInSamples` - shows only required fields in request samples. * `pathInMiddlePanel` - show path link and HTTP verb in the middle panel instead of the right one. * `requiredPropsFirst` - show required properties first ordered in the same order as in `required` array. +* `reverseEventsReadOnlyProps`, `reverseEventsWriteOnlyProps` - reverse readOnly/writeOnly schema key for webhooks and callbacks. * `scrollYOffset` - If set, specifies a vertical scroll-offset. This is often useful when there are fixed positioned elements at the top of the page, such as navbars, headers etc; `scrollYOffset` can be specified in various ways: * **number**: A fixed number of pixels to be used as offset. From 1487b24776ed73111dee48c096659a1c4c9833b1 Mon Sep 17 00:00:00 2001 From: Andriy Zaleskyy Date: Fri, 27 Aug 2021 12:22:57 +0300 Subject: [PATCH 3/8] Revert "feat: Webhooks option to reverse readOnly/writeOnly properties #1720" This reverts commit 7486e7003938dad73cdec34f9c3969221d08fb7e. --- 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, 61 insertions(+), 178 deletions(-) diff --git a/demo/openapi.yaml b/demo/openapi.yaml index c570cc6080..74c16b70f2 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 + description: Return a 200 status to indicate that the data was received successfully \ No newline at end of file diff --git a/demo/playground/hmr-playground.tsx b/demo/playground/hmr-playground.tsx index f378ca23e5..efd843abcd 100644 --- a/demo/playground/hmr-playground.tsx +++ b/demo/playground/hmr-playground.tsx @@ -11,11 +11,6 @@ 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, - reverseEventsReadOnlyProps: true, - reverseEventsWriteOnlyProps: true, -}; +const options: RedocRawOptions = { nativeScrollbars: false, maxDisplayedEnumValues: 3 }; render(, document.getElementById('example')); diff --git a/demo/playground/index.html b/demo/playground/index.html index 7a3a5092f1..d322ee3e29 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 086a623417..ce003c5612 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, ReverseEventsRWOProps } from '../../services/models'; +import { OperationModel } from '../../services/models'; import styled from '../../styled-components'; import { Endpoint } from '../Endpoint/Endpoint'; import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation'; @@ -14,13 +14,12 @@ import { CallbackDetailsWrap } from './styled.elements'; export interface CallbackDetailsProps { operation: OperationModel; - reverseEventsReadWriteOnly?: ReverseEventsRWOProps; } @observer export class CallbackDetails extends React.Component { render() { - const { operation, reverseEventsReadWriteOnly } = this.props; + const { operation } = this.props; const { description, externalDocs } = operation; const hasDescription = !!(description || externalDocs); @@ -35,16 +34,8 @@ export class CallbackDetails extends React.Component { - - + + ); } diff --git a/src/components/Callbacks/CallbackOperation.tsx b/src/components/Callbacks/CallbackOperation.tsx index 4e40ee2b10..f811cdb583 100644 --- a/src/components/Callbacks/CallbackOperation.tsx +++ b/src/components/Callbacks/CallbackOperation.tsx @@ -1,24 +1,18 @@ import { observer } from 'mobx-react'; import * as React from 'react'; -import { OperationModel, ReverseEventsRWOProps } from '../../services/models'; +import { OperationModel } 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 { +export class CallbackOperation extends React.Component<{ callbackOperation: OperationModel }> { toggle = () => { this.props.callbackOperation.toggle(); }; render() { const { name, expanded, httpVerb, deprecated } = this.props.callbackOperation; - const { reverseEventsReadWriteOnly } = this.props; return ( <> @@ -29,12 +23,7 @@ export class CallbackOperation extends React.Component { httpVerb={httpVerb} deprecated={deprecated} /> - {expanded && - - } + {expanded && } ); } diff --git a/src/components/Callbacks/CallbacksList.tsx b/src/components/Callbacks/CallbacksList.tsx index 0dae2c5bba..8ef40654c3 100644 --- a/src/components/Callbacks/CallbacksList.tsx +++ b/src/components/Callbacks/CallbacksList.tsx @@ -1,17 +1,16 @@ import * as React from 'react'; -import { CallbackModel, ReverseEventsRWOProps } from '../../services/models'; +import { CallbackModel } 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, reverseEventsReadWriteOnly } = this.props; + const { callbacks } = this.props; if (!callbacks || callbacks.length === 0) { return null; @@ -23,11 +22,7 @@ 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 00f7c83b21..57776cf0d7 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, ReverseEventsRWOProps } from '../../services/models'; +import { OperationModel } 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, reverseEventsReadWriteOnly = {} } = operation; + + const { name: summary, description, deprecated, externalDocs, isWebhook } = operation; const hasDescription = !!(description || externalDocs); return ( @@ -61,19 +61,9 @@ export class Operation extends React.Component { )} - - - + + + {!options.pathInMiddlePanel && !isWebhook && } diff --git a/src/components/Parameters/Parameters.tsx b/src/components/Parameters/Parameters.tsx index dfbd653d00..17ab9c484d 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, ReverseEventsRWOProps } from '../../services'; +import { MediaContentModel } from '../../services'; import { FieldModel, RequestBodyModel } from '../../services/models'; import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch'; import { Schema } from '../Schema'; @@ -21,7 +21,6 @@ function safePush(obj, prop, item) { export interface ParametersProps { parameters?: FieldModel[]; body?: RequestBodyModel; - reverseEventsReadWriteOnly?: ReverseEventsRWOProps; } const PARAM_PLACES = ['path', 'query', 'cookie', 'header']; @@ -36,7 +35,7 @@ export class Parameters extends React.PureComponent { } render() { - const { body, parameters = [], reverseEventsReadWriteOnly } = this.props; + const { body, parameters = [] } = this.props; if (body === undefined && parameters === undefined) { return null; } @@ -54,13 +53,7 @@ export class Parameters extends React.PureComponent { {paramsPlaces.map(place => ( ))} - {bodyContent && - - } + {bodyContent && } ); } @@ -74,17 +67,15 @@ function DropdownWithinHeader(props) { ); } -export function BodyContent(props: { content: MediaContentModel; description?: string, reverseEventsReadWriteOnly?: ReverseEventsRWOProps }): JSX.Element { +export function BodyContent(props: { content: MediaContentModel; description?: string }): 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 0691c67a91..58e0ae32e2 100644 --- a/src/components/Responses/Response.tsx +++ b/src/components/Responses/Response.tsx @@ -1,25 +1,18 @@ import { observer } from 'mobx-react'; import * as React from 'react'; -import { ResponseModel, ReverseEventsRWOProps } from '../../services/models'; +import { ResponseModel } 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 { +export class ResponseView extends React.Component<{ response: ResponseModel }> { toggle = () => { this.props.response.toggle(); }; render() { - const { - reverseEventsReadWriteOnly, response: { headers, type, summary, description, code, expanded, content } - } = this.props; + const { headers, type, summary, description, code, expanded, content } = this.props.response; const mimes = content === undefined ? [] : content.mediaTypes.filter(mime => mime.schema !== undefined); @@ -37,10 +30,7 @@ export class ResponseView extends React.Component { /> {expanded && !empty && ( - + )} diff --git a/src/components/Responses/ResponseDetails.tsx b/src/components/Responses/ResponseDetails.tsx index 1b7712ddc6..821fc2da99 100644 --- a/src/components/Responses/ResponseDetails.tsx +++ b/src/components/Responses/ResponseDetails.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { ResponseModel, ReverseEventsRWOProps } from '../../services/models'; +import { ResponseModel } from '../../services/models'; import { UnderlinedHeader } from '../../common-elements'; import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel'; @@ -10,22 +10,16 @@ import { Schema } from '../Schema'; import { Markdown } from '../Markdown/Markdown'; import { ResponseHeaders } from './ResponseHeaders'; -export interface ResponseDetailsProps { - response: ResponseModel; - reverseEventsReadWriteOnly?: ReverseEventsRWOProps; -} - -export class ResponseDetails extends React.PureComponent { +export class ResponseDetails extends React.PureComponent<{ response: ResponseModel }> { render() { - const { reverseEventsReadWriteOnly = {}, response: { description, headers, content } } = this.props; - const skipWriteOnly = !reverseEventsReadWriteOnly.reverseEventsWriteOnlyProps; + const { description, headers, content } = this.props.response; return ( <> {description && } {({ schema }) => { - return ; + return ; }} diff --git a/src/components/Responses/ResponsesList.tsx b/src/components/Responses/ResponsesList.tsx index f7c4b362ec..e578a1cf81 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, ReverseEventsRWOProps } from '../../services/models'; +import { ResponseModel } from '../../services/models'; import styled from '../../styled-components'; import { ResponseView } from './Response'; @@ -15,12 +15,11 @@ const ResponsesHeader = styled.h3` export interface ResponseListProps { responses: ResponseModel[]; isCallback?: boolean; - reverseEventsReadWriteOnly?: ReverseEventsRWOProps; } export class ResponsesList extends React.PureComponent { render() { - const { responses, isCallback, reverseEventsReadWriteOnly } = this.props; + const { responses, isCallback } = this.props; if (!responses || responses.length === 0) { return null; @@ -29,11 +28,9 @@ export class ResponsesList extends React.PureComponent { return (
{isCallback ? l('callbackResponses') : l('responses')} - {responses.map(response => )} + {responses.map(response => { + return ; + })}
); } diff --git a/src/services/RedocNormalizedOptions.ts b/src/services/RedocNormalizedOptions.ts index 83dc2baa46..e310a9adcd 100644 --- a/src/services/RedocNormalizedOptions.ts +++ b/src/services/RedocNormalizedOptions.ts @@ -42,8 +42,6 @@ export interface RedocRawOptions { maxDisplayedEnumValues?: number; ignoreNamedSchemas?: string[] | string; hideSchemaPattern?: boolean; - reverseEventsReadOnlyProps?: boolean; - reverseEventsWriteOnlyProps?: boolean; } export function argValueToBoolean(val?: string | boolean, defaultValue?: boolean): boolean { @@ -51,7 +49,7 @@ export function argValueToBoolean(val?: string | boolean, defaultValue?: boolean return defaultValue || false; } if (typeof val === 'string') { - return val !== 'false'; + return val === 'false' ? false : true; } return val; } @@ -199,9 +197,6 @@ 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; @@ -262,7 +257,5 @@ 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 6626f6edbc..ef031ab894 100644 --- a/src/services/models/Field.ts +++ b/src/services/models/Field.ts @@ -13,7 +13,6 @@ 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, @@ -65,7 +64,6 @@ export class FieldModel { infoOrRef: Referenced & { name?: string; kind?: string }, pointer: string, options: RedocNormalizedOptions, - reverseEventsReadWriteOnly?: ReverseEventsRWOProps, ) { makeObservable(this); @@ -82,7 +80,7 @@ export class FieldModel { fieldSchema = info.content[serializationMime] && info.content[serializationMime].schema; } - this.schema = new SchemaModel(parser, fieldSchema || {}, pointer, options, false, reverseEventsReadWriteOnly); + this.schema = new SchemaModel(parser, fieldSchema || {}, pointer, options); 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 a6dfa1fc51..e993da91e5 100644 --- a/src/services/models/MediaContent.ts +++ b/src/services/models/MediaContent.ts @@ -6,7 +6,6 @@ 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 @@ -26,7 +25,6 @@ export class MediaContentModel { info: Record, public isRequestType: boolean, options: RedocNormalizedOptions, - reverseEventsReadWriteOnly?: ReverseEventsRWOProps, ) { makeObservable(this); @@ -37,7 +35,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, reverseEventsReadWriteOnly); + return new MediaTypeModel(parser, name, isRequestType, mime, options); }); } diff --git a/src/services/models/MediaType.ts b/src/services/models/MediaType.ts index 096de6310c..9283fbaecb 100644 --- a/src/services/models/MediaType.ts +++ b/src/services/models/MediaType.ts @@ -7,7 +7,6 @@ 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 }; @@ -25,11 +24,10 @@ 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, false, reverseEventsReadWriteOnly); + this.schema = info.schema && new SchemaModel(parser, info.schema, '', options); this.onlyRequiredInSamples = options.onlyRequiredInSamples; if (info.examples !== undefined) { this.examples = mapValues( @@ -46,16 +44,15 @@ export class MediaTypeModel { ), }; } else if (isJsonLike(name)) { - this.generateExample(parser, info, reverseEventsReadWriteOnly); + this.generateExample(parser, info); } } - generateExample(parser: OpenAPIParser, info: OpenAPIMediaType, reverseEventsReadWriteOnly: ReverseEventsRWOProps) { - const { reverseEventsReadOnlyProps, reverseEventsWriteOnlyProps } = reverseEventsReadWriteOnly; + generateExample(parser: OpenAPIParser, info: OpenAPIMediaType) { const samplerOptions = { - skipReadOnly: reverseEventsReadOnlyProps ? !this.isRequestType : this.isRequestType, - skipWriteOnly: reverseEventsWriteOnlyProps ? this.isRequestType : !this.isRequestType, + skipReadOnly: 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 5ae29a7a22..d00461a136 100644 --- a/src/services/models/Operation.ts +++ b/src/services/models/Operation.ts @@ -33,11 +33,6 @@ export interface XPayloadSample { source: string; } -export interface ReverseEventsRWOProps { - reverseEventsReadOnlyProps?: boolean; - reverseEventsWriteOnlyProps?: boolean; -} - export function isPayloadSample( sample: XPayloadSample | OpenAPIXCodeSample, ): sample is XPayloadSample { @@ -81,7 +76,6 @@ export class OperationModel implements IMenuItem { extensions: Record; isCallback: boolean; isWebhook: boolean; - reverseEventsReadWriteOnly?: ReverseEventsRWOProps; constructor( private parser: OpenAPIParser, @@ -106,11 +100,6 @@ 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) { @@ -183,11 +172,7 @@ export class OperationModel implements IMenuItem { get requestBody() { return ( this.operationSpec.requestBody && - new RequestBodyModel(this.parser, - this.operationSpec.requestBody, - this.options, - this.reverseEventsReadWriteOnly, - ) + new RequestBodyModel(this.parser, this.operationSpec.requestBody, this.options) ); } @@ -227,13 +212,7 @@ 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, - this.reverseEventsReadWriteOnly, - )); + ).map((paramOrRef) => new FieldModel(this.parser, paramOrRef, this.pointer, this.options)); if (this.options.sortPropsAlphabetically) { return sortByField(_parameters, 'name'); @@ -267,7 +246,6 @@ 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 4295eee4d3..f3b45959cb 100644 --- a/src/services/models/RequestBody.ts +++ b/src/services/models/RequestBody.ts @@ -3,7 +3,6 @@ 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; @@ -14,14 +13,13 @@ 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, reverseEventsReadWriteOnly); + this.content = new MediaContentModel(parser, info.content, true, options); } } } diff --git a/src/services/models/Response.ts b/src/services/models/Response.ts index 7504d8b141..f50ee0f1ca 100644 --- a/src/services/models/Response.ts +++ b/src/services/models/Response.ts @@ -7,7 +7,6 @@ import { OpenAPIParser } from '../OpenAPIParser'; import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; import { FieldModel } from './Field'; import { MediaContentModel } from './MediaContent'; -import { ReverseEventsRWOProps } from './Operation'; export class ResponseModel { @observable @@ -26,7 +25,6 @@ export class ResponseModel { defaultAsError: boolean, infoOrRef: Referenced, options: RedocNormalizedOptions, - reverseEventsReadWriteOnly?: ReverseEventsRWOProps, ) { makeObservable(this); @@ -36,7 +34,7 @@ export class ResponseModel { parser.exitRef(infoOrRef); this.code = code; if (info.content !== undefined) { - this.content = new MediaContentModel(parser, info.content, false, options, reverseEventsReadWriteOnly); + this.content = new MediaContentModel(parser, info.content, false, options); } if (info['x-summary'] !== undefined) { @@ -53,7 +51,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, reverseEventsReadWriteOnly); + return new FieldModel(parser, { ...header, name }, '', options); }); } } diff --git a/src/services/models/Schema.ts b/src/services/models/Schema.ts index 6f0f59cafe..d79d738ba4 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, ReverseEventsRWOProps } from '../'; +import { MergedOpenAPISchema } from '../'; import { detectType, extractExtensions, @@ -74,7 +74,6 @@ export class SchemaModel { pointer: string, private options: RedocNormalizedOptions, isChild: boolean = false, - reverseEventsReadWriteOnly: ReverseEventsRWOProps = {}, ) { makeObservable(this); @@ -82,7 +81,7 @@ export class SchemaModel { this.rawSchema = parser.deref(schemaOrRef, false, true); this.schema = parser.mergeAllOf(this.rawSchema, this.pointer, isChild); - this.init(parser, isChild, reverseEventsReadWriteOnly); + this.init(parser, isChild); parser.exitRef(schemaOrRef); parser.exitParents(this.schema); @@ -105,7 +104,7 @@ export class SchemaModel { return this.type === type || (Array.isArray(this.type) && this.type.includes(type)); } - init(parser: OpenAPIParser, isChild: boolean, reverseEventsReadWriteOnly: ReverseEventsRWOProps) { + init(parser: OpenAPIParser, isChild: boolean) { const schema = this.schema; this.isCircular = schema['x-circular-ref']; @@ -147,7 +146,7 @@ export class SchemaModel { } if (!isChild && getDiscriminator(schema) !== undefined) { - this.initDiscriminator(schema, parser, reverseEventsReadWriteOnly); + this.initDiscriminator(schema, parser); return; } else if ( isChild && @@ -159,7 +158,7 @@ export class SchemaModel { } if (schema.oneOf !== undefined) { - this.initOneOf(schema.oneOf, parser, reverseEventsReadWriteOnly); + this.initOneOf(schema.oneOf, parser); this.oneOfType = 'One of'; if (schema.anyOf !== undefined) { console.warn( @@ -170,17 +169,15 @@ export class SchemaModel { } if (schema.anyOf !== undefined) { - this.initOneOf(schema.anyOf, parser, reverseEventsReadWriteOnly); + this.initOneOf(schema.anyOf, parser); this.oneOfType = 'Any of'; return; } if (this.hasType('object')) { - this.fields = buildFields(parser, schema, this.pointer, this.options, reverseEventsReadWriteOnly); + this.fields = buildFields(parser, schema, this.pointer, this.options); } else if (this.hasType('array') && schema.items) { - this.items = new SchemaModel( - parser, schema.items, this.pointer + '/items', this.options, false, reverseEventsReadWriteOnly - ); + this.items = new SchemaModel(parser, schema.items, this.pointer + '/items', this.options); this.displayType = pluralizeType(this.items.displayType); this.displayFormat = this.items.format; this.typePrefix = this.items.typePrefix + l('arrayOf'); @@ -204,7 +201,7 @@ export class SchemaModel { } } - private initOneOf(oneOf: OpenAPISchema[], parser: OpenAPIParser, reverseEventsReadWriteOnly: ReverseEventsRWOProps) { + private initOneOf(oneOf: OpenAPISchema[], parser: OpenAPIParser) { this.oneOf = oneOf!.map((variant, idx) => { const derefVariant = parser.deref(variant, false, true); @@ -227,8 +224,6 @@ export class SchemaModel { } as OpenAPISchema, this.pointer + '/oneOf/' + idx, this.options, - false, - reverseEventsReadWriteOnly, ); parser.exitRef(variant); @@ -262,7 +257,6 @@ export class SchemaModel { parentRefs?: string[]; }, parser: OpenAPIParser, - reverseEventsReadWriteOnly, ) { const discriminator = getDiscriminator(schema)!; this.discriminatorProp = discriminator.propertyName; @@ -350,7 +344,7 @@ export class SchemaModel { } this.oneOf = refs.map(({ $ref, name }) => { - const innerSchema = new SchemaModel(parser, parser.byRef($ref)!, $ref, this.options, true, reverseEventsReadWriteOnly); + const innerSchema = new SchemaModel(parser, parser.byRef($ref)!, $ref, this.options, true); innerSchema.title = name; return innerSchema; }); @@ -362,7 +356,6 @@ function buildFields( schema: OpenAPISchema, $ref: string, options: RedocNormalizedOptions, - reverseEventsReadWriteOnly: ReverseEventsRWOProps, ): FieldModel[] { const props = schema.properties || {}; const additionalProps = schema.additionalProperties; @@ -392,7 +385,6 @@ function buildFields( }, $ref + '/properties/' + fieldName, options, - reverseEventsReadWriteOnly, ); }); @@ -419,7 +411,6 @@ function buildFields( }, $ref + '/additionalProperties', options, - reverseEventsReadWriteOnly, ), ); } From 2a0b0866efe9ed67b92f5fd303c0835b24389271 Mon Sep 17 00:00:00 2001 From: Andriy Zaleskyy Date: Fri, 27 Aug 2021 15:41:27 +0300 Subject: [PATCH 4/8] feat: Option to reverse readOnly/writeOnly properties #1720 --- README.md | 2 +- demo/openapi.yaml | 4 ++-- src/components/Parameters/Parameters.tsx | 3 ++- src/services/RedocNormalizedOptions.ts | 5 ++++- src/services/models/MediaType.ts | 2 +- src/services/models/Operation.ts | 26 ++++++++++++++++-------- src/services/models/RequestBody.ts | 17 ++++++++++------ src/services/models/Response.ts | 21 +++++++++++-------- 8 files changed, 51 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 432a9d924d..5fb2ef9038 100644 --- a/README.md +++ b/README.md @@ -248,7 +248,7 @@ You can use all of the following options with standalone version on tag * `onlyRequiredInSamples` - shows only required fields in request samples. * `pathInMiddlePanel` - show path link and HTTP verb in the middle panel instead of the right one. * `requiredPropsFirst` - show required properties first ordered in the same order as in `required` array. -* `reverseEventsReadOnlyProps`, `reverseEventsWriteOnlyProps` - reverse readOnly/writeOnly schema key for webhooks and callbacks. +* `reverseEventsReadWriteProps` - reverse readOnly/writeOnly schema key for webhooks and callbacks. * `scrollYOffset` - If set, specifies a vertical scroll-offset. This is often useful when there are fixed positioned elements at the top of the page, such as navbars, headers etc; `scrollYOffset` can be specified in various ways: * **number**: A fixed number of pixels to be used as offset. 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/src/components/Parameters/Parameters.tsx b/src/components/Parameters/Parameters.tsx index 17ab9c484d..eb3d2d6187 100644 --- a/src/components/Parameters/Parameters.tsx +++ b/src/components/Parameters/Parameters.tsx @@ -69,13 +69,14 @@ function DropdownWithinHeader(props) { export function BodyContent(props: { content: MediaContentModel; description?: string }): JSX.Element { const { content, description } = props; + const { isRequestType } = content; return ( {({ schema }) => { return ( <> {description !== undefined && } - + ); }} diff --git a/src/services/RedocNormalizedOptions.ts b/src/services/RedocNormalizedOptions.ts index e310a9adcd..75c3b0ddfa 100644 --- a/src/services/RedocNormalizedOptions.ts +++ b/src/services/RedocNormalizedOptions.ts @@ -42,6 +42,7 @@ export interface RedocRawOptions { maxDisplayedEnumValues?: number; ignoreNamedSchemas?: string[] | string; hideSchemaPattern?: boolean; + reverseEventsReadWriteProps?: boolean; } export function argValueToBoolean(val?: string | boolean, defaultValue?: boolean): boolean { @@ -49,7 +50,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; } @@ -196,6 +197,7 @@ export class RedocNormalizedOptions { ignoreNamedSchemas: Set; hideSchemaPattern: boolean; + reverseEventsReadWriteProps: boolean; constructor(raw: RedocRawOptions, defaults: RedocRawOptions = {}) { raw = { ...defaults, ...raw }; @@ -257,5 +259,6 @@ export class RedocNormalizedOptions { : raw.ignoreNamedSchemas?.split(',').map((s) => s.trim()); this.ignoreNamedSchemas = new Set(ignoreNamedSchemas); this.hideSchemaPattern = argValueToBoolean(raw.hideSchemaPattern); + this.reverseEventsReadWriteProps = argValueToBoolean(raw.reverseEventsReadWriteProps); } } diff --git a/src/services/models/MediaType.ts b/src/services/models/MediaType.ts index 9283fbaecb..8987fbbbc8 100644 --- a/src/services/models/MediaType.ts +++ b/src/services/models/MediaType.ts @@ -51,8 +51,8 @@ export class MediaTypeModel { generateExample(parser: OpenAPIParser, info: OpenAPIMediaType) { const samplerOptions = { skipReadOnly: this.isRequestType, - skipNonRequired: this.isRequestType && this.onlyRequiredInSamples, skipWriteOnly: !this.isRequestType, + skipNonRequired: this.isRequestType && this.onlyRequiredInSamples, maxSampleDepth: 10, }; if (this.schema && this.schema.oneOf) { diff --git a/src/services/models/Operation.ts b/src/services/models/Operation.ts index d00461a136..7304f3a6e1 100644 --- a/src/services/models/Operation.ts +++ b/src/services/models/Operation.ts @@ -76,6 +76,7 @@ export class OperationModel implements IMenuItem { extensions: Record; isCallback: boolean; isWebhook: boolean; + reverseEventsReadWriteProps: boolean; constructor( private parser: OpenAPIParser, @@ -98,7 +99,9 @@ export class OperationModel implements IMenuItem { this.operationId = operationSpec.operationId; this.path = operationSpec.pathName; this.isCallback = isCallback; - this.isWebhook = !!operationSpec.isWebhook; + this.isWebhook = operationSpec.isWebhook; + this.reverseEventsReadWriteProps = options.reverseEventsReadWriteProps && + (this.isCallback || this.isWebhook); this.name = getOperationSummary(operationSpec); @@ -171,8 +174,12 @@ export class OperationModel implements IMenuItem { @memoize get requestBody() { return ( - this.operationSpec.requestBody && - new RequestBodyModel(this.parser, this.operationSpec.requestBody, this.options) + this.operationSpec.requestBody && new RequestBodyModel({ + parser: this.parser, + infoOrRef: this.operationSpec.requestBody, + options: this.options, + reverseEventsReadWriteProps: this.reverseEventsReadWriteProps, + }) ); } @@ -240,13 +247,14 @@ export class OperationModel implements IMenuItem { return isStatusCode(code); }) // filter out other props (e.g. x-props) .map((code) => { - return new ResponseModel( - this.parser, + return new ResponseModel({ + parser: this.parser, code, - hasSuccessResponses, - this.operationSpec.responses[code], - this.options, - ); + defaultAsError: hasSuccessResponses, + infoOrRef: this.operationSpec.responses[code], + options: this.options, + reverseEventsReadWriteProps: this.reverseEventsReadWriteProps, + }); }); } diff --git a/src/services/models/RequestBody.ts b/src/services/models/RequestBody.ts index f3b45959cb..faec735517 100644 --- a/src/services/models/RequestBody.ts +++ b/src/services/models/RequestBody.ts @@ -4,22 +4,27 @@ import { OpenAPIParser } from '../OpenAPIParser'; import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; import { MediaContentModel } from './MediaContent'; +type RequestBodyProps = { + parser: OpenAPIParser; + infoOrRef: Referenced; + options: RedocNormalizedOptions; + reverseEventsReadWriteProps: boolean; +} + export class RequestBodyModel { description: string; required: boolean; content?: MediaContentModel; - constructor( - parser: OpenAPIParser, - infoOrRef: Referenced, - options: RedocNormalizedOptions, - ) { + constructor(props: RequestBodyProps) { + const { parser, infoOrRef, options, reverseEventsReadWriteProps } = props; + const isRequest = reverseEventsReadWriteProps ? false : true; 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, isRequest, options); } } } diff --git a/src/services/models/Response.ts b/src/services/models/Response.ts index f50ee0f1ca..8cc5163156 100644 --- a/src/services/models/Response.ts +++ b/src/services/models/Response.ts @@ -8,6 +8,15 @@ import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; import { FieldModel } from './Field'; import { MediaContentModel } from './MediaContent'; +type ResponseProps = { + parser: OpenAPIParser, + code: string, + defaultAsError: boolean, + infoOrRef: Referenced, + options: RedocNormalizedOptions, + reverseEventsReadWriteProps: boolean, +} + export class ResponseModel { @observable expanded: boolean = false; @@ -19,13 +28,9 @@ export class ResponseModel { type: string; headers: FieldModel[] = []; - constructor( - parser: OpenAPIParser, - code: string, - defaultAsError: boolean, - infoOrRef: Referenced, - options: RedocNormalizedOptions, - ) { + constructor(props: ResponseProps) { + const { parser, code, defaultAsError, infoOrRef, options, reverseEventsReadWriteProps } = props; + const isRequest = reverseEventsReadWriteProps ? true : false; makeObservable(this); this.expanded = options.expandResponses === 'all' || options.expandResponses[code]; @@ -34,7 +39,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, isRequest, options); } if (info['x-summary'] !== undefined) { From 13b57acb86e35fd45d2a6e5420fb14903508d499 Mon Sep 17 00:00:00 2001 From: Andriy Zaleskyy Date: Fri, 27 Aug 2021 16:12:53 +0300 Subject: [PATCH 5/8] fix: fix tests #1720 --- .../__tests__/models/Response.test.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/services/__tests__/models/Response.test.ts b/src/services/__tests__/models/Response.test.ts index 3fb220f2df..b8a5a9fbb4 100644 --- a/src/services/__tests__/models/Response.test.ts +++ b/src/services/__tests__/models/Response.test.ts @@ -5,30 +5,38 @@ import { RedocNormalizedOptions } from '../../RedocNormalizedOptions'; const opts = new RedocNormalizedOptions({}); describe('Models', () => { describe('ResponseModel', () => { - let parser; + let parser, props; beforeEach(() => { parser = new OpenAPIParser({ openapi: '3.0.0' } as any, undefined, opts); + props = { + parser, + defaultAsError: false, + infoOrRef: {}, + options: opts, + reverseEventsReadWriteProps: false, + code: 'default', + }; }); test('should calculate response type based on code', () => { - let resp = new ResponseModel(parser, '200', false, {}, opts); + let resp = new ResponseModel({...props, code: '200' }); expect(resp.type).toEqual('success'); - resp = new ResponseModel(parser, '120', false, {}, opts); + resp = new ResponseModel({...props, code: '120' }); expect(resp.type).toEqual('info'); - resp = new ResponseModel(parser, '301', false, {}, opts); + resp = new ResponseModel({...props, code: '301' }); expect(resp.type).toEqual('redirect'); - resp = new ResponseModel(parser, '400', false, {}, opts); + resp = new ResponseModel({...props, code: '400' }); expect(resp.type).toEqual('error'); }); test('default should be successful by default', () => { - const resp = new ResponseModel(parser, 'default', false, {}, opts); + const resp = new ResponseModel({...props, code: 'default' }); expect(resp.type).toEqual('success'); }); test('default should be error if defaultAsError is true', () => { - const resp = new ResponseModel(parser, 'default', true, {}, opts); + const resp = new ResponseModel({...props, code: 'default', defaultAsError: true }); expect(resp.type).toEqual('error'); }); }); From 5fa68a1c9dba122770e8db72dfb683d5f4cdb448 Mon Sep 17 00:00:00 2001 From: Andriy Zaleskyy Date: Fri, 27 Aug 2021 17:47:13 +0300 Subject: [PATCH 6/8] fix: set reversing of read/write only by default --- README.md | 1 - src/services/RedocNormalizedOptions.ts | 3 --- src/services/models/Operation.ts | 3 +-- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 5fb2ef9038..6c63e3e677 100644 --- a/README.md +++ b/README.md @@ -248,7 +248,6 @@ You can use all of the following options with standalone version on tag * `onlyRequiredInSamples` - shows only required fields in request samples. * `pathInMiddlePanel` - show path link and HTTP verb in the middle panel instead of the right one. * `requiredPropsFirst` - show required properties first ordered in the same order as in `required` array. -* `reverseEventsReadWriteProps` - reverse readOnly/writeOnly schema key for webhooks and callbacks. * `scrollYOffset` - If set, specifies a vertical scroll-offset. This is often useful when there are fixed positioned elements at the top of the page, such as navbars, headers etc; `scrollYOffset` can be specified in various ways: * **number**: A fixed number of pixels to be used as offset. diff --git a/src/services/RedocNormalizedOptions.ts b/src/services/RedocNormalizedOptions.ts index 75c3b0ddfa..c2407b9754 100644 --- a/src/services/RedocNormalizedOptions.ts +++ b/src/services/RedocNormalizedOptions.ts @@ -42,7 +42,6 @@ export interface RedocRawOptions { maxDisplayedEnumValues?: number; ignoreNamedSchemas?: string[] | string; hideSchemaPattern?: boolean; - reverseEventsReadWriteProps?: boolean; } export function argValueToBoolean(val?: string | boolean, defaultValue?: boolean): boolean { @@ -197,7 +196,6 @@ export class RedocNormalizedOptions { ignoreNamedSchemas: Set; hideSchemaPattern: boolean; - reverseEventsReadWriteProps: boolean; constructor(raw: RedocRawOptions, defaults: RedocRawOptions = {}) { raw = { ...defaults, ...raw }; @@ -259,6 +257,5 @@ export class RedocNormalizedOptions { : raw.ignoreNamedSchemas?.split(',').map((s) => s.trim()); this.ignoreNamedSchemas = new Set(ignoreNamedSchemas); this.hideSchemaPattern = argValueToBoolean(raw.hideSchemaPattern); - this.reverseEventsReadWriteProps = argValueToBoolean(raw.reverseEventsReadWriteProps); } } diff --git a/src/services/models/Operation.ts b/src/services/models/Operation.ts index 7304f3a6e1..8d5e71bfad 100644 --- a/src/services/models/Operation.ts +++ b/src/services/models/Operation.ts @@ -100,8 +100,7 @@ export class OperationModel implements IMenuItem { this.path = operationSpec.pathName; this.isCallback = isCallback; this.isWebhook = operationSpec.isWebhook; - this.reverseEventsReadWriteProps = options.reverseEventsReadWriteProps && - (this.isCallback || this.isWebhook); + this.reverseEventsReadWriteProps = this.isCallback || this.isWebhook; this.name = getOperationSummary(operationSpec); From 02c57403db4ffbd7cc4d3a3160f8d8351165bd8f Mon Sep 17 00:00:00 2001 From: Andriy Zaleskyy Date: Tue, 31 Aug 2021 10:23:55 +0300 Subject: [PATCH 7/8] fix: actualize naming --- src/services/__tests__/models/Response.test.ts | 2 +- src/services/models/Operation.ts | 8 ++++---- src/services/models/RequestBody.ts | 6 +++--- src/services/models/Response.ts | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/services/__tests__/models/Response.test.ts b/src/services/__tests__/models/Response.test.ts index b8a5a9fbb4..ae3c1fd1cb 100644 --- a/src/services/__tests__/models/Response.test.ts +++ b/src/services/__tests__/models/Response.test.ts @@ -14,7 +14,7 @@ describe('Models', () => { defaultAsError: false, infoOrRef: {}, options: opts, - reverseEventsReadWriteProps: false, + isEvent: false, code: 'default', }; }); diff --git a/src/services/models/Operation.ts b/src/services/models/Operation.ts index 8d5e71bfad..5b1b5aca70 100644 --- a/src/services/models/Operation.ts +++ b/src/services/models/Operation.ts @@ -76,7 +76,7 @@ export class OperationModel implements IMenuItem { extensions: Record; isCallback: boolean; isWebhook: boolean; - reverseEventsReadWriteProps: boolean; + isEvent: boolean; constructor( private parser: OpenAPIParser, @@ -100,7 +100,7 @@ export class OperationModel implements IMenuItem { this.path = operationSpec.pathName; this.isCallback = isCallback; this.isWebhook = operationSpec.isWebhook; - this.reverseEventsReadWriteProps = this.isCallback || this.isWebhook; + this.isEvent = this.isCallback || this.isWebhook; this.name = getOperationSummary(operationSpec); @@ -177,7 +177,7 @@ export class OperationModel implements IMenuItem { parser: this.parser, infoOrRef: this.operationSpec.requestBody, options: this.options, - reverseEventsReadWriteProps: this.reverseEventsReadWriteProps, + isEvent: this.isEvent, }) ); } @@ -252,7 +252,7 @@ export class OperationModel implements IMenuItem { defaultAsError: hasSuccessResponses, infoOrRef: this.operationSpec.responses[code], options: this.options, - reverseEventsReadWriteProps: this.reverseEventsReadWriteProps, + isEvent: this.isEvent, }); }); } diff --git a/src/services/models/RequestBody.ts b/src/services/models/RequestBody.ts index faec735517..3ca3dc2119 100644 --- a/src/services/models/RequestBody.ts +++ b/src/services/models/RequestBody.ts @@ -8,7 +8,7 @@ type RequestBodyProps = { parser: OpenAPIParser; infoOrRef: Referenced; options: RedocNormalizedOptions; - reverseEventsReadWriteProps: boolean; + isEvent: boolean; } export class RequestBodyModel { @@ -17,8 +17,8 @@ export class RequestBodyModel { content?: MediaContentModel; constructor(props: RequestBodyProps) { - const { parser, infoOrRef, options, reverseEventsReadWriteProps } = props; - const isRequest = reverseEventsReadWriteProps ? false : true; + const { parser, infoOrRef, options, isEvent } = props; + const isRequest = isEvent ? false : true; const info = parser.deref(infoOrRef); this.description = info.description || ''; this.required = !!info.required; diff --git a/src/services/models/Response.ts b/src/services/models/Response.ts index 8cc5163156..d3ed554a33 100644 --- a/src/services/models/Response.ts +++ b/src/services/models/Response.ts @@ -14,7 +14,7 @@ type ResponseProps = { defaultAsError: boolean, infoOrRef: Referenced, options: RedocNormalizedOptions, - reverseEventsReadWriteProps: boolean, + isEvent: boolean, } export class ResponseModel { @@ -29,8 +29,8 @@ export class ResponseModel { headers: FieldModel[] = []; constructor(props: ResponseProps) { - const { parser, code, defaultAsError, infoOrRef, options, reverseEventsReadWriteProps } = props; - const isRequest = reverseEventsReadWriteProps ? true : false; + const { parser, code, defaultAsError, infoOrRef, options, isEvent } = props; + const isRequest = isEvent ? true : false; makeObservable(this); this.expanded = options.expandResponses === 'all' || options.expandResponses[code]; From c51cb33064d43335eabc79a6af29be8f752d4742 Mon Sep 17 00:00:00 2001 From: Andriy Zaleskyy Date: Tue, 31 Aug 2021 15:36:40 +0300 Subject: [PATCH 8/8] chore: add test --- .../__tests__/models/RequestBody.test.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/services/__tests__/models/RequestBody.test.ts diff --git a/src/services/__tests__/models/RequestBody.test.ts b/src/services/__tests__/models/RequestBody.test.ts new file mode 100644 index 0000000000..554a1222a6 --- /dev/null +++ b/src/services/__tests__/models/RequestBody.test.ts @@ -0,0 +1,27 @@ +import { RequestBodyModel } from '../../models/RequestBody'; +import { OpenAPIParser } from '../../OpenAPIParser'; +import { RedocNormalizedOptions } from '../../RedocNormalizedOptions'; + +const opts = new RedocNormalizedOptions({}); +describe('Models', () => { + describe('RequestBodyModel', () => { + let parser, props; + + beforeEach(() => { + parser = new OpenAPIParser({ openapi: '3.0.0' } as any, undefined, opts); + props = { + parser, + infoOrRef: {}, + options: opts, + isEvent: false, + }; + }); + + test('should work with default props', () => { + const consoleError = jest.spyOn(global.console, 'error'); + const req = new RequestBodyModel(props); + expect(consoleError).not.toHaveBeenCalled(); + expect(req).toEqual({ description: '', required: false }); + }); + }); +});