diff --git a/demo/openapi.yaml b/demo/openapi.yaml index d10fdf73..5c8814c5 100644 --- a/demo/openapi.yaml +++ b/demo/openapi.yaml @@ -398,10 +398,16 @@ paths: content: application/json: schema: - type: object - additionalProperties: - type: integer - format: int32 + allOf: + - type: object + additionalProperties: + x-additionalPropertiesName: status + type: integer + format: int32 + - required: + - inStock + - soldOut + security: - api_key: [] /store/order: diff --git a/src/common-elements/fields.ts b/src/common-elements/fields.ts index ef1c07e8..1bfa5d92 100644 --- a/src/common-elements/fields.ts +++ b/src/common-elements/fields.ts @@ -45,6 +45,16 @@ export const RequiredLabel = styled(FieldLabel.withComponent('div'))` 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)` color: ${({ theme }) => theme.colors.warning.main}; font-size: 13px; diff --git a/src/components/Fields/Field.tsx b/src/components/Fields/Field.tsx index ef969b2d..31c00fe4 100644 --- a/src/components/Fields/Field.tsx +++ b/src/components/Fields/Field.tsx @@ -1,7 +1,11 @@ import { observer } from 'mobx-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 { @@ -34,7 +38,7 @@ export class Field extends React.Component { }; render() { 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 paramName = withSubSchema ? ( @@ -53,6 +57,13 @@ export class Field extends React.Component { {name} + {requiredNames && ( + requiredNames.map((value, idx) => { + return ( + {value} + ); + }) + )} {required && required } ); diff --git a/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap b/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap index 745c4cad..9ae0f7f9 100644 --- a/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap @@ -15,6 +15,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat "kind": "field", "name": "packSize", "required": false, + "requiredNames": undefined, "schema": SchemaModel { "activeOneOf": 0, "constraints": Array [], @@ -65,6 +66,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat "kind": "field", "name": "type", "required": true, + "requiredNames": undefined, "schema": SchemaModel { "activeOneOf": 0, "constraints": Array [], diff --git a/src/services/models/Field.ts b/src/services/models/Field.ts index 5302a09f..3195ff8c 100644 --- a/src/services/models/Field.ts +++ b/src/services/models/Field.ts @@ -35,6 +35,7 @@ export class FieldModel { schema: SchemaModel; name: string; required: boolean; + requiredNames?: string[]; description: string; example?: string; deprecated: boolean; @@ -57,6 +58,7 @@ export class FieldModel { this.name = infoOrRef.name || info.name; this.in = info.in; this.required = !!info.required; + this.requiredNames = info.requiredNames; let fieldSchema = info.schema; let serializationMime = ''; diff --git a/src/services/models/Schema.ts b/src/services/models/Schema.ts index 6b228711..e8833664 100644 --- a/src/services/models/Schema.ts +++ b/src/services/models/Schema.ts @@ -287,16 +287,22 @@ function buildFields( 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) { + const additionalPropertiesName = typeof additionalProps === 'object' + ? additionalProps['x-additionalPropertiesName'] || 'property name' + : 'property name'; + fields.push( new FieldModel( parser, { - name: (typeof additionalProps === 'object' - ? additionalProps['x-additionalPropertiesName'] || 'property name' - : 'property name' - ).concat('*'), - required: false, + name: additionalPropertiesName.concat('*'), + requiredNames: implicitRequiredNames, + required: implicitRequiredNames.length > 0, schema: additionalProps === true ? {} : additionalProps, kind: 'additionalProperties', }, @@ -304,6 +310,23 @@ function buildFields( 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; diff --git a/src/types/open-api.d.ts b/src/types/open-api.d.ts index 4fb04a83..8df65f33 100644 --- a/src/types/open-api.d.ts +++ b/src/types/open-api.d.ts @@ -84,6 +84,7 @@ export interface OpenAPIParameter { in?: OpenAPIParameterLocation; description?: string; required?: boolean; + requiredNames?: string[]; deprecated?: boolean; allowEmptyValue?: boolean; style?: OpenAPIParameterStyle; diff --git a/src/utils/openapi.ts b/src/utils/openapi.ts index 2836cfed..d2eaf487 100644 --- a/src/utils/openapi.ts +++ b/src/utils/openapi.ts @@ -114,6 +114,9 @@ export function isPrimitiveType(schema: OpenAPISchema, type: string | undefined } if (type === 'object') { + if (schema.required !== undefined && schema.required.length !== 0) { + return false; + } return schema.properties !== undefined ? Object.keys(schema.properties).length === 0 : schema.additionalProperties === undefined;