mirror of
https://github.com/Redocly/redoc.git
synced 2025-08-09 14:44:51 +03:00
Merge branch 'master' into sample-collapse-level-option
This commit is contained in:
commit
cc8964f71c
30
CHANGELOG.md
30
CHANGELOG.md
|
@ -1,3 +1,33 @@
|
||||||
|
# [2.0.0-rc.10](https://github.com/Redocly/redoc/compare/v2.0.0-rc.9...v2.0.0-rc.10) (2019-07-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* broken headings with single quote ([51d3b9b](https://github.com/Redocly/redoc/commit/51d3b9b)), closes [#955](https://github.com/Redocly/redoc/issues/955)
|
||||||
|
* fix fields table overflow if deeply nested with long title ([12b7057](https://github.com/Redocly/redoc/commit/12b7057))
|
||||||
|
* hide empty example when it is not defined ([4bd499f](https://github.com/Redocly/redoc/commit/4bd499f))
|
||||||
|
* markdown in examples descriptions + minor ui tweaks ([f52d9e8](https://github.com/Redocly/redoc/commit/f52d9e8))
|
||||||
|
* organize response examples in dropdown and display description ([995e557](https://github.com/Redocly/redoc/commit/995e557))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.9](https://github.com/Redocly/redoc/compare/v2.0.0-rc.8-1...v2.0.0-rc.9) (2019-06-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix regression double slashes added to full URL display ([f29a4fe](https://github.com/Redocly/redoc/commit/f29a4fe))
|
||||||
|
* IE11, add missing Object.assign polyfill ([888f04e](https://github.com/Redocly/redoc/commit/888f04e))
|
||||||
|
* serialize parameter example values according to the spec ([#917](https://github.com/Redocly/redoc/issues/917)) ([3939286](https://github.com/Redocly/redoc/commit/3939286))
|
||||||
|
* styled-component style error in tabs ([#946](https://github.com/Redocly/redoc/issues/946)) ([c488bbf](https://github.com/Redocly/redoc/commit/c488bbf))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add x-additionalPropertiesName ([#622](https://github.com/Redocly/redoc/issues/622)) ([#944](https://github.com/Redocly/redoc/issues/944)) ([0eb1e66](https://github.com/Redocly/redoc/commit/0eb1e66))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [2.0.0-rc.8-1](https://github.com/Rebilly/ReDoc/compare/v2.0.0-rc.8...v2.0.0-rc.8-1) (2019-05-13)
|
# [2.0.0-rc.8-1](https://github.com/Rebilly/ReDoc/compare/v2.0.0-rc.8...v2.0.0-rc.8-1) (2019-05-13)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -218,6 +218,7 @@ ReDoc makes use of the following [vendor extensions](https://swagger.io/specific
|
||||||
* [`x-tagGroups`](docs/redoc-vendor-extensions.md#x-tagGroups) - group tags by categories in the side menu
|
* [`x-tagGroups`](docs/redoc-vendor-extensions.md#x-tagGroups) - group tags by categories in the side menu
|
||||||
* [`x-servers`](docs/redoc-vendor-extensions.md#x-servers) - ability to specify different servers for API (backported from OpenAPI 3.0)
|
* [`x-servers`](docs/redoc-vendor-extensions.md#x-servers) - ability to specify different servers for API (backported from OpenAPI 3.0)
|
||||||
* [`x-ignoredHeaderParameters`](docs/redoc-vendor-extensions.md#x-ignoredHeaderParameters) - ability to specify header parameter names to ignore
|
* [`x-ignoredHeaderParameters`](docs/redoc-vendor-extensions.md#x-ignoredHeaderParameters) - ability to specify header parameter names to ignore
|
||||||
|
* [`x-additionalPropertiesName`](docs/redoc-vendor-extensions.md#x-additionalPropertiesName) - ability to supply a descriptive name for the additional property keys
|
||||||
|
|
||||||
### `<redoc>` options object
|
### `<redoc>` options object
|
||||||
You can use all of the following options with standalone version on <redoc> tag by kebab-casing them, e.g. `scrollYOffset` becomes `scroll-y-offset` and `expandResponses` becomes `expand-responses`.
|
You can use all of the following options with standalone version on <redoc> tag by kebab-casing them, e.g. `scrollYOffset` becomes `scroll-y-offset` and `expandResponses` becomes `expand-responses`.
|
||||||
|
|
|
@ -1,25 +1,28 @@
|
||||||
{
|
{
|
||||||
"name": "redoc-cli",
|
"name": "redoc-cli",
|
||||||
"version": "0.8.4",
|
"version": "0.8.5",
|
||||||
"description": "ReDoc's Command Line Interface",
|
"description": "ReDoc's Command Line Interface",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"bin": "index.js",
|
"bin": "index.js",
|
||||||
"repository": "https://github.com/Redocly/redoc",
|
"repository": "https://github.com/Redocly/redoc",
|
||||||
"author": "Roman Hotsiy <gotsijroman@gmail.com>",
|
"author": "Roman Hotsiy <gotsijroman@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chokidar": "^2.0.4",
|
"chokidar": "^3.0.2",
|
||||||
"handlebars": "^4.0.11",
|
"handlebars": "^4.1.2",
|
||||||
"isarray": "^2.0.4",
|
"isarray": "^2.0.4",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"mobx": "^4.2.0",
|
"mobx": "^4.2.0",
|
||||||
"node-libs-browser": "^2.2.0",
|
"node-libs-browser": "^2.2.1",
|
||||||
"react": "^16.8.4",
|
"react": "^16.8.6",
|
||||||
"react-dom": "^16.8.4",
|
"react-dom": "^16.8.6",
|
||||||
"redoc": "^2.0.0-rc.8-1",
|
"redoc": "^2.0.0-rc.10",
|
||||||
"styled-components": "^4.1.3",
|
"styled-components": "^4.3.2",
|
||||||
"tslib": "^1.9.3",
|
"tslib": "^1.10.0",
|
||||||
"yargs": "^12.0.5"
|
"yargs": "^13.2.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ci-publish": "ci-publish"
|
"ci-publish": "ci-publish"
|
||||||
|
@ -28,7 +31,7 @@
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chokidar": "^1.7.5",
|
"@types/chokidar": "^2.1.3",
|
||||||
"@types/handlebars": "^4.0.39",
|
"@types/handlebars": "^4.0.39",
|
||||||
"@types/mkdirp": "^0.5.2",
|
"@types/mkdirp": "^0.5.2",
|
||||||
"ci-publish": "^1.3.1"
|
"ci-publish": "^1.3.1"
|
||||||
|
|
1222
cli/yarn.lock
1222
cli/yarn.lock
File diff suppressed because it is too large
Load Diff
|
@ -278,3 +278,31 @@ PayPalPayment:
|
||||||
|
|
||||||
In the example above the names of definitions (`PayPalPayment`) are named differently than
|
In the example above the names of definitions (`PayPalPayment`) are named differently than
|
||||||
names in the payload (`paypal`) which is not supported by default `discriminator`.
|
names in the payload (`paypal`) which is not supported by default `discriminator`.
|
||||||
|
|
||||||
|
#### x-additionalPropertiesName
|
||||||
|
**ATTENTION**: This is ReDoc-specific vendor extension. It won't be supported by other tools.
|
||||||
|
|
||||||
|
Extends the `additionalProperties` property of the schema object.
|
||||||
|
|
||||||
|
| Field Name | Type | Description |
|
||||||
|
| :------------- | :------: | :---------- |
|
||||||
|
| x-additionalPropertiesName | string | descriptive name of additional properties keys |
|
||||||
|
|
||||||
|
###### Usage in ReDoc
|
||||||
|
ReDoc uses this extension to display a more descriptive property name in objects with `additionalProperties` when viewing the property list with an `object`.
|
||||||
|
|
||||||
|
###### x-additionalPropertiesName example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
Player:
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
additionalProperties:
|
||||||
|
x-additionalPropertiesName: attribute-name
|
||||||
|
type: string
|
||||||
|
```
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "redoc",
|
"name": "redoc",
|
||||||
"version": "2.0.0-rc.8-1",
|
"version": "2.0.0-rc.10",
|
||||||
"description": "ReDoc",
|
"description": "ReDoc",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -129,7 +129,8 @@
|
||||||
"mobx": "^4.2.0 || ^5.0.0",
|
"mobx": "^4.2.0 || ^5.0.0",
|
||||||
"react": "^16.8.4",
|
"react": "^16.8.4",
|
||||||
"react-dom": "^16.8.4",
|
"react-dom": "^16.8.4",
|
||||||
"styled-components": "^4.1.1"
|
"styled-components": "^4.1.1",
|
||||||
|
"core-js": "^2.6.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
|
@ -154,7 +155,8 @@
|
||||||
"slugify": "^1.3.4",
|
"slugify": "^1.3.4",
|
||||||
"stickyfill": "^1.1.1",
|
"stickyfill": "^1.1.1",
|
||||||
"swagger2openapi": "^5.2.3",
|
"swagger2openapi": "^5.2.3",
|
||||||
"tslib": "^1.9.3"
|
"tslib": "^1.9.3",
|
||||||
|
"uri-template-lite": "^19.4.0"
|
||||||
},
|
},
|
||||||
"bundlesize": [
|
"bundlesize": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,11 +5,7 @@ import { ClipboardService } from '../services/ClipboardService';
|
||||||
|
|
||||||
export interface CopyButtonWrapperProps {
|
export interface CopyButtonWrapperProps {
|
||||||
data: any;
|
data: any;
|
||||||
children: (
|
children: (props: { renderCopyButton: () => React.ReactNode }) => React.ReactNode;
|
||||||
props: {
|
|
||||||
renderCopyButton: (() => React.ReactNode);
|
|
||||||
},
|
|
||||||
) => React.ReactNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CopyButtonWrapper extends React.PureComponent<
|
export class CopyButtonWrapper extends React.PureComponent<
|
||||||
|
|
|
@ -55,7 +55,7 @@ export const StyledDropdown = styled(Dropdown)`
|
||||||
display: block;
|
display: block;
|
||||||
height: 0;
|
height: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0.35em;
|
right: 0.6em;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
margin-top: -0.125em;
|
margin-top: -0.125em;
|
||||||
width: 0;
|
width: 0;
|
||||||
|
|
|
@ -32,6 +32,7 @@ export const TypeName = styled(FieldLabel)`
|
||||||
|
|
||||||
export const TypeTitle = styled(FieldLabel)`
|
export const TypeTitle = styled(FieldLabel)`
|
||||||
color: ${props => props.theme.schema.typeTitleColor};
|
color: ${props => props.theme.schema.typeTitleColor};
|
||||||
|
word-break: break-word;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TypeFormat = TypeName;
|
export const TypeFormat = TypeName;
|
||||||
|
|
|
@ -98,7 +98,7 @@ export const SmallTabs = styled(Tabs)`
|
||||||
> .react-tabs__tab-panel {
|
> .react-tabs__tab-panel {
|
||||||
& > div,
|
& > div,
|
||||||
& > pre {
|
& > pre {
|
||||||
padding: ${props => props.theme.spacing.unit * 2} 0;
|
padding: ${props => props.theme.spacing.unit * 2}px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -44,8 +44,7 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
|
||||||
null;
|
null;
|
||||||
|
|
||||||
const website =
|
const website =
|
||||||
(info.contact &&
|
(info.contact && info.contact.url && (
|
||||||
info.contact.url && (
|
|
||||||
<InfoSpan>
|
<InfoSpan>
|
||||||
URL: <a href={info.contact.url}>{info.contact.url}</a>
|
URL: <a href={info.contact.url}>{info.contact.url}</a>
|
||||||
</InfoSpan>
|
</InfoSpan>
|
||||||
|
@ -53,8 +52,7 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
|
||||||
null;
|
null;
|
||||||
|
|
||||||
const email =
|
const email =
|
||||||
(info.contact &&
|
(info.contact && info.contact.email && (
|
||||||
info.contact.email && (
|
|
||||||
<InfoSpan>
|
<InfoSpan>
|
||||||
{info.contact.name || 'E-mail'}:{' '}
|
{info.contact.name || 'E-mail'}:{' '}
|
||||||
<a href={'mailto:' + info.contact.email}>{info.contact.email}</a>
|
<a href={'mailto:' + info.contact.email}>{info.contact.email}</a>
|
||||||
|
@ -70,11 +68,7 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
|
||||||
)) ||
|
)) ||
|
||||||
null;
|
null;
|
||||||
|
|
||||||
const version =
|
const version = (info.version && <span>({info.version})</span>) || null;
|
||||||
(info.version && (
|
|
||||||
<span>({info.version})</span>
|
|
||||||
)) ||
|
|
||||||
null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Section>
|
<Section>
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Markdown } from '../Markdown/Markdown';
|
||||||
import { OptionsContext } from '../OptionsProvider';
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
import { SelectOnClick } from '../SelectOnClick/SelectOnClick';
|
import { SelectOnClick } from '../SelectOnClick/SelectOnClick';
|
||||||
|
|
||||||
import { getBasePath } from '../../utils';
|
import { expandDefaultServerVariables, getBasePath } from '../../utils';
|
||||||
import {
|
import {
|
||||||
EndpointInfo,
|
EndpointInfo,
|
||||||
HttpVerb,
|
HttpVerb,
|
||||||
|
@ -60,21 +60,24 @@ export class Endpoint extends React.Component<EndpointProps, EndpointState> {
|
||||||
/>
|
/>
|
||||||
</EndpointInfo>
|
</EndpointInfo>
|
||||||
<ServersOverlay expanded={expanded}>
|
<ServersOverlay expanded={expanded}>
|
||||||
{operation.servers.map(server => (
|
{operation.servers.map(server => {
|
||||||
<ServerItem key={server.url}>
|
const normalizedUrl = expandDefaultServerVariables(server.url, server.variables);
|
||||||
|
return (
|
||||||
|
<ServerItem key={normalizedUrl}>
|
||||||
<Markdown source={server.description || ''} compact={true} />
|
<Markdown source={server.description || ''} compact={true} />
|
||||||
<SelectOnClick>
|
<SelectOnClick>
|
||||||
<ServerUrl>
|
<ServerUrl>
|
||||||
<span>
|
<span>
|
||||||
{hideHostname || options.hideHostname
|
{hideHostname || options.hideHostname
|
||||||
? getBasePath(server.url)
|
? getBasePath(normalizedUrl)
|
||||||
: server.url}
|
: normalizedUrl}
|
||||||
</span>
|
</span>
|
||||||
{operation.path}
|
{operation.path}
|
||||||
</ServerUrl>
|
</ServerUrl>
|
||||||
</SelectOnClick>
|
</SelectOnClick>
|
||||||
</ServerItem>
|
</ServerItem>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</ServersOverlay>
|
</ServersOverlay>
|
||||||
</OperationEndpointWrap>
|
</OperationEndpointWrap>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import * as React from 'react';
|
||||||
import { ExampleValue, FieldLabel } from '../../common-elements/fields';
|
import { ExampleValue, FieldLabel } from '../../common-elements/fields';
|
||||||
|
|
||||||
import { l } from '../../services/Labels';
|
import { l } from '../../services/Labels';
|
||||||
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
|
|
||||||
export interface EnumValuesProps {
|
export interface EnumValuesProps {
|
||||||
values: string[];
|
values: string[];
|
||||||
|
@ -9,8 +10,10 @@ export interface EnumValuesProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EnumValues extends React.PureComponent<EnumValuesProps> {
|
export class EnumValues extends React.PureComponent<EnumValuesProps> {
|
||||||
|
static contextType = OptionsContext;
|
||||||
render() {
|
render() {
|
||||||
const { values, type } = this.props;
|
const { values, type } = this.props;
|
||||||
|
const { enumSkipQuotes } = this.context;
|
||||||
if (!values.length) {
|
if (!values.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -21,11 +24,14 @@ export class EnumValues extends React.PureComponent<EnumValuesProps> {
|
||||||
{type === 'array' ? l('enumArray') : ''}{' '}
|
{type === 'array' ? l('enumArray') : ''}{' '}
|
||||||
{values.length === 1 ? l('enumSingleValue') : l('enum')}:
|
{values.length === 1 ? l('enumSingleValue') : l('enum')}:
|
||||||
</FieldLabel>
|
</FieldLabel>
|
||||||
{values.map((value, idx) => (
|
{values.map((value, idx) => {
|
||||||
|
const exampleValue = enumSkipQuotes ? value : JSON.stringify(value);
|
||||||
|
return (
|
||||||
<React.Fragment key={idx}>
|
<React.Fragment key={idx}>
|
||||||
<ExampleValue>{JSON.stringify(value)}</ExampleValue>{' '}
|
<ExampleValue>{exampleValue}</ExampleValue>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,8 +65,7 @@ export class Field extends React.Component<FieldProps> {
|
||||||
<FieldDetails {...this.props} />
|
<FieldDetails {...this.props} />
|
||||||
</PropertyDetailsCell>
|
</PropertyDetailsCell>
|
||||||
</tr>
|
</tr>
|
||||||
{field.expanded &&
|
{field.expanded && withSubSchema && (
|
||||||
withSubSchema && (
|
|
||||||
<tr key={field.name + 'inner'}>
|
<tr key={field.name + 'inner'}>
|
||||||
<PropertyCellWithInner colSpan={2}>
|
<PropertyCellWithInner colSpan={2}>
|
||||||
<InnerPropertiesWrap>
|
<InnerPropertiesWrap>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { ExampleValue, FieldLabel } from '../../common-elements/fields';
|
||||||
export interface FieldDetailProps {
|
export interface FieldDetailProps {
|
||||||
value?: any;
|
value?: any;
|
||||||
label: string;
|
label: string;
|
||||||
|
raw?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FieldDetail extends React.PureComponent<FieldDetailProps> {
|
export class FieldDetail extends React.PureComponent<FieldDetailProps> {
|
||||||
|
@ -11,12 +12,12 @@ export class FieldDetail extends React.PureComponent<FieldDetailProps> {
|
||||||
if (this.props.value === undefined) {
|
if (this.props.value === undefined) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const value = this.props.raw ? this.props.value : JSON.stringify(this.props.value);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<FieldLabel> {this.props.label} </FieldLabel>{' '}
|
<FieldLabel> {this.props.label} </FieldLabel> <ExampleValue>{value}</ExampleValue>
|
||||||
<ExampleValue>
|
|
||||||
{JSON.stringify(this.props.value)}
|
|
||||||
</ExampleValue>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
TypePrefix,
|
TypePrefix,
|
||||||
TypeTitle,
|
TypeTitle,
|
||||||
} from '../../common-elements/fields';
|
} from '../../common-elements/fields';
|
||||||
|
import { serializeParameterValue } from '../../utils/openapi';
|
||||||
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';
|
||||||
|
@ -20,13 +21,29 @@ import { FieldDetail } from './FieldDetail';
|
||||||
import { Badge } from '../../common-elements/';
|
import { Badge } from '../../common-elements/';
|
||||||
|
|
||||||
import { l } from '../../services/Labels';
|
import { l } from '../../services/Labels';
|
||||||
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
|
|
||||||
export class FieldDetails extends React.PureComponent<FieldProps> {
|
export class FieldDetails extends React.PureComponent<FieldProps> {
|
||||||
|
static contextType = OptionsContext;
|
||||||
render() {
|
render() {
|
||||||
const { showExamples, field, renderDiscriminatorSwitch } = this.props;
|
const { showExamples, field, renderDiscriminatorSwitch } = this.props;
|
||||||
|
const { enumSkipQuotes } = this.context;
|
||||||
|
|
||||||
const { schema, description, example, deprecated } = field;
|
const { schema, description, example, deprecated } = field;
|
||||||
|
|
||||||
|
let exampleField: JSX.Element | null = null;
|
||||||
|
|
||||||
|
if (showExamples) {
|
||||||
|
const label = l('example') + ':';
|
||||||
|
if (field.in && field.style) {
|
||||||
|
const serializedValue =
|
||||||
|
example !== undefined ? serializeParameterValue(field, example) : undefined;
|
||||||
|
exampleField = <FieldDetail label={label} value={serializedValue} raw={true} />;
|
||||||
|
} else {
|
||||||
|
exampleField = <FieldDetail label={label} value={example} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -51,9 +68,9 @@ export class FieldDetails extends React.PureComponent<FieldProps> {
|
||||||
<Badge type="warning"> {l('deprecated')} </Badge>
|
<Badge type="warning"> {l('deprecated')} </Badge>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<FieldDetail label={l('default') + ':'} value={schema.default} />
|
<FieldDetail raw={enumSkipQuotes} label={l('default') + ':'} value={schema.default} />
|
||||||
{!renderDiscriminatorSwitch && <EnumValues type={schema.type} values={schema.enum} />}{' '}
|
{!renderDiscriminatorSwitch && <EnumValues type={schema.type} values={schema.enum} />}{' '}
|
||||||
{showExamples && <FieldDetail label={l('example') + ':'} value={example} />}
|
{exampleField}
|
||||||
{<Extensions extensions={{ ...field.extensions, ...schema.extensions }} />}
|
{<Extensions extensions={{ ...field.extensions, ...schema.extensions }} />}
|
||||||
<div>
|
<div>
|
||||||
<Markdown compact={true} source={description} />
|
<Markdown compact={true} source={description} />
|
||||||
|
|
|
@ -3,6 +3,7 @@ import * as React from 'react';
|
||||||
|
|
||||||
import { DropdownProps } from '../../common-elements/dropdown';
|
import { DropdownProps } from '../../common-elements/dropdown';
|
||||||
import { MediaContentModel, MediaTypeModel, SchemaModel } from '../../services/models';
|
import { MediaContentModel, MediaTypeModel, SchemaModel } from '../../services/models';
|
||||||
|
import { DropdownLabel, DropdownWrapper } from '../PayloadSamples/styled.elements';
|
||||||
|
|
||||||
export interface MediaTypeChildProps {
|
export interface MediaTypeChildProps {
|
||||||
schema: SchemaModel;
|
schema: SchemaModel;
|
||||||
|
@ -11,6 +12,8 @@ export interface MediaTypeChildProps {
|
||||||
|
|
||||||
export interface MediaTypesSwitchProps {
|
export interface MediaTypesSwitchProps {
|
||||||
content?: MediaContentModel;
|
content?: MediaContentModel;
|
||||||
|
withLabel?: boolean;
|
||||||
|
|
||||||
renderDropdown: (props: DropdownProps) => JSX.Element;
|
renderDropdown: (props: DropdownProps) => JSX.Element;
|
||||||
children: (activeMime: MediaTypeModel) => JSX.Element;
|
children: (activeMime: MediaTypeModel) => JSX.Element;
|
||||||
}
|
}
|
||||||
|
@ -37,13 +40,25 @@ export class MediaTypesSwitch extends React.Component<MediaTypesSwitchProps> {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const Wrapper = ({ children }) =>
|
||||||
|
this.props.withLabel ? (
|
||||||
|
<DropdownWrapper>
|
||||||
|
<DropdownLabel>Content type</DropdownLabel>
|
||||||
|
{children}
|
||||||
|
</DropdownWrapper>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Wrapper>
|
||||||
{this.props.renderDropdown({
|
{this.props.renderDropdown({
|
||||||
value: options[activeMimeIdx],
|
value: options[activeMimeIdx],
|
||||||
options,
|
options,
|
||||||
onChange: this.switchMedia,
|
onChange: this.switchMedia,
|
||||||
})}
|
})}
|
||||||
|
</Wrapper>
|
||||||
{this.props.children(content.active)}
|
{this.props.children(content.active)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,17 +1,31 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { SmallTabs, Tab, TabList, TabPanel } from '../../common-elements';
|
import { DropdownProps } from '../../common-elements';
|
||||||
import { MediaTypeModel } from '../../services/models';
|
import { MediaTypeModel } from '../../services/models';
|
||||||
|
import { Markdown } from '../Markdown/Markdown';
|
||||||
import { Example } from './Example';
|
import { Example } from './Example';
|
||||||
import { NoSampleLabel } from './styled.elements';
|
import { DropdownLabel, DropdownWrapper, NoSampleLabel } from './styled.elements';
|
||||||
|
|
||||||
export interface PayloadSamplesProps {
|
export interface PayloadSamplesProps {
|
||||||
mediaType: MediaTypeModel;
|
mediaType: MediaTypeModel;
|
||||||
|
renderDropdown: (props: DropdownProps) => JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MediaTypeSamples extends React.Component<PayloadSamplesProps> {
|
interface MediaTypeSamplesState {
|
||||||
|
activeIdx: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MediaTypeSamples extends React.Component<PayloadSamplesProps, MediaTypeSamplesState> {
|
||||||
|
state = {
|
||||||
|
activeIdx: 0,
|
||||||
|
};
|
||||||
|
switchMedia = ({ value }) => {
|
||||||
|
this.setState({
|
||||||
|
activeIdx: parseInt(value, 10),
|
||||||
|
});
|
||||||
|
};
|
||||||
render() {
|
render() {
|
||||||
|
const { activeIdx } = this.state;
|
||||||
const examples = this.props.mediaType.examples || {};
|
const examples = this.props.mediaType.examples || {};
|
||||||
const mimeType = this.props.mediaType.name;
|
const mimeType = this.props.mediaType.name;
|
||||||
|
|
||||||
|
@ -21,26 +35,40 @@ export class MediaTypeSamples extends React.Component<PayloadSamplesProps> {
|
||||||
if (examplesNames.length === 0) {
|
if (examplesNames.length === 0) {
|
||||||
return noSample;
|
return noSample;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (examplesNames.length > 1) {
|
if (examplesNames.length > 1) {
|
||||||
|
const options = examplesNames.map((name, idx) => {
|
||||||
|
return {
|
||||||
|
label: examples[name].summary || name,
|
||||||
|
value: idx.toString(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const example = examples[examplesNames[activeIdx]];
|
||||||
|
const description = example.description;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SmallTabs defaultIndex={0}>
|
<>
|
||||||
<TabList>
|
<DropdownWrapper>
|
||||||
{examplesNames.map(name => (
|
<DropdownLabel>Example</DropdownLabel>
|
||||||
<Tab key={name}> {examples[name].summary || name} </Tab>
|
{this.props.renderDropdown({
|
||||||
))}
|
value: options[activeIdx],
|
||||||
</TabList>
|
options,
|
||||||
{examplesNames.map(name => (
|
onChange: this.switchMedia,
|
||||||
<TabPanel key={name}>
|
})}
|
||||||
<Example example={examples[name]} mimeType={mimeType} />
|
</DropdownWrapper>
|
||||||
</TabPanel>
|
<div>
|
||||||
))}
|
{description && <Markdown source={description} />}
|
||||||
</SmallTabs>
|
<Example example={example} mimeType={mimeType} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const name = examplesNames[0];
|
const example = examples[examplesNames[0]];
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Example example={examples[name]} mimeType={mimeType} />
|
{example.description && <Markdown source={example.description} />}
|
||||||
|
<Example example={example} mimeType={mimeType} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { MediaTypeSamples } from './MediaTypeSamples';
|
||||||
|
|
||||||
import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch';
|
import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch';
|
||||||
|
|
||||||
|
import styled from '../../../src/styled-components';
|
||||||
import { MediaContentModel } from '../../services/models';
|
import { MediaContentModel } from '../../services/models';
|
||||||
import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel';
|
import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel';
|
||||||
import { InvertedSimpleDropdown, MimeLabel } from './styled.elements';
|
import { InvertedSimpleDropdown, MimeLabel } from './styled.elements';
|
||||||
|
@ -21,8 +22,16 @@ export class PayloadSamples extends React.Component<PayloadSamplesProps> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MediaTypesSwitch content={mimeContent} renderDropdown={this.renderDropdown}>
|
<MediaTypesSwitch content={mimeContent} renderDropdown={this.renderDropdown} withLabel={true}>
|
||||||
{mediaType => <MediaTypeSamples key="samples" mediaType={mediaType} />}
|
{mediaType => (
|
||||||
|
<SamplesWrapper>
|
||||||
|
<MediaTypeSamples
|
||||||
|
key="samples"
|
||||||
|
mediaType={mediaType}
|
||||||
|
renderDropdown={this.renderDropdown}
|
||||||
|
/>
|
||||||
|
</SamplesWrapper>
|
||||||
|
)}
|
||||||
</MediaTypesSwitch>
|
</MediaTypesSwitch>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -31,3 +40,7 @@ export class PayloadSamples extends React.Component<PayloadSamplesProps> {
|
||||||
return <DropdownOrLabel Label={MimeLabel} Dropdown={InvertedSimpleDropdown} {...props} />;
|
return <DropdownOrLabel Label={MimeLabel} Dropdown={InvertedSimpleDropdown} {...props} />;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SamplesWrapper = styled.div`
|
||||||
|
margin-top: 15px;
|
||||||
|
`;
|
||||||
|
|
|
@ -13,8 +13,7 @@ export function useExternalExample(example: ExampleModel, mimeType: string) {
|
||||||
|
|
||||||
prevRef.current = example;
|
prevRef.current = example;
|
||||||
|
|
||||||
useEffect(
|
useEffect(() => {
|
||||||
() => {
|
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
|
@ -26,9 +25,7 @@ export function useExternalExample(example: ExampleModel, mimeType: string) {
|
||||||
};
|
};
|
||||||
|
|
||||||
load();
|
load();
|
||||||
},
|
}, [example, mimeType]);
|
||||||
[example, mimeType],
|
|
||||||
);
|
|
||||||
|
|
||||||
return value.current;
|
return value.current;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,48 @@
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import Dropdown from 'react-dropdown';
|
import Dropdown from 'react-dropdown';
|
||||||
|
|
||||||
|
import { transparentize } from 'polished';
|
||||||
import styled from '../../styled-components';
|
import styled from '../../styled-components';
|
||||||
|
|
||||||
import { StyledDropdown } from '../../common-elements';
|
import { StyledDropdown } from '../../common-elements';
|
||||||
|
|
||||||
export const MimeLabel = styled.div`
|
export const MimeLabel = styled.div`
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.9);
|
padding: 12px;
|
||||||
|
background-color: ${({ theme }) => transparentize(0.6, theme.rightPanel.backgroundColor)};
|
||||||
margin: 0 0 10px 0;
|
margin: 0 0 10px 0;
|
||||||
display: block;
|
display: block;
|
||||||
color: rgba(255, 255, 255, 0.8);
|
`;
|
||||||
|
|
||||||
|
export const DropdownLabel = styled.span`
|
||||||
|
font-family: ${({ theme }) => theme.typography.headings.fontFamily};
|
||||||
|
font-size: 12px;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
top: -11px;
|
||||||
|
left: 12px;
|
||||||
|
font-weight: ${({ theme }) => theme.typography.fontWeightBold};
|
||||||
|
color: ${({ theme }) => transparentize(0.6, theme.rightPanel.textColor)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DropdownWrapper = styled.div`
|
||||||
|
position: relative;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const InvertedSimpleDropdown = styled(StyledDropdown)`
|
export const InvertedSimpleDropdown = styled(StyledDropdown)`
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
font-size: 0.929em;
|
font-size: 0.929em;
|
||||||
border-bottom: 1px solid ${({ theme }) => theme.rightPanel.textColor};
|
|
||||||
margin: 0 0 10px 0;
|
margin: 0 0 10px 0;
|
||||||
display: block;
|
display: block;
|
||||||
|
background-color: ${({ theme }) => transparentize(0.6, theme.rightPanel.backgroundColor)};
|
||||||
|
.Dropdown-control {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
.Dropdown-control,
|
.Dropdown-control,
|
||||||
.Dropdown-control:hover {
|
.Dropdown-control:hover {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0 1.2em 0 0;
|
padding: 0.9em 1.6em 0.9em 0.9em;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: ${({ theme }) => theme.rightPanel.textColor};
|
color: ${({ theme }) => theme.rightPanel.textColor};
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
@ -34,6 +53,7 @@ export const InvertedSimpleDropdown = styled(StyledDropdown)`
|
||||||
}
|
}
|
||||||
.Dropdown-menu {
|
.Dropdown-menu {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -28,8 +28,7 @@ export class ResponseView extends React.Component<{ response: ResponseModel }> {
|
||||||
code={code}
|
code={code}
|
||||||
opened={expanded}
|
opened={expanded}
|
||||||
/>
|
/>
|
||||||
{expanded &&
|
{expanded && !empty && (
|
||||||
!empty && (
|
|
||||||
<ResponseDetailsWrap>
|
<ResponseDetailsWrap>
|
||||||
<ResponseDetails response={this.props.response} />
|
<ResponseDetails response={this.props.response} />
|
||||||
</ResponseDetailsWrap>
|
</ResponseDetailsWrap>
|
||||||
|
|
|
@ -57,16 +57,13 @@ export class MenuItem extends React.Component<MenuItemProps> {
|
||||||
{item.name}
|
{item.name}
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</MenuItemTitle>
|
</MenuItemTitle>
|
||||||
{(item.depth > 0 &&
|
{(item.depth > 0 && item.items.length > 0 && (
|
||||||
item.items.length > 0 && (
|
|
||||||
<ShelfIcon float={'right'} direction={item.expanded ? 'down' : 'right'} />
|
<ShelfIcon float={'right'} direction={item.expanded ? 'down' : 'right'} />
|
||||||
)) ||
|
)) ||
|
||||||
null}
|
null}
|
||||||
</MenuItemLabel>
|
</MenuItemLabel>
|
||||||
)}
|
)}
|
||||||
{!withoutChildren &&
|
{!withoutChildren && item.items && item.items.length > 0 && (
|
||||||
item.items &&
|
|
||||||
item.items.length > 0 && (
|
|
||||||
<MenuItems
|
<MenuItems
|
||||||
expanded={item.expanded}
|
expanded={item.expanded}
|
||||||
items={item.items}
|
items={item.items}
|
||||||
|
@ -83,7 +80,7 @@ export interface OperationMenuItemContentProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class OperationMenuItemContent extends React.Component<OperationMenuItemContentProps> {
|
export class OperationMenuItemContent extends React.Component<OperationMenuItemContentProps> {
|
||||||
render() {
|
render() {
|
||||||
const { item } = this.props;
|
const { item } = this.props;
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -10,6 +10,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
||||||
"description": "",
|
"description": "",
|
||||||
"example": undefined,
|
"example": undefined,
|
||||||
"expanded": false,
|
"expanded": false,
|
||||||
|
"explode": false,
|
||||||
"in": undefined,
|
"in": undefined,
|
||||||
"kind": "field",
|
"kind": "field",
|
||||||
"name": "packSize",
|
"name": "packSize",
|
||||||
|
@ -59,6 +60,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
||||||
"description": "",
|
"description": "",
|
||||||
"example": undefined,
|
"example": undefined,
|
||||||
"expanded": false,
|
"expanded": false,
|
||||||
|
"explode": false,
|
||||||
"in": undefined,
|
"in": undefined,
|
||||||
"kind": "field",
|
"kind": "field",
|
||||||
"name": "type",
|
"name": "type",
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'core-js/fn/array/find';
|
||||||
import 'core-js/fn/object/assign';
|
import 'core-js/fn/object/assign';
|
||||||
import 'core-js/fn/string/ends-with';
|
import 'core-js/fn/string/ends-with';
|
||||||
import 'core-js/fn/string/starts-with';
|
import 'core-js/fn/string/starts-with';
|
||||||
|
import 'core-js/fn/object/assign';
|
||||||
|
|
||||||
import 'core-js/es6/map';
|
import 'core-js/es6/map';
|
||||||
import 'core-js/es6/symbol';
|
import 'core-js/es6/symbol';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as marked from 'marked';
|
import * as marked from 'marked';
|
||||||
|
|
||||||
import { highlight, safeSlugify } from '../utils';
|
import { highlight, safeSlugify, unescapeHTMLChars } from '../utils';
|
||||||
import { AppStore } from './AppStore';
|
import { AppStore } from './AppStore';
|
||||||
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||||
|
|
||||||
|
@ -65,6 +65,7 @@ export class MarkdownRenderer {
|
||||||
container: MarkdownHeading[] = this.headings,
|
container: MarkdownHeading[] = this.headings,
|
||||||
parentId?: string,
|
parentId?: string,
|
||||||
): MarkdownHeading {
|
): MarkdownHeading {
|
||||||
|
name = unescapeHTMLChars(name);
|
||||||
const item = {
|
const item = {
|
||||||
id: parentId ? `${parentId}/${safeSlugify(name)}` : `section/${safeSlugify(name)}`,
|
id: parentId ? `${parentId}/${safeSlugify(name)}` : `section/${safeSlugify(name)}`,
|
||||||
name,
|
name,
|
||||||
|
@ -88,7 +89,7 @@ export class MarkdownRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
attachHeadingsDescriptions(rawText: string) {
|
attachHeadingsDescriptions(rawText: string) {
|
||||||
const buildRegexp = heading => {
|
const buildRegexp = (heading: MarkdownHeading) => {
|
||||||
return new RegExp(`##?\\s+${heading.name.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}`);
|
return new RegExp(`##?\\s+${heading.name.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ export interface RedocRawOptions {
|
||||||
allowedMdComponents?: Dict<MDXComponentMeta>;
|
allowedMdComponents?: Dict<MDXComponentMeta>;
|
||||||
|
|
||||||
labels?: LabelsConfigRaw;
|
labels?: LabelsConfigRaw;
|
||||||
|
enumSkipQuotes?: boolean | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function argValueToBoolean(val?: string | boolean): boolean {
|
function argValueToBoolean(val?: string | boolean): boolean {
|
||||||
|
@ -137,6 +138,7 @@ export class RedocNormalizedOptions {
|
||||||
showExtensions: boolean | string[];
|
showExtensions: boolean | string[];
|
||||||
hideSingleRequestSampleTab: boolean;
|
hideSingleRequestSampleTab: boolean;
|
||||||
jsonSampleExpandLevel: number;
|
jsonSampleExpandLevel: number;
|
||||||
|
enumSkipQuotes: boolean;
|
||||||
|
|
||||||
/* tslint:disable-next-line */
|
/* tslint:disable-next-line */
|
||||||
unstable_ignoreMimeParameters: boolean;
|
unstable_ignoreMimeParameters: boolean;
|
||||||
|
@ -171,6 +173,7 @@ export class RedocNormalizedOptions {
|
||||||
this.jsonSampleExpandLevel = RedocNormalizedOptions.normalizeJsonSampleExpandLevel(
|
this.jsonSampleExpandLevel = RedocNormalizedOptions.normalizeJsonSampleExpandLevel(
|
||||||
raw.jsonSampleExpandLevel,
|
raw.jsonSampleExpandLevel,
|
||||||
);
|
);
|
||||||
|
this.enumSkipQuotes = argValueToBoolean(raw.enumSkipQuotes);
|
||||||
|
|
||||||
this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters);
|
this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters);
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,13 @@
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"name": "test_name",
|
"name": "test_name",
|
||||||
"schema": { "type": "string" }
|
"schema": { "type": "string" }
|
||||||
|
},
|
||||||
|
"serializationParam": {
|
||||||
|
"in": "query",
|
||||||
|
"name": "serialization_test_name",
|
||||||
|
"schema": { "type": "array" },
|
||||||
|
"style": "form",
|
||||||
|
"explode": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"headers": {
|
"headers": {
|
||||||
|
|
|
@ -26,6 +26,23 @@ describe('Models', () => {
|
||||||
expect(field.schema.type).toEqual('string');
|
expect(field.schema.type).toEqual('string');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('field details relevant for parameter serialization', () => {
|
||||||
|
const field = new FieldModel(
|
||||||
|
parser,
|
||||||
|
{
|
||||||
|
$ref: '#/components/parameters/serializationParam',
|
||||||
|
},
|
||||||
|
'#/components/parameters/serializationParam',
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(field.name).toEqual('serialization_test_name');
|
||||||
|
expect(field.in).toEqual('query');
|
||||||
|
expect(field.schema.type).toEqual('array');
|
||||||
|
expect(field.style).toEqual('form');
|
||||||
|
expect(field.explode).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
test('field name should populated from name even if $ref (headers)', () => {
|
test('field name should populated from name even if $ref (headers)', () => {
|
||||||
const field = new FieldModel(
|
const field = new FieldModel(
|
||||||
parser,
|
parser,
|
||||||
|
|
|
@ -1,12 +1,30 @@
|
||||||
import { action, observable } from 'mobx';
|
import { action, observable } from 'mobx';
|
||||||
|
|
||||||
import { OpenAPIParameter, Referenced } from '../../types';
|
import {
|
||||||
|
OpenAPIParameter,
|
||||||
|
OpenAPIParameterLocation,
|
||||||
|
OpenAPIParameterStyle,
|
||||||
|
Referenced,
|
||||||
|
} from '../../types';
|
||||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||||
|
|
||||||
import { extractExtensions } from '../../utils/openapi';
|
import { extractExtensions } from '../../utils/openapi';
|
||||||
import { OpenAPIParser } from '../OpenAPIParser';
|
import { OpenAPIParser } from '../OpenAPIParser';
|
||||||
import { SchemaModel } from './Schema';
|
import { SchemaModel } from './Schema';
|
||||||
|
|
||||||
|
function getDefaultStyleValue(parameterLocation: OpenAPIParameterLocation): OpenAPIParameterStyle {
|
||||||
|
switch (parameterLocation) {
|
||||||
|
case 'header':
|
||||||
|
return 'simple';
|
||||||
|
case 'query':
|
||||||
|
return 'form';
|
||||||
|
case 'path':
|
||||||
|
return 'simple';
|
||||||
|
default:
|
||||||
|
return 'form';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Field or Parameter model ready to be used by components
|
* Field or Parameter model ready to be used by components
|
||||||
*/
|
*/
|
||||||
|
@ -20,9 +38,11 @@ export class FieldModel {
|
||||||
description: string;
|
description: string;
|
||||||
example?: string;
|
example?: string;
|
||||||
deprecated: boolean;
|
deprecated: boolean;
|
||||||
in?: string;
|
in?: OpenAPIParameterLocation;
|
||||||
kind: string;
|
kind: string;
|
||||||
extensions?: Dict<any>;
|
extensions?: Dict<any>;
|
||||||
|
explode: boolean;
|
||||||
|
style?: OpenAPIParameterStyle;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
parser: OpenAPIParser,
|
parser: OpenAPIParser,
|
||||||
|
@ -40,6 +60,14 @@ export class FieldModel {
|
||||||
info.description === undefined ? this.schema.description || '' : info.description;
|
info.description === undefined ? this.schema.description || '' : info.description;
|
||||||
this.example = info.example || this.schema.example;
|
this.example = info.example || this.schema.example;
|
||||||
|
|
||||||
|
if (info.style) {
|
||||||
|
this.style = info.style;
|
||||||
|
} else if (this.in) {
|
||||||
|
this.style = getDefaultStyleValue(this.in);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.explode = !!info.explode;
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
|
|
|
@ -292,7 +292,10 @@ function buildFields(
|
||||||
new FieldModel(
|
new FieldModel(
|
||||||
parser,
|
parser,
|
||||||
{
|
{
|
||||||
name: 'property name *',
|
name: (typeof additionalProps === 'object'
|
||||||
|
? additionalProps['x-additionalPropertiesName'] || 'property name'
|
||||||
|
: 'property name'
|
||||||
|
).concat('*'),
|
||||||
required: false,
|
required: false,
|
||||||
schema: additionalProps === true ? {} : additionalProps,
|
schema: additionalProps === true ? {} : additionalProps,
|
||||||
kind: 'additionalProperties',
|
kind: 'additionalProperties',
|
||||||
|
|
|
@ -8,10 +8,11 @@ import {
|
||||||
mergeParams,
|
mergeParams,
|
||||||
normalizeServers,
|
normalizeServers,
|
||||||
pluralizeType,
|
pluralizeType,
|
||||||
|
serializeParameterValue,
|
||||||
} from '../';
|
} from '../';
|
||||||
|
|
||||||
import { OpenAPIParser } from '../../services';
|
import { OpenAPIParser } from '../../services';
|
||||||
import { OpenAPIParameter } from '../../types';
|
import { OpenAPIParameter, OpenAPIParameterLocation, OpenAPIParameterStyle } from '../../types';
|
||||||
|
|
||||||
describe('Utils', () => {
|
describe('Utils', () => {
|
||||||
describe('openapi getStatusCode', () => {
|
describe('openapi getStatusCode', () => {
|
||||||
|
@ -377,4 +378,190 @@ describe('Utils', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('openapi serializeParameter', () => {
|
||||||
|
interface TestCase {
|
||||||
|
style: OpenAPIParameterStyle;
|
||||||
|
explode: boolean;
|
||||||
|
expected: string;
|
||||||
|
}
|
||||||
|
interface TestValueTypeGroup {
|
||||||
|
value: any;
|
||||||
|
description: string;
|
||||||
|
cases: TestCase[];
|
||||||
|
}
|
||||||
|
interface TestLocationGroup {
|
||||||
|
location: OpenAPIParameterLocation;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
cases: TestValueTypeGroup[];
|
||||||
|
}
|
||||||
|
const testCases: TestLocationGroup[] = [
|
||||||
|
{
|
||||||
|
location: 'path',
|
||||||
|
name: 'id',
|
||||||
|
description: 'path parameters',
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
value: 5,
|
||||||
|
description: 'primitive values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'simple', explode: false, expected: '5' },
|
||||||
|
{ style: 'simple', explode: true, expected: '5' },
|
||||||
|
{ style: 'label', explode: false, expected: '.5' },
|
||||||
|
{ style: 'label', explode: true, expected: '.5' },
|
||||||
|
{ style: 'matrix', explode: false, expected: ';id=5' },
|
||||||
|
{ style: 'matrix', explode: true, expected: ';id=5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: [3, 4, 5],
|
||||||
|
description: 'array values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'simple', explode: false, expected: '3,4,5' },
|
||||||
|
{ style: 'simple', explode: true, expected: '3,4,5' },
|
||||||
|
{ style: 'label', explode: false, expected: '.3,4,5' },
|
||||||
|
{ style: 'label', explode: true, expected: '.3.4.5' },
|
||||||
|
{ style: 'matrix', explode: false, expected: ';id=3,4,5' },
|
||||||
|
{ style: 'matrix', explode: true, expected: ';id=3;id=4;id=5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: { role: 'admin', firstName: 'Alex' },
|
||||||
|
description: 'object values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'simple', explode: false, expected: 'role,admin,firstName,Alex' },
|
||||||
|
{ style: 'simple', explode: true, expected: 'role=admin,firstName=Alex' },
|
||||||
|
{ style: 'label', explode: false, expected: '.role,admin,firstName,Alex' },
|
||||||
|
{ style: 'label', explode: true, expected: '.role=admin,firstName=Alex' },
|
||||||
|
{ style: 'matrix', explode: false, expected: ';id=role,admin,firstName,Alex' },
|
||||||
|
{ style: 'matrix', explode: true, expected: ';role=admin;firstName=Alex' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
location: 'query',
|
||||||
|
name: 'id',
|
||||||
|
description: 'query parameters',
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
value: 5,
|
||||||
|
description: 'primitive values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'form', explode: true, expected: 'id=5' },
|
||||||
|
{ style: 'form', explode: false, expected: 'id=5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: [3, 4, 5],
|
||||||
|
description: 'array values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'form', explode: true, expected: 'id=3&id=4&id=5' },
|
||||||
|
{ style: 'form', explode: false, expected: 'id=3,4,5' },
|
||||||
|
{ style: 'spaceDelimited', explode: true, expected: 'id=3&id=4&id=5' },
|
||||||
|
{ style: 'spaceDelimited', explode: false, expected: 'id=3%204%205' },
|
||||||
|
{ style: 'pipeDelimited', explode: true, expected: 'id=3&id=4&id=5' },
|
||||||
|
{ style: 'pipeDelimited', explode: false, expected: 'id=3|4|5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: { role: 'admin', firstName: 'Alex' },
|
||||||
|
description: 'object values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'form', explode: true, expected: 'role=admin&firstName=Alex' },
|
||||||
|
{ style: 'form', explode: false, expected: 'id=role,admin,firstName,Alex' },
|
||||||
|
{ style: 'deepObject', explode: true, expected: 'id[role]=admin&id[firstName]=Alex' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
location: 'cookie',
|
||||||
|
name: 'id',
|
||||||
|
description: 'cookie parameters',
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
value: 5,
|
||||||
|
description: 'primitive values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'form', explode: true, expected: 'id=5' },
|
||||||
|
{ style: 'form', explode: false, expected: 'id=5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: [3, 4, 5],
|
||||||
|
description: 'array values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'form', explode: true, expected: 'id=3&id=4&id=5' },
|
||||||
|
{ style: 'form', explode: false, expected: 'id=3,4,5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: { role: 'admin', firstName: 'Alex' },
|
||||||
|
description: 'object values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'form', explode: true, expected: 'role=admin&firstName=Alex' },
|
||||||
|
{ style: 'form', explode: false, expected: 'id=role,admin,firstName,Alex' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
location: 'header',
|
||||||
|
name: 'id',
|
||||||
|
description: 'header parameters',
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
value: 5,
|
||||||
|
description: 'primitive values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'simple', explode: false, expected: '5' },
|
||||||
|
{ style: 'simple', explode: true, expected: '5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: [3, 4, 5],
|
||||||
|
description: 'array values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'simple', explode: false, expected: '3,4,5' },
|
||||||
|
{ style: 'simple', explode: true, expected: '3,4,5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: { role: 'admin', firstName: 'Alex' },
|
||||||
|
description: 'object values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'simple', explode: false, expected: 'role,admin,firstName,Alex' },
|
||||||
|
{ style: 'simple', explode: true, expected: 'role=admin,firstName=Alex' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
testCases.forEach(locationTestGroup => {
|
||||||
|
describe(locationTestGroup.description, () => {
|
||||||
|
locationTestGroup.cases.forEach(valueTypeTestGroup => {
|
||||||
|
describe(valueTypeTestGroup.description, () => {
|
||||||
|
valueTypeTestGroup.cases.forEach(testCase => {
|
||||||
|
it(`should serialize correctly when style is ${testCase.style} and explode is ${
|
||||||
|
testCase.explode
|
||||||
|
}`, () => {
|
||||||
|
const parameter: OpenAPIParameter = {
|
||||||
|
name: locationTestGroup.name,
|
||||||
|
in: locationTestGroup.location,
|
||||||
|
style: testCase.style,
|
||||||
|
explode: testCase.explode,
|
||||||
|
};
|
||||||
|
const serialized = serializeParameterValue(parameter, valueTypeTestGroup.value);
|
||||||
|
|
||||||
|
expect(serialized).toEqual(testCase.expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -194,3 +194,7 @@ function parseURL(url: string) {
|
||||||
return new URL(url);
|
return new URL(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function unescapeHTMLChars(str: string): string {
|
||||||
|
return str.replace(/&#(\d+);/g, (_m, code) => String.fromCharCode(parseInt(code, 10)));
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { dirname } from 'path';
|
import { dirname } from 'path';
|
||||||
|
import { URI } from 'uri-template-lite';
|
||||||
|
|
||||||
import { OpenAPIParser } from '../services/OpenAPIParser';
|
import { OpenAPIParser } from '../services/OpenAPIParser';
|
||||||
import {
|
import {
|
||||||
|
@ -6,6 +7,7 @@ import {
|
||||||
OpenAPIMediaType,
|
OpenAPIMediaType,
|
||||||
OpenAPIOperation,
|
OpenAPIOperation,
|
||||||
OpenAPIParameter,
|
OpenAPIParameter,
|
||||||
|
OpenAPIParameterStyle,
|
||||||
OpenAPISchema,
|
OpenAPISchema,
|
||||||
OpenAPIServer,
|
OpenAPIServer,
|
||||||
Referenced,
|
Referenced,
|
||||||
|
@ -135,36 +137,6 @@ export function isFormUrlEncoded(contentType: string): boolean {
|
||||||
return contentType === 'application/x-www-form-urlencoded';
|
return contentType === 'application/x-www-form-urlencoded';
|
||||||
}
|
}
|
||||||
|
|
||||||
function formEncodeField(fieldVal: any, fieldName: string, explode: boolean): string {
|
|
||||||
if (!fieldVal || !fieldVal.length) {
|
|
||||||
return fieldName + '=';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(fieldVal)) {
|
|
||||||
if (explode) {
|
|
||||||
return fieldVal.map(val => `${fieldName}=${val}`).join('&');
|
|
||||||
} else {
|
|
||||||
return fieldName + '=' + fieldVal.map(val => val.toString()).join(',');
|
|
||||||
}
|
|
||||||
} else if (typeof fieldVal === 'object') {
|
|
||||||
if (explode) {
|
|
||||||
return Object.keys(fieldVal)
|
|
||||||
.map(k => `${k}=${fieldVal[k]}`)
|
|
||||||
.join('&');
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
fieldName +
|
|
||||||
'=' +
|
|
||||||
Object.keys(fieldVal)
|
|
||||||
.map(k => `${k},${fieldVal[k]}`)
|
|
||||||
.join(',')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fieldName + '=' + fieldVal.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function delimitedEncodeField(fieldVal: any, fieldName: string, delimeter: string): string {
|
function delimitedEncodeField(fieldVal: any, fieldName: string, delimeter: string): string {
|
||||||
if (Array.isArray(fieldVal)) {
|
if (Array.isArray(fieldVal)) {
|
||||||
return fieldVal.map(v => v.toString()).join(delimeter);
|
return fieldVal.map(v => v.toString()).join(delimeter);
|
||||||
|
@ -191,6 +163,13 @@ function deepObjectEncodeField(fieldVal: any, fieldName: string): string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function serializeFormValue(name: string, explode: boolean, value: any) {
|
||||||
|
const suffix = explode ? '*' : '';
|
||||||
|
const template = new URI.Template(`{?${name}${suffix}}`);
|
||||||
|
|
||||||
|
return template.expand({ [name]: value }).substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Should be used only for url-form-encoded body payloads
|
* Should be used only for url-form-encoded body payloads
|
||||||
* To be used for parmaters should be extended with other style values
|
* To be used for parmaters should be extended with other style values
|
||||||
|
@ -208,7 +187,7 @@ export function urlFormEncodePayload(
|
||||||
const { style = 'form', explode = true } = encoding[fieldName] || {};
|
const { style = 'form', explode = true } = encoding[fieldName] || {};
|
||||||
switch (style) {
|
switch (style) {
|
||||||
case 'form':
|
case 'form':
|
||||||
return formEncodeField(fieldVal, fieldName, explode);
|
return serializeFormValue(fieldName, explode, fieldVal);
|
||||||
break;
|
break;
|
||||||
case 'spaceDelimited':
|
case 'spaceDelimited':
|
||||||
return delimitedEncodeField(fieldVal, fieldName, '%20');
|
return delimitedEncodeField(fieldVal, fieldName, '%20');
|
||||||
|
@ -226,6 +205,124 @@ export function urlFormEncodePayload(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function serializePathParameter(
|
||||||
|
name: string,
|
||||||
|
style: OpenAPIParameterStyle,
|
||||||
|
explode: boolean,
|
||||||
|
value: any,
|
||||||
|
): string {
|
||||||
|
const suffix = explode ? '*' : '';
|
||||||
|
let prefix = '';
|
||||||
|
|
||||||
|
if (style === 'label') {
|
||||||
|
prefix = '.';
|
||||||
|
} else if (style === 'matrix') {
|
||||||
|
prefix = ';';
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = new URI.Template(`{${prefix}${name}${suffix}}`);
|
||||||
|
|
||||||
|
return template.expand({ [name]: value });
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeQueryParameter(
|
||||||
|
name: string,
|
||||||
|
style: OpenAPIParameterStyle,
|
||||||
|
explode: boolean,
|
||||||
|
value: any,
|
||||||
|
): string {
|
||||||
|
switch (style) {
|
||||||
|
case 'form':
|
||||||
|
return serializeFormValue(name, explode, value);
|
||||||
|
case 'spaceDelimited':
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
console.warn('The style spaceDelimited is only applicable to arrays');
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (explode) {
|
||||||
|
return serializeFormValue(name, explode, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${name}=${value.join('%20')}`;
|
||||||
|
case 'pipeDelimited':
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
console.warn('The style pipeDelimited is only applicable to arrays');
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (explode) {
|
||||||
|
return serializeFormValue(name, explode, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${name}=${value.join('|')}`;
|
||||||
|
case 'deepObject':
|
||||||
|
if (!explode || Array.isArray(value) || typeof value !== 'object') {
|
||||||
|
console.warn('The style deepObject is only applicable for objects with expolde=true');
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return deepObjectEncodeField(value, name);
|
||||||
|
default:
|
||||||
|
console.warn('Unexpected style for query: ' + style);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeHeaderParameter(
|
||||||
|
name: string,
|
||||||
|
style: OpenAPIParameterStyle,
|
||||||
|
explode: boolean,
|
||||||
|
value: any,
|
||||||
|
): string {
|
||||||
|
switch (style) {
|
||||||
|
case 'simple':
|
||||||
|
const suffix = explode ? '*' : '';
|
||||||
|
const template = new URI.Template(`{${name}${suffix}}`);
|
||||||
|
|
||||||
|
return template.expand({ [name]: value });
|
||||||
|
default:
|
||||||
|
console.warn('Unexpected style for header: ' + style);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeCookieParameter(
|
||||||
|
name: string,
|
||||||
|
style: OpenAPIParameterStyle,
|
||||||
|
explode: boolean,
|
||||||
|
value: any,
|
||||||
|
): string {
|
||||||
|
switch (style) {
|
||||||
|
case 'form':
|
||||||
|
return serializeFormValue(name, explode, value);
|
||||||
|
default:
|
||||||
|
console.warn('Unexpected style for cookie: ' + style);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serializeParameterValue(parameter: OpenAPIParameter, value: any): string {
|
||||||
|
const { name, style, explode = false } = parameter;
|
||||||
|
|
||||||
|
if (!style) {
|
||||||
|
console.warn(`Missing style attribute for parameter ${name}`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (parameter.in) {
|
||||||
|
case 'path':
|
||||||
|
return serializePathParameter(name, style, explode, value);
|
||||||
|
case 'query':
|
||||||
|
return serializeQueryParameter(name, style, explode, value);
|
||||||
|
case 'header':
|
||||||
|
return serializeHeaderParameter(name, style, explode, value);
|
||||||
|
case 'cookie':
|
||||||
|
return serializeCookieParameter(name, style, explode, value);
|
||||||
|
default:
|
||||||
|
console.warn('Unexpected parameter location: ' + parameter.in);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function langFromMime(contentType: string): string {
|
export function langFromMime(contentType: string): string {
|
||||||
if (contentType.search(/xml/i) !== -1) {
|
if (contentType.search(/xml/i) !== -1) {
|
||||||
return 'xml';
|
return 'xml';
|
||||||
|
@ -356,7 +453,7 @@ export function mergeSimilarMediaTypes(types: Dict<OpenAPIMediaType>): Dict<Open
|
||||||
return mergedTypes;
|
return mergedTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
function expandVariables(url: string, variables: object = {}) {
|
export function expandDefaultServerVariables(url: string, variables: object = {}) {
|
||||||
return url.replace(
|
return url.replace(
|
||||||
/(?:{)(\w+)(?:})/g,
|
/(?:{)(\w+)(?:})/g,
|
||||||
(match, name) => (variables[name] && variables[name].default) || match,
|
(match, name) => (variables[name] && variables[name].default) || match,
|
||||||
|
@ -385,15 +482,14 @@ export function normalizeServers(
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeUrl(url: string, variables: object | undefined): string {
|
function normalizeUrl(url: string): string {
|
||||||
url = expandVariables(url, variables);
|
|
||||||
return resolveUrl(baseUrl, url);
|
return resolveUrl(baseUrl, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
return servers.map(server => {
|
return servers.map(server => {
|
||||||
return {
|
return {
|
||||||
...server,
|
...server,
|
||||||
url: normalizeUrl(server.url, server.variables),
|
url: normalizeUrl(server.url),
|
||||||
description: server.description || '',
|
description: server.description || '',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -423,6 +519,7 @@ export function isRedocExtension(key: string): boolean {
|
||||||
'x-servers': true,
|
'x-servers': true,
|
||||||
'x-tagGroups': true,
|
'x-tagGroups': true,
|
||||||
'x-traitTag': true,
|
'x-traitTag': true,
|
||||||
|
'x-additionalPropertiesName': true,
|
||||||
};
|
};
|
||||||
|
|
||||||
return key in redocExtensions;
|
return key in redocExtensions;
|
||||||
|
|
|
@ -9651,6 +9651,11 @@ uri-js@^4.2.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
punycode "^2.1.0"
|
punycode "^2.1.0"
|
||||||
|
|
||||||
|
uri-template-lite@^19.4.0:
|
||||||
|
version "19.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/uri-template-lite/-/uri-template-lite-19.4.0.tgz#cbc2c072cf4931428a2f9d3aea36b8254a33cce5"
|
||||||
|
integrity sha512-VY8dgwyMwnCztkzhq0cA/YhNmO+YZqow//5FdmgE2fZU/JPi+U0rPL7MRDi0F+Ch4vJ7nYidWzeWAeY7uywe9g==
|
||||||
|
|
||||||
urix@^0.1.0:
|
urix@^0.1.0:
|
||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
|
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user