mirror of
https://github.com/Redocly/redoc.git
synced 2025-08-08 06:04:56 +03:00
Merge branch 'master' into only-required-parameter
This commit is contained in:
commit
84f3a2505f
10
README.md
10
README.md
|
@ -3,9 +3,9 @@
|
||||||
|
|
||||||
**OpenAPI/Swagger-generated API Reference Documentation**
|
**OpenAPI/Swagger-generated API Reference Documentation**
|
||||||
|
|
||||||
[](https://travis-ci.org/Rebilly/ReDoc) [](https://coveralls.io/github/Rebilly/ReDoc?branch=master) [](https://david-dm.org/Rebilly/ReDoc) [](https://david-dm.org/Rebilly/ReDoc#info=devDependencies) [](https://www.npmjs.com/package/redoc) [](https://github.com/Rebilly/ReDoc/blob/master/LICENSE)
|
[](https://travis-ci.org/Rebilly/ReDoc) [](https://coveralls.io/github/Rebilly/ReDoc?branch=master) [](https://david-dm.org/Rebilly/ReDoc) [](https://david-dm.org/Rebilly/ReDoc#info=devDependencies) [](https://www.npmjs.com/package/redoc) [](https://github.com/Rebilly/ReDoc/blob/master/LICENSE)
|
||||||
|
|
||||||
[](https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js) [](https://www.npmjs.com/package/redoc) [](https://www.jsdelivr.com/package/npm/redoc)
|
[](https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js) [](https://www.npmjs.com/package/redoc) [](https://www.jsdelivr.com/package/npm/redoc)
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -135,6 +135,10 @@ For npm:
|
||||||
|
|
||||||
## Usage as a React component
|
## Usage as a React component
|
||||||
|
|
||||||
|
Install peer dependencies required by ReDoc if you don't have them installed already:
|
||||||
|
|
||||||
|
npm i react react-dom mobx@^4.2.0 styled-components
|
||||||
|
|
||||||
Import `RedocStandalone` component from 'redoc' module:
|
Import `RedocStandalone` component from 'redoc' module:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
@ -215,6 +219,8 @@ 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.
|
||||||
|
* `sortPropsAlphabetically` - sort properties alphabetically
|
||||||
|
* `showExtensions` - show vendor extensions ("x-" fields). Extensions used by ReDoc are ignored. Can be boolean or an array of `string` with names of extensions to display
|
||||||
* `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
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "redoc-cli",
|
"name": "redoc-cli",
|
||||||
"version": "0.6.2",
|
"version": "0.6.4",
|
||||||
"description": "ReDoc's Command Line Interface",
|
"description": "ReDoc's Command Line Interface",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"bin": "index.js",
|
"bin": "index.js",
|
||||||
|
@ -15,6 +15,7 @@
|
||||||
"react": "^16.4.2",
|
"react": "^16.4.2",
|
||||||
"react-dom": "^16.4.2",
|
"react-dom": "^16.4.2",
|
||||||
"redoc": "^2.0.0-alpha.37",
|
"redoc": "^2.0.0-alpha.37",
|
||||||
|
"styled-components": "^3.4.0",
|
||||||
"tslib": "^1.9.3",
|
"tslib": "^1.9.3",
|
||||||
"yargs": "^12.0.1"
|
"yargs": "^12.0.1"
|
||||||
},
|
},
|
||||||
|
|
|
@ -22,6 +22,7 @@ const tsLoader = env => ({
|
||||||
options: {
|
options: {
|
||||||
compilerOptions: {
|
compilerOptions: {
|
||||||
module: env.bench ? 'esnext' : 'es2015',
|
module: env.bench ? 'esnext' : 'es2015',
|
||||||
|
declaration: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -108,7 +109,7 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /node_modules\/(swagger2openapi|reftools)\/.*\.js$/,
|
test: /node_modules\/(swagger2openapi|reftools|oas-resolver|oas-kit-common|oas-schema-walker)\/.*\.js$/,
|
||||||
use: {
|
use: {
|
||||||
loader: 'ts-loader',
|
loader: 'ts-loader',
|
||||||
options: {
|
options: {
|
||||||
|
|
|
@ -136,7 +136,7 @@
|
||||||
"json-schema-ref-parser": "^5.1.2",
|
"json-schema-ref-parser": "^5.1.2",
|
||||||
"lunr": "^2.3.2",
|
"lunr": "^2.3.2",
|
||||||
"mark.js": "^8.11.1",
|
"mark.js": "^8.11.1",
|
||||||
"marked": "0.3.18",
|
"marked": "^0.5.1",
|
||||||
"memoize-one": "^4.0.0",
|
"memoize-one": "^4.0.0",
|
||||||
"mobx-react": "^5.2.5",
|
"mobx-react": "^5.2.5",
|
||||||
"openapi-sampler": "1.0.0-beta.14",
|
"openapi-sampler": "1.0.0-beta.14",
|
||||||
|
|
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>
|
||||||
|
|
|
@ -42,7 +42,7 @@ export class AdvancedMarkdown extends React.Component<AdvancedMarkdownProps> {
|
||||||
{ key: idx },
|
{ key: idx },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <part.component key={idx} {...{ ...part.attrs, ...part.propsSelector(store) }} />;
|
return <part.component key={idx} {...{ ...part.props, ...part.propsSelector(store) }} />;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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} />
|
||||||
|
|
|
@ -13,14 +13,18 @@ marked.setOptions({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const LEGACY_REGEXP = '^\\s*<!-- ReDoc-Inject:\\s+?<{component}\\s*?/?>\\s+?-->\\s*$';
|
export const LEGACY_REGEXP = '^ {0,3}<!-- ReDoc-Inject:\\s+?<({component}).*?/?>\\s+?-->\\s*$';
|
||||||
export const MDX_COMPONENT_REGEXP = '^\\s*<{component}\\s*?/>\\s*$';
|
|
||||||
|
// prettier-ignore
|
||||||
|
export const MDX_COMPONENT_REGEXP = '(?:^ {0,3}<({component})([\\s\\S]*?)>([\\s\\S]*?)</\\2>' // with children
|
||||||
|
+ '|^ {0,3}<({component})([\\s\\S]*?)(?:/>|\\n{2,}))'; // self-closing
|
||||||
|
|
||||||
export const COMPONENT_REGEXP = '(?:' + LEGACY_REGEXP + '|' + MDX_COMPONENT_REGEXP + ')';
|
export const COMPONENT_REGEXP = '(?:' + LEGACY_REGEXP + '|' + MDX_COMPONENT_REGEXP + ')';
|
||||||
|
|
||||||
export interface MDXComponentMeta {
|
export interface MDXComponentMeta {
|
||||||
component: React.ComponentType;
|
component: React.ComponentType;
|
||||||
propsSelector: (store?: AppStore) => any;
|
propsSelector: (store?: AppStore) => any;
|
||||||
attrs?: object;
|
props?: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MarkdownHeading {
|
export interface MarkdownHeading {
|
||||||
|
@ -37,11 +41,8 @@ export function buildComponentComment(name: string) {
|
||||||
|
|
||||||
export class MarkdownRenderer {
|
export class MarkdownRenderer {
|
||||||
static containsComponent(rawText: string, componentName: string) {
|
static containsComponent(rawText: string, componentName: string) {
|
||||||
const anyCompRegexp = new RegExp(
|
const compRegexp = new RegExp(COMPONENT_REGEXP.replace(/{component}/g, componentName), 'gmi');
|
||||||
COMPONENT_REGEXP.replace(/{component}/g, componentName),
|
return compRegexp.test(rawText);
|
||||||
'gmi',
|
|
||||||
);
|
|
||||||
return anyCompRegexp.test(rawText);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
headings: MarkdownHeading[] = [];
|
headings: MarkdownHeading[] = [];
|
||||||
|
@ -147,32 +148,41 @@ export class MarkdownRenderer {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: rewrite this completelly! Regexp-based 👎
|
// regexp-based 👎: remark is slow and too big so for now using marked + regexps soup
|
||||||
// Use marked ecosystem
|
|
||||||
renderMdWithComponents(rawText: string): Array<string | MDXComponentMeta> {
|
renderMdWithComponents(rawText: string): Array<string | MDXComponentMeta> {
|
||||||
const components = this.options && this.options.allowedMdComponents;
|
const components = this.options && this.options.allowedMdComponents;
|
||||||
if (!components || Object.keys(components).length === 0) {
|
if (!components || Object.keys(components).length === 0) {
|
||||||
return [this.renderMd(rawText)];
|
return [this.renderMd(rawText)];
|
||||||
}
|
}
|
||||||
|
|
||||||
const componentDefs: string[] = [];
|
const names = Object.keys(components).join('|');
|
||||||
const names = '(?:' + Object.keys(components).join('|') + ')';
|
const componentsRegexp = new RegExp(COMPONENT_REGEXP.replace(/{component}/g, names), 'mig');
|
||||||
|
|
||||||
const anyCompRegexp = new RegExp(
|
const htmlParts: string[] = [];
|
||||||
COMPONENT_REGEXP.replace(/{component}/g, '(' + names + '.*?)'),
|
const componentDefs: MDXComponentMeta[] = [];
|
||||||
'gmi',
|
|
||||||
);
|
let match = componentsRegexp.exec(rawText);
|
||||||
let match = anyCompRegexp.exec(rawText);
|
let lasxtIdx = 0;
|
||||||
while (match) {
|
while (match) {
|
||||||
componentDefs.push(match[1] || match[2]);
|
htmlParts.push(rawText.substring(lasxtIdx, match.index));
|
||||||
match = anyCompRegexp.exec(rawText);
|
lasxtIdx = componentsRegexp.lastIndex;
|
||||||
}
|
const compName = match[1] || match[2] || match[5];
|
||||||
|
const componentMeta = components[compName];
|
||||||
|
|
||||||
|
const props = match[3] || match[6];
|
||||||
|
const children = match[4];
|
||||||
|
|
||||||
|
if (componentMeta) {
|
||||||
|
componentDefs.push({
|
||||||
|
component: componentMeta.component,
|
||||||
|
propsSelector: componentMeta.propsSelector,
|
||||||
|
props: { ...parseProps(props), ...componentMeta.props, children },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
match = componentsRegexp.exec(rawText);
|
||||||
|
}
|
||||||
|
htmlParts.push(rawText.substring(lasxtIdx));
|
||||||
|
|
||||||
const splitCompRegexp = new RegExp(
|
|
||||||
COMPONENT_REGEXP.replace(/{component}/g, names + '.*?'),
|
|
||||||
'mi',
|
|
||||||
);
|
|
||||||
const htmlParts = rawText.split(splitCompRegexp);
|
|
||||||
const res: any[] = [];
|
const res: any[] = [];
|
||||||
for (let i = 0; i < htmlParts.length; i++) {
|
for (let i = 0; i < htmlParts.length; i++) {
|
||||||
const htmlPart = htmlParts[i];
|
const htmlPart = htmlParts[i];
|
||||||
|
@ -180,46 +190,37 @@ export class MarkdownRenderer {
|
||||||
res.push(this.renderMd(htmlPart));
|
res.push(this.renderMd(htmlPart));
|
||||||
}
|
}
|
||||||
if (componentDefs[i]) {
|
if (componentDefs[i]) {
|
||||||
const { componentName, attrs } = parseComponent(componentDefs[i]);
|
res.push(componentDefs[i]);
|
||||||
if (!componentName) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
res.push({
|
|
||||||
...components[componentName],
|
|
||||||
attrs,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseComponent(
|
function parseProps(props: string): object {
|
||||||
htmlTag: string,
|
if (!props) {
|
||||||
): {
|
return {};
|
||||||
componentName?: string;
|
|
||||||
attrs: any;
|
|
||||||
} {
|
|
||||||
const match = /([\w_-]+)(\s+[\w_-]+\s*={[^}]*?})*/.exec(htmlTag);
|
|
||||||
if (match === null || match.length <= 1) {
|
|
||||||
return { componentName: undefined, attrs: {} };
|
|
||||||
}
|
}
|
||||||
const componentName = match[1];
|
|
||||||
const attrs = {};
|
|
||||||
for (let i = 2; i < match.length; i++) {
|
|
||||||
if (!match[i]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const [name, value] = match[i]
|
|
||||||
.trim()
|
|
||||||
.split('=')
|
|
||||||
.map(p => p.trim());
|
|
||||||
|
|
||||||
// tslint:disable-next-line
|
const regex = /([\w-]+)\s*=\s*(?:{([^}]+?)}|"([^"]+?)")/gim;
|
||||||
attrs[name] = value.startsWith('{') ? eval(value.substr(1, value.length - 2)) : eval(value);
|
const parsed = {};
|
||||||
|
let match;
|
||||||
|
// tslint:disable-next-line
|
||||||
|
while ((match = regex.exec(props)) !== null) {
|
||||||
|
if (match[3]) {
|
||||||
|
// string prop match (in double quotes)
|
||||||
|
parsed[match[1]] = match[3];
|
||||||
|
} else if (match[2]) {
|
||||||
|
// jsx prop match (in curly braces)
|
||||||
|
let val;
|
||||||
|
try {
|
||||||
|
val = JSON.parse(match[2]);
|
||||||
|
} catch (e) {
|
||||||
|
/* noop */
|
||||||
|
}
|
||||||
|
parsed[match[1]] = val;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
componentName,
|
return parsed;
|
||||||
attrs,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ export interface RedocRawOptions {
|
||||||
hideHostname?: boolean | string;
|
hideHostname?: boolean | string;
|
||||||
expandResponses?: string | 'all';
|
expandResponses?: string | 'all';
|
||||||
requiredPropsFirst?: boolean | string;
|
requiredPropsFirst?: boolean | string;
|
||||||
|
sortPropsAlphabetically?: boolean | string;
|
||||||
noAutoAuth?: boolean | string;
|
noAutoAuth?: boolean | string;
|
||||||
nativeScrollbars?: boolean | string;
|
nativeScrollbars?: boolean | string;
|
||||||
pathInMiddlePanel?: boolean | string;
|
pathInMiddlePanel?: boolean | string;
|
||||||
|
@ -18,6 +19,7 @@ export interface RedocRawOptions {
|
||||||
hideDownloadButton?: boolean | string;
|
hideDownloadButton?: boolean | string;
|
||||||
disableSearch?: boolean | string;
|
disableSearch?: boolean | string;
|
||||||
onlyRequiredInSamples?: boolean | string;
|
onlyRequiredInSamples?: boolean | string;
|
||||||
|
showExtensions?: boolean | string | string[];
|
||||||
|
|
||||||
unstable_ignoreMimeParameters?: boolean;
|
unstable_ignoreMimeParameters?: boolean;
|
||||||
|
|
||||||
|
@ -89,11 +91,27 @@ 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;
|
||||||
expandResponses: { [code: string]: boolean } | 'all';
|
expandResponses: { [code: string]: boolean } | 'all';
|
||||||
requiredPropsFirst: boolean;
|
requiredPropsFirst: boolean;
|
||||||
|
sortPropsAlphabetically: boolean;
|
||||||
noAutoAuth: boolean;
|
noAutoAuth: boolean;
|
||||||
nativeScrollbars: boolean;
|
nativeScrollbars: boolean;
|
||||||
pathInMiddlePanel: boolean;
|
pathInMiddlePanel: boolean;
|
||||||
|
@ -101,6 +119,7 @@ export class RedocNormalizedOptions {
|
||||||
hideDownloadButton: boolean;
|
hideDownloadButton: boolean;
|
||||||
disableSearch: boolean;
|
disableSearch: boolean;
|
||||||
onlyRequiredInSamples: boolean;
|
onlyRequiredInSamples: boolean;
|
||||||
|
showExtensions: boolean | string[];
|
||||||
|
|
||||||
/* tslint:disable-next-line */
|
/* tslint:disable-next-line */
|
||||||
unstable_ignoreMimeParameters: boolean;
|
unstable_ignoreMimeParameters: boolean;
|
||||||
|
@ -120,6 +139,7 @@ export class RedocNormalizedOptions {
|
||||||
this.hideHostname = RedocNormalizedOptions.normalizeHideHostname(raw.hideHostname);
|
this.hideHostname = RedocNormalizedOptions.normalizeHideHostname(raw.hideHostname);
|
||||||
this.expandResponses = RedocNormalizedOptions.normalizeExpandResponses(raw.expandResponses);
|
this.expandResponses = RedocNormalizedOptions.normalizeExpandResponses(raw.expandResponses);
|
||||||
this.requiredPropsFirst = argValueToBoolean(raw.requiredPropsFirst);
|
this.requiredPropsFirst = argValueToBoolean(raw.requiredPropsFirst);
|
||||||
|
this.sortPropsAlphabetically = argValueToBoolean(raw.sortPropsAlphabetically);
|
||||||
this.noAutoAuth = argValueToBoolean(raw.noAutoAuth);
|
this.noAutoAuth = argValueToBoolean(raw.noAutoAuth);
|
||||||
this.nativeScrollbars = argValueToBoolean(raw.nativeScrollbars);
|
this.nativeScrollbars = argValueToBoolean(raw.nativeScrollbars);
|
||||||
this.pathInMiddlePanel = argValueToBoolean(raw.pathInMiddlePanel);
|
this.pathInMiddlePanel = argValueToBoolean(raw.pathInMiddlePanel);
|
||||||
|
@ -127,6 +147,7 @@ export class RedocNormalizedOptions {
|
||||||
this.hideDownloadButton = argValueToBoolean(raw.hideDownloadButton);
|
this.hideDownloadButton = argValueToBoolean(raw.hideDownloadButton);
|
||||||
this.disableSearch = argValueToBoolean(raw.disableSearch);
|
this.disableSearch = argValueToBoolean(raw.disableSearch);
|
||||||
this.onlyRequiredInSamples = argValueToBoolean(raw.onlyRequiredInSamples);
|
this.onlyRequiredInSamples = argValueToBoolean(raw.onlyRequiredInSamples);
|
||||||
|
this.showExtensions = RedocNormalizedOptions.normalizeShowExtensions(raw.showExtensions);
|
||||||
|
|
||||||
this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters);
|
this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters);
|
||||||
|
|
||||||
|
|
|
@ -53,11 +53,47 @@ describe('Markdown renderer', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renderMdWithComponents should parse attribute names', () => {
|
test('renderMdWithComponents should parse attribute names', () => {
|
||||||
const source = '<security-definitions pointer={"test"}/>';
|
const source = '<security-definitions pointer={"test"} />';
|
||||||
const parts = renderer.renderMdWithComponents(source);
|
const parts = renderer.renderMdWithComponents(source);
|
||||||
expect(parts).toHaveLength(1);
|
expect(parts).toHaveLength(1);
|
||||||
const part = parts[0] as MDXComponentMeta;
|
const part = parts[0] as MDXComponentMeta;
|
||||||
expect(part.component).toBe(TestComponent);
|
expect(part.component).toBe(TestComponent);
|
||||||
expect(part.attrs).toEqual({ pointer: 'test' });
|
expect(part.props).toEqual({ pointer: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renderMdWithComponents should parse string attribute names', () => {
|
||||||
|
const source = '<security-definitions pointer="test" />';
|
||||||
|
const parts = renderer.renderMdWithComponents(source);
|
||||||
|
expect(parts).toHaveLength(1);
|
||||||
|
const part = parts[0] as MDXComponentMeta;
|
||||||
|
expect(part.component).toBe(TestComponent);
|
||||||
|
expect(part.props).toEqual({ pointer: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renderMdWithComponents should parse string attribute with spaces new-lines', () => {
|
||||||
|
const source = '<security-definitions \n pointer = "test" \n flag-dash={ \nfalse } />';
|
||||||
|
const parts = renderer.renderMdWithComponents(source);
|
||||||
|
expect(parts).toHaveLength(1);
|
||||||
|
const part = parts[0] as MDXComponentMeta;
|
||||||
|
expect(part.component).toBe(TestComponent);
|
||||||
|
expect(part.props).toEqual({ pointer: 'test', 'flag-dash': false });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renderMdWithComponents should parse children', () => {
|
||||||
|
const source = '<security-definitions> Test Test </security-definitions>';
|
||||||
|
const parts = renderer.renderMdWithComponents(source);
|
||||||
|
expect(parts).toHaveLength(1);
|
||||||
|
const part = parts[0] as MDXComponentMeta;
|
||||||
|
expect(part.component).toBe(TestComponent);
|
||||||
|
expect(part.props).toEqual({ children: ' Test Test ' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renderMdWithComponents should parse children', () => {
|
||||||
|
const source = '<security-definitions> Test Test </security-definitions>';
|
||||||
|
const parts = renderer.renderMdWithComponents(source);
|
||||||
|
expect(parts).toHaveLength(1);
|
||||||
|
const part = parts[0] as MDXComponentMeta;
|
||||||
|
expect(part.component).toBe(TestComponent);
|
||||||
|
expect(part.props).toEqual({ children: ' Test Test ' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -4,9 +4,15 @@ import { IMenuItem } from '../MenuStore';
|
||||||
import { GroupModel } from './Group.model';
|
import { GroupModel } from './Group.model';
|
||||||
import { SecurityRequirementModel } from './SecurityRequirement';
|
import { SecurityRequirementModel } from './SecurityRequirement';
|
||||||
|
|
||||||
import { OpenAPIExternalDocumentation, OpenAPIServer, OpenAPIXCodeSample } from '../../types';
|
import {
|
||||||
|
OpenAPIExternalDocumentation,
|
||||||
|
OpenAPIPath,
|
||||||
|
OpenAPIServer,
|
||||||
|
OpenAPIXCodeSample,
|
||||||
|
} from '../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
extractExtensions,
|
||||||
getOperationSummary,
|
getOperationSummary,
|
||||||
getStatusCodeType,
|
getStatusCodeType,
|
||||||
isStatusCode,
|
isStatusCode,
|
||||||
|
@ -14,6 +20,7 @@ import {
|
||||||
memoize,
|
memoize,
|
||||||
mergeParams,
|
mergeParams,
|
||||||
normalizeServers,
|
normalizeServers,
|
||||||
|
sortByField,
|
||||||
sortByRequired,
|
sortByRequired,
|
||||||
} from '../../utils';
|
} from '../../utils';
|
||||||
import { ContentItemModel, ExtendedOpenAPIOperation } from '../MenuBuilder';
|
import { ContentItemModel, ExtendedOpenAPIOperation } from '../MenuBuilder';
|
||||||
|
@ -56,6 +63,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,
|
||||||
|
@ -83,14 +91,23 @@ export class OperationModel implements IMenuItem {
|
||||||
this.operationId = operationSpec.operationId;
|
this.operationId = operationSpec.operationId;
|
||||||
this.codeSamples = operationSpec['x-code-samples'] || [];
|
this.codeSamples = operationSpec['x-code-samples'] || [];
|
||||||
this.path = operationSpec.pathName;
|
this.path = operationSpec.pathName;
|
||||||
|
|
||||||
|
const pathInfo = parser.byRef<OpenAPIPath>(
|
||||||
|
JsonPointer.compile(['paths', operationSpec.pathName]),
|
||||||
|
);
|
||||||
|
|
||||||
this.servers = normalizeServers(
|
this.servers = normalizeServers(
|
||||||
parser.specUrl,
|
parser.specUrl,
|
||||||
operationSpec.servers || parser.spec.servers || [],
|
operationSpec.servers || (pathInfo && pathInfo.servers) || parser.spec.servers || [],
|
||||||
);
|
);
|
||||||
|
|
||||||
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -136,6 +153,9 @@ export class OperationModel implements IMenuItem {
|
||||||
// TODO: fix pointer
|
// TODO: fix pointer
|
||||||
).map(paramOrRef => new FieldModel(this.parser, paramOrRef, this.pointer, this.options));
|
).map(paramOrRef => new FieldModel(this.parser, paramOrRef, this.pointer, this.options));
|
||||||
|
|
||||||
|
if (this.options.sortPropsAlphabetically) {
|
||||||
|
sortByField(_parameters, 'name');
|
||||||
|
}
|
||||||
if (this.options.requiredPropsFirst) {
|
if (this.options.requiredPropsFirst) {
|
||||||
sortByRequired(_parameters);
|
sortByRequired(_parameters);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,12 @@ import { FieldModel } from './Field';
|
||||||
import { MergedOpenAPISchema } from '../';
|
import { MergedOpenAPISchema } from '../';
|
||||||
import {
|
import {
|
||||||
detectType,
|
detectType,
|
||||||
|
extractExtensions,
|
||||||
humanizeConstraints,
|
humanizeConstraints,
|
||||||
isNamedDefinition,
|
isNamedDefinition,
|
||||||
isPrimitiveType,
|
isPrimitiveType,
|
||||||
JsonPointer,
|
JsonPointer,
|
||||||
|
sortByField,
|
||||||
sortByRequired,
|
sortByRequired,
|
||||||
} from '../../utils/';
|
} from '../../utils/';
|
||||||
|
|
||||||
|
@ -54,6 +56,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 +80,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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -161,9 +168,10 @@ export class SchemaModel {
|
||||||
(variant, idx) =>
|
(variant, idx) =>
|
||||||
new SchemaModel(
|
new SchemaModel(
|
||||||
parser,
|
parser,
|
||||||
|
// merge base schema into each of oneOf's subschemas
|
||||||
{
|
{
|
||||||
// merge base schema into each of oneOf's subschemas
|
// variant may already have allOf so merge it to not get overwritten
|
||||||
...variant,
|
...parser.mergeAllOf(variant, this.pointer + '/oneOf/' + idx),
|
||||||
allOf: [{ ...this.schema, oneOf: undefined, anyOf: undefined }],
|
allOf: [{ ...this.schema, oneOf: undefined, anyOf: undefined }],
|
||||||
} as OpenAPISchema,
|
} as OpenAPISchema,
|
||||||
this.pointer + '/oneOf/' + idx,
|
this.pointer + '/oneOf/' + idx,
|
||||||
|
@ -254,8 +262,12 @@ function buildFields(
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (options.sortPropsAlphabetically) {
|
||||||
|
sortByField(fields, 'name');
|
||||||
|
}
|
||||||
if (options.requiredPropsFirst) {
|
if (options.requiredPropsFirst) {
|
||||||
sortByRequired(fields, schema.required);
|
// if not sort alphabetically sort in the order from required keyword
|
||||||
|
sortByRequired(fields, !options.sortPropsAlphabetically ? schema.required : undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof additionalProps === 'object' || additionalProps === true) {
|
if (typeof additionalProps === 'object' || additionalProps === true) {
|
||||||
|
|
|
@ -196,13 +196,19 @@ export function sortByRequired(
|
||||||
} else if (a.required && !b.required) {
|
} else if (a.required && !b.required) {
|
||||||
return -1;
|
return -1;
|
||||||
} else if (a.required && b.required) {
|
} else if (a.required && b.required) {
|
||||||
return order.indexOf(a.name) > order.indexOf(b.name) ? 1 : -1;
|
return order.indexOf(a.name) - order.indexOf(b.name);
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function sortByField<T extends string>(fields: Array<{ [P in T]: string }>, param: T) {
|
||||||
|
fields.sort((a, b) => {
|
||||||
|
return a[param].localeCompare(b[param]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function mergeParams(
|
export function mergeParams(
|
||||||
parser: OpenAPIParser,
|
parser: OpenAPIParser,
|
||||||
pathParams: Array<Referenced<OpenAPIParameter>> = [],
|
pathParams: Array<Referenced<OpenAPIParameter>> = [],
|
||||||
|
@ -286,3 +292,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;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
|
@ -5580,9 +5580,9 @@ mark.js@^8.11.1:
|
||||||
version "8.11.1"
|
version "8.11.1"
|
||||||
resolved "https://registry.yarnpkg.com/mark.js/-/mark.js-8.11.1.tgz#180f1f9ebef8b0e638e4166ad52db879beb2ffc5"
|
resolved "https://registry.yarnpkg.com/mark.js/-/mark.js-8.11.1.tgz#180f1f9ebef8b0e638e4166ad52db879beb2ffc5"
|
||||||
|
|
||||||
marked@0.3.18:
|
marked@^0.5.1:
|
||||||
version "0.3.18"
|
version "0.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.18.tgz#3ef058cd926101849b92a7a7c15db18c7fc76b2f"
|
resolved "https://registry.yarnpkg.com/marked/-/marked-0.5.1.tgz#062f43b88b02ee80901e8e8d8e6a620ddb3aa752"
|
||||||
|
|
||||||
math-random@^1.0.1:
|
math-random@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user