feat: support multiple examples for parameters (#1470)

This commit is contained in:
Roman Hotsiy 2020-11-23 14:00:17 +02:00 committed by GitHub
parent 340a568262
commit d12e410d99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 63 additions and 10 deletions

View File

@ -9,6 +9,8 @@ import {
TypePrefix, TypePrefix,
TypeTitle, TypeTitle,
ToggleButton, ToggleButton,
FieldLabel,
ExampleValue,
} from '../../common-elements/fields'; } from '../../common-elements/fields';
import { serializeParameterValue } from '../../utils/openapi'; import { serializeParameterValue } from '../../utils/openapi';
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation'; import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
@ -23,6 +25,8 @@ import { Badge } from '../../common-elements/';
import { l } from '../../services/Labels'; import { l } from '../../services/Labels';
import { OptionsContext } from '../OptionsProvider'; import { OptionsContext } from '../OptionsProvider';
import { FieldModel } from '../../services/models/Field';
import styled from '../../styled-components';
const MAX_PATTERN_LENGTH = 45; const MAX_PATTERN_LENGTH = 45;
@ -44,20 +48,19 @@ export class FieldDetails extends React.PureComponent<FieldProps, { patternShown
const { patternShown } = this.state; const { patternShown } = this.state;
const { enumSkipQuotes, hideSchemaTitles } = this.context; const { enumSkipQuotes, hideSchemaTitles } = this.context;
const { schema, description, example, deprecated } = field; const { schema, description, example, deprecated, examples } = field;
const rawDefault = !!enumSkipQuotes || field.in === 'header'; // having quotes around header field default values is confusing and inappropriate const rawDefault = !!enumSkipQuotes || field.in === 'header'; // having quotes around header field default values is confusing and inappropriate
let exampleField: JSX.Element | null = null; let renderedExamples: JSX.Element | null = null;
if (showExamples && example !== undefined) { if (showExamples && (example !== undefined || examples !== undefined)) {
const label = l('example') + ':'; if (examples !== undefined) {
if (field.in && (field.style || field.serializationMime)) { renderedExamples = <Examples field={field} />;
// decode for better readability in examples: see https://github.com/Redocly/redoc/issues/1138
const serializedValue = decodeURIComponent(serializeParameterValue(field, example));
exampleField = <FieldDetail label={label} value={serializedValue} raw={true} />;
} else { } else {
exampleField = <FieldDetail label={label} value={example} />; const label = l('example') + ':';
const raw = !!field.in;
renderedExamples = <FieldDetail label={label} value={getSerializedValue(field, field.example)} raw={raw} />;
} }
} }
@ -100,7 +103,7 @@ export class FieldDetails extends React.PureComponent<FieldProps, { patternShown
)} )}
<FieldDetail raw={rawDefault} label={l('default') + ':'} value={schema.default} /> <FieldDetail raw={rawDefault} label={l('default') + ':'} value={schema.default} />
{!renderDiscriminatorSwitch && <EnumValues type={schema.type} values={schema.enum} />}{' '} {!renderDiscriminatorSwitch && <EnumValues type={schema.type} values={schema.enum} />}{' '}
{exampleField} {renderedExamples}
{<Extensions extensions={{ ...field.extensions, ...schema.extensions }} />} {<Extensions extensions={{ ...field.extensions, ...schema.extensions }} />}
<div> <div>
<Markdown compact={true} source={description} /> <Markdown compact={true} source={description} />
@ -113,3 +116,40 @@ export class FieldDetails extends React.PureComponent<FieldProps, { patternShown
); );
} }
} }
function Examples({ field }: { field: FieldModel }) {
if (!field.examples) {
return null;
}
return (
<>
<FieldLabel> {l('examples')}: </FieldLabel>
<ExamplesList>
{Object.values(field.examples).map((example, idx) => {
return (
<li key={idx}>
<ExampleValue>{getSerializedValue(field, example.value)}</ExampleValue> - {example.summary || example.description}
</li>
);
})}
</ExamplesList>
</>
);
}
function getSerializedValue(field: FieldModel, example: any) {
if (field.in) {
// decode for better readability in examples: see https://github.com/Redocly/redoc/issues/1138
return decodeURIComponent(serializeParameterValue(field, example));
} else {
return example;
}
}
const ExamplesList = styled.ul`
margin-top: 1em;
padding-left: 0;
list-style-position: inside;
`;

View File

@ -5,6 +5,7 @@ export interface LabelsConfig {
default: string; default: string;
deprecated: string; deprecated: string;
example: string; example: string;
examples: string;
nullable: string; nullable: string;
recursive: string; recursive: string;
arrayOf: string; arrayOf: string;
@ -20,6 +21,7 @@ const labels: LabelsConfig = {
default: 'Default', default: 'Default',
deprecated: 'Deprecated', deprecated: 'Deprecated',
example: 'Example', example: 'Example',
examples: 'Examples',
nullable: 'Nullable', nullable: 'Nullable',
recursive: 'Recursive', recursive: 'Recursive',
arrayOf: 'Array of ', arrayOf: 'Array of ',

View File

@ -11,6 +11,8 @@ import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
import { extractExtensions } from '../../utils/openapi'; import { extractExtensions } from '../../utils/openapi';
import { OpenAPIParser } from '../OpenAPIParser'; import { OpenAPIParser } from '../OpenAPIParser';
import { SchemaModel } from './Schema'; import { SchemaModel } from './Schema';
import { ExampleModel } from './Example';
import { mapValues } from '../../utils/helpers';
const DEFAULT_SERIALIZATION: Record< const DEFAULT_SERIALIZATION: Record<
OpenAPIParameterLocation, OpenAPIParameterLocation,
@ -46,6 +48,7 @@ export class FieldModel {
required: boolean; required: boolean;
description: string; description: string;
example?: string; example?: string;
examples?: Record<string, ExampleModel>;
deprecated: boolean; deprecated: boolean;
in?: OpenAPIParameterLocation; in?: OpenAPIParameterLocation;
kind: string; kind: string;
@ -81,6 +84,13 @@ export class FieldModel {
info.description === undefined ? this.schema.description || '' : info.description; info.description === undefined ? this.schema.description || '' : info.description;
this.example = info.example || this.schema.example; this.example = info.example || this.schema.example;
if (info.examples !== undefined) {
this.examples = mapValues(
info.examples,
example => new ExampleModel(parser, example, name, info.encoding),
);
}
if (serializationMime) { if (serializationMime) {
this.serializationMime = serializationMime; this.serializationMime = serializationMime;
} else if (info.style) { } else if (info.style) {

View File

@ -95,6 +95,7 @@ export interface OpenAPIParameter {
example?: any; example?: any;
examples?: { [media: string]: Referenced<OpenAPIExample> }; examples?: { [media: string]: Referenced<OpenAPIExample> };
content?: { [media: string]: OpenAPIMediaType }; content?: { [media: string]: OpenAPIMediaType };
encoding?: Record<string, OpenAPIEncoding>;
} }
export interface OpenAPIExample { export interface OpenAPIExample {