Implement requiredPropsFirst opttion

This commit is contained in:
Roman Hotsiy 2017-11-21 13:24:41 +02:00
parent f52a05a2b5
commit a28e24ec92
No known key found for this signature in database
GPG Key ID: 5CB7B3ACABA57CB0
11 changed files with 835 additions and 33 deletions

View File

@ -1,3 +1,4 @@
import { RedocNormalizedOptions } from '../../../services/RedocNormalizedOptions';
import * as React from 'react'; import * as React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import toJson from 'enzyme-to-json'; import toJson from 'enzyme-to-json';
@ -17,6 +18,7 @@ describe('Components', () => {
parser, parser,
{ $ref: '#/components/schemas/Pet' }, { $ref: '#/components/schemas/Pet' },
'#/components/schemas/Pet', '#/components/schemas/Pet',
new RedocNormalizedOptions({}),
); );
const schemaView = shallow(<Schema schema={schema} />); const schemaView = shallow(<Schema schema={schema} />);
expect(toJson(schemaView)).toMatchSnapshot(); expect(toJson(schemaView)).toMatchSnapshot();
@ -29,6 +31,7 @@ describe('Components', () => {
parser, parser,
{ $ref: '#/components/schemas/Pet' }, { $ref: '#/components/schemas/Pet' },
'#/components/schemas/Pet', '#/components/schemas/Pet',
new RedocNormalizedOptions({}),
); );
const schemaView = shallow( const schemaView = shallow(
<ObjectSchema <ObjectSchema

View File

@ -54,6 +54,68 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"isCircular": undefined, "isCircular": undefined,
"isPrimitive": true, "isPrimitive": true,
"nullable": false, "nullable": false,
"options": RedocNormalizedOptions {
"expandResponses": Object {},
"hideHostname": false,
"requiredPropsFirst": false,
"scrollYOffset": [Function],
"theme": Object {
"baseFont": Object {
"family": "Roboto, sans-serif",
"lineHeight": "1.5",
"optimizeSpeed": true,
"size": "14px",
"smoothing": "antialiased",
"weight": "300",
},
"code": Object {
"fontFamily": "\\"Lucida Console\\", Monaco, monospace",
"fontSize": "13px",
},
"colors": Object {
"error": "#e53935",
"http": Object {
"basic": "#999",
"delete": "#e27a7a",
"get": "#6bbd5b",
"link": "#31bbb6",
"options": "#d3ca12",
"patch": "#e09d43",
"post": "#248fb2",
"put": "#9b708b",
},
"info": "skyblue",
"main": "#32329f",
"redirect": "orange",
"success": "#00aa13",
"text": "#263238",
"warning": "#f1c400",
},
"headingsFont": Object {
"family": "Montserrat, sans-serif",
},
"links": Object {
"color": undefined,
"hover": undefined,
"visited": undefined,
},
"logo": Object {
"maxHeight": "120px",
},
"menu": Object {
"backgroundColor": "#fafafa",
"width": "260px",
},
"rightPanel": Object {
"backgroundColor": "#263238",
"width": 40,
},
"schemaView": Object {
"linesColor": "#7f99cf",
},
"spacingUnit": 20,
},
},
"pattern": undefined, "pattern": undefined,
"rawSchema": Object { "rawSchema": Object {
"default": undefined, "default": undefined,
@ -92,6 +154,68 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"isCircular": undefined, "isCircular": undefined,
"isPrimitive": true, "isPrimitive": true,
"nullable": false, "nullable": false,
"options": RedocNormalizedOptions {
"expandResponses": Object {},
"hideHostname": false,
"requiredPropsFirst": false,
"scrollYOffset": [Function],
"theme": Object {
"baseFont": Object {
"family": "Roboto, sans-serif",
"lineHeight": "1.5",
"optimizeSpeed": true,
"size": "14px",
"smoothing": "antialiased",
"weight": "300",
},
"code": Object {
"fontFamily": "\\"Lucida Console\\", Monaco, monospace",
"fontSize": "13px",
},
"colors": Object {
"error": "#e53935",
"http": Object {
"basic": "#999",
"delete": "#e27a7a",
"get": "#6bbd5b",
"link": "#31bbb6",
"options": "#d3ca12",
"patch": "#e09d43",
"post": "#248fb2",
"put": "#9b708b",
},
"info": "skyblue",
"main": "#32329f",
"redirect": "orange",
"success": "#00aa13",
"text": "#263238",
"warning": "#f1c400",
},
"headingsFont": Object {
"family": "Montserrat, sans-serif",
},
"links": Object {
"color": undefined,
"hover": undefined,
"visited": undefined,
},
"logo": Object {
"maxHeight": "120px",
},
"menu": Object {
"backgroundColor": "#fafafa",
"width": "260px",
},
"rightPanel": Object {
"backgroundColor": "#263238",
"width": 40,
},
"schemaView": Object {
"linesColor": "#7f99cf",
},
"spacingUnit": 20,
},
},
"pattern": undefined, "pattern": undefined,
"rawSchema": Object { "rawSchema": Object {
"default": undefined, "default": undefined,
@ -113,6 +237,68 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"isCircular": undefined, "isCircular": undefined,
"isPrimitive": false, "isPrimitive": false,
"nullable": false, "nullable": false,
"options": RedocNormalizedOptions {
"expandResponses": Object {},
"hideHostname": false,
"requiredPropsFirst": false,
"scrollYOffset": [Function],
"theme": Object {
"baseFont": Object {
"family": "Roboto, sans-serif",
"lineHeight": "1.5",
"optimizeSpeed": true,
"size": "14px",
"smoothing": "antialiased",
"weight": "300",
},
"code": Object {
"fontFamily": "\\"Lucida Console\\", Monaco, monospace",
"fontSize": "13px",
},
"colors": Object {
"error": "#e53935",
"http": Object {
"basic": "#999",
"delete": "#e27a7a",
"get": "#6bbd5b",
"link": "#31bbb6",
"options": "#d3ca12",
"patch": "#e09d43",
"post": "#248fb2",
"put": "#9b708b",
},
"info": "skyblue",
"main": "#32329f",
"redirect": "orange",
"success": "#00aa13",
"text": "#263238",
"warning": "#f1c400",
},
"headingsFont": Object {
"family": "Montserrat, sans-serif",
},
"links": Object {
"color": undefined,
"hover": undefined,
"visited": undefined,
},
"logo": Object {
"maxHeight": "120px",
},
"menu": Object {
"backgroundColor": "#fafafa",
"width": "260px",
},
"rightPanel": Object {
"backgroundColor": "#263238",
"width": 40,
},
"schemaView": Object {
"linesColor": "#7f99cf",
},
"spacingUnit": 20,
},
},
"pattern": undefined, "pattern": undefined,
"rawSchema": Object { "rawSchema": Object {
"allOf": Array [ "allOf": Array [
@ -188,6 +374,68 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"isCircular": undefined, "isCircular": undefined,
"isPrimitive": true, "isPrimitive": true,
"nullable": false, "nullable": false,
"options": RedocNormalizedOptions {
"expandResponses": Object {},
"hideHostname": false,
"requiredPropsFirst": false,
"scrollYOffset": [Function],
"theme": Object {
"baseFont": Object {
"family": "Roboto, sans-serif",
"lineHeight": "1.5",
"optimizeSpeed": true,
"size": "14px",
"smoothing": "antialiased",
"weight": "300",
},
"code": Object {
"fontFamily": "\\"Lucida Console\\", Monaco, monospace",
"fontSize": "13px",
},
"colors": Object {
"error": "#e53935",
"http": Object {
"basic": "#999",
"delete": "#e27a7a",
"get": "#6bbd5b",
"link": "#31bbb6",
"options": "#d3ca12",
"patch": "#e09d43",
"post": "#248fb2",
"put": "#9b708b",
},
"info": "skyblue",
"main": "#32329f",
"redirect": "orange",
"success": "#00aa13",
"text": "#263238",
"warning": "#f1c400",
},
"headingsFont": Object {
"family": "Montserrat, sans-serif",
},
"links": Object {
"color": undefined,
"hover": undefined,
"visited": undefined,
},
"logo": Object {
"maxHeight": "120px",
},
"menu": Object {
"backgroundColor": "#fafafa",
"width": "260px",
},
"rightPanel": Object {
"backgroundColor": "#263238",
"width": 40,
},
"schemaView": Object {
"linesColor": "#7f99cf",
},
"spacingUnit": 20,
},
},
"pattern": undefined, "pattern": undefined,
"rawSchema": Object { "rawSchema": Object {
"default": undefined, "default": undefined,
@ -226,6 +474,68 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"isCircular": undefined, "isCircular": undefined,
"isPrimitive": true, "isPrimitive": true,
"nullable": false, "nullable": false,
"options": RedocNormalizedOptions {
"expandResponses": Object {},
"hideHostname": false,
"requiredPropsFirst": false,
"scrollYOffset": [Function],
"theme": Object {
"baseFont": Object {
"family": "Roboto, sans-serif",
"lineHeight": "1.5",
"optimizeSpeed": true,
"size": "14px",
"smoothing": "antialiased",
"weight": "300",
},
"code": Object {
"fontFamily": "\\"Lucida Console\\", Monaco, monospace",
"fontSize": "13px",
},
"colors": Object {
"error": "#e53935",
"http": Object {
"basic": "#999",
"delete": "#e27a7a",
"get": "#6bbd5b",
"link": "#31bbb6",
"options": "#d3ca12",
"patch": "#e09d43",
"post": "#248fb2",
"put": "#9b708b",
},
"info": "skyblue",
"main": "#32329f",
"redirect": "orange",
"success": "#00aa13",
"text": "#263238",
"warning": "#f1c400",
},
"headingsFont": Object {
"family": "Montserrat, sans-serif",
},
"links": Object {
"color": undefined,
"hover": undefined,
"visited": undefined,
},
"logo": Object {
"maxHeight": "120px",
},
"menu": Object {
"backgroundColor": "#fafafa",
"width": "260px",
},
"rightPanel": Object {
"backgroundColor": "#263238",
"width": 40,
},
"schemaView": Object {
"linesColor": "#7f99cf",
},
"spacingUnit": 20,
},
},
"pattern": undefined, "pattern": undefined,
"rawSchema": Object { "rawSchema": Object {
"default": undefined, "default": undefined,
@ -247,6 +557,68 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"isCircular": undefined, "isCircular": undefined,
"isPrimitive": true, "isPrimitive": true,
"nullable": false, "nullable": false,
"options": RedocNormalizedOptions {
"expandResponses": Object {},
"hideHostname": false,
"requiredPropsFirst": false,
"scrollYOffset": [Function],
"theme": Object {
"baseFont": Object {
"family": "Roboto, sans-serif",
"lineHeight": "1.5",
"optimizeSpeed": true,
"size": "14px",
"smoothing": "antialiased",
"weight": "300",
},
"code": Object {
"fontFamily": "\\"Lucida Console\\", Monaco, monospace",
"fontSize": "13px",
},
"colors": Object {
"error": "#e53935",
"http": Object {
"basic": "#999",
"delete": "#e27a7a",
"get": "#6bbd5b",
"link": "#31bbb6",
"options": "#d3ca12",
"patch": "#e09d43",
"post": "#248fb2",
"put": "#9b708b",
},
"info": "skyblue",
"main": "#32329f",
"redirect": "orange",
"success": "#00aa13",
"text": "#263238",
"warning": "#f1c400",
},
"headingsFont": Object {
"family": "Montserrat, sans-serif",
},
"links": Object {
"color": undefined,
"hover": undefined,
"visited": undefined,
},
"logo": Object {
"maxHeight": "120px",
},
"menu": Object {
"backgroundColor": "#fafafa",
"width": "260px",
},
"rightPanel": Object {
"backgroundColor": "#263238",
"width": 40,
},
"schemaView": Object {
"linesColor": "#7f99cf",
},
"spacingUnit": 20,
},
},
"pattern": undefined, "pattern": undefined,
"rawSchema": Object { "rawSchema": Object {
"allOf": Array [ "allOf": Array [
@ -292,6 +664,68 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"writeOnly": false, "writeOnly": false,
}, },
], ],
"options": RedocNormalizedOptions {
"expandResponses": Object {},
"hideHostname": false,
"requiredPropsFirst": false,
"scrollYOffset": [Function],
"theme": Object {
"baseFont": Object {
"family": "Roboto, sans-serif",
"lineHeight": "1.5",
"optimizeSpeed": true,
"size": "14px",
"smoothing": "antialiased",
"weight": "300",
},
"code": Object {
"fontFamily": "\\"Lucida Console\\", Monaco, monospace",
"fontSize": "13px",
},
"colors": Object {
"error": "#e53935",
"http": Object {
"basic": "#999",
"delete": "#e27a7a",
"get": "#6bbd5b",
"link": "#31bbb6",
"options": "#d3ca12",
"patch": "#e09d43",
"post": "#248fb2",
"put": "#9b708b",
},
"info": "skyblue",
"main": "#32329f",
"redirect": "orange",
"success": "#00aa13",
"text": "#263238",
"warning": "#f1c400",
},
"headingsFont": Object {
"family": "Montserrat, sans-serif",
},
"links": Object {
"color": undefined,
"hover": undefined,
"visited": undefined,
},
"logo": Object {
"maxHeight": "120px",
},
"menu": Object {
"backgroundColor": "#fafafa",
"width": "260px",
},
"rightPanel": Object {
"backgroundColor": "#263238",
"width": 40,
},
"schemaView": Object {
"linesColor": "#7f99cf",
},
"spacingUnit": 20,
},
},
"pattern": undefined, "pattern": undefined,
"rawSchema": Object { "rawSchema": Object {
"discriminator": Object { "discriminator": Object {
@ -363,6 +797,68 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"isCircular": undefined, "isCircular": undefined,
"isPrimitive": true, "isPrimitive": true,
"nullable": false, "nullable": false,
"options": RedocNormalizedOptions {
"expandResponses": Object {},
"hideHostname": false,
"requiredPropsFirst": false,
"scrollYOffset": [Function],
"theme": Object {
"baseFont": Object {
"family": "Roboto, sans-serif",
"lineHeight": "1.5",
"optimizeSpeed": true,
"size": "14px",
"smoothing": "antialiased",
"weight": "300",
},
"code": Object {
"fontFamily": "\\"Lucida Console\\", Monaco, monospace",
"fontSize": "13px",
},
"colors": Object {
"error": "#e53935",
"http": Object {
"basic": "#999",
"delete": "#e27a7a",
"get": "#6bbd5b",
"link": "#31bbb6",
"options": "#d3ca12",
"patch": "#e09d43",
"post": "#248fb2",
"put": "#9b708b",
},
"info": "skyblue",
"main": "#32329f",
"redirect": "orange",
"success": "#00aa13",
"text": "#263238",
"warning": "#f1c400",
},
"headingsFont": Object {
"family": "Montserrat, sans-serif",
},
"links": Object {
"color": undefined,
"hover": undefined,
"visited": undefined,
},
"logo": Object {
"maxHeight": "120px",
},
"menu": Object {
"backgroundColor": "#fafafa",
"width": "260px",
},
"rightPanel": Object {
"backgroundColor": "#263238",
"width": 40,
},
"schemaView": Object {
"linesColor": "#7f99cf",
},
"spacingUnit": 20,
},
},
"pattern": undefined, "pattern": undefined,
"rawSchema": Object { "rawSchema": Object {
"default": undefined, "default": undefined,
@ -401,6 +897,68 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"isCircular": undefined, "isCircular": undefined,
"isPrimitive": true, "isPrimitive": true,
"nullable": false, "nullable": false,
"options": RedocNormalizedOptions {
"expandResponses": Object {},
"hideHostname": false,
"requiredPropsFirst": false,
"scrollYOffset": [Function],
"theme": Object {
"baseFont": Object {
"family": "Roboto, sans-serif",
"lineHeight": "1.5",
"optimizeSpeed": true,
"size": "14px",
"smoothing": "antialiased",
"weight": "300",
},
"code": Object {
"fontFamily": "\\"Lucida Console\\", Monaco, monospace",
"fontSize": "13px",
},
"colors": Object {
"error": "#e53935",
"http": Object {
"basic": "#999",
"delete": "#e27a7a",
"get": "#6bbd5b",
"link": "#31bbb6",
"options": "#d3ca12",
"patch": "#e09d43",
"post": "#248fb2",
"put": "#9b708b",
},
"info": "skyblue",
"main": "#32329f",
"redirect": "orange",
"success": "#00aa13",
"text": "#263238",
"warning": "#f1c400",
},
"headingsFont": Object {
"family": "Montserrat, sans-serif",
},
"links": Object {
"color": undefined,
"hover": undefined,
"visited": undefined,
},
"logo": Object {
"maxHeight": "120px",
},
"menu": Object {
"backgroundColor": "#fafafa",
"width": "260px",
},
"rightPanel": Object {
"backgroundColor": "#263238",
"width": 40,
},
"schemaView": Object {
"linesColor": "#7f99cf",
},
"spacingUnit": 20,
},
},
"pattern": undefined, "pattern": undefined,
"rawSchema": Object { "rawSchema": Object {
"default": undefined, "default": undefined,
@ -422,6 +980,68 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"isCircular": undefined, "isCircular": undefined,
"isPrimitive": false, "isPrimitive": false,
"nullable": false, "nullable": false,
"options": RedocNormalizedOptions {
"expandResponses": Object {},
"hideHostname": false,
"requiredPropsFirst": false,
"scrollYOffset": [Function],
"theme": Object {
"baseFont": Object {
"family": "Roboto, sans-serif",
"lineHeight": "1.5",
"optimizeSpeed": true,
"size": "14px",
"smoothing": "antialiased",
"weight": "300",
},
"code": Object {
"fontFamily": "\\"Lucida Console\\", Monaco, monospace",
"fontSize": "13px",
},
"colors": Object {
"error": "#e53935",
"http": Object {
"basic": "#999",
"delete": "#e27a7a",
"get": "#6bbd5b",
"link": "#31bbb6",
"options": "#d3ca12",
"patch": "#e09d43",
"post": "#248fb2",
"put": "#9b708b",
},
"info": "skyblue",
"main": "#32329f",
"redirect": "orange",
"success": "#00aa13",
"text": "#263238",
"warning": "#f1c400",
},
"headingsFont": Object {
"family": "Montserrat, sans-serif",
},
"links": Object {
"color": undefined,
"hover": undefined,
"visited": undefined,
},
"logo": Object {
"maxHeight": "120px",
},
"menu": Object {
"backgroundColor": "#fafafa",
"width": "260px",
},
"rightPanel": Object {
"backgroundColor": "#263238",
"width": 40,
},
"schemaView": Object {
"linesColor": "#7f99cf",
},
"spacingUnit": 20,
},
},
"pattern": undefined, "pattern": undefined,
"rawSchema": Object { "rawSchema": Object {
"allOf": Array [ "allOf": Array [
@ -495,6 +1115,68 @@ exports[`Components SchemaView discriminator should correctly render discriminat
"isCircular": undefined, "isCircular": undefined,
"isPrimitive": true, "isPrimitive": true,
"nullable": false, "nullable": false,
"options": RedocNormalizedOptions {
"expandResponses": Object {},
"hideHostname": false,
"requiredPropsFirst": false,
"scrollYOffset": [Function],
"theme": Object {
"baseFont": Object {
"family": "Roboto, sans-serif",
"lineHeight": "1.5",
"optimizeSpeed": true,
"size": "14px",
"smoothing": "antialiased",
"weight": "300",
},
"code": Object {
"fontFamily": "\\"Lucida Console\\", Monaco, monospace",
"fontSize": "13px",
},
"colors": Object {
"error": "#e53935",
"http": Object {
"basic": "#999",
"delete": "#e27a7a",
"get": "#6bbd5b",
"link": "#31bbb6",
"options": "#d3ca12",
"patch": "#e09d43",
"post": "#248fb2",
"put": "#9b708b",
},
"info": "skyblue",
"main": "#32329f",
"redirect": "orange",
"success": "#00aa13",
"text": "#263238",
"warning": "#f1c400",
},
"headingsFont": Object {
"family": "Montserrat, sans-serif",
},
"links": Object {
"color": undefined,
"hover": undefined,
"visited": undefined,
},
"logo": Object {
"maxHeight": "120px",
},
"menu": Object {
"backgroundColor": "#fafafa",
"width": "260px",
},
"rightPanel": Object {
"backgroundColor": "#263238",
"width": 40,
},
"schemaView": Object {
"linesColor": "#7f99cf",
},
"spacingUnit": 20,
},
},
"pattern": undefined, "pattern": undefined,
"rawSchema": Object { "rawSchema": Object {
"default": undefined, "default": undefined,
@ -540,6 +1222,68 @@ exports[`Components SchemaView discriminator should correctly render discriminat
"isCircular": undefined, "isCircular": undefined,
"isPrimitive": true, "isPrimitive": true,
"nullable": false, "nullable": false,
"options": RedocNormalizedOptions {
"expandResponses": Object {},
"hideHostname": false,
"requiredPropsFirst": false,
"scrollYOffset": [Function],
"theme": Object {
"baseFont": Object {
"family": "Roboto, sans-serif",
"lineHeight": "1.5",
"optimizeSpeed": true,
"size": "14px",
"smoothing": "antialiased",
"weight": "300",
},
"code": Object {
"fontFamily": "\\"Lucida Console\\", Monaco, monospace",
"fontSize": "13px",
},
"colors": Object {
"error": "#e53935",
"http": Object {
"basic": "#999",
"delete": "#e27a7a",
"get": "#6bbd5b",
"link": "#31bbb6",
"options": "#d3ca12",
"patch": "#e09d43",
"post": "#248fb2",
"put": "#9b708b",
},
"info": "skyblue",
"main": "#32329f",
"redirect": "orange",
"success": "#00aa13",
"text": "#263238",
"warning": "#f1c400",
},
"headingsFont": Object {
"family": "Montserrat, sans-serif",
},
"links": Object {
"color": undefined,
"hover": undefined,
"visited": undefined,
},
"logo": Object {
"maxHeight": "120px",
},
"menu": Object {
"backgroundColor": "#fafafa",
"width": "260px",
},
"rightPanel": Object {
"backgroundColor": "#263238",
"width": 40,
},
"schemaView": Object {
"linesColor": "#7f99cf",
},
"spacingUnit": 20,
},
},
"pattern": undefined, "pattern": undefined,
"rawSchema": Object { "rawSchema": Object {
"default": undefined, "default": undefined,

View File

@ -7,6 +7,13 @@ export interface RedocRawOptions {
scrollYOffset?: number | string | Function; scrollYOffset?: number | string | Function;
hideHostname?: boolean | string; hideHostname?: boolean | string;
expandResponses?: string | 'all'; expandResponses?: string | 'all';
requiredPropsFirst?: boolean | string;
}
function argValueToBoolean(val?: string | boolean): boolean {
if (val === undefined) return false;
if (typeof val === 'string') return true;
return val;
} }
export class RedocNormalizedOptions { export class RedocNormalizedOptions {
@ -14,12 +21,14 @@ export class RedocNormalizedOptions {
scrollYOffset: () => number; scrollYOffset: () => number;
hideHostname: boolean; hideHostname: boolean;
expandResponses: { [code: string]: boolean } | 'all'; expandResponses: { [code: string]: boolean } | 'all';
requiredPropsFirst: boolean;
constructor(raw: RedocRawOptions) { constructor(raw: RedocRawOptions) {
this.theme = { ...(raw.theme || {}), ...defaultTheme }; // todo: merge deep this.theme = { ...(raw.theme || {}), ...defaultTheme }; // todo: merge deep
this.scrollYOffset = RedocNormalizedOptions.normalizeScrollYOffset(raw.scrollYOffset); this.scrollYOffset = RedocNormalizedOptions.normalizeScrollYOffset(raw.scrollYOffset);
this.hideHostname = RedocNormalizedOptions.normalizeHideHostname(raw.hideHostname); this.hideHostname = RedocNormalizedOptions.normalizeHideHostname(raw.hideHostname);
this.expandResponses = RedocNormalizedOptions.normalizeExpandResponses(raw.expandResponses); this.expandResponses = RedocNormalizedOptions.normalizeExpandResponses(raw.expandResponses);
this.requiredPropsFirst = argValueToBoolean(raw.requiredPropsFirst);
} }
static normalizeExpandResponses(value: RedocRawOptions['expandResponses']) { static normalizeExpandResponses(value: RedocRawOptions['expandResponses']) {
@ -32,12 +41,12 @@ export class RedocNormalizedOptions {
res[code.trim()] = true; res[code.trim()] = true;
}); });
return res; return res;
} else { } else if (value !== undefined) {
console.warn( console.warn(
`expandResponses must be a string but received value "${value}" of type ${typeof value}`, `expandResponses must be a string but received value "${value}" of type ${typeof value}`,
); );
return {};
} }
return {};
} }
static normalizeHideHostname(value: RedocRawOptions['hideHostname']): boolean { static normalizeHideHostname(value: RedocRawOptions['hideHostname']): boolean {

View File

@ -1,6 +1,8 @@
import { ResponseModel } from '../../models/Response'; import { ResponseModel } from '../../models/Response';
import { OpenAPIParser } from '../../OpenAPIParser'; import { OpenAPIParser } from '../../OpenAPIParser';
import { RedocNormalizedOptions } from '../../RedocNormalizedOptions';
const opts = new RedocNormalizedOptions({});
describe('Models', () => { describe('Models', () => {
describe('ResponseModel', () => { describe('ResponseModel', () => {
let parser; let parser;
@ -10,23 +12,23 @@ describe('Models', () => {
}); });
test('should calculate response type based on code', () => { test('should calculate response type based on code', () => {
let resp = new ResponseModel(parser, '200', false, {}); let resp = new ResponseModel(parser, '200', false, {}, opts);
expect(resp.type).toEqual('success'); expect(resp.type).toEqual('success');
resp = new ResponseModel(parser, '120', false, {}); resp = new ResponseModel(parser, '120', false, {}, opts);
expect(resp.type).toEqual('info'); expect(resp.type).toEqual('info');
resp = new ResponseModel(parser, '301', false, {}); resp = new ResponseModel(parser, '301', false, {}, opts);
expect(resp.type).toEqual('redirect'); expect(resp.type).toEqual('redirect');
resp = new ResponseModel(parser, '400', false, {}); resp = new ResponseModel(parser, '400', false, {}, opts);
expect(resp.type).toEqual('error'); expect(resp.type).toEqual('error');
}); });
test('default should be sucessful by default', () => { test('default should be sucessful by default', () => {
let resp = new ResponseModel(parser, 'default', false, {}); let resp = new ResponseModel(parser, 'default', false, {}, opts);
expect(resp.type).toEqual('success'); expect(resp.type).toEqual('success');
}); });
test('default should be error if defaultAsError is true', () => { test('default should be error if defaultAsError is true', () => {
let resp = new ResponseModel(parser, 'default', true, {}); let resp = new ResponseModel(parser, 'default', true, {}, opts);
expect(resp.type).toEqual('error'); expect(resp.type).toEqual('error');
}); });
}); });

View File

@ -1,6 +1,7 @@
import { observable, action } from 'mobx'; import { observable, action } from 'mobx';
import { OpenAPIParameter, Referenced } from '../../types'; import { OpenAPIParameter, Referenced } from '../../types';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
import { SchemaModel } from './Schema'; import { SchemaModel } from './Schema';
import { OpenAPIParser } from '../OpenAPIParser'; import { OpenAPIParser } from '../OpenAPIParser';
@ -19,13 +20,18 @@ export class FieldModel {
public deprecated: boolean; public deprecated: boolean;
public in?: string; public in?: string;
constructor(parser: OpenAPIParser, infoOrRef: Referenced<OpenAPIParameter>, pointer: string) { constructor(
parser: OpenAPIParser,
infoOrRef: Referenced<OpenAPIParameter>,
pointer: string,
options: RedocNormalizedOptions,
) {
const info = parser.deref(infoOrRef); const info = parser.deref(infoOrRef);
this.name = info.name; this.name = info.name;
this.in = info.in; this.in = info.in;
this.required = !!info.required; this.required = !!info.required;
this.schema = new SchemaModel(parser, info.schema, pointer + '/schema'); this.schema = new SchemaModel(parser, info.schema || {}, pointer + '/schema', options);
this.description = this.description =
info.description === undefined ? this.schema.description || '' : info.description; info.description === undefined ? this.schema.description || '' : info.description;
const example = info.example || this.schema.example; const example = info.example || this.schema.example;

View File

@ -3,6 +3,7 @@ import { observable, action, computed } from 'mobx';
import { OpenAPIMediaType } from '../../types'; import { OpenAPIMediaType } from '../../types';
import { MediaTypeModel } from './MediaType'; import { MediaTypeModel } from './MediaType';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
import { OpenAPIParser } from '../OpenAPIParser'; import { OpenAPIParser } from '../OpenAPIParser';
/** /**
@ -20,13 +21,14 @@ export class MediaContentModel {
constructor( constructor(
public parser: OpenAPIParser, public parser: OpenAPIParser,
info: { [mime: string]: OpenAPIMediaType }, info: { [mime: string]: OpenAPIMediaType },
public isRequestType: boolean = false, public isRequestType: boolean,
options: RedocNormalizedOptions,
) { ) {
this.mediaTypes = Object.keys(info).map(name => { this.mediaTypes = Object.keys(info).map(name => {
const mime = info[name]; const mime = info[name];
// reset deref cache just in case something is left there // reset deref cache just in case something is left there
parser.resetVisited(); parser.resetVisited();
return new MediaTypeModel(parser, name, isRequestType, mime); return new MediaTypeModel(parser, name, isRequestType, mime, options);
}); });
} }

View File

@ -2,6 +2,7 @@ import * as Sampler from 'openapi-sampler';
import { OpenAPIExample, OpenAPIMediaType } from '../../types'; import { OpenAPIExample, OpenAPIMediaType } from '../../types';
import { SchemaModel } from './Schema'; import { SchemaModel } from './Schema';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
import { mapValues, isJsonLike } from '../../utils'; import { mapValues, isJsonLike } from '../../utils';
import { OpenAPIParser } from '../OpenAPIParser'; import { OpenAPIParser } from '../OpenAPIParser';
@ -16,10 +17,16 @@ export class MediaTypeModel {
/** /**
* @param isRequestType needed to know if skipe RO/RW fields in objects * @param isRequestType needed to know if skipe RO/RW fields in objects
*/ */
constructor(parser: OpenAPIParser, name: string, isRequestType: boolean, info: OpenAPIMediaType) { constructor(
parser: OpenAPIParser,
name: string,
isRequestType: boolean,
info: OpenAPIMediaType,
options: RedocNormalizedOptions,
) {
this.name = name; this.name = name;
this.isRequestType = isRequestType; this.isRequestType = isRequestType;
this.schema = info.schema && new SchemaModel(parser, info.schema, ''); this.schema = info.schema && new SchemaModel(parser, info.schema, '', options);
if (info.examples !== undefined) { if (info.examples !== undefined) {
this.examples = mapValues(info.examples, example => new ExampleModel(parser, example)); this.examples = mapValues(info.examples, example => new ExampleModel(parser, example));
} else if (info.example !== undefined) { } else if (info.example !== undefined) {

View File

@ -71,12 +71,12 @@ export class OperationModel implements IMenuItem {
this.deprecated = !!operationSpec.deprecated; this.deprecated = !!operationSpec.deprecated;
this.operationId = operationSpec.operationId; this.operationId = operationSpec.operationId;
this.requestBody = this.requestBody =
operationSpec.requestBody && new RequestBodyModel(parser, operationSpec.requestBody); operationSpec.requestBody && new RequestBodyModel(parser, operationSpec.requestBody, options);
this.codeSamples = operationSpec['x-code-samples'] || []; this.codeSamples = operationSpec['x-code-samples'] || [];
this.path = JsonPointer.baseName(this._$ref, 2); this.path = JsonPointer.baseName(this._$ref, 2);
this.parameters = (operationSpec.parameters || []).map( this.parameters = (operationSpec.parameters || []).map(
paramOrRef => new FieldModel(parser, paramOrRef, this._$ref), paramOrRef => new FieldModel(parser, paramOrRef, this._$ref, options),
); );
let hasSuccessResponses = false; let hasSuccessResponses = false;

View File

@ -2,19 +2,24 @@ import { OpenAPIRequestBody, Referenced } from '../../types';
import { MediaContentModel } from './MediaContent'; import { MediaContentModel } from './MediaContent';
import { OpenAPIParser } from '../OpenAPIParser'; import { OpenAPIParser } from '../OpenAPIParser';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
export class RequestBodyModel { export class RequestBodyModel {
description: string; description: string;
required: boolean; required: boolean;
content?: MediaContentModel; content?: MediaContentModel;
constructor(parser: OpenAPIParser, infoOrRef: Referenced<OpenAPIRequestBody>) { constructor(
parser: OpenAPIParser,
infoOrRef: Referenced<OpenAPIRequestBody>,
options: RedocNormalizedOptions,
) {
const info = parser.deref(infoOrRef); const info = parser.deref(infoOrRef);
this.description = info.description || ''; this.description = info.description || '';
this.required = !!info.required; this.required = !!info.required;
parser.exitRef(infoOrRef); parser.exitRef(infoOrRef);
if (info.content !== undefined) { if (info.content !== undefined) {
this.content = new MediaContentModel(parser, info.content, true); this.content = new MediaContentModel(parser, info.content, true, options);
} }
} }
} }

View File

@ -30,7 +30,7 @@ export class ResponseModel {
parser.exitRef(infoOrRef); parser.exitRef(infoOrRef);
this.code = code; this.code = code;
if (info.content !== undefined) { if (info.content !== undefined) {
this.content = new MediaContentModel(parser, info.content, false); this.content = new MediaContentModel(parser, info.content, false, options);
} }
this.description = info.description || ''; this.description = info.description || '';
this.type = getStatusCodeType(code, defaultAsError); this.type = getStatusCodeType(code, defaultAsError);
@ -39,7 +39,7 @@ export class ResponseModel {
if (headers !== undefined) { if (headers !== undefined) {
this.headers = Object.keys(headers).map(name => { this.headers = Object.keys(headers).map(name => {
const header = headers[name]; const header = headers[name];
return new FieldModel(parser, { ...header, name }, ''); return new FieldModel(parser, { ...header, name }, '', options);
}); });
} }
} }

View File

@ -1,10 +1,11 @@
import { MergedOpenAPISchema } from '../';
import { observable, action } from 'mobx'; import { observable, action } from 'mobx';
import { OpenAPISchema, Referenced } from '../../types'; import { OpenAPISchema, Referenced } from '../../types';
import { FieldModel } from './Field'; import { FieldModel } from './Field';
import { OpenAPIParser } from '../OpenAPIParser'; import { OpenAPIParser } from '../OpenAPIParser';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
import { import {
detectType, detectType,
humanizeConstraints, humanizeConstraints,
@ -12,6 +13,7 @@ import {
isPrimitiveType, isPrimitiveType,
JsonPointer, JsonPointer,
} from '../../utils/'; } from '../../utils/';
import { MergedOpenAPISchema } from '../';
// TODO: refactor this model, maybe use getters instead of copying all the values // TODO: refactor this model, maybe use getters instead of copying all the values
export class SchemaModel { export class SchemaModel {
@ -55,14 +57,11 @@ export class SchemaModel {
*/ */
constructor( constructor(
parser: OpenAPIParser, parser: OpenAPIParser,
schemaOrRef?: Referenced<OpenAPISchema>, schemaOrRef: Referenced<OpenAPISchema>,
$ref?: string, $ref: string,
private options: RedocNormalizedOptions,
isChild: boolean = false, isChild: boolean = false,
) { ) {
if (schemaOrRef === undefined) {
return;
}
this._$ref = schemaOrRef.$ref || $ref || ''; this._$ref = schemaOrRef.$ref || $ref || '';
this.rawSchema = parser.deref(schemaOrRef); this.rawSchema = parser.deref(schemaOrRef);
this.schema = parser.mergeAllOf(this.rawSchema, this._$ref, isChild); this.schema = parser.mergeAllOf(this.rawSchema, this._$ref, isChild);
@ -133,9 +132,9 @@ export class SchemaModel {
} }
if (this.type === 'object') { if (this.type === 'object') {
this.fields = buildFields(parser, schema, this._$ref); this.fields = buildFields(parser, schema, this._$ref, this.options);
} else if (this.type === 'array' && schema.items) { } else if (this.type === 'array' && schema.items) {
this.items = new SchemaModel(parser, schema.items, this._$ref + '/items'); this.items = new SchemaModel(parser, schema.items, this._$ref + '/items', this.options);
this.displayType = this.items.displayType; this.displayType = this.items.displayType;
this.typePrefix = this.items.typePrefix + 'Array of '; this.typePrefix = this.items.typePrefix + 'Array of ';
this.isPrimitive = this.items.isPrimitive; this.isPrimitive = this.items.isPrimitive;
@ -149,7 +148,7 @@ export class SchemaModel {
this.oneOf = oneOf!.map( this.oneOf = oneOf!.map(
(variant, idx) => (variant, idx) =>
// TODO: merge base schema into each oneOf // TODO: merge base schema into each oneOf
new SchemaModel(parser, variant, this._$ref + '/oneOf/' + idx), new SchemaModel(parser, variant, this._$ref + '/oneOf/' + idx, this.options),
); );
this.displayType = this.oneOf.map(schema => schema.displayType).join(' or '); this.displayType = this.oneOf.map(schema => schema.displayType).join(' or ');
} }
@ -178,15 +177,20 @@ export class SchemaModel {
const refs = Object.keys(derived); const refs = Object.keys(derived);
this.oneOf = refs.map(ref => { this.oneOf = refs.map(ref => {
const schema = new SchemaModel(parser, parser.byRef(ref)!, ref, true); const schema = new SchemaModel(parser, parser.byRef(ref)!, ref, this.options, true);
schema.title = derived[ref]; schema.title = derived[ref];
return schema; return schema;
}); });
} }
} }
function buildFields(parser: OpenAPIParser, schema: OpenAPISchema, $ref: string): FieldModel[] { function buildFields(
const props = schema.properties || []; parser: OpenAPIParser,
schema: OpenAPISchema,
$ref: string,
options: RedocNormalizedOptions,
): FieldModel[] {
const props = schema.properties || {};
const additionalProps = schema.additionalProperties; const additionalProps = schema.additionalProperties;
const defaults = schema.default || {}; const defaults = schema.default || {};
const fields = Object.keys(props || []).map(fieldName => { const fields = Object.keys(props || []).map(fieldName => {
@ -204,9 +208,14 @@ function buildFields(parser: OpenAPIParser, schema: OpenAPISchema, $ref: string)
}, },
}, },
$ref + '/properties/' + fieldName, $ref + '/properties/' + fieldName,
options,
); );
}); });
if (options.requiredPropsFirst) {
sortFields(fields, schema.required);
}
if (typeof additionalProps === 'object') { if (typeof additionalProps === 'object') {
fields.push( fields.push(
new FieldModel( new FieldModel(
@ -217,9 +226,24 @@ function buildFields(parser: OpenAPIParser, schema: OpenAPISchema, $ref: string)
schema: additionalProps, schema: additionalProps,
}, },
$ref + '/additionalProperties', $ref + '/additionalProperties',
options,
), ),
); );
} }
return fields; return fields;
} }
function sortFields(fields: FieldModel[], order: string[] = []) {
fields.sort((a, b) => {
if (!a.required && b.required) {
return 1;
} else if (a.required && !b.required) {
return -1;
} else if (a.required && b.required) {
return order.indexOf(a.name) > order.indexOf(b.name) ? 1 : -1;
} else {
return 0;
}
});
}