From e92f00edb2918c2cb423c4a04758a700eda8c39c Mon Sep 17 00:00:00 2001 From: Roman Hotsiy Date: Tue, 25 Sep 2018 18:50:08 +0300 Subject: [PATCH] feat: new option showExtensions (support list of extensions) --- src/components/Fields/Extensions.tsx | 36 ++++++++++++++------------ src/components/Fields/FieldDetails.tsx | 2 +- src/components/Operation/Operation.tsx | 2 ++ src/services/RedocNormalizedOptions.ts | 21 ++++++++++++--- src/services/models/Field.ts | 6 +++++ src/services/models/Operation.ts | 6 +++++ src/services/models/Schema.ts | 6 +++++ src/utils/openapi.ts | 14 ++++++++++ 8 files changed, 72 insertions(+), 21 deletions(-) diff --git a/src/components/Fields/Extensions.tsx b/src/components/Fields/Extensions.tsx index e730d983..d39e40c6 100644 --- a/src/components/Fields/Extensions.tsx +++ b/src/components/Fields/Extensions.tsx @@ -1,35 +1,37 @@ import * as React from 'react'; +import styled from '../../styled-components'; -import { isRedocExtension } from '../../utils/openapi'; import { OptionsContext } from '../OptionsProvider'; -import { SchemaModel } from '../../services/models'; -import { FieldDetail } from './FieldDetail'; +import { StyledMarkdownBlock } from '../Markdown/styled.elements'; + +const Extension = styled(StyledMarkdownBlock)` + opacity: 0.9; + margin: 2px 0; +`; + +const ExtensionLable = styled.span` + font-style: italic; +`; export interface ExtensionsProps { - schema: SchemaModel; + extensions: { + [k: string]: any; + }; } export class Extensions extends React.PureComponent { - constructor(props) { - super(props); - this.getExtensions = this.getExtensions.bind(this); - } - - getExtensions() { - const { schema } = this.props; - const fullSchema = schema.schema; - return Object.keys(fullSchema).filter(key => key.startsWith('x-') && !isRedocExtension(key)); - } - render() { return ( {options => ( <> {options.showExtensions && - this.getExtensions().map(key => ( - + Object.keys(this.props.extensions).map(key => ( + + {key}:{' '} + {JSON.stringify(this.props.extensions[key])} + ))} )} diff --git a/src/components/Fields/FieldDetails.tsx b/src/components/Fields/FieldDetails.tsx index 60ac95ab..32371688 100644 --- a/src/components/Fields/FieldDetails.tsx +++ b/src/components/Fields/FieldDetails.tsx @@ -52,7 +52,7 @@ export class FieldDetails extends React.PureComponent { {!renderDiscriminatorSwitch && }{' '} {showExamples && } - {} + {}
diff --git a/src/components/Operation/Operation.tsx b/src/components/Operation/Operation.tsx index 320a6621..1283a6ad 100644 --- a/src/components/Operation/Operation.tsx +++ b/src/components/Operation/Operation.tsx @@ -18,6 +18,7 @@ import { ResponseSamples } from '../ResponseSamples/ResponseSamples'; import { OperationModel as OperationType } from '../../services/models'; import styled from '../../styled-components'; +import { Extensions } from '../Fields/Extensions'; const OperationRow = styled(Row)` backface-visibility: hidden; @@ -58,6 +59,7 @@ export class Operation extends React.Component { {externalDocs && } )} + diff --git a/src/services/RedocNormalizedOptions.ts b/src/services/RedocNormalizedOptions.ts index 69f6072c..c0368a3b 100644 --- a/src/services/RedocNormalizedOptions.ts +++ b/src/services/RedocNormalizedOptions.ts @@ -17,7 +17,7 @@ export interface RedocRawOptions { hideLoading?: boolean | string; hideDownloadButton?: boolean | string; disableSearch?: boolean | string; - showExtensions?: boolean | string; + showExtensions?: boolean | string | string[]; unstable_ignoreMimeParameters?: boolean; @@ -89,6 +89,21 @@ export class RedocNormalizedOptions { return () => 0; } + static normalizeShowExtensions(value: RedocRawOptions['showExtensions']): string[] | boolean { + if (typeof value === 'undefined') { + return false; + } + if (value === '') { + return true; + } + + if (typeof value === 'string') { + return value.split(',').map(ext => ext.trim()); + } + + return value; + } + theme: ResolvedThemeInterface; scrollYOffset: () => number; hideHostname: boolean; @@ -100,7 +115,7 @@ export class RedocNormalizedOptions { untrustedSpec: boolean; hideDownloadButton: boolean; disableSearch: boolean; - showExtensions: boolean; + showExtensions: boolean | string[]; /* tslint:disable-next-line */ unstable_ignoreMimeParameters: boolean; @@ -126,7 +141,7 @@ export class RedocNormalizedOptions { this.untrustedSpec = argValueToBoolean(raw.untrustedSpec); this.hideDownloadButton = argValueToBoolean(raw.hideDownloadButton); this.disableSearch = argValueToBoolean(raw.disableSearch); - this.showExtensions = argValueToBoolean(raw.showExtensions); + this.showExtensions = RedocNormalizedOptions.normalizeShowExtensions(raw.showExtensions); this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters); diff --git a/src/services/models/Field.ts b/src/services/models/Field.ts index 192c9ee2..f54a3cf8 100644 --- a/src/services/models/Field.ts +++ b/src/services/models/Field.ts @@ -3,6 +3,7 @@ import { action, observable } from 'mobx'; import { OpenAPIParameter, Referenced } from '../../types'; import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; +import { extractExtensions } from '../../utils/openapi'; import { OpenAPIParser } from '../OpenAPIParser'; import { SchemaModel } from './Schema'; @@ -21,6 +22,7 @@ export class FieldModel { deprecated: boolean; in?: string; kind: string; + extensions?: Dict; constructor( parser: OpenAPIParser, @@ -40,6 +42,10 @@ export class FieldModel { this.deprecated = info.deprecated === undefined ? !!this.schema.deprecated : info.deprecated; parser.exitRef(infoOrRef); + + if (options.showExtensions) { + this.extensions = extractExtensions(info, options.showExtensions); + } } @action diff --git a/src/services/models/Operation.ts b/src/services/models/Operation.ts index a63fa776..c8d75123 100644 --- a/src/services/models/Operation.ts +++ b/src/services/models/Operation.ts @@ -7,6 +7,7 @@ import { SecurityRequirementModel } from './SecurityRequirement'; import { OpenAPIExternalDocumentation, OpenAPIServer, OpenAPIXCodeSample } from '../../types'; import { + extractExtensions, getOperationSummary, getStatusCodeType, isStatusCode, @@ -56,6 +57,7 @@ export class OperationModel implements IMenuItem { servers: OpenAPIServer[]; security: SecurityRequirementModel[]; codeSamples: OpenAPIXCodeSample[]; + extensions: Dict; constructor( private parser: OpenAPIParser, @@ -91,6 +93,10 @@ export class OperationModel implements IMenuItem { this.security = (operationSpec.security || parser.spec.security || []).map( security => new SecurityRequirementModel(security, parser), ); + + if (options.showExtensions) { + this.extensions = extractExtensions(operationSpec, options.showExtensions); + } } /** diff --git a/src/services/models/Schema.ts b/src/services/models/Schema.ts index 69f0e0b0..2b4edb57 100644 --- a/src/services/models/Schema.ts +++ b/src/services/models/Schema.ts @@ -14,6 +14,7 @@ import { isPrimitiveType, JsonPointer, sortByRequired, + extractExtensions, } from '../../utils/'; // TODO: refactor this model, maybe use getters instead of copying all the values @@ -54,6 +55,7 @@ export class SchemaModel { rawSchema: OpenAPISchema; schema: MergedOpenAPISchema; + extensions?: Dict; /** * @param isChild if schema discriminator Child @@ -77,6 +79,10 @@ export class SchemaModel { // exit all the refs visited during allOf traverse parser.exitRef({ $ref: parent$ref }); } + + if (options.showExtensions) { + this.extensions = extractExtensions(this.schema, options.showExtensions); + } } /** diff --git a/src/utils/openapi.ts b/src/utils/openapi.ts index 190d3b2e..16c74a76 100644 --- a/src/utils/openapi.ts +++ b/src/utils/openapi.ts @@ -303,3 +303,17 @@ export function isRedocExtension(key: string): boolean { return key in redocExtensions; } + +export function extractExtensions(obj: object, showExtensions: string[] | true): Dict { + return Object.keys(obj) + .filter(key => { + if (showExtensions === true) { + return key.startsWith('x-') && !isRedocExtension(key); + } + return key.startsWith('x-') && showExtensions.indexOf(key) > -1; + }) + .reduce((acc, key) => { + acc[key] = obj[key]; + return acc; + }, {}); +}