Skip to content

Commit

Permalink
fix: x-examples for request body param does not display #1743 (#1826)
Browse files Browse the repository at this point in the history
* fix: x-examples for request body param does not display #1743

* lint

* fix ts
  • Loading branch information
Oprysk authored Dec 9, 2021
1 parent 913b3a4 commit aaa3b32
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 8 deletions.
1 change: 0 additions & 1 deletion src/components/Operation/Operation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { ResponsesList } from '../Responses/ResponsesList';
import { ResponseSamples } from '../ResponseSamples/ResponseSamples';
import { SecurityRequirements } from '../SecurityRequirement/SecurityRequirement';


const Description = styled.div`
margin-bottom: ${({ theme }) => theme.spacing.unit * 6}px;
`;
Expand Down
9 changes: 6 additions & 3 deletions src/services/models/RequestBody.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { OpenAPIRequestBody, Referenced } from '../../types';
import { OpenAPIParser } from '../OpenAPIParser';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
import { MediaContentModel } from './MediaContent';
import { getContentWithLegacyExamples } from '../../utils';

type RequestBodyProps = {
parser: OpenAPIParser;
Expand All @@ -18,13 +19,15 @@ export class RequestBodyModel {

constructor(props: RequestBodyProps) {
const { parser, infoOrRef, options, isEvent } = props;
const isRequest = isEvent ? false : true;
const isRequest = !isEvent;
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, isRequest, options);

const mediaContent = getContentWithLegacyExamples(info);
if (mediaContent !== undefined) {
this.content = new MediaContentModel(parser, mediaContent, isRequest, options);
}
}
}
9 changes: 6 additions & 3 deletions src/types/open-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,17 +186,20 @@ export interface OpenAPIRequestBody {
description?: string;
required?: boolean;
content: { [mime: string]: OpenAPIMediaType };

'x-examples'?: { [mime: string]: { [name: string]: Referenced<OpenAPIExample> } };
'x-example'?: { [mime: string]: any };
}

export interface OpenAPIResponses {
[code: string]: OpenAPIResponse;
}

export interface OpenAPIResponse {
description?: string;
export interface OpenAPIResponse
extends Pick<OpenAPIRequestBody, 'description' | 'x-examples' | 'x-example'> {
headers?: { [name: string]: Referenced<OpenAPIHeader> };
content?: { [mime: string]: OpenAPIMediaType };
links?: { [name: string]: Referenced<OpenAPILink> };
content?: { [mime: string]: OpenAPIMediaType };
}

export interface OpenAPILink {
Expand Down
110 changes: 109 additions & 1 deletion src/utils/__tests__/openapi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@ import {
serializeParameterValue,
sortByRequired,
humanizeNumberRange,
getContentWithLegacyExamples,
} from '../';

import { FieldModel, OpenAPIParser, RedocNormalizedOptions } from '../../services';
import { OpenAPIParameter, OpenAPIParameterLocation, OpenAPIParameterStyle } from '../../types';
import {
OpenAPIMediaType,
OpenAPIParameter,
OpenAPIParameterLocation,
OpenAPIParameterStyle,
} from '../../types';
import { expandDefaultServerVariables } from '../openapi';

describe('Utils', () => {
Expand Down Expand Up @@ -1161,4 +1167,106 @@ describe('Utils', () => {
]);
});
});

describe('OpenAPI getContentWithLegacyExamples', () => {
it('should return undefined if no x-examples/x-example and no content', () => {
expect(getContentWithLegacyExamples({})).toBeUndefined();
});

it('should return unmodified object if no x-examples or x-example', () => {
const info = {
content: {
'application/json': {},
},
};

const content = getContentWithLegacyExamples(info);
expect(content).toStrictEqual(info.content);
});

it('should create a new content object if no content and x-examples', () => {
const info = {
'x-examples': {
'application/json': {
name: {
value: 'test',
},
},
},
};

const content = getContentWithLegacyExamples(info);
expect(content).toEqual({
'application/json': {
examples: {
name: {
value: 'test',
},
},
},
});
});

it('should create a new content object if no content and x-example', () => {
const info = {
'x-example': {
'application/json': 'test',
},
};

const content = getContentWithLegacyExamples(info);
expect(content).toEqual({
'application/json': { example: 'test' },
});
});

it('should return copy of content with injected x-example', () => {
const info = {
'x-example': {
'application/json': 'test',
},
content: {
'application/json': {
schema: { type: 'string' },
},
'text/plain': { schema: { type: 'string' } },
},
};

const content = getContentWithLegacyExamples(info) as { [mime: string]: OpenAPIMediaType };
expect(content).toEqual({
'application/json': { schema: { type: 'string' }, example: 'test' },
'text/plain': { schema: { type: 'string' } },
});
expect(content).not.toStrictEqual(info.content);
expect(content['application/json']).not.toStrictEqual(info.content['application/json']);
expect(content['text/plain']).toStrictEqual(info.content['text/plain']);
});

it('should prefer x-examples over x-example', () => {
const info = {
'x-example': {
'application/json': 'test',
},
'x-examples': {
'application/json': { name: { value: 'test' } },
},
content: {
'application/json': {
schema: { type: 'string' },
},
'text/plain': { schema: { type: 'string' } },
},
};

const content = getContentWithLegacyExamples(info) as { [mime: string]: OpenAPIMediaType };
expect(content).toEqual({
'application/json': { schema: { type: 'string' }, examples: { name: { value: 'test' } } },
'text/plain': { schema: { type: 'string' } },
});
expect(content).not.toStrictEqual(info.content);
expect(content['application/json']).not.toStrictEqual(info.content['application/json']);
expect(content['text/plain']).toStrictEqual(info.content['text/plain']);
});
});
});
32 changes: 32 additions & 0 deletions src/utils/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
OpenAPIMediaType,
OpenAPIParameter,
OpenAPIParameterStyle,
OpenAPIRequestBody,
OpenAPIResponse,
OpenAPISchema,
OpenAPIServer,
Referenced,
Expand Down Expand Up @@ -638,3 +640,33 @@ export function pluralizeType(displayType: string): string {
.map(type => type.replace(/^(string|object|number|integer|array|boolean)s?( ?.*)/, '$1s$2'))
.join(' or ');
}

export function getContentWithLegacyExamples(
info: OpenAPIRequestBody | OpenAPIResponse,
): { [mime: string]: OpenAPIMediaType } | undefined {
let mediaContent = info.content;
const xExamples = info['x-examples']; // converted from OAS2 body param
const xExample = info['x-example']; // converted from OAS2 body param

if (xExamples) {
mediaContent = { ...mediaContent };
for (const mime of Object.keys(xExamples)) {
const examples = xExamples[mime];
mediaContent[mime] = {
...mediaContent[mime],
examples,
};
}
} else if (xExample) {
mediaContent = { ...mediaContent };
for (const mime of Object.keys(xExample)) {
const example = xExample[mime];
mediaContent[mime] = {
...mediaContent[mime],
example,
};
}
}

return mediaContent;
}

0 comments on commit aaa3b32

Please sign in to comment.