mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-29 03:53:43 +03:00
feat: display patternProperties (#2008)
This commit is contained in:
parent
58cd3cb782
commit
660cc857bc
|
@ -1,4 +1,4 @@
|
||||||
import styled, { extensionsHook, media } from '../styled-components';
|
import styled, { extensionsHook, media, css } from '../styled-components';
|
||||||
import { deprecatedCss } from './mixins';
|
import { deprecatedCss } from './mixins';
|
||||||
|
|
||||||
export const PropertiesTableCaption = styled.caption`
|
export const PropertiesTableCaption = styled.caption`
|
||||||
|
@ -72,7 +72,26 @@ export const PropertyNameCell = styled(PropertyCell)`
|
||||||
${deprecatedCss};
|
${deprecatedCss};
|
||||||
}
|
}
|
||||||
|
|
||||||
${({ kind }) => (kind !== 'field' ? 'font-style: italic' : '')};
|
${({ kind }) =>
|
||||||
|
kind === 'patternProperties' &&
|
||||||
|
css`
|
||||||
|
> span.property-name {
|
||||||
|
display: inline-table;
|
||||||
|
white-space: break-spaces;
|
||||||
|
margin-right: 20px;
|
||||||
|
|
||||||
|
::before,
|
||||||
|
::after {
|
||||||
|
content: '/';
|
||||||
|
filter: opacity(0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
|
||||||
|
${({ kind = '' }) =>
|
||||||
|
['field', 'additionalProperties', 'patternProperties'].includes(kind)
|
||||||
|
? ''
|
||||||
|
: 'font-style: italic'};
|
||||||
|
|
||||||
${extensionsHook('PropertyNameCell')};
|
${extensionsHook('PropertyNameCell')};
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { transparentize } from 'polished';
|
import { transparentize } from 'polished';
|
||||||
|
|
||||||
import styled, { extensionsHook } from '../styled-components';
|
import styled, { extensionsHook, css } from '../styled-components';
|
||||||
import { PropertyNameCell } from './fields-layout';
|
import { PropertyNameCell } from './fields-layout';
|
||||||
import { ShelfIcon } from './shelfs';
|
import { ShelfIcon } from './shelfs';
|
||||||
|
|
||||||
|
@ -17,6 +17,27 @@ export const ClickablePropertyNameCell = styled(PropertyNameCell)`
|
||||||
&:focus {
|
&:focus {
|
||||||
font-weight: ${({ theme }) => theme.typography.fontWeightBold};
|
font-weight: ${({ theme }) => theme.typography.fontWeightBold};
|
||||||
}
|
}
|
||||||
|
${({ kind }) =>
|
||||||
|
kind === 'patternProperties' &&
|
||||||
|
css`
|
||||||
|
display: inline-flex;
|
||||||
|
margin-right: 20px;
|
||||||
|
|
||||||
|
> span.property-name {
|
||||||
|
white-space: break-spaces;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
::before,
|
||||||
|
::after {
|
||||||
|
content: '/';
|
||||||
|
filter: opacity(0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
`}
|
||||||
}
|
}
|
||||||
${ShelfIcon} {
|
${ShelfIcon} {
|
||||||
height: ${({ theme }) => theme.schema.arrow.size};
|
height: ${({ theme }) => theme.schema.arrow.size};
|
||||||
|
@ -56,6 +77,10 @@ export const RequiredLabel = styled(FieldLabel.withComponent('div'))`
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const PropertyLabel = styled(RequiredLabel)`
|
||||||
|
color: ${props => props.theme.colors.primary.light};
|
||||||
|
`;
|
||||||
|
|
||||||
export const RecursiveLabel = styled(FieldLabel)`
|
export const RecursiveLabel = styled(FieldLabel)`
|
||||||
color: ${({ theme }) => theme.colors.warning.main};
|
color: ${({ theme }) => theme.colors.warning.main};
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
|
|
@ -37,6 +37,7 @@ class IntShelfIcon extends React.PureComponent<{
|
||||||
export const ShelfIcon = styled(IntShelfIcon)`
|
export const ShelfIcon = styled(IntShelfIcon)`
|
||||||
height: ${props => props.size || '18px'};
|
height: ${props => props.size || '18px'};
|
||||||
width: ${props => props.size || '18px'};
|
width: ${props => props.size || '18px'};
|
||||||
|
min-width: ${props => props.size || '18px'};
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
float: ${props => props.float || ''};
|
float: ${props => props.float || ''};
|
||||||
transition: transform 0.2s ease-out;
|
transition: transform 0.2s ease-out;
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { ClickablePropertyNameCell, RequiredLabel } from '../../common-elements/fields';
|
import {
|
||||||
|
ClickablePropertyNameCell,
|
||||||
|
PropertyLabel,
|
||||||
|
RequiredLabel,
|
||||||
|
} from '../../common-elements/fields';
|
||||||
import { FieldDetails } from './FieldDetails';
|
import { FieldDetails } from './FieldDetails';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
InnerPropertiesWrap,
|
InnerPropertiesWrap,
|
||||||
PropertyBullet,
|
PropertyBullet,
|
||||||
|
@ -11,11 +14,10 @@ import {
|
||||||
PropertyDetailsCell,
|
PropertyDetailsCell,
|
||||||
PropertyNameCell,
|
PropertyNameCell,
|
||||||
} from '../../common-elements/fields-layout';
|
} from '../../common-elements/fields-layout';
|
||||||
|
|
||||||
import { ShelfIcon } from '../../common-elements/';
|
import { ShelfIcon } from '../../common-elements/';
|
||||||
|
import { Schema } from '../Schema/Schema';
|
||||||
import { FieldModel } from '../../services/models';
|
import type { SchemaOptions } from '../Schema/Schema';
|
||||||
import { Schema, SchemaOptions } from '../Schema/Schema';
|
import type { FieldModel } from '../../services/models';
|
||||||
|
|
||||||
export interface FieldProps extends SchemaOptions {
|
export interface FieldProps extends SchemaOptions {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -52,6 +54,14 @@ export class Field extends React.Component<FieldProps> {
|
||||||
|
|
||||||
const expanded = field.expanded === undefined ? expandByDefault : field.expanded;
|
const expanded = field.expanded === undefined ? expandByDefault : field.expanded;
|
||||||
|
|
||||||
|
const labels = (
|
||||||
|
<>
|
||||||
|
{kind === 'additionalProperties' && <PropertyLabel>additional property</PropertyLabel>}
|
||||||
|
{kind === 'patternProperties' && <PropertyLabel>pattern property</PropertyLabel>}
|
||||||
|
{required && <RequiredLabel>required</RequiredLabel>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
const paramName = withSubSchema ? (
|
const paramName = withSubSchema ? (
|
||||||
<ClickablePropertyNameCell
|
<ClickablePropertyNameCell
|
||||||
className={deprecated ? 'deprecated' : ''}
|
className={deprecated ? 'deprecated' : ''}
|
||||||
|
@ -64,16 +74,16 @@ export class Field extends React.Component<FieldProps> {
|
||||||
onKeyPress={this.handleKeyPress}
|
onKeyPress={this.handleKeyPress}
|
||||||
aria-label="expand properties"
|
aria-label="expand properties"
|
||||||
>
|
>
|
||||||
<span>{name}</span>
|
<span className="property-name">{name}</span>
|
||||||
<ShelfIcon direction={expanded ? 'down' : 'right'} />
|
<ShelfIcon direction={expanded ? 'down' : 'right'} />
|
||||||
</button>
|
</button>
|
||||||
{required && <RequiredLabel> required </RequiredLabel>}
|
{labels}
|
||||||
</ClickablePropertyNameCell>
|
</ClickablePropertyNameCell>
|
||||||
) : (
|
) : (
|
||||||
<PropertyNameCell className={deprecated ? 'deprecated' : undefined} kind={kind} title={name}>
|
<PropertyNameCell className={deprecated ? 'deprecated' : undefined} kind={kind} title={name}>
|
||||||
<PropertyBullet />
|
<PropertyBullet />
|
||||||
<span>{name}</span>
|
<span className="property-name">{name}</span>
|
||||||
{required && <RequiredLabel> required </RequiredLabel>}
|
{labels}
|
||||||
</PropertyNameCell>
|
</PropertyNameCell>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
32
src/services/__tests__/fixtures/3.1/patternProperties.json
Normal file
32
src/services/__tests__/fixtures/3.1/patternProperties.json
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"info": {
|
||||||
|
"title": "Schema definition with unevaluatedProperties",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "example.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"Patterns": {
|
||||||
|
"type": "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^S_\\w+\\.[1-9]{2,4}$": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"^O_\\w+\\.[1-9]{2,4}$": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"x-nestedProperty": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -76,5 +76,16 @@ describe('Models', () => {
|
||||||
expect(schema.fields![1].kind).toEqual('additionalProperties');
|
expect(schema.fields![1].kind).toEqual('additionalProperties');
|
||||||
expect(schema.fields![1].schema.type).toEqual('boolean');
|
expect(schema.fields![1].schema.type).toEqual('boolean');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('schemaDefinition should resolve patternProperties', () => {
|
||||||
|
const spec = require('../fixtures/3.1/patternProperties.json');
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(parser, spec.components.schemas.Patterns, '', opts);
|
||||||
|
expect(schema.fields).toHaveLength(2);
|
||||||
|
expect(schema.fields![0].kind).toEqual('patternProperties');
|
||||||
|
expect(schema.fields![0].schema.type).toEqual('string');
|
||||||
|
expect(schema.fields![1].kind).toEqual('patternProperties');
|
||||||
|
expect(schema.fields![1].schema.type).toEqual('object');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -364,6 +364,7 @@ function buildFields(
|
||||||
options: RedocNormalizedOptions,
|
options: RedocNormalizedOptions,
|
||||||
): FieldModel[] {
|
): FieldModel[] {
|
||||||
const props = schema.properties || {};
|
const props = schema.properties || {};
|
||||||
|
const patternProps = schema.patternProperties || {};
|
||||||
const additionalProps = schema.additionalProperties || schema.unevaluatedProperties;
|
const additionalProps = schema.additionalProperties || schema.unevaluatedProperties;
|
||||||
const defaults = schema.default;
|
const defaults = schema.default;
|
||||||
let fields = Object.keys(props || []).map(fieldName => {
|
let fields = Object.keys(props || []).map(fieldName => {
|
||||||
|
@ -402,6 +403,31 @@ function buildFields(
|
||||||
fields = sortByRequired(fields, !options.sortPropsAlphabetically ? schema.required : undefined);
|
fields = sortByRequired(fields, !options.sortPropsAlphabetically ? schema.required : undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fields.push(
|
||||||
|
...Object.keys(patternProps).map(fieldName => {
|
||||||
|
let field = patternProps[fieldName];
|
||||||
|
|
||||||
|
if (!field) {
|
||||||
|
console.warn(
|
||||||
|
`Field "${fieldName}" is invalid, skipping.\n Field must be an object but got ${typeof field} at "${$ref}"`,
|
||||||
|
);
|
||||||
|
field = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FieldModel(
|
||||||
|
parser,
|
||||||
|
{
|
||||||
|
name: fieldName,
|
||||||
|
required: false,
|
||||||
|
schema: field,
|
||||||
|
kind: 'patternProperties',
|
||||||
|
},
|
||||||
|
`${$ref}/patternProperties/${fieldName}`,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
if (typeof additionalProps === 'object' || additionalProps === true) {
|
if (typeof additionalProps === 'object' || additionalProps === true) {
|
||||||
fields.push(
|
fields.push(
|
||||||
new FieldModel(
|
new FieldModel(
|
||||||
|
|
|
@ -113,6 +113,7 @@ export interface OpenAPISchema {
|
||||||
$ref?: string;
|
$ref?: string;
|
||||||
type?: string | string[];
|
type?: string | string[];
|
||||||
properties?: { [name: string]: OpenAPISchema };
|
properties?: { [name: string]: OpenAPISchema };
|
||||||
|
patternProperties?: { [name: string]: OpenAPISchema };
|
||||||
additionalProperties?: boolean | OpenAPISchema;
|
additionalProperties?: boolean | OpenAPISchema;
|
||||||
unevaluatedProperties?: boolean | OpenAPISchema;
|
unevaluatedProperties?: boolean | OpenAPISchema;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|
|
@ -99,6 +99,7 @@ const schemaKeywordTypes = {
|
||||||
additionalProperties: 'object',
|
additionalProperties: 'object',
|
||||||
unevaluatedProperties: 'object',
|
unevaluatedProperties: 'object',
|
||||||
properties: 'object',
|
properties: 'object',
|
||||||
|
patternProperties: 'object',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function detectType(schema: OpenAPISchema): string {
|
export function detectType(schema: OpenAPISchema): string {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user