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

Add expandSchemas to expand all properties for all Schemas #1435

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
8 changes: 8 additions & 0 deletions e2e/expandSchemas.html
@@ -0,0 +1,8 @@
<html>

<body>
<redoc spec-url="../demo/openapi.yaml" expand-schemas="true"></redoc>
<script src="../bundles/redoc.standalone.js"></script>
</body>

</html>
17 changes: 17 additions & 0 deletions e2e/integration/expandSchemas.e2e.ts
@@ -0,0 +1,17 @@
describe('Schemas', () => {
it('expandSchemas != true', () => {
cy.visit('e2e/standalone.html');

cy.get('.api-content')
.find('.expanded')
.should('have.length', 0);
});

it('expandSchemas == true', () => {
cy.visit('e2e/expandSchemas.html');

cy.get('.api-content')
.find('.expanded')
.should('have.length', 146);
});
});
5 changes: 3 additions & 2 deletions src/components/Responses/Response.tsx
Expand Up @@ -12,11 +12,12 @@ export class ResponseView extends React.Component<{ response: ResponseModel }> {
};

render() {
const { headers, type, summary, description, code, expanded, content } = this.props.response;
const { extensions, headers, type, summary, description, code, expanded, content } = this.props.response;
const mimes =
content === undefined ? [] : content.mediaTypes.filter(mime => mime.schema !== undefined);

const empty = headers.length === 0 && mimes.length === 0 && !description;
const empty = (!extensions || Object.keys(extensions).length === 0) &&
headers.length === 0 && mimes.length === 0 && !description;

return (
<div>
Expand Down
4 changes: 3 additions & 1 deletion src/components/Responses/ResponseDetails.tsx
Expand Up @@ -7,15 +7,17 @@ import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel';
import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch';
import { Schema } from '../Schema';

import { Extensions } from '../Fields/Extensions';
import { Markdown } from '../Markdown/Markdown';
import { ResponseHeaders } from './ResponseHeaders';

export class ResponseDetails extends React.PureComponent<{ response: ResponseModel }> {
render() {
const { description, headers, content } = this.props.response;
const { description, extensions, headers, content } = this.props.response;
return (
<>
{description && <Markdown source={description} />}
<Extensions extensions={extensions} />
<ResponseHeaders headers={headers} />
<MediaTypesSwitch content={content} renderDropdown={this.renderDropdown}>
{({ schema }) => {
Expand Down
7 changes: 7 additions & 0 deletions src/services/RedocNormalizedOptions.ts
Expand Up @@ -41,6 +41,7 @@ export interface RedocRawOptions {
expandDefaultServerVariables?: boolean;
maxDisplayedEnumValues?: number;
ignoreNamedSchemas?: string[] | string;
expandSchemas?: boolean;
hideSchemaPattern?: boolean;
}

Expand Down Expand Up @@ -87,6 +88,10 @@ export class RedocNormalizedOptions {
return !!value;
}

static normalizeExpandSchemas(value: RedocRawOptions['expandSchemas']): boolean {
return !!value;
}

static normalizeScrollYOffset(value: RedocRawOptions['scrollYOffset']): () => number {
// just number is not valid selector and leads to crash so checking if isNumeric here
if (typeof value === 'string' && !isNumeric(value)) {
Expand Down Expand Up @@ -195,6 +200,7 @@ export class RedocNormalizedOptions {
maxDisplayedEnumValues?: number;

ignoreNamedSchemas: Set<string>;
expandSchemas: boolean;
hideSchemaPattern: boolean;

constructor(raw: RedocRawOptions, defaults: RedocRawOptions = {}) {
Expand Down Expand Up @@ -256,6 +262,7 @@ export class RedocNormalizedOptions {
? raw.ignoreNamedSchemas
: raw.ignoreNamedSchemas?.split(',').map((s) => s.trim());
this.ignoreNamedSchemas = new Set(ignoreNamedSchemas);
this.expandSchemas = RedocNormalizedOptions.normalizeExpandSchemas(raw.expandSchemas);
this.hideSchemaPattern = argValueToBoolean(raw.hideSchemaPattern);
}
}
20 changes: 20 additions & 0 deletions src/services/__tests__/fixtures/expandSchemas.json
@@ -0,0 +1,20 @@
{
"openapi": "3.0.0",
"info": {
"version": "1.0",
"title": "Foo"
},
"components": {
"schemas": {
"Foo": {
"type": "object",
"properties": {
"foo": {
"type": "string"
}
},
"additionalProperties": true
}
}
}
}
7 changes: 7 additions & 0 deletions src/services/__tests__/models/Response.test.ts
Expand Up @@ -31,5 +31,12 @@ describe('Models', () => {
const resp = new ResponseModel(parser, 'default', true, {}, opts);
expect(resp.type).toEqual('error');
});

test('ensure extensions are shown if showExtensions is true', () => {
const options = new RedocNormalizedOptions({ showExtensions: true });
const resp = new ResponseModel(parser, 'default', true, { 'x-example': {a: 1} } as any, options);
expect(Object.keys(resp.extensions).length).toEqual(1);
expect(resp.extensions['x-example']).toEqual({a: 1});
});
});
});
20 changes: 20 additions & 0 deletions src/services/__tests__/models/Schema.test.ts
Expand Up @@ -40,5 +40,25 @@ describe('Models', () => {
expect(schema.oneOf).toHaveLength(2);
expect(schema.displayType).toBe('(Array of strings or numbers) or string');
});

test('expandSchemas != true', () => {
const spec = require('../fixtures/expandSchemas.json');
parser = new OpenAPIParser(spec, undefined, opts);
const schema = new SchemaModel(parser, spec.components.schemas.Foo, '', opts);
expect(schema.fields).toHaveLength(2);
expect(schema.fields![0].expanded).toEqual(false);
expect(schema.fields![1].expanded).toEqual(false);
});

test('expandSchemas == true', () => {
const opts = new RedocNormalizedOptions({ expandSchemas: true});

const spec = require('../fixtures/expandSchemas.json');
parser = new OpenAPIParser(spec, undefined, opts);
const schema = new SchemaModel(parser, spec.components.schemas.Foo, '', opts);
expect(schema.fields).toHaveLength(2);
expect(schema.fields![0].expanded).toEqual(true);
expect(schema.fields![1].expanded).toEqual(true);
});
});
});
11 changes: 10 additions & 1 deletion src/services/models/Response.ts
Expand Up @@ -2,7 +2,10 @@ import { action, observable, makeObservable } from 'mobx';

import { OpenAPIResponse, Referenced } from '../../types';

import { getStatusCodeType } from '../../utils';
import {
extractExtensions,
getStatusCodeType,
} from '../../utils';
import { OpenAPIParser } from '../OpenAPIParser';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
import { FieldModel } from './Field';
Expand All @@ -19,6 +22,8 @@ export class ResponseModel {
type: string;
headers: FieldModel[] = [];

extensions: Record<string, any>;

constructor(
parser: OpenAPIParser,
code: string,
Expand Down Expand Up @@ -54,6 +59,10 @@ export class ResponseModel {
return new FieldModel(parser, { ...header, name }, '', options);
});
}

if (options.showExtensions) {
this.extensions = extractExtensions(info, options.showExtensions);
}
}

@action
Expand Down
34 changes: 18 additions & 16 deletions src/services/models/Schema.ts
Expand Up @@ -348,7 +348,7 @@ function buildFields(
const required =
schema.required === undefined ? false : schema.required.indexOf(fieldName) > -1;

return new FieldModel(
const fieldModel = new FieldModel(
parser,
{
name: fieldName,
Expand All @@ -361,6 +361,8 @@ function buildFields(
$ref + '/properties/' + fieldName,
options,
);
fieldModel.expanded = options.expandSchemas;
return fieldModel;
});

if (options.sortPropsAlphabetically) {
Expand All @@ -372,22 +374,22 @@ function buildFields(
}

if (typeof additionalProps === 'object' || additionalProps === true) {
fields.push(
new FieldModel(
parser,
{
name: (typeof additionalProps === 'object'
? additionalProps['x-additionalPropertiesName'] || 'property name'
: 'property name'
).concat('*'),
required: false,
schema: additionalProps === true ? {} : additionalProps,
kind: 'additionalProperties',
},
$ref + '/additionalProperties',
options,
),
const fieldModel = new FieldModel(
parser,
{
name: (typeof additionalProps === 'object'
? additionalProps['x-additionalPropertiesName'] || 'property name'
: 'property name'
).concat('*'),
required: false,
schema: additionalProps === true ? {} : additionalProps,
kind: 'additionalProperties',
},
$ref + '/additionalProperties',
options,
);
fieldModel.expanded = options.expandSchemas;
fields.push(fieldModel);
}

return fields;
Expand Down
2 changes: 1 addition & 1 deletion src/types/open-api.d.ts
Expand Up @@ -182,7 +182,7 @@ export interface OpenAPIRequestBody {
}

export interface OpenAPIResponses {
[code: string]: OpenAPIResponse;
[code: string]: Referenced<OpenAPIResponse>;
}

export interface OpenAPIResponse {
Expand Down