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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ export interface OpenAPIRequestBody {
}

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

export interface OpenAPIResponse {
Expand Down