feat: new option showExtensions (support list of extensions)

This commit is contained in:
Roman Hotsiy 2018-09-25 18:50:08 +03:00
parent 3000b67637
commit e92f00edb2
No known key found for this signature in database
GPG Key ID: 5CB7B3ACABA57CB0
8 changed files with 72 additions and 21 deletions

View File

@ -1,35 +1,37 @@
import * as React from 'react'; import * as React from 'react';
import styled from '../../styled-components';
import { isRedocExtension } from '../../utils/openapi';
import { OptionsContext } from '../OptionsProvider'; import { OptionsContext } from '../OptionsProvider';
import { SchemaModel } from '../../services/models'; import { StyledMarkdownBlock } from '../Markdown/styled.elements';
import { FieldDetail } from './FieldDetail';
const Extension = styled(StyledMarkdownBlock)`
opacity: 0.9;
margin: 2px 0;
`;
const ExtensionLable = styled.span`
font-style: italic;
`;
export interface ExtensionsProps { export interface ExtensionsProps {
schema: SchemaModel; extensions: {
[k: string]: any;
};
} }
export class Extensions extends React.PureComponent<ExtensionsProps> { export class Extensions extends React.PureComponent<ExtensionsProps> {
constructor(props) {
super(props);
this.getExtensions = this.getExtensions.bind(this);
}
getExtensions() {
const { schema } = this.props;
const fullSchema = schema.schema;
return Object.keys(fullSchema).filter(key => key.startsWith('x-') && !isRedocExtension(key));
}
render() { render() {
return ( return (
<OptionsContext.Consumer> <OptionsContext.Consumer>
{options => ( {options => (
<> <>
{options.showExtensions && {options.showExtensions &&
this.getExtensions().map(key => ( Object.keys(this.props.extensions).map(key => (
<FieldDetail key={key} label={key} value={this.props.schema.schema[key]} /> <Extension key={key}>
<ExtensionLable>{key}</ExtensionLable>:{' '}
<code>{JSON.stringify(this.props.extensions[key])}</code>
</Extension>
))} ))}
</> </>
)} )}

View File

@ -52,7 +52,7 @@ export class FieldDetails extends React.PureComponent<FieldProps> {
<FieldDetail label={'Default:'} value={schema.default} /> <FieldDetail label={'Default:'} value={schema.default} />
{!renderDiscriminatorSwitch && <EnumValues type={schema.type} values={schema.enum} />}{' '} {!renderDiscriminatorSwitch && <EnumValues type={schema.type} values={schema.enum} />}{' '}
{showExamples && <FieldDetail label={'Example:'} value={example} />} {showExamples && <FieldDetail label={'Example:'} value={example} />}
{<Extensions schema={schema} />} {<Extensions extensions={{ ...field.extensions, ...schema.extensions }} />}
<div> <div>
<Markdown compact={true} source={description} /> <Markdown compact={true} source={description} />
</div> </div>

View File

@ -18,6 +18,7 @@ import { ResponseSamples } from '../ResponseSamples/ResponseSamples';
import { OperationModel as OperationType } from '../../services/models'; import { OperationModel as OperationType } from '../../services/models';
import styled from '../../styled-components'; import styled from '../../styled-components';
import { Extensions } from '../Fields/Extensions';
const OperationRow = styled(Row)` const OperationRow = styled(Row)`
backface-visibility: hidden; backface-visibility: hidden;
@ -58,6 +59,7 @@ export class Operation extends React.Component<OperationProps> {
{externalDocs && <ExternalDocumentation externalDocs={externalDocs} />} {externalDocs && <ExternalDocumentation externalDocs={externalDocs} />}
</Description> </Description>
)} )}
<Extensions extensions={operation.extensions} />
<SecurityRequirements securities={operation.security} /> <SecurityRequirements securities={operation.security} />
<Parameters parameters={operation.parameters} body={operation.requestBody} /> <Parameters parameters={operation.parameters} body={operation.requestBody} />
<ResponsesList responses={operation.responses} /> <ResponsesList responses={operation.responses} />

View File

@ -17,7 +17,7 @@ export interface RedocRawOptions {
hideLoading?: boolean | string; hideLoading?: boolean | string;
hideDownloadButton?: boolean | string; hideDownloadButton?: boolean | string;
disableSearch?: boolean | string; disableSearch?: boolean | string;
showExtensions?: boolean | string; showExtensions?: boolean | string | string[];
unstable_ignoreMimeParameters?: boolean; unstable_ignoreMimeParameters?: boolean;
@ -89,6 +89,21 @@ export class RedocNormalizedOptions {
return () => 0; return () => 0;
} }
static normalizeShowExtensions(value: RedocRawOptions['showExtensions']): string[] | boolean {
if (typeof value === 'undefined') {
return false;
}
if (value === '') {
return true;
}
if (typeof value === 'string') {
return value.split(',').map(ext => ext.trim());
}
return value;
}
theme: ResolvedThemeInterface; theme: ResolvedThemeInterface;
scrollYOffset: () => number; scrollYOffset: () => number;
hideHostname: boolean; hideHostname: boolean;
@ -100,7 +115,7 @@ export class RedocNormalizedOptions {
untrustedSpec: boolean; untrustedSpec: boolean;
hideDownloadButton: boolean; hideDownloadButton: boolean;
disableSearch: boolean; disableSearch: boolean;
showExtensions: boolean; showExtensions: boolean | string[];
/* tslint:disable-next-line */ /* tslint:disable-next-line */
unstable_ignoreMimeParameters: boolean; unstable_ignoreMimeParameters: boolean;
@ -126,7 +141,7 @@ export class RedocNormalizedOptions {
this.untrustedSpec = argValueToBoolean(raw.untrustedSpec); this.untrustedSpec = argValueToBoolean(raw.untrustedSpec);
this.hideDownloadButton = argValueToBoolean(raw.hideDownloadButton); this.hideDownloadButton = argValueToBoolean(raw.hideDownloadButton);
this.disableSearch = argValueToBoolean(raw.disableSearch); this.disableSearch = argValueToBoolean(raw.disableSearch);
this.showExtensions = argValueToBoolean(raw.showExtensions); this.showExtensions = RedocNormalizedOptions.normalizeShowExtensions(raw.showExtensions);
this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters); this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters);

View File

@ -3,6 +3,7 @@ import { action, observable } from 'mobx';
import { OpenAPIParameter, Referenced } from '../../types'; import { OpenAPIParameter, Referenced } from '../../types';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
import { extractExtensions } from '../../utils/openapi';
import { OpenAPIParser } from '../OpenAPIParser'; import { OpenAPIParser } from '../OpenAPIParser';
import { SchemaModel } from './Schema'; import { SchemaModel } from './Schema';
@ -21,6 +22,7 @@ export class FieldModel {
deprecated: boolean; deprecated: boolean;
in?: string; in?: string;
kind: string; kind: string;
extensions?: Dict<any>;
constructor( constructor(
parser: OpenAPIParser, parser: OpenAPIParser,
@ -40,6 +42,10 @@ export class FieldModel {
this.deprecated = info.deprecated === undefined ? !!this.schema.deprecated : info.deprecated; this.deprecated = info.deprecated === undefined ? !!this.schema.deprecated : info.deprecated;
parser.exitRef(infoOrRef); parser.exitRef(infoOrRef);
if (options.showExtensions) {
this.extensions = extractExtensions(info, options.showExtensions);
}
} }
@action @action

View File

@ -7,6 +7,7 @@ import { SecurityRequirementModel } from './SecurityRequirement';
import { OpenAPIExternalDocumentation, OpenAPIServer, OpenAPIXCodeSample } from '../../types'; import { OpenAPIExternalDocumentation, OpenAPIServer, OpenAPIXCodeSample } from '../../types';
import { import {
extractExtensions,
getOperationSummary, getOperationSummary,
getStatusCodeType, getStatusCodeType,
isStatusCode, isStatusCode,
@ -56,6 +57,7 @@ export class OperationModel implements IMenuItem {
servers: OpenAPIServer[]; servers: OpenAPIServer[];
security: SecurityRequirementModel[]; security: SecurityRequirementModel[];
codeSamples: OpenAPIXCodeSample[]; codeSamples: OpenAPIXCodeSample[];
extensions: Dict<any>;
constructor( constructor(
private parser: OpenAPIParser, private parser: OpenAPIParser,
@ -91,6 +93,10 @@ export class OperationModel implements IMenuItem {
this.security = (operationSpec.security || parser.spec.security || []).map( this.security = (operationSpec.security || parser.spec.security || []).map(
security => new SecurityRequirementModel(security, parser), security => new SecurityRequirementModel(security, parser),
); );
if (options.showExtensions) {
this.extensions = extractExtensions(operationSpec, options.showExtensions);
}
} }
/** /**

View File

@ -14,6 +14,7 @@ import {
isPrimitiveType, isPrimitiveType,
JsonPointer, JsonPointer,
sortByRequired, sortByRequired,
extractExtensions,
} from '../../utils/'; } from '../../utils/';
// TODO: refactor this model, maybe use getters instead of copying all the values // TODO: refactor this model, maybe use getters instead of copying all the values
@ -54,6 +55,7 @@ export class SchemaModel {
rawSchema: OpenAPISchema; rawSchema: OpenAPISchema;
schema: MergedOpenAPISchema; schema: MergedOpenAPISchema;
extensions?: Dict<any>;
/** /**
* @param isChild if schema discriminator Child * @param isChild if schema discriminator Child
@ -77,6 +79,10 @@ export class SchemaModel {
// exit all the refs visited during allOf traverse // exit all the refs visited during allOf traverse
parser.exitRef({ $ref: parent$ref }); parser.exitRef({ $ref: parent$ref });
} }
if (options.showExtensions) {
this.extensions = extractExtensions(this.schema, options.showExtensions);
}
} }
/** /**

View File

@ -303,3 +303,17 @@ export function isRedocExtension(key: string): boolean {
return key in redocExtensions; return key in redocExtensions;
} }
export function extractExtensions(obj: object, showExtensions: string[] | true): Dict<any> {
return Object.keys(obj)
.filter(key => {
if (showExtensions === true) {
return key.startsWith('x-') && !isRedocExtension(key);
}
return key.startsWith('x-') && showExtensions.indexOf(key) > -1;
})
.reduce((acc, key) => {
acc[key] = obj[key];
return acc;
}, {});
}