diff --git a/README.md b/README.md index 432a9d92..5fb2ef90 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 74c16b70..c570cc60 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 17ab9c48..eb3d2d61 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 e310a9ad..75c3b0dd 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 9283fbae..8987fbbb 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 d00461a1..7304f3a6 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 f3b45959..faec7355 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 f50ee0f1..8cc51631 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) {