Support for required additionalProperties

This commit is contained in:
Vasily Fedoseyev 2019-12-03 19:20:32 +03:00
parent dc5430e53d
commit ceaa2ead53
8 changed files with 69 additions and 11 deletions

View File

@ -398,10 +398,16 @@ paths:
content: content:
application/json: application/json:
schema: schema:
type: object allOf:
additionalProperties: - type: object
type: integer additionalProperties:
format: int32 x-additionalPropertiesName: status
type: integer
format: int32
- required:
- inStock
- soldOut
security: security:
- api_key: [] - api_key: []
/store/order: /store/order:

View File

@ -45,6 +45,16 @@ export const RequiredLabel = styled(FieldLabel.withComponent('div'))`
line-height: 1; line-height: 1;
`; `;
export const RequiredImplicitFieldName = styled(FieldLabel.withComponent('div'))`
line-height: 20px;
white-space: nowrap;
font-size: 1em;
font-family: ${props => props.theme.typography.code.fontFamily};
font-style: normal;
font-weight: normal;
margin-left: 20px;
`;
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;

View File

@ -1,7 +1,11 @@
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,
RequiredImplicitFieldName,
RequiredLabel,
} from '../../common-elements/fields';
import { FieldDetails } from './FieldDetails'; import { FieldDetails } from './FieldDetails';
import { import {
@ -34,7 +38,7 @@ export class Field extends React.Component<FieldProps> {
}; };
render() { render() {
const { className, field, isLast } = this.props; const { className, field, isLast } = this.props;
const { name, expanded, deprecated, required, kind } = field; const { name, expanded, deprecated, required, requiredNames, kind } = field;
const withSubSchema = !field.schema.isPrimitive && !field.schema.isCircular; const withSubSchema = !field.schema.isPrimitive && !field.schema.isCircular;
const paramName = withSubSchema ? ( const paramName = withSubSchema ? (
@ -53,6 +57,13 @@ export class Field extends React.Component<FieldProps> {
<PropertyNameCell className={deprecated ? 'deprecated' : undefined} kind={kind} title={name}> <PropertyNameCell className={deprecated ? 'deprecated' : undefined} kind={kind} title={name}>
<PropertyBullet /> <PropertyBullet />
{name} {name}
{requiredNames && (
requiredNames.map((value, idx) => {
return (
<RequiredImplicitFieldName key={idx}> {value} </RequiredImplicitFieldName>
);
})
)}
{required && <RequiredLabel> required </RequiredLabel>} {required && <RequiredLabel> required </RequiredLabel>}
</PropertyNameCell> </PropertyNameCell>
); );

View File

@ -15,6 +15,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat
"kind": "field", "kind": "field",
"name": "packSize", "name": "packSize",
"required": false, "required": false,
"requiredNames": undefined,
"schema": SchemaModel { "schema": SchemaModel {
"activeOneOf": 0, "activeOneOf": 0,
"constraints": Array [], "constraints": Array [],
@ -65,6 +66,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat
"kind": "field", "kind": "field",
"name": "type", "name": "type",
"required": true, "required": true,
"requiredNames": undefined,
"schema": SchemaModel { "schema": SchemaModel {
"activeOneOf": 0, "activeOneOf": 0,
"constraints": Array [], "constraints": Array [],

View File

@ -35,6 +35,7 @@ export class FieldModel {
schema: SchemaModel; schema: SchemaModel;
name: string; name: string;
required: boolean; required: boolean;
requiredNames?: string[];
description: string; description: string;
example?: string; example?: string;
deprecated: boolean; deprecated: boolean;
@ -57,6 +58,7 @@ export class FieldModel {
this.name = infoOrRef.name || info.name; this.name = infoOrRef.name || info.name;
this.in = info.in; this.in = info.in;
this.required = !!info.required; this.required = !!info.required;
this.requiredNames = info.requiredNames;
let fieldSchema = info.schema; let fieldSchema = info.schema;
let serializationMime = ''; let serializationMime = '';

View File

@ -287,16 +287,22 @@ function buildFields(
sortByRequired(fields, !options.sortPropsAlphabetically ? schema.required : undefined); sortByRequired(fields, !options.sortPropsAlphabetically ? schema.required : undefined);
} }
const explicitFieldNames = fields.map(field => field.name);
const implicitRequiredNames = schema.required === undefined ? [] :
schema.required.filter(fieldName => !explicitFieldNames.includes(fieldName));
if (typeof additionalProps === 'object' || additionalProps === true) { if (typeof additionalProps === 'object' || additionalProps === true) {
const additionalPropertiesName = typeof additionalProps === 'object'
? additionalProps['x-additionalPropertiesName'] || 'property name'
: 'property name';
fields.push( fields.push(
new FieldModel( new FieldModel(
parser, parser,
{ {
name: (typeof additionalProps === 'object' name: additionalPropertiesName.concat('*'),
? additionalProps['x-additionalPropertiesName'] || 'property name' requiredNames: implicitRequiredNames,
: 'property name' required: implicitRequiredNames.length > 0,
).concat('*'),
required: false,
schema: additionalProps === true ? {} : additionalProps, schema: additionalProps === true ? {} : additionalProps,
kind: 'additionalProperties', kind: 'additionalProperties',
}, },
@ -304,6 +310,23 @@ function buildFields(
options, options,
), ),
); );
} else if (implicitRequiredNames.length > 0) {
const firstImplicitName = additionalProps === false ? '(incorrectly required)' :
(implicitRequiredNames.shift() || '');
fields.push(
new FieldModel(
parser,
{
name: firstImplicitName,
requiredNames: implicitRequiredNames,
required: true,
schema: {},
kind: 'field',
},
$ref + '/properties/' + firstImplicitName,
options,
),
);
} }
return fields; return fields;

View File

@ -84,6 +84,7 @@ export interface OpenAPIParameter {
in?: OpenAPIParameterLocation; in?: OpenAPIParameterLocation;
description?: string; description?: string;
required?: boolean; required?: boolean;
requiredNames?: string[];
deprecated?: boolean; deprecated?: boolean;
allowEmptyValue?: boolean; allowEmptyValue?: boolean;
style?: OpenAPIParameterStyle; style?: OpenAPIParameterStyle;

View File

@ -114,6 +114,9 @@ export function isPrimitiveType(schema: OpenAPISchema, type: string | undefined
} }
if (type === 'object') { if (type === 'object') {
if (schema.required !== undefined && schema.required.length !== 0) {
return false;
}
return schema.properties !== undefined return schema.properties !== undefined
? Object.keys(schema.properties).length === 0 ? Object.keys(schema.properties).length === 0
: schema.additionalProperties === undefined; : schema.additionalProperties === undefined;