Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions demo/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
3 changes: 2 additions & 1 deletion src/components/Parameters/Parameters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<MediaTypesSwitch content={content} renderDropdown={DropdownWithinHeader}>
{({ schema }) => {
return (
<>
{description !== undefined && <Markdown source={description} />}
<Schema skipReadOnly={true} key="schema" schema={schema} />
<Schema skipReadOnly={isRequestType} key="schema" schema={schema} />
</>
);
}}
Expand Down
2 changes: 1 addition & 1 deletion src/services/RedocNormalizedOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,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;
}
Expand Down
27 changes: 27 additions & 0 deletions src/services/__tests__/models/RequestBody.test.ts
Original file line number Diff line number Diff line change
@@ -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 });
});
});
});
22 changes: 15 additions & 7 deletions src/services/__tests__/models/Response.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
isEvent: 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');
});
});
Expand Down
2 changes: 1 addition & 1 deletion src/services/models/MediaType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
25 changes: 16 additions & 9 deletions src/services/models/Operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export class OperationModel implements IMenuItem {
extensions: Record<string, any>;
isCallback: boolean;
isWebhook: boolean;
isEvent: boolean;

constructor(
private parser: OpenAPIParser,
Expand All @@ -98,7 +99,8 @@ 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.isEvent = this.isCallback || this.isWebhook;

this.name = getOperationSummary(operationSpec);

Expand Down Expand Up @@ -171,8 +173,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,
isEvent: this.isEvent,
})
);
}

Expand Down Expand Up @@ -240,13 +246,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,
isEvent: this.isEvent,
});
});
}

Expand Down
17 changes: 11 additions & 6 deletions src/services/models/RequestBody.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,27 @@ import { OpenAPIParser } from '../OpenAPIParser';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
import { MediaContentModel } from './MediaContent';

type RequestBodyProps = {
parser: OpenAPIParser;
infoOrRef: Referenced<OpenAPIRequestBody>;
options: RedocNormalizedOptions;
isEvent: boolean;
}

export class RequestBodyModel {
description: string;
required: boolean;
content?: MediaContentModel;

constructor(
parser: OpenAPIParser,
infoOrRef: Referenced<OpenAPIRequestBody>,
options: RedocNormalizedOptions,
) {
constructor(props: RequestBodyProps) {
const { parser, infoOrRef, options, isEvent } = props;
const isRequest = isEvent ? 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);
}
}
}
21 changes: 13 additions & 8 deletions src/services/models/Response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<OpenAPIResponse>,
options: RedocNormalizedOptions,
isEvent: boolean,
}

export class ResponseModel {
@observable
expanded: boolean = false;
Expand All @@ -19,13 +28,9 @@ export class ResponseModel {
type: string;
headers: FieldModel[] = [];

constructor(
parser: OpenAPIParser,
code: string,
defaultAsError: boolean,
infoOrRef: Referenced<OpenAPIResponse>,
options: RedocNormalizedOptions,
) {
constructor(props: ResponseProps) {
const { parser, code, defaultAsError, infoOrRef, options, isEvent } = props;
const isRequest = isEvent ? true : false;
makeObservable(this);

this.expanded = options.expandResponses === 'all' || options.expandResponses[code];
Expand All @@ -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) {
Expand Down