mirror of
https://github.com/Redocly/redoc.git
synced 2025-07-10 16:22:27 +03:00
feat: show minProperties maxProperties (#2015)
This commit is contained in:
parent
0aafee1131
commit
82712c5b40
|
@ -88,7 +88,7 @@ paths:
|
||||||
parameters:
|
parameters:
|
||||||
- name: Accept-Language
|
- name: Accept-Language
|
||||||
in: header
|
in: header
|
||||||
description: "The language you prefer for messages. Supported values are en-AU, en-CA, en-GB, en-US"
|
description: 'The language you prefer for messages. Supported values are en-AU, en-CA, en-GB, en-US'
|
||||||
example: en-US
|
example: en-US
|
||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
|
@ -254,7 +254,7 @@ paths:
|
||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
example: "Bearer <TOKEN>"
|
example: 'Bearer <TOKEN>'
|
||||||
- name: petId
|
- name: petId
|
||||||
in: path
|
in: path
|
||||||
description: Pet id to delete
|
description: Pet id to delete
|
||||||
|
@ -401,6 +401,7 @@ paths:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
|
minProperties: 2
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
type: integer
|
type: integer
|
||||||
format: int32
|
format: int32
|
||||||
|
@ -429,7 +430,7 @@ paths:
|
||||||
application/json:
|
application/json:
|
||||||
example:
|
example:
|
||||||
status: 400
|
status: 400
|
||||||
message: "Invalid Order"
|
message: 'Invalid Order'
|
||||||
requestBody:
|
requestBody:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
|
@ -877,11 +878,11 @@ paths:
|
||||||
type: string
|
type: string
|
||||||
examples:
|
examples:
|
||||||
response:
|
response:
|
||||||
value: <Message> OK </Message>
|
value: <Message> OK </Message>
|
||||||
text/plain:
|
text/plain:
|
||||||
examples:
|
examples:
|
||||||
response:
|
response:
|
||||||
value: OK
|
value: OK
|
||||||
'400':
|
'400':
|
||||||
description: Invalid username/password supplied
|
description: Invalid username/password supplied
|
||||||
/user/logout:
|
/user/logout:
|
||||||
|
@ -1027,8 +1028,8 @@ components:
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
externalDocs:
|
externalDocs:
|
||||||
description: "Find more info here"
|
description: 'Find more info here'
|
||||||
url: "https://example.com"
|
url: 'https://example.com'
|
||||||
description: Pet ID
|
description: Pet ID
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/components/schemas/Id'
|
- $ref: '#/components/schemas/Id'
|
||||||
|
@ -1201,7 +1202,7 @@ x-webhooks:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/Pet"
|
$ref: '#/components/schemas/Pet'
|
||||||
responses:
|
responses:
|
||||||
"200":
|
'200':
|
||||||
description: Return a 200 status to indicate that the data was received successfully
|
description: Return a 200 status to indicate that the data was received successfully
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch';
|
||||||
import { Schema } from '../Schema';
|
import { Schema } from '../Schema';
|
||||||
|
|
||||||
import { Markdown } from '../Markdown/Markdown';
|
import { Markdown } from '../Markdown/Markdown';
|
||||||
|
import { ConstraintsView } from '../Fields/FieldContstraints';
|
||||||
|
|
||||||
function safePush(obj, prop, item) {
|
function safePush(obj, prop, item) {
|
||||||
if (!obj[prop]) {
|
if (!obj[prop]) {
|
||||||
|
@ -79,6 +80,7 @@ export function BodyContent(props: {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{description !== undefined && <Markdown source={description} />}
|
{description !== undefined && <Markdown source={description} />}
|
||||||
|
<ConstraintsView constraints={schema?.constraints || []} />
|
||||||
<Schema
|
<Schema
|
||||||
skipReadOnly={isRequestType}
|
skipReadOnly={isRequestType}
|
||||||
skipWriteOnly={!isRequestType}
|
skipWriteOnly={!isRequestType}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { Schema } from '../Schema';
|
||||||
import { Extensions } from '../Fields/Extensions';
|
import { Extensions } from '../Fields/Extensions';
|
||||||
import { Markdown } from '../Markdown/Markdown';
|
import { Markdown } from '../Markdown/Markdown';
|
||||||
import { ResponseHeaders } from './ResponseHeaders';
|
import { ResponseHeaders } from './ResponseHeaders';
|
||||||
|
import { ConstraintsView } from '../Fields/FieldContstraints';
|
||||||
|
|
||||||
export class ResponseDetails extends React.PureComponent<{ response: ResponseModel }> {
|
export class ResponseDetails extends React.PureComponent<{ response: ResponseModel }> {
|
||||||
render() {
|
render() {
|
||||||
|
@ -21,7 +22,12 @@ export class ResponseDetails extends React.PureComponent<{ response: ResponseMod
|
||||||
<ResponseHeaders headers={headers} />
|
<ResponseHeaders headers={headers} />
|
||||||
<MediaTypesSwitch content={content} renderDropdown={this.renderDropdown}>
|
<MediaTypesSwitch content={content} renderDropdown={this.renderDropdown}>
|
||||||
{({ schema }) => {
|
{({ schema }) => {
|
||||||
return <Schema skipWriteOnly={true} key="schema" schema={schema} />;
|
return (
|
||||||
|
<>
|
||||||
|
<ConstraintsView constraints={schema?.constraints || []} />
|
||||||
|
<Schema skipWriteOnly={true} key="schema" schema={schema} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
</MediaTypesSwitch>
|
</MediaTypesSwitch>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
} from '../../common-elements/schema';
|
} from '../../common-elements/schema';
|
||||||
import { Badge } from '../../common-elements/shelfs';
|
import { Badge } from '../../common-elements/shelfs';
|
||||||
import { SchemaModel } from '../../services/models';
|
import { SchemaModel } from '../../services/models';
|
||||||
|
import { ConstraintsView } from '../Fields/FieldContstraints';
|
||||||
import { Schema, SchemaProps } from './Schema';
|
import { Schema, SchemaProps } from './Schema';
|
||||||
|
|
||||||
export interface OneOfButtonProps {
|
export interface OneOfButtonProps {
|
||||||
|
@ -47,6 +48,8 @@ export class OneOfSchema extends React.Component<SchemaProps> {
|
||||||
if (oneOf === undefined) {
|
if (oneOf === undefined) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const activeSchema = oneOf[schema.activeOneOf];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<OneOfLabel> {schema.oneOfType} </OneOfLabel>
|
<OneOfLabel> {schema.oneOfType} </OneOfLabel>
|
||||||
|
@ -58,7 +61,8 @@ export class OneOfSchema extends React.Component<SchemaProps> {
|
||||||
<div>
|
<div>
|
||||||
{oneOf[schema.activeOneOf].deprecated && <Badge type="warning">Deprecated</Badge>}
|
{oneOf[schema.activeOneOf].deprecated && <Badge type="warning">Deprecated</Badge>}
|
||||||
</div>
|
</div>
|
||||||
<Schema {...this.props} schema={oneOf[schema.activeOneOf]} />
|
<ConstraintsView constraints={activeSchema.constraints} />
|
||||||
|
<Schema {...this.props} schema={activeSchema} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,5 +53,30 @@ describe('Components', () => {
|
||||||
expect(component.render()).toMatchSnapshot();
|
expect(component.render()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Show minProperties/maxProperties constraints oneOf', () => {
|
||||||
|
const schema = new SchemaModel(
|
||||||
|
parser,
|
||||||
|
{
|
||||||
|
oneOf: [
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
description: 'Test description',
|
||||||
|
minProperties: 1,
|
||||||
|
maxProperties: 1,
|
||||||
|
additionalProperties: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The name and value o',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'',
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
const component = shallow(withTheme(<Schema schema={schema} />));
|
||||||
|
expect(component.html().includes('= 1 properties')).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
67
src/components/__tests__/Schema.test.tsx
Normal file
67
src/components/__tests__/Schema.test.tsx
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
/* tslint:disable:no-implicit-dependencies */
|
||||||
|
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { Schema } from '../';
|
||||||
|
import { OpenAPIParser, SchemaModel } from '../../services';
|
||||||
|
import { RedocNormalizedOptions } from '../../services/RedocNormalizedOptions';
|
||||||
|
import { withTheme } from '../testProviders';
|
||||||
|
|
||||||
|
const options = new RedocNormalizedOptions({});
|
||||||
|
describe('Components', () => {
|
||||||
|
describe('SchemaView', () => {
|
||||||
|
const parser = new OpenAPIParser(
|
||||||
|
{ openapi: '3.0', info: { title: 'test', version: '0' }, paths: {} },
|
||||||
|
undefined,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('Show minProperties/maxProperties constraints', () => {
|
||||||
|
const schema = new SchemaModel(
|
||||||
|
parser,
|
||||||
|
{
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'object',
|
||||||
|
minProperties: 1,
|
||||||
|
properties: {
|
||||||
|
address: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'',
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
const component = shallow(withTheme(<Schema schema={schema} />));
|
||||||
|
expect(component.html().includes('non-empty')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Show range minProperties/maxProperties constraints', () => {
|
||||||
|
const schema = new SchemaModel(
|
||||||
|
parser,
|
||||||
|
{
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'object',
|
||||||
|
minProperties: 2,
|
||||||
|
maxProperties: 10,
|
||||||
|
additionalProperties: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'',
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
it('should includes [ 2 .. 10 ] properties', () => {
|
||||||
|
const component = shallow(withTheme(<Schema schema={schema} />));
|
||||||
|
expect(component.html().includes('[ 2 .. 10 ] properties')).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -966,6 +966,7 @@ try {
|
||||||
"format": "int32",
|
"format": "int32",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
},
|
},
|
||||||
|
"minProperties": 2,
|
||||||
"type": "object",
|
"type": "object",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -553,7 +553,7 @@ describe('Utils', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have a humanized constraint when minItems and maxItems are the same', () => {
|
it('should have a humanized constraint when minItems and maxItems are the same', () => {
|
||||||
expect(humanizeConstraints(itemConstraintSchema(7, 7))).toContain('7 items');
|
expect(humanizeConstraints(itemConstraintSchema(7, 7))).toContain('= 7 items');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have a humanized constraint when justMinItems is set, and it is equal to 1', () => {
|
it('should have a humanized constraint when justMinItems is set, and it is equal to 1', () => {
|
||||||
|
|
|
@ -423,7 +423,7 @@ function humanizeRangeConstraint(
|
||||||
let stringRange;
|
let stringRange;
|
||||||
if (min !== undefined && max !== undefined) {
|
if (min !== undefined && max !== undefined) {
|
||||||
if (min === max) {
|
if (min === max) {
|
||||||
stringRange = `${min} ${description}`;
|
stringRange = `= ${min} ${description}`;
|
||||||
} else {
|
} else {
|
||||||
stringRange = `[ ${min} .. ${max} ] ${description}`;
|
stringRange = `[ ${min} .. ${max} ] ${description}`;
|
||||||
}
|
}
|
||||||
|
@ -476,6 +476,15 @@ export function humanizeConstraints(schema: OpenAPISchema): string[] {
|
||||||
res.push(arrayRange);
|
res.push(arrayRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const propertiesRange = humanizeRangeConstraint(
|
||||||
|
'properties',
|
||||||
|
schema.minProperties,
|
||||||
|
schema.maxProperties,
|
||||||
|
);
|
||||||
|
if (propertiesRange !== undefined) {
|
||||||
|
res.push(propertiesRange);
|
||||||
|
}
|
||||||
|
|
||||||
const multipleOfConstraint = humanizeMultipleOfConstraint(schema.multipleOf);
|
const multipleOfConstraint = humanizeMultipleOfConstraint(schema.multipleOf);
|
||||||
if (multipleOfConstraint !== undefined) {
|
if (multipleOfConstraint !== undefined) {
|
||||||
res.push(multipleOfConstraint);
|
res.push(multipleOfConstraint);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user