diff --git a/demo/openapi.yaml b/demo/openapi.yaml index 5cf19340eb..c18063e79c 100644 --- a/demo/openapi.yaml +++ b/demo/openapi.yaml @@ -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: @@ -254,7 +254,7 @@ paths: required: false schema: type: string - example: "Bearer " + example: 'Bearer ' - name: petId in: path description: Pet id to delete @@ -401,6 +401,7 @@ paths: application/json: schema: type: object + minProperties: 2 additionalProperties: type: integer format: int32 @@ -429,7 +430,7 @@ paths: application/json: example: status: 400 - message: "Invalid Order" + message: 'Invalid Order' requestBody: content: application/json: @@ -877,11 +878,11 @@ paths: type: string examples: response: - value: OK + value: OK text/plain: examples: response: - value: OK + value: OK '400': description: Invalid username/password supplied /user/logout: @@ -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' @@ -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 diff --git a/src/components/Parameters/Parameters.tsx b/src/components/Parameters/Parameters.tsx index ed9141bc72..6ff7e76930 100644 --- a/src/components/Parameters/Parameters.tsx +++ b/src/components/Parameters/Parameters.tsx @@ -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]) { @@ -79,6 +80,7 @@ export function BodyContent(props: { return ( <> {description !== undefined && } + { render() { @@ -21,7 +22,12 @@ export class ResponseDetails extends React.PureComponent<{ response: ResponseMod {({ schema }) => { - return ; + return ( + <> + + + + ); }} diff --git a/src/components/Schema/OneOfSchema.tsx b/src/components/Schema/OneOfSchema.tsx index b0864c3331..311a3258a8 100644 --- a/src/components/Schema/OneOfSchema.tsx +++ b/src/components/Schema/OneOfSchema.tsx @@ -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 { @@ -47,6 +48,8 @@ export class OneOfSchema extends React.Component { if (oneOf === undefined) { return null; } + const activeSchema = oneOf[schema.activeOneOf]; + return (
{schema.oneOfType} @@ -58,7 +61,8 @@ export class OneOfSchema extends React.Component {
{oneOf[schema.activeOneOf].deprecated && Deprecated}
- + +
); } diff --git a/src/components/__tests__/OneOfSchema.test.tsx b/src/components/__tests__/OneOfSchema.test.tsx index e9425db7f4..28b1b26591 100644 --- a/src/components/__tests__/OneOfSchema.test.tsx +++ b/src/components/__tests__/OneOfSchema.test.tsx @@ -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()); + expect(component.html().includes('= 1 properties')).toBe(true); + }); }); }); diff --git a/src/components/__tests__/Schema.test.tsx b/src/components/__tests__/Schema.test.tsx new file mode 100644 index 0000000000..919e416fa4 --- /dev/null +++ b/src/components/__tests__/Schema.test.tsx @@ -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()); + 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()); + expect(component.html().includes('[ 2 .. 10 ] properties')).toBe(true); + }); + }); + }); +}); diff --git a/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap b/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap index a2c8c1896c..0c3f8d4f6d 100644 --- a/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap +++ b/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap @@ -966,6 +966,7 @@ try { "format": "int32", "type": "integer", }, + "minProperties": 2, "type": "object", }, }, diff --git a/src/utils/__tests__/openapi.test.ts b/src/utils/__tests__/openapi.test.ts index 653a911801..8e7aa3d882 100644 --- a/src/utils/__tests__/openapi.test.ts +++ b/src/utils/__tests__/openapi.test.ts @@ -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', () => { diff --git a/src/utils/openapi.ts b/src/utils/openapi.ts index accf169f1c..5c9986c5f7 100644 --- a/src/utils/openapi.ts +++ b/src/utils/openapi.ts @@ -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}`; } @@ -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);