mirror of
https://github.com/Redocly/redoc.git
synced 2025-02-16 18:00:33 +03:00
feet: add the option to render vendor extensions (#552)
* add the option to render vendor extensions * refactor Extensions, move Redoc extension list to utils file * feat: new option showExtensions (support list of extensions)
This commit is contained in:
parent
fe3383d1a3
commit
e9610e92d4
|
@ -3,9 +3,9 @@
|
||||||
|
|
||||||
**OpenAPI/Swagger-generated API Reference Documentation**
|
**OpenAPI/Swagger-generated API Reference Documentation**
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/Rebilly/ReDoc.svg?branch=master)](https://travis-ci.org/Rebilly/ReDoc) [![Coverage Status](https://coveralls.io/repos/Rebilly/ReDoc/badge.svg?branch=master&service=github)](https://coveralls.io/github/Rebilly/ReDoc?branch=master) [![dependencies Status](https://david-dm.org/Rebilly/ReDoc/status.svg)](https://david-dm.org/Rebilly/ReDoc) [![devDependencies Status](https://david-dm.org/Rebilly/ReDoc/dev-status.svg)](https://david-dm.org/Rebilly/ReDoc#info=devDependencies) [![npm](http://img.shields.io/npm/v/redoc.svg)](https://www.npmjs.com/package/redoc) [![License](https://img.shields.io/npm/l/redoc.svg)](https://github.com/Rebilly/ReDoc/blob/master/LICENSE)
|
[![Build Status](https://travis-ci.org/Rebilly/ReDoc.svg?branch=master)](https://travis-ci.org/Rebilly/ReDoc) [![Coverage Status](https://coveralls.io/repos/Rebilly/ReDoc/badge.svg?branch=master&service=github)](https://coveralls.io/github/Rebilly/ReDoc?branch=master) [![dependencies Status](https://david-dm.org/Rebilly/ReDoc/status.svg)](https://david-dm.org/Rebilly/ReDoc) [![devDependencies Status](https://david-dm.org/Rebilly/ReDoc/dev-status.svg)](https://david-dm.org/Rebilly/ReDoc#info=devDependencies) [![npm](http://img.shields.io/npm/v/redoc.svg)](https://www.npmjs.com/package/redoc) [![License](https://img.shields.io/npm/l/redoc.svg)](https://github.com/Rebilly/ReDoc/blob/master/LICENSE)
|
||||||
|
|
||||||
[![bundle size](http://img.badgesize.io/https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js?compression=gzip&max=300000)](https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js) [![npm](https://img.shields.io/npm/dm/redoc.svg)](https://www.npmjs.com/package/redoc) [![](https://data.jsdelivr.com/v1/package/npm/redoc/badge)](https://www.jsdelivr.com/package/npm/redoc)
|
[![bundle size](http://img.badgesize.io/https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js?compression=gzip&max=300000)](https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js) [![npm](https://img.shields.io/npm/dm/redoc.svg)](https://www.npmjs.com/package/redoc) [![](https://data.jsdelivr.com/v1/package/npm/redoc/badge)](https://www.jsdelivr.com/package/npm/redoc)
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -215,6 +215,7 @@ You can use all of the following options with standalone version on <redoc> tag
|
||||||
* `hideHostname` - if set, the protocol and hostname is not shown in the operation definition.
|
* `hideHostname` - if set, the protocol and hostname is not shown in the operation definition.
|
||||||
* `expandResponses` - specify which responses to expand by default by response codes. Values should be passed as comma-separated list without spaces e.g. `expandResponses="200,201"`. Special value `"all"` expands all responses by default. Be careful: this option can slow-down documentation rendering time.
|
* `expandResponses` - specify which responses to expand by default by response codes. Values should be passed as comma-separated list without spaces e.g. `expandResponses="200,201"`. Special value `"all"` expands all responses by default. Be careful: this option can slow-down documentation rendering time.
|
||||||
* `requiredPropsFirst` - show required properties first ordered in the same order as in `required` array.
|
* `requiredPropsFirst` - show required properties first ordered in the same order as in `required` array.
|
||||||
|
* `showExtensions` - show vendor extensions ("x-" fields). Extensions used by ReDoc are ignored.
|
||||||
* `noAutoAuth` - do not inject Authentication section automatically
|
* `noAutoAuth` - do not inject Authentication section automatically
|
||||||
* `pathInMiddlePanel` - show path link and HTTP verb in the middle panel instead of the right one
|
* `pathInMiddlePanel` - show path link and HTTP verb in the middle panel instead of the right one
|
||||||
* `hideLoading` - do not show loading animation. Useful for small docs
|
* `hideLoading` - do not show loading animation. Useful for small docs
|
||||||
|
@ -242,4 +243,4 @@ Redoc.init('http://petstore.swagger.io/v2/swagger.json', {
|
||||||
|
|
||||||
-----------
|
-----------
|
||||||
## Development
|
## Development
|
||||||
see [CONTRIBUTING.md](.github/CONTRIBUTING.md)
|
see [CONTRIBUTING.md](.github/CONTRIBUTING.md)
|
||||||
|
|
41
src/components/Fields/Extensions.tsx
Normal file
41
src/components/Fields/Extensions.tsx
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import styled from '../../styled-components';
|
||||||
|
|
||||||
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
|
|
||||||
|
import { StyledMarkdownBlock } from '../Markdown/styled.elements';
|
||||||
|
|
||||||
|
const Extension = styled(StyledMarkdownBlock)`
|
||||||
|
opacity: 0.9;
|
||||||
|
margin: 2px 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ExtensionLable = styled.span`
|
||||||
|
font-style: italic;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export interface ExtensionsProps {
|
||||||
|
extensions: {
|
||||||
|
[k: string]: any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Extensions extends React.PureComponent<ExtensionsProps> {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<OptionsContext.Consumer>
|
||||||
|
{options => (
|
||||||
|
<>
|
||||||
|
{options.showExtensions &&
|
||||||
|
Object.keys(this.props.extensions).map(key => (
|
||||||
|
<Extension key={key}>
|
||||||
|
<ExtensionLable>{key}</ExtensionLable>:{' '}
|
||||||
|
<code>{JSON.stringify(this.props.extensions[key])}</code>
|
||||||
|
</Extension>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</OptionsContext.Consumer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import {
|
||||||
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
||||||
import { Markdown } from '../Markdown/Markdown';
|
import { Markdown } from '../Markdown/Markdown';
|
||||||
import { EnumValues } from './EnumValues';
|
import { EnumValues } from './EnumValues';
|
||||||
|
import { Extensions } from './Extensions';
|
||||||
import { FieldProps } from './Field';
|
import { FieldProps } from './Field';
|
||||||
import { ConstraintsView } from './FieldContstraints';
|
import { ConstraintsView } from './FieldContstraints';
|
||||||
import { FieldDetail } from './FieldDetail';
|
import { FieldDetail } from './FieldDetail';
|
||||||
|
@ -51,6 +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 extensions={{ ...field.extensions, ...schema.extensions }} />}
|
||||||
<div>
|
<div>
|
||||||
<Markdown compact={true} source={description} />
|
<Markdown compact={true} source={description} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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} />
|
||||||
|
|
|
@ -17,6 +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 | string[];
|
||||||
|
|
||||||
unstable_ignoreMimeParameters?: boolean;
|
unstable_ignoreMimeParameters?: boolean;
|
||||||
|
|
||||||
|
@ -88,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;
|
||||||
|
@ -99,6 +115,7 @@ export class RedocNormalizedOptions {
|
||||||
untrustedSpec: boolean;
|
untrustedSpec: boolean;
|
||||||
hideDownloadButton: boolean;
|
hideDownloadButton: boolean;
|
||||||
disableSearch: boolean;
|
disableSearch: boolean;
|
||||||
|
showExtensions: boolean | string[];
|
||||||
|
|
||||||
/* tslint:disable-next-line */
|
/* tslint:disable-next-line */
|
||||||
unstable_ignoreMimeParameters: boolean;
|
unstable_ignoreMimeParameters: boolean;
|
||||||
|
@ -124,6 +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 = RedocNormalizedOptions.normalizeShowExtensions(raw.showExtensions);
|
||||||
|
|
||||||
this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters);
|
this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters);
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
extractExtensions,
|
||||||
getOperationSummary,
|
getOperationSummary,
|
||||||
getStatusCodeType,
|
getStatusCodeType,
|
||||||
isStatusCode,
|
isStatusCode,
|
||||||
|
@ -61,6 +62,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,
|
||||||
|
@ -101,6 +103,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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -286,3 +286,34 @@ export const shortenHTTPVerb = verb =>
|
||||||
delete: 'del',
|
delete: 'del',
|
||||||
options: 'opts',
|
options: 'opts',
|
||||||
}[verb] || verb);
|
}[verb] || verb);
|
||||||
|
|
||||||
|
export function isRedocExtension(key: string): boolean {
|
||||||
|
const redocExtensions = {
|
||||||
|
'x-circular-ref': true,
|
||||||
|
'x-code-samples': true,
|
||||||
|
'x-displayName': true,
|
||||||
|
'x-examples': true,
|
||||||
|
'x-ignoredHeaderParameters': true,
|
||||||
|
'x-logo': true,
|
||||||
|
'x-nullable': true,
|
||||||
|
'x-servers': true,
|
||||||
|
'x-tagGroups': true,
|
||||||
|
'x-traitTag': true,
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user