Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: show min properties max properties #2015

Merged
merged 3 commits into from
May 20, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions demo/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ paths:
parameters:
- name: Accept-Language
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
required: false
schema:
Expand Down Expand Up @@ -254,7 +254,7 @@ paths:
required: false
schema:
type: string
example: "Bearer <TOKEN>"
example: 'Bearer <TOKEN>'
- name: petId
in: path
description: Pet id to delete
Expand Down Expand Up @@ -401,6 +401,7 @@ paths:
application/json:
schema:
type: object
minProperties: 2
additionalProperties:
type: integer
format: int32
Expand Down Expand Up @@ -429,7 +430,7 @@ paths:
application/json:
example:
status: 400
message: "Invalid Order"
message: 'Invalid Order'
requestBody:
content:
application/json:
Expand Down Expand Up @@ -877,11 +878,11 @@ paths:
type: string
examples:
response:
value: <Message> OK </Message>
value: <Message> OK </Message>
text/plain:
examples:
response:
value: OK
value: OK
'400':
description: Invalid username/password supplied
/user/logout:
Expand Down Expand Up @@ -1027,8 +1028,8 @@ components:
properties:
id:
externalDocs:
description: "Find more info here"
url: "https://example.com"
description: 'Find more info here'
url: 'https://example.com'
description: Pet ID
allOf:
- $ref: '#/components/schemas/Id'
Expand Down Expand Up @@ -1201,7 +1202,7 @@ x-webhooks:
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
$ref: '#/components/schemas/Pet'
responses:
"200":
'200':
description: Return a 200 status to indicate that the data was received successfully
2 changes: 2 additions & 0 deletions src/components/Parameters/Parameters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch';
import { Schema } from '../Schema';

import { Markdown } from '../Markdown/Markdown';
import { ConstraintsView } from '../Fields/FieldContstraints';

function safePush(obj, prop, item) {
if (!obj[prop]) {
Expand Down Expand Up @@ -79,6 +80,7 @@ export function BodyContent(props: {
return (
<>
{description !== undefined && <Markdown source={description} />}
<ConstraintsView constraints={schema?.constraints || []} />
<Schema
skipReadOnly={isRequestType}
skipWriteOnly={!isRequestType}
Expand Down
8 changes: 7 additions & 1 deletion src/components/Responses/ResponseDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Schema } from '../Schema';
import { Extensions } from '../Fields/Extensions';
import { Markdown } from '../Markdown/Markdown';
import { ResponseHeaders } from './ResponseHeaders';
import { ConstraintsView } from '../Fields/FieldContstraints';

export class ResponseDetails extends React.PureComponent<{ response: ResponseModel }> {
render() {
Expand All @@ -21,7 +22,12 @@ export class ResponseDetails extends React.PureComponent<{ response: ResponseMod
<ResponseHeaders headers={headers} />
<MediaTypesSwitch content={content} renderDropdown={this.renderDropdown}>
{({ schema }) => {
return <Schema skipWriteOnly={true} key="schema" schema={schema} />;
return (
<>
<ConstraintsView constraints={schema?.constraints || []} />
<Schema skipWriteOnly={true} key="schema" schema={schema} />
</>
);
}}
</MediaTypesSwitch>
</>
Expand Down
6 changes: 5 additions & 1 deletion src/components/Schema/OneOfSchema.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from '../../common-elements/schema';
import { Badge } from '../../common-elements/shelfs';
import { SchemaModel } from '../../services/models';
import { ConstraintsView } from '../Fields/FieldContstraints';
import { Schema, SchemaProps } from './Schema';

export interface OneOfButtonProps {
Expand Down Expand Up @@ -47,6 +48,8 @@ export class OneOfSchema extends React.Component<SchemaProps> {
if (oneOf === undefined) {
return null;
}
const activeSchema = oneOf[schema.activeOneOf];

return (
<div>
<OneOfLabel> {schema.oneOfType} </OneOfLabel>
Expand All @@ -58,7 +61,8 @@ export class OneOfSchema extends React.Component<SchemaProps> {
<div>
{oneOf[schema.activeOneOf].deprecated && <Badge type="warning">Deprecated</Badge>}
</div>
<Schema {...this.props} schema={oneOf[schema.activeOneOf]} />
<ConstraintsView constraints={activeSchema.constraints} />
<Schema {...this.props} schema={activeSchema} />
</div>
);
}
Expand Down
27 changes: 26 additions & 1 deletion src/components/__tests__/OneOfSchema.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { withTheme } from '../testProviders';

const options = new RedocNormalizedOptions({});
describe('Components', () => {
describe('SchemaView', () => {
describe('SchemaView OneOf', () => {
const parser = new OpenAPIParser(
{ openapi: '3.0', info: { title: 'test', version: '0' }, paths: {} },
undefined,
Expand Down Expand Up @@ -53,5 +53,30 @@ describe('Components', () => {
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 changes: 67 additions & 0 deletions src/components/__tests__/Schema.test.tsx
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Components SchemaView OneOf deprecated should match snapshot 1`] = `
exports[`Components SchemaView OneOf OneOf deprecated should match snapshot 1`] = `
<div>
<span
class="sc-kfYoZR juYXUf"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -966,6 +966,7 @@ try {
"format": "int32",
"type": "integer",
},
"minProperties": 2,
"type": "object",
},
},
Expand Down
2 changes: 1 addition & 1 deletion src/utils/__tests__/openapi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ describe('Utils', () => {
});

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', () => {
Expand Down
11 changes: 10 additions & 1 deletion src/utils/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ function humanizeRangeConstraint(
let stringRange;
if (min !== undefined && max !== undefined) {
if (min === max) {
stringRange = `${min} ${description}`;
stringRange = `= ${min} ${description}`;
} else {
stringRange = `[ ${min} .. ${max} ] ${description}`;
}
Expand Down Expand Up @@ -476,6 +476,15 @@ export function humanizeConstraints(schema: OpenAPISchema): string[] {
res.push(arrayRange);
}

const propertiesRange = humanizeRangeConstraint(
'properties',
schema.minProperties,
schema.maxProperties,
);
if (propertiesRange !== undefined) {
res.push(propertiesRange);
}

const multipleOfConstraint = humanizeMultipleOfConstraint(schema.multipleOf);
if (multipleOfConstraint !== undefined) {
res.push(multipleOfConstraint);
Expand Down