Skip to content

Commit

Permalink
feat: support multiple examples for parameters (#1470)
Browse files Browse the repository at this point in the history
  • Loading branch information
RomanHotsiy authored Nov 23, 2020
1 parent 340a568 commit d12e410
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 10 deletions.
60 changes: 50 additions & 10 deletions src/components/Fields/FieldDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
TypePrefix,
TypeTitle,
ToggleButton,
FieldLabel,
ExampleValue,
} from '../../common-elements/fields';
import { serializeParameterValue } from '../../utils/openapi';
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
Expand All @@ -23,6 +25,8 @@ import { Badge } from '../../common-elements/';

import { l } from '../../services/Labels';
import { OptionsContext } from '../OptionsProvider';
import { FieldModel } from '../../services/models/Field';
import styled from '../../styled-components';

const MAX_PATTERN_LENGTH = 45;

Expand All @@ -44,20 +48,19 @@ export class FieldDetails extends React.PureComponent<FieldProps, { patternShown
const { patternShown } = this.state;
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

let exampleField: JSX.Element | null = null;
let renderedExamples: JSX.Element | null = null;

if (showExamples && example !== undefined) {
const label = l('example') + ':';
if (field.in && (field.style || field.serializationMime)) {
// 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} />;
if (showExamples && (example !== undefined || examples !== undefined)) {
if (examples !== undefined) {
renderedExamples = <Examples field={field} />;
} 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} />;
}
}

Expand Down Expand Up @@ -100,7 +103,7 @@ export class FieldDetails extends React.PureComponent<FieldProps, { patternShown
)}
<FieldDetail raw={rawDefault} label={l('default') + ':'} value={schema.default} />
{!renderDiscriminatorSwitch && <EnumValues type={schema.type} values={schema.enum} />}{' '}
{exampleField}
{renderedExamples}
{<Extensions extensions={{ ...field.extensions, ...schema.extensions }} />}
<div>
<Markdown compact={true} source={description} />
Expand All @@ -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;
`;
2 changes: 2 additions & 0 deletions src/services/Labels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface LabelsConfig {
default: string;
deprecated: string;
example: string;
examples: string;
nullable: string;
recursive: string;
arrayOf: string;
Expand All @@ -20,6 +21,7 @@ const labels: LabelsConfig = {
default: 'Default',
deprecated: 'Deprecated',
example: 'Example',
examples: 'Examples',
nullable: 'Nullable',
recursive: 'Recursive',
arrayOf: 'Array of ',
Expand Down
10 changes: 10 additions & 0 deletions src/services/models/Field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
import { extractExtensions } from '../../utils/openapi';
import { OpenAPIParser } from '../OpenAPIParser';
import { SchemaModel } from './Schema';
import { ExampleModel } from './Example';
import { mapValues } from '../../utils/helpers';

const DEFAULT_SERIALIZATION: Record<
OpenAPIParameterLocation,
Expand Down Expand Up @@ -46,6 +48,7 @@ export class FieldModel {
required: boolean;
description: string;
example?: string;
examples?: Record<string, ExampleModel>;
deprecated: boolean;
in?: OpenAPIParameterLocation;
kind: string;
Expand Down Expand Up @@ -81,6 +84,13 @@ export class FieldModel {
info.description === undefined ? this.schema.description || '' : info.description;
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) {
this.serializationMime = serializationMime;
} else if (info.style) {
Expand Down
1 change: 1 addition & 0 deletions src/types/open-api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export interface OpenAPIParameter {
example?: any;
examples?: { [media: string]: Referenced<OpenAPIExample> };
content?: { [media: string]: OpenAPIMediaType };
encoding?: Record<string, OpenAPIEncoding>;
}

export interface OpenAPIExample {
Expand Down

0 comments on commit d12e410

Please sign in to comment.