FBI-451: Provide codeSamplesLanguage as an option

This commit is contained in:
Rishi Tank 2023-06-14 11:30:03 +01:00
parent 18aadd5497
commit d1b54be4c5
No known key found for this signature in database
GPG Key ID: BF025217B93532E7
7 changed files with 158 additions and 65 deletions

View File

@ -251,6 +251,7 @@ You can use all of the following options with the standalone version of the <red
* **path-only**: displays a path in the sidebar navigation item.
* **id-only**: displays the operation id with a fallback to the path in the sidebar navigation item.
* `showWebhookVerb` - when set to `true`, shows the HTTP request method for webhooks in operations and in the sidebar.
* `codeSamplesLanguage` - enables code sample generation for the provided list of languages.
### `<redoc>` theme object
* `spacing`
@ -324,6 +325,17 @@ You can use all of the following options with the standalone version of the <red
* `backgroundColor`: '#263238'
* `color`: '#ffffff'
## Auto generated code samples
A new parameter called `codeSamplesLanguages` was added to `options` Object to provide code sample generation. You can pass an array like this to enable all languages supported by the code generation:
```javascript
['json','xml']
```
Where `['json']` is provided by default.
When the `x-codeSamples` and `x-code-samples` are not set, it will
automatically generate the code samples based on the language(s) you set.
-----------
## Development
see [CONTRIBUTING.md](.github/CONTRIBUTING.md)

View File

@ -11,6 +11,10 @@ const userUrl = window.location.search.match(/url=(.*)$/);
const specUrl =
(userUrl && userUrl[1]) || (swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml');
const options: RedocRawOptions = { nativeScrollbars: false, maxDisplayedEnumValues: 3 };
const options: RedocRawOptions = {
nativeScrollbars: false,
maxDisplayedEnumValues: 3,
codeSamplesLanguages: ['json', 'xml'],
};
render(<RedocStandalone specUrl={specUrl} options={options} />, document.getElementById('example'));

View File

@ -76,6 +76,9 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"minItems": undefined,
"options": RedocNormalizedOptions {
"allowedMdComponents": Object {},
"codeSamplesLanguages": Array [
"json",
],
"disableSearch": false,
"downloadDefinitionUrl": undefined,
"downloadFileName": undefined,
@ -347,6 +350,9 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"minItems": undefined,
"options": RedocNormalizedOptions {
"allowedMdComponents": Object {},
"codeSamplesLanguages": Array [
"json",
],
"disableSearch": false,
"downloadDefinitionUrl": undefined,
"downloadFileName": undefined,
@ -605,6 +611,9 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"minItems": undefined,
"options": RedocNormalizedOptions {
"allowedMdComponents": Object {},
"codeSamplesLanguages": Array [
"json",
],
"disableSearch": false,
"downloadDefinitionUrl": undefined,
"downloadFileName": undefined,
@ -925,6 +934,9 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"minItems": undefined,
"options": RedocNormalizedOptions {
"allowedMdComponents": Object {},
"codeSamplesLanguages": Array [
"json",
],
"disableSearch": false,
"downloadDefinitionUrl": undefined,
"downloadFileName": undefined,
@ -1208,6 +1220,9 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"minItems": undefined,
"options": RedocNormalizedOptions {
"allowedMdComponents": Object {},
"codeSamplesLanguages": Array [
"json",
],
"disableSearch": false,
"downloadDefinitionUrl": undefined,
"downloadFileName": undefined,
@ -1462,6 +1477,9 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"minItems": undefined,
"options": RedocNormalizedOptions {
"allowedMdComponents": Object {},
"codeSamplesLanguages": Array [
"json",
],
"disableSearch": false,
"downloadDefinitionUrl": undefined,
"downloadFileName": undefined,
@ -1741,6 +1759,9 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
],
"options": RedocNormalizedOptions {
"allowedMdComponents": Object {},
"codeSamplesLanguages": Array [
"json",
],
"disableSearch": false,
"downloadDefinitionUrl": undefined,
"downloadFileName": undefined,
@ -2050,6 +2071,9 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"minItems": undefined,
"options": RedocNormalizedOptions {
"allowedMdComponents": Object {},
"codeSamplesLanguages": Array [
"json",
],
"disableSearch": false,
"downloadDefinitionUrl": undefined,
"downloadFileName": undefined,
@ -2321,6 +2345,9 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"minItems": undefined,
"options": RedocNormalizedOptions {
"allowedMdComponents": Object {},
"codeSamplesLanguages": Array [
"json",
],
"disableSearch": false,
"downloadDefinitionUrl": undefined,
"downloadFileName": undefined,
@ -2579,6 +2606,9 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"minItems": undefined,
"options": RedocNormalizedOptions {
"allowedMdComponents": Object {},
"codeSamplesLanguages": Array [
"json",
],
"disableSearch": false,
"downloadDefinitionUrl": undefined,
"downloadFileName": undefined,

View File

@ -6,6 +6,8 @@ import { setRedocLabels } from './Labels';
import { SideNavStyleEnum } from './types';
import type { LabelsConfigRaw, MDXComponentMeta } from './types';
export type CodeSamplesLanguage = 'json' | 'xml';
export interface RedocRawOptions {
theme?: ThemeInterface;
scrollYOffset?: number | string | (() => number);
@ -56,6 +58,7 @@ export interface RedocRawOptions {
hideFab?: boolean;
minCharacterLengthToInitSearch?: number;
showWebhookVerb?: boolean;
codeSamplesLanguages?: CodeSamplesLanguage[];
}
export function argValueToBoolean(val?: string | boolean, defaultValue?: boolean): boolean {
@ -211,6 +214,16 @@ export class RedocNormalizedOptions {
return 10;
}
private static normalizeCodeSamplesLanguages(
value?: CodeSamplesLanguage[],
): CodeSamplesLanguage[] {
if (isArray(value)) {
return value.map(lang => lang.toLowerCase()) as CodeSamplesLanguage[];
}
return ['json'];
}
theme: ResolvedThemeInterface;
scrollYOffset: () => number;
hideHostname: boolean;
@ -258,6 +271,7 @@ export class RedocNormalizedOptions {
showWebhookVerb: boolean;
nonce?: string;
codeSamplesLanguages: CodeSamplesLanguage[];
constructor(raw: RedocRawOptions, defaults: RedocRawOptions = {}) {
raw = { ...defaults, ...raw };
@ -335,5 +349,8 @@ export class RedocNormalizedOptions {
this.hideFab = argValueToBoolean(raw.hideFab);
this.minCharacterLengthToInitSearch = argValueToNumber(raw.minCharacterLengthToInitSearch) || 3;
this.showWebhookVerb = argValueToBoolean(raw.showWebhookVerb);
this.codeSamplesLanguages = RedocNormalizedOptions.normalizeCodeSamplesLanguages(
raw.codeSamplesLanguages,
);
}
}

View File

@ -4,7 +4,7 @@ import type { OpenAPIMediaType } from '../../types';
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
import { SchemaModel } from './Schema';
import { isJsonLike, isXml, mapValues } from '../../utils';
import { isXml, mapValues } from '../../utils';
import type { OpenAPIParser } from '../OpenAPIParser';
import { ExampleModel } from './Example';
import { ConfigAccessOptions, FinalExamples, generateXmlExample } from '../../utils/xml';
@ -34,6 +34,9 @@ export class MediaTypeModel {
this.schema = info.schema && new SchemaModel(parser, info.schema, '', options);
this.onlyRequiredInSamples = options.onlyRequiredInSamples;
this.generatedPayloadSamplesMaxDepth = options.generatedPayloadSamplesMaxDepth;
const isCodeGenerationSupported = options.codeSamplesLanguages.some(lang =>
name.toLowerCase().includes(lang),
);
if (info.examples !== undefined) {
this.examples = mapValues(
info.examples,
@ -48,7 +51,7 @@ export class MediaTypeModel {
info.encoding,
),
};
} else if (isJsonLike(name) || isXml(name)) {
} else if (isCodeGenerationSupported) {
this.generateExample(parser, info);
}
}
@ -78,8 +81,9 @@ export class MediaTypeModel {
this.name,
info.encoding,
);
if (isXml(this.name)) {
const xmlExamples = this.resolveXmlExample(parser, sample as OpenAPIExample);
const xmlExamples = this.resolveXmlExample(parser, sample as OpenAPIExample);
if (xmlExamples[0]) {
this.examples[subSchema.title].value = xmlExamples[0]?.exampleValue;
}
}

85
src/utils/jsonToXml.ts Normal file
View File

@ -0,0 +1,85 @@
/**
* @license
* MIT License
*
* Copyright (c) 2022 Mrinmoy Majumdar
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* json2xml
* @example
* Schema: {
* 'prop1' : 'one',
* 'prop2' : 'two',
* 'prop3' : [ 'a', 'b', 'c' ],
* 'prop4' : {
* 'ob1' : 'val-1',
* 'ob2' : 'val-2'
* }
* }
* XML:
* <root>
* <prop1>simple</prop1>
* <prop2>
* <0>a</0>
* <1>b</1>
* <2>c</2>
* </prop2>
* <prop3>
* <ob1>val-1</ob1>
* <ob2>val-2</ob2>
* </prop3>
* </root>
**/
export const json2xml = (obj: any, level: number = 1): string => {
const indent = ' '.repeat(level);
let xmlText = '';
if (level === 1 && typeof obj !== 'object') {
return `\n${indent}${obj.toString()}`;
}
for (const prop in obj) {
const tagNameOrProp = obj[prop]?.['::XML_TAG'] || prop;
let tagName = '';
if (Array.isArray(obj[prop])) {
tagName = tagNameOrProp[0]?.['::XML_TAG'] || `${prop}`;
} else {
tagName = tagNameOrProp;
}
if (prop.startsWith('::')) {
continue;
}
if (Array.isArray(obj[prop])) {
xmlText = `${xmlText}\n${indent}<${tagName}>${json2xml(
obj[prop],
level + 1,
)}\n${indent}</${tagName}>`;
} else if (typeof obj[prop] === 'object') {
xmlText = `${xmlText}\n${indent}<${tagName}>${json2xml(
obj[prop],
level + 1,
)}\n${indent}</${tagName}>`;
} else {
xmlText = `${xmlText}\n${indent}<${tagName}>${obj[prop].toString()}</${tagName}>`;
}
}
return xmlText;
};

View File

@ -1,5 +1,6 @@
import { MergedOpenAPISchema } from '../services';
import { OpenAPISchema } from '../types';
import { json2xml } from './jsonToXml';
export interface ConfigAccessOptions {
includeReadOnly?: boolean;
@ -22,66 +23,6 @@ export interface FinalExamples {
exampleValue: string;
}
/**
* json2xml
* @example
* Schema: {
* 'prop1' : 'one',
* 'prop2' : 'two',
* 'prop3' : [ 'a', 'b', 'c' ],
* 'prop4' : {
* 'ob1' : 'val-1',
* 'ob2' : 'val-2'
* }
* }
* XML:
* <root>
* <prop1>simple</prop1>
* <prop2>
* <0>a</0>
* <1>b</1>
* <2>c</2>
* </prop2>
* <prop3>
* <ob1>val-1</ob1>
* <ob2>val-2</ob2>
* </prop3>
* </root>
**/
const json2xml = (obj: any, level: number = 1): string => {
const indent = ' '.repeat(level);
let xmlText = '';
if (level === 1 && typeof obj !== 'object') {
return `\n${indent}${obj.toString()}`;
}
for (const prop in obj) {
const tagNameOrProp = obj[prop]?.['::XML_TAG'] || prop;
let tagName = '';
if (Array.isArray(obj[prop])) {
tagName = tagNameOrProp[0]?.['::XML_TAG'] || `${prop}`;
} else {
tagName = tagNameOrProp;
}
if (prop.startsWith('::')) {
continue;
}
if (Array.isArray(obj[prop])) {
xmlText = `${xmlText}\n${indent}<${tagName}>${json2xml(
obj[prop],
level + 1,
)}\n${indent}</${tagName}>`;
} else if (typeof obj[prop] === 'object') {
xmlText = `${xmlText}\n${indent}<${tagName}>${json2xml(
obj[prop],
level + 1,
)}\n${indent}</${tagName}>`;
} else {
xmlText = `${xmlText}\n${indent}<${tagName}>${obj[prop].toString()}</${tagName}>`;
}
}
return xmlText;
};
const mergePropertyExamples = (
obj: { [x: string]: any },
propertyName: string,