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
|
@ -135,6 +135,10 @@ For npm:
|
|||
|
||||
## 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:
|
||||
|
||||
```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.
|
||||
* `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.
|
||||
* `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
|
||||
* `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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "redoc-cli",
|
||||
"version": "0.6.2",
|
||||
"version": "0.6.4",
|
||||
"description": "ReDoc's Command Line Interface",
|
||||
"main": "index.js",
|
||||
"bin": "index.js",
|
||||
|
@ -15,6 +15,7 @@
|
|||
"react": "^16.4.2",
|
||||
"react-dom": "^16.4.2",
|
||||
"redoc": "^2.0.0-alpha.37",
|
||||
"styled-components": "^3.4.0",
|
||||
"tslib": "^1.9.3",
|
||||
"yargs": "^12.0.1"
|
||||
},
|
||||
|
|
|
@ -22,6 +22,7 @@ const tsLoader = env => ({
|
|||
options: {
|
||||
compilerOptions: {
|
||||
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: {
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
|
|
|
@ -136,7 +136,7 @@
|
|||
"json-schema-ref-parser": "^5.1.2",
|
||||
"lunr": "^2.3.2",
|
||||
"mark.js": "^8.11.1",
|
||||
"marked": "0.3.18",
|
||||
"marked": "^0.5.1",
|
||||
"memoize-one": "^4.0.0",
|
||||
"mobx-react": "^5.2.5",
|
||||
"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 { Markdown } from '../Markdown/Markdown';
|
||||
import { EnumValues } from './EnumValues';
|
||||
import { Extensions } from './Extensions';
|
||||
import { FieldProps } from './Field';
|
||||
import { ConstraintsView } from './FieldContstraints';
|
||||
import { FieldDetail } from './FieldDetail';
|
||||
|
@ -51,6 +52,7 @@ export class FieldDetails extends React.PureComponent<FieldProps> {
|
|||
<FieldDetail label={'Default:'} value={schema.default} />
|
||||
{!renderDiscriminatorSwitch && <EnumValues type={schema.type} values={schema.enum} />}{' '}
|
||||
{showExamples && <FieldDetail label={'Example:'} value={example} />}
|
||||
{<Extensions extensions={{ ...field.extensions, ...schema.extensions }} />}
|
||||
<div>
|
||||
<Markdown compact={true} source={description} />
|
||||
</div>
|
||||
|
|
|
@ -42,7 +42,7 @@ export class AdvancedMarkdown extends React.Component<AdvancedMarkdownProps> {
|
|||
{ 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 styled from '../../styled-components';
|
||||
import { Extensions } from '../Fields/Extensions';
|
||||
|
||||
const OperationRow = styled(Row)`
|
||||
backface-visibility: hidden;
|
||||
|
@ -58,6 +59,7 @@ export class Operation extends React.Component<OperationProps> {
|
|||
{externalDocs && <ExternalDocumentation externalDocs={externalDocs} />}
|
||||
</Description>
|
||||
)}
|
||||
<Extensions extensions={operation.extensions} />
|
||||
<SecurityRequirements securities={operation.security} />
|
||||
<Parameters parameters={operation.parameters} body={operation.requestBody} />
|
||||
<ResponsesList responses={operation.responses} />
|
||||
|
|
|
@ -13,14 +13,18 @@ marked.setOptions({
|
|||
},
|
||||
});
|
||||
|
||||
export const LEGACY_REGEXP = '^\\s*<!-- ReDoc-Inject:\\s+?<{component}\\s*?/?>\\s+?-->\\s*$';
|
||||
export const MDX_COMPONENT_REGEXP = '^\\s*<{component}\\s*?/>\\s*$';
|
||||
export const LEGACY_REGEXP = '^ {0,3}<!-- ReDoc-Inject:\\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 interface MDXComponentMeta {
|
||||
component: React.ComponentType;
|
||||
propsSelector: (store?: AppStore) => any;
|
||||
attrs?: object;
|
||||
props?: object;
|
||||
}
|
||||
|
||||
export interface MarkdownHeading {
|
||||
|
@ -37,11 +41,8 @@ export function buildComponentComment(name: string) {
|
|||
|
||||
export class MarkdownRenderer {
|
||||
static containsComponent(rawText: string, componentName: string) {
|
||||
const anyCompRegexp = new RegExp(
|
||||
COMPONENT_REGEXP.replace(/{component}/g, componentName),
|
||||
'gmi',
|
||||
);
|
||||
return anyCompRegexp.test(rawText);
|
||||
const compRegexp = new RegExp(COMPONENT_REGEXP.replace(/{component}/g, componentName), 'gmi');
|
||||
return compRegexp.test(rawText);
|
||||
}
|
||||
|
||||
headings: MarkdownHeading[] = [];
|
||||
|
@ -147,32 +148,41 @@ export class MarkdownRenderer {
|
|||
return res;
|
||||
}
|
||||
|
||||
// TODO: rewrite this completelly! Regexp-based 👎
|
||||
// Use marked ecosystem
|
||||
// regexp-based 👎: remark is slow and too big so for now using marked + regexps soup
|
||||
renderMdWithComponents(rawText: string): Array<string | MDXComponentMeta> {
|
||||
const components = this.options && this.options.allowedMdComponents;
|
||||
if (!components || Object.keys(components).length === 0) {
|
||||
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(
|
||||
COMPONENT_REGEXP.replace(/{component}/g, '(' + names + '.*?)'),
|
||||
'gmi',
|
||||
);
|
||||
let match = anyCompRegexp.exec(rawText);
|
||||
const htmlParts: string[] = [];
|
||||
const componentDefs: MDXComponentMeta[] = [];
|
||||
|
||||
let match = componentsRegexp.exec(rawText);
|
||||
let lasxtIdx = 0;
|
||||
while (match) {
|
||||
componentDefs.push(match[1] || match[2]);
|
||||
match = anyCompRegexp.exec(rawText);
|
||||
}
|
||||
htmlParts.push(rawText.substring(lasxtIdx, match.index));
|
||||
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[] = [];
|
||||
for (let i = 0; i < htmlParts.length; i++) {
|
||||
const htmlPart = htmlParts[i];
|
||||
|
@ -180,46 +190,37 @@ export class MarkdownRenderer {
|
|||
res.push(this.renderMd(htmlPart));
|
||||
}
|
||||
if (componentDefs[i]) {
|
||||
const { componentName, attrs } = parseComponent(componentDefs[i]);
|
||||
if (!componentName) {
|
||||
continue;
|
||||
}
|
||||
res.push({
|
||||
...components[componentName],
|
||||
attrs,
|
||||
});
|
||||
res.push(componentDefs[i]);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
function parseComponent(
|
||||
htmlTag: string,
|
||||
): {
|
||||
componentName?: string;
|
||||
attrs: any;
|
||||
} {
|
||||
const match = /([\w_-]+)(\s+[\w_-]+\s*={[^}]*?})*/.exec(htmlTag);
|
||||
if (match === null || match.length <= 1) {
|
||||
return { componentName: undefined, attrs: {} };
|
||||
function parseProps(props: string): object {
|
||||
if (!props) {
|
||||
return {};
|
||||
}
|
||||
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());
|
||||
|
||||
const regex = /([\w-]+)\s*=\s*(?:{([^}]+?)}|"([^"]+?)")/gim;
|
||||
const parsed = {};
|
||||
let match;
|
||||
// tslint:disable-next-line
|
||||
attrs[name] = value.startsWith('{') ? eval(value.substr(1, value.length - 2)) : eval(value);
|
||||
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 */
|
||||
}
|
||||
return {
|
||||
componentName,
|
||||
attrs,
|
||||
};
|
||||
parsed[match[1]] = val;
|
||||
}
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ export interface RedocRawOptions {
|
|||
hideHostname?: boolean | string;
|
||||
expandResponses?: string | 'all';
|
||||
requiredPropsFirst?: boolean | string;
|
||||
sortPropsAlphabetically?: boolean | string;
|
||||
noAutoAuth?: boolean | string;
|
||||
nativeScrollbars?: boolean | string;
|
||||
pathInMiddlePanel?: boolean | string;
|
||||
|
@ -18,6 +19,7 @@ export interface RedocRawOptions {
|
|||
hideDownloadButton?: boolean | string;
|
||||
disableSearch?: boolean | string;
|
||||
onlyRequiredInSamples?: boolean | string;
|
||||
showExtensions?: boolean | string | string[];
|
||||
|
||||
unstable_ignoreMimeParameters?: boolean;
|
||||
|
||||
|
@ -89,11 +91,27 @@ export class RedocNormalizedOptions {
|
|||
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;
|
||||
scrollYOffset: () => number;
|
||||
hideHostname: boolean;
|
||||
expandResponses: { [code: string]: boolean } | 'all';
|
||||
requiredPropsFirst: boolean;
|
||||
sortPropsAlphabetically: boolean;
|
||||
noAutoAuth: boolean;
|
||||
nativeScrollbars: boolean;
|
||||
pathInMiddlePanel: boolean;
|
||||
|
@ -101,6 +119,7 @@ export class RedocNormalizedOptions {
|
|||
hideDownloadButton: boolean;
|
||||
disableSearch: boolean;
|
||||
onlyRequiredInSamples: boolean;
|
||||
showExtensions: boolean | string[];
|
||||
|
||||
/* tslint:disable-next-line */
|
||||
unstable_ignoreMimeParameters: boolean;
|
||||
|
@ -120,6 +139,7 @@ export class RedocNormalizedOptions {
|
|||
this.hideHostname = RedocNormalizedOptions.normalizeHideHostname(raw.hideHostname);
|
||||
this.expandResponses = RedocNormalizedOptions.normalizeExpandResponses(raw.expandResponses);
|
||||
this.requiredPropsFirst = argValueToBoolean(raw.requiredPropsFirst);
|
||||
this.sortPropsAlphabetically = argValueToBoolean(raw.sortPropsAlphabetically);
|
||||
this.noAutoAuth = argValueToBoolean(raw.noAutoAuth);
|
||||
this.nativeScrollbars = argValueToBoolean(raw.nativeScrollbars);
|
||||
this.pathInMiddlePanel = argValueToBoolean(raw.pathInMiddlePanel);
|
||||
|
@ -127,6 +147,7 @@ export class RedocNormalizedOptions {
|
|||
this.hideDownloadButton = argValueToBoolean(raw.hideDownloadButton);
|
||||
this.disableSearch = argValueToBoolean(raw.disableSearch);
|
||||
this.onlyRequiredInSamples = argValueToBoolean(raw.onlyRequiredInSamples);
|
||||
this.showExtensions = RedocNormalizedOptions.normalizeShowExtensions(raw.showExtensions);
|
||||
|
||||
this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters);
|
||||
|
||||
|
|
|
@ -53,11 +53,47 @@ describe('Markdown renderer', () => {
|
|||
});
|
||||
|
||||
test('renderMdWithComponents should parse attribute names', () => {
|
||||
const source = '<security-definitions pointer={"test"}/>';
|
||||
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.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 { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
|
||||
import { extractExtensions } from '../../utils/openapi';
|
||||
import { OpenAPIParser } from '../OpenAPIParser';
|
||||
import { SchemaModel } from './Schema';
|
||||
|
||||
|
@ -21,6 +22,7 @@ export class FieldModel {
|
|||
deprecated: boolean;
|
||||
in?: string;
|
||||
kind: string;
|
||||
extensions?: Dict<any>;
|
||||
|
||||
constructor(
|
||||
parser: OpenAPIParser,
|
||||
|
@ -40,6 +42,10 @@ export class FieldModel {
|
|||
|
||||
this.deprecated = info.deprecated === undefined ? !!this.schema.deprecated : info.deprecated;
|
||||
parser.exitRef(infoOrRef);
|
||||
|
||||
if (options.showExtensions) {
|
||||
this.extensions = extractExtensions(info, options.showExtensions);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
|
|
|
@ -4,9 +4,15 @@ import { IMenuItem } from '../MenuStore';
|
|||
import { GroupModel } from './Group.model';
|
||||
import { SecurityRequirementModel } from './SecurityRequirement';
|
||||
|
||||
import { OpenAPIExternalDocumentation, OpenAPIServer, OpenAPIXCodeSample } from '../../types';
|
||||
import {
|
||||
OpenAPIExternalDocumentation,
|
||||
OpenAPIPath,
|
||||
OpenAPIServer,
|
||||
OpenAPIXCodeSample,
|
||||
} from '../../types';
|
||||
|
||||
import {
|
||||
extractExtensions,
|
||||
getOperationSummary,
|
||||
getStatusCodeType,
|
||||
isStatusCode,
|
||||
|
@ -14,6 +20,7 @@ import {
|
|||
memoize,
|
||||
mergeParams,
|
||||
normalizeServers,
|
||||
sortByField,
|
||||
sortByRequired,
|
||||
} from '../../utils';
|
||||
import { ContentItemModel, ExtendedOpenAPIOperation } from '../MenuBuilder';
|
||||
|
@ -56,6 +63,7 @@ export class OperationModel implements IMenuItem {
|
|||
servers: OpenAPIServer[];
|
||||
security: SecurityRequirementModel[];
|
||||
codeSamples: OpenAPIXCodeSample[];
|
||||
extensions: Dict<any>;
|
||||
|
||||
constructor(
|
||||
private parser: OpenAPIParser,
|
||||
|
@ -83,14 +91,23 @@ export class OperationModel implements IMenuItem {
|
|||
this.operationId = operationSpec.operationId;
|
||||
this.codeSamples = operationSpec['x-code-samples'] || [];
|
||||
this.path = operationSpec.pathName;
|
||||
|
||||
const pathInfo = parser.byRef<OpenAPIPath>(
|
||||
JsonPointer.compile(['paths', operationSpec.pathName]),
|
||||
);
|
||||
|
||||
this.servers = normalizeServers(
|
||||
parser.specUrl,
|
||||
operationSpec.servers || parser.spec.servers || [],
|
||||
operationSpec.servers || (pathInfo && pathInfo.servers) || parser.spec.servers || [],
|
||||
);
|
||||
|
||||
this.security = (operationSpec.security || parser.spec.security || []).map(
|
||||
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
|
||||
).map(paramOrRef => new FieldModel(this.parser, paramOrRef, this.pointer, this.options));
|
||||
|
||||
if (this.options.sortPropsAlphabetically) {
|
||||
sortByField(_parameters, 'name');
|
||||
}
|
||||
if (this.options.requiredPropsFirst) {
|
||||
sortByRequired(_parameters);
|
||||
}
|
||||
|
|
|
@ -9,10 +9,12 @@ import { FieldModel } from './Field';
|
|||
import { MergedOpenAPISchema } from '../';
|
||||
import {
|
||||
detectType,
|
||||
extractExtensions,
|
||||
humanizeConstraints,
|
||||
isNamedDefinition,
|
||||
isPrimitiveType,
|
||||
JsonPointer,
|
||||
sortByField,
|
||||
sortByRequired,
|
||||
} from '../../utils/';
|
||||
|
||||
|
@ -54,6 +56,7 @@ export class SchemaModel {
|
|||
|
||||
rawSchema: OpenAPISchema;
|
||||
schema: MergedOpenAPISchema;
|
||||
extensions?: Dict<any>;
|
||||
|
||||
/**
|
||||
* @param isChild if schema discriminator Child
|
||||
|
@ -77,6 +80,10 @@ export class SchemaModel {
|
|||
// exit all the refs visited during allOf traverse
|
||||
parser.exitRef({ $ref: parent$ref });
|
||||
}
|
||||
|
||||
if (options.showExtensions) {
|
||||
this.extensions = extractExtensions(this.schema, options.showExtensions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -161,9 +168,10 @@ export class SchemaModel {
|
|||
(variant, idx) =>
|
||||
new SchemaModel(
|
||||
parser,
|
||||
{
|
||||
// merge base schema into each of oneOf's subschemas
|
||||
...variant,
|
||||
{
|
||||
// variant may already have allOf so merge it to not get overwritten
|
||||
...parser.mergeAllOf(variant, this.pointer + '/oneOf/' + idx),
|
||||
allOf: [{ ...this.schema, oneOf: undefined, anyOf: undefined }],
|
||||
} as OpenAPISchema,
|
||||
this.pointer + '/oneOf/' + idx,
|
||||
|
@ -254,8 +262,12 @@ function buildFields(
|
|||
);
|
||||
});
|
||||
|
||||
if (options.sortPropsAlphabetically) {
|
||||
sortByField(fields, 'name');
|
||||
}
|
||||
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) {
|
||||
|
|
|
@ -196,13 +196,19 @@ export function sortByRequired(
|
|||
} else if (a.required && !b.required) {
|
||||
return -1;
|
||||
} 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 {
|
||||
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(
|
||||
parser: OpenAPIParser,
|
||||
pathParams: Array<Referenced<OpenAPIParameter>> = [],
|
||||
|
@ -286,3 +292,34 @@ export const shortenHTTPVerb = verb =>
|
|||
delete: 'del',
|
||||
options: 'opts',
|
||||
}[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"
|
||||
resolved "https://registry.yarnpkg.com/mark.js/-/mark.js-8.11.1.tgz#180f1f9ebef8b0e638e4166ad52db879beb2ffc5"
|
||||
|
||||
marked@0.3.18:
|
||||
version "0.3.18"
|
||||
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.18.tgz#3ef058cd926101849b92a7a7c15db18c7fc76b2f"
|
||||
marked@^0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/marked/-/marked-0.5.1.tgz#062f43b88b02ee80901e8e8d8e6a620ddb3aa752"
|
||||
|
||||
math-random@^1.0.1:
|
||||
version "1.0.1"
|
||||
|
|
Loading…
Reference in New Issue
Block a user