mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-28 19:43:43 +03:00
Merge pull request #5 from Adthena/feature-FBI-459-implement-csv-code-sample-generation-for-redoc
FBI-459: Implement CSV code sample generation for redoc
This commit is contained in:
commit
8080b338fd
|
@ -14,7 +14,7 @@ const specUrl =
|
||||||
const options: RedocRawOptions = {
|
const options: RedocRawOptions = {
|
||||||
nativeScrollbars: false,
|
nativeScrollbars: false,
|
||||||
maxDisplayedEnumValues: 3,
|
maxDisplayedEnumValues: 3,
|
||||||
codeSamplesLanguages: ['json', 'xml'],
|
codeSamplesLanguages: ['json', 'xml', 'csv'],
|
||||||
};
|
};
|
||||||
|
|
||||||
render(<RedocStandalone specUrl={specUrl} options={options} />, document.getElementById('example'));
|
render(<RedocStandalone specUrl={specUrl} options={options} />, document.getElementById('example'));
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
export const CODE_SAMPLE_LANGUAGES = {
|
export const CODE_SAMPLE_LANGUAGES = {
|
||||||
JSON: 'json',
|
JSON: 'json',
|
||||||
XML: 'xml',
|
XML: 'xml',
|
||||||
|
CSV: 'csv',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
@ -336,6 +336,13 @@ export class OpenAPIParser {
|
||||||
return receiver;
|
return receiver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively deref the properties of a schema and attach examples
|
||||||
|
*
|
||||||
|
* @param {MergedOpenAPISchema} schema
|
||||||
|
* @param {OpenAPIExample & OpenAPISchema} example
|
||||||
|
* @returns {OpenAPISchema}
|
||||||
|
*/
|
||||||
derefSchemaWithExample(
|
derefSchemaWithExample(
|
||||||
schema: MergedOpenAPISchema,
|
schema: MergedOpenAPISchema,
|
||||||
example: OpenAPIExample & OpenAPISchema,
|
example: OpenAPIExample & OpenAPISchema,
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
import * as Sampler from 'openapi-sampler';
|
import * as Sampler from 'openapi-sampler';
|
||||||
|
|
||||||
import type { OpenAPIMediaType } from '../../types';
|
import { OpenAPIExample, OpenAPIMediaType, OpenAPISchema, Referenced } from '../../types';
|
||||||
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||||
import { SchemaModel } from './Schema';
|
import { SchemaModel } from './Schema';
|
||||||
|
|
||||||
import { isXml, mapValues } from '../../utils';
|
import { mapValues } from '../../utils';
|
||||||
import type { OpenAPIParser } from '../OpenAPIParser';
|
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||||
import { ExampleModel } from './Example';
|
import { ExampleModel } from './Example';
|
||||||
import { ConfigAccessOptions, FinalExamples, generateXmlExample } from '../../utils/xml';
|
import { ConfigAccessOptions, generateXmlExample } from '../../utils/xml';
|
||||||
import { MergedOpenAPISchema } from '../types';
|
import { MergedOpenAPISchema } from '../types';
|
||||||
import { OpenAPIExample, Referenced } from '../../types';
|
import { generateCsvExample } from '../../utils/csv';
|
||||||
|
import { CODE_SAMPLE_LANGUAGES } from '../../constants/languages';
|
||||||
|
import { Example } from '../../types/example';
|
||||||
|
|
||||||
export class MediaTypeModel {
|
export class MediaTypeModel {
|
||||||
examples?: { [name: string]: ExampleModel };
|
examples?: { [name: string]: ExampleModel };
|
||||||
|
@ -18,6 +20,12 @@ export class MediaTypeModel {
|
||||||
isRequestType: boolean;
|
isRequestType: boolean;
|
||||||
onlyRequiredInSamples: boolean;
|
onlyRequiredInSamples: boolean;
|
||||||
generatedPayloadSamplesMaxDepth: number;
|
generatedPayloadSamplesMaxDepth: number;
|
||||||
|
private readonly samplerOptions: {
|
||||||
|
maxSampleDepth: number;
|
||||||
|
skipNonRequired: boolean;
|
||||||
|
skipReadOnly: boolean;
|
||||||
|
skipWriteOnly: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param isRequestType needed to know if skipe RO/RW fields in objects
|
* @param isRequestType needed to know if skipe RO/RW fields in objects
|
||||||
|
@ -37,6 +45,12 @@ export class MediaTypeModel {
|
||||||
const isCodeGenerationSupported = options.codeSamplesLanguages.some(lang =>
|
const isCodeGenerationSupported = options.codeSamplesLanguages.some(lang =>
|
||||||
name.toLowerCase().includes(lang),
|
name.toLowerCase().includes(lang),
|
||||||
);
|
);
|
||||||
|
this.samplerOptions = {
|
||||||
|
maxSampleDepth: this.generatedPayloadSamplesMaxDepth,
|
||||||
|
skipNonRequired: this.isRequestType && this.onlyRequiredInSamples,
|
||||||
|
skipReadOnly: this.isRequestType,
|
||||||
|
skipWriteOnly: !this.isRequestType,
|
||||||
|
};
|
||||||
if (info.examples !== undefined) {
|
if (info.examples !== undefined) {
|
||||||
this.examples = mapValues(
|
this.examples = mapValues(
|
||||||
info.examples,
|
info.examples,
|
||||||
|
@ -57,17 +71,15 @@ export class MediaTypeModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
generateExample(parser: OpenAPIParser, info: OpenAPIMediaType) {
|
generateExample(parser: OpenAPIParser, info: OpenAPIMediaType) {
|
||||||
const samplerOptions = {
|
|
||||||
skipReadOnly: this.isRequestType,
|
|
||||||
skipWriteOnly: !this.isRequestType,
|
|
||||||
skipNonRequired: this.isRequestType && this.onlyRequiredInSamples,
|
|
||||||
maxSampleDepth: this.generatedPayloadSamplesMaxDepth,
|
|
||||||
};
|
|
||||||
if (this.schema) {
|
if (this.schema) {
|
||||||
if (this.schema.oneOf) {
|
if (this.schema.oneOf) {
|
||||||
this.examples = {};
|
this.examples = {};
|
||||||
for (const subSchema of this.schema.oneOf) {
|
for (const subSchema of this.schema.oneOf) {
|
||||||
const sample = Sampler.sample(subSchema.rawSchema as any, samplerOptions, parser.spec);
|
const sample = Sampler.sample(
|
||||||
|
subSchema.rawSchema as any,
|
||||||
|
this.samplerOptions,
|
||||||
|
parser.spec,
|
||||||
|
);
|
||||||
|
|
||||||
if (this.schema.discriminatorProp && typeof sample === 'object' && sample) {
|
if (this.schema.discriminatorProp && typeof sample === 'object' && sample) {
|
||||||
sample[this.schema.discriminatorProp] = subSchema.title;
|
sample[this.schema.discriminatorProp] = subSchema.title;
|
||||||
|
@ -82,24 +94,26 @@ export class MediaTypeModel {
|
||||||
info.encoding,
|
info.encoding,
|
||||||
);
|
);
|
||||||
|
|
||||||
const xmlExamples = this.resolveXmlExample(parser, sample as OpenAPIExample);
|
const [generatedExample] = this.resolveGeneratedExample(parser, sample as OpenAPIExample);
|
||||||
if (xmlExamples[0]) {
|
if (generatedExample) {
|
||||||
this.examples[subSchema.title].value = xmlExamples[0].exampleValue;
|
this.examples[subSchema.title].value = generatedExample.exampleValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const infoOrRef: Referenced<OpenAPIExample> = {
|
let infoOrRef: Referenced<OpenAPIExample> = {
|
||||||
value: Sampler.sample(info.schema as any, samplerOptions, parser.spec),
|
value: Sampler.sample(info.schema as any, this.samplerOptions, parser.spec),
|
||||||
};
|
};
|
||||||
const xmlExamples = this.resolveXmlExample(parser, infoOrRef.value);
|
const generatedExamples = this.resolveGeneratedExample(parser, infoOrRef.value);
|
||||||
|
|
||||||
if (xmlExamples.length > 1) {
|
if (generatedExamples.length > 1) {
|
||||||
this.examples = Object.fromEntries(
|
this.examples = Object.fromEntries(
|
||||||
xmlExamples.map(item => [
|
generatedExamples.map(item => [
|
||||||
item.exampleId,
|
item.exampleId,
|
||||||
new ExampleModel(
|
new ExampleModel(
|
||||||
parser,
|
parser,
|
||||||
{
|
{
|
||||||
|
description: item.exampleDescription,
|
||||||
|
summary: item.exampleSummary,
|
||||||
value: item.exampleValue,
|
value: item.exampleValue,
|
||||||
},
|
},
|
||||||
this.name,
|
this.name,
|
||||||
|
@ -108,30 +122,45 @@ export class MediaTypeModel {
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
const [generatedExample] = generatedExamples;
|
||||||
|
if (generatedExample) {
|
||||||
|
infoOrRef = {
|
||||||
|
description: generatedExample.exampleDescription,
|
||||||
|
summary: generatedExample.exampleSummary,
|
||||||
|
value: generatedExample.exampleValue,
|
||||||
|
};
|
||||||
|
}
|
||||||
this.examples = {
|
this.examples = {
|
||||||
default: new ExampleModel(
|
default: new ExampleModel(parser, infoOrRef, this.name, info.encoding),
|
||||||
parser,
|
|
||||||
{
|
|
||||||
value: xmlExamples[0]?.exampleValue || infoOrRef.value,
|
|
||||||
},
|
|
||||||
this.name,
|
|
||||||
info.encoding,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveXmlExample(parser: OpenAPIParser, sample: OpenAPIExample) {
|
private resolveGeneratedExample(parser: OpenAPIParser, sample: OpenAPIExample): Example[] {
|
||||||
|
const mimeType = this.name.toLowerCase();
|
||||||
|
switch (true) {
|
||||||
|
case mimeType.includes(CODE_SAMPLE_LANGUAGES.JSON):
|
||||||
|
return []; // Already supported
|
||||||
|
case mimeType.includes(CODE_SAMPLE_LANGUAGES.XML):
|
||||||
|
return this.resolveXmlExample(parser, sample);
|
||||||
|
case mimeType.includes(CODE_SAMPLE_LANGUAGES.CSV):
|
||||||
|
return this.resolveCsvExample(parser, sample);
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported code sample language: ${this.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private resolveXmlExample(parser: OpenAPIParser, sample: OpenAPIExample) {
|
||||||
const configAccessOptions: ConfigAccessOptions = {
|
const configAccessOptions: ConfigAccessOptions = {
|
||||||
includeReadOnly: !this.isRequestType,
|
includeReadOnly: !this.isRequestType,
|
||||||
includeWriteOnly: this.isRequestType,
|
includeWriteOnly: this.isRequestType,
|
||||||
};
|
};
|
||||||
const subSchema = this.schema?.schema;
|
const subSchema = this.schema?.schema;
|
||||||
let xmlExamples: FinalExamples[] = [];
|
let xmlExamples: Example[] = [];
|
||||||
if (subSchema && isXml(this.name)) {
|
if (subSchema) {
|
||||||
let resolved;
|
let resolved: OpenAPISchema;
|
||||||
if (subSchema.items) {
|
if (subSchema.items) {
|
||||||
resolved = {
|
resolved = {
|
||||||
...subSchema,
|
...subSchema,
|
||||||
|
@ -152,4 +181,14 @@ export class MediaTypeModel {
|
||||||
|
|
||||||
return xmlExamples;
|
return xmlExamples;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private resolveCsvExample(parser: OpenAPIParser, sample: OpenAPIExample): Example[] {
|
||||||
|
const subSchema = this.schema?.schema;
|
||||||
|
return generateCsvExample({
|
||||||
|
parser,
|
||||||
|
schema: subSchema as MergedOpenAPISchema,
|
||||||
|
sample,
|
||||||
|
samplerOptions: this.samplerOptions,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
6
src/types/example.ts
Normal file
6
src/types/example.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export interface Example {
|
||||||
|
exampleDescription: string;
|
||||||
|
exampleId: string;
|
||||||
|
exampleSummary: string;
|
||||||
|
exampleValue: string;
|
||||||
|
}
|
45
src/utils/__tests__/__snapshots__/csv.test.ts.snap
Normal file
45
src/utils/__tests__/__snapshots__/csv.test.ts.snap
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`generateCsvExample generates a csv example for an array of items using $ref 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"exampleDescription": "",
|
||||||
|
"exampleId": "Example 1",
|
||||||
|
"exampleSummary": "Example CSV",
|
||||||
|
"exampleValue": "Competitor,2023-02-02,2023-02-03
|
||||||
|
google.com,0.356263,0.1251661",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`generateCsvExample generates a csv example for an array of items using allOf 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"exampleDescription": "",
|
||||||
|
"exampleId": "Example 1",
|
||||||
|
"exampleSummary": "Example CSV",
|
||||||
|
"exampleValue": "Competitor,2023-02-02,2023-02-03
|
||||||
|
google.com,0.356263,0.1251661
|
||||||
|
facebook.com,0.74324,0.73542",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`generateCsvExample generates a csv example for an array of items using oneOf 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"exampleDescription": "",
|
||||||
|
"exampleId": "Example 1",
|
||||||
|
"exampleSummary": "Example CSV",
|
||||||
|
"exampleValue": "Competitor,2023-02-02,2023-02-03
|
||||||
|
google.com,0.356263,0.1251661",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"exampleDescription": "",
|
||||||
|
"exampleId": "Example 2",
|
||||||
|
"exampleSummary": "Example CSV",
|
||||||
|
"exampleValue": "Competitor,2023-02-02,2023-02-03
|
||||||
|
facebook.com,0.74324,0.73542",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
104
src/utils/__tests__/csv.test.ts
Normal file
104
src/utils/__tests__/csv.test.ts
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import { generateCsvExample } from '../csv';
|
||||||
|
import { OpenAPIParser, RedocNormalizedOptions } from '../../services';
|
||||||
|
import { OpenAPIExample } from '../../types';
|
||||||
|
|
||||||
|
const opts = new RedocNormalizedOptions({});
|
||||||
|
const samplerOptions = {};
|
||||||
|
|
||||||
|
describe('generateCsvExample', () => {
|
||||||
|
const spec = require('./fixtures/csv-compatible-schema.json');
|
||||||
|
let parser;
|
||||||
|
|
||||||
|
it('generates a csv example for an array of items using allOf', () => {
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const sample = [
|
||||||
|
{
|
||||||
|
Competitor: 'google.com',
|
||||||
|
'2023-02-02': 0.356263,
|
||||||
|
'2023-02-03': 0.1251661,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Competitor: 'facebook.com',
|
||||||
|
'2023-02-02': 0.74324,
|
||||||
|
'2023-02-03': 0.73542,
|
||||||
|
},
|
||||||
|
] as unknown as OpenAPIExample;
|
||||||
|
|
||||||
|
const examples = generateCsvExample({
|
||||||
|
parser,
|
||||||
|
schema: spec.components.schemas.test,
|
||||||
|
sample,
|
||||||
|
samplerOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(examples).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generates a csv example for an array of items using oneOf', () => {
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const sample = [
|
||||||
|
{
|
||||||
|
Competitor: 'facebook.com',
|
||||||
|
'2023-02-02': 0.74324,
|
||||||
|
'2023-02-03': 0.73542,
|
||||||
|
},
|
||||||
|
] as unknown as OpenAPIExample;
|
||||||
|
|
||||||
|
const examples = generateCsvExample({
|
||||||
|
parser,
|
||||||
|
schema: spec.components.schemas.test2,
|
||||||
|
sample,
|
||||||
|
samplerOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(examples).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generates a csv example for an array of items using $ref', () => {
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const sample = [
|
||||||
|
{
|
||||||
|
Competitor: 'google.com',
|
||||||
|
'2023-02-02': 0.356263,
|
||||||
|
'2023-02-03': 0.1251661,
|
||||||
|
},
|
||||||
|
] as unknown as OpenAPIExample;
|
||||||
|
|
||||||
|
const examples = generateCsvExample({
|
||||||
|
parser,
|
||||||
|
schema: spec.components.schemas.test3,
|
||||||
|
sample,
|
||||||
|
samplerOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(examples).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
{
|
||||||
|
prop: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop2: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop3: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop4: undefined,
|
||||||
|
},
|
||||||
|
] as unknown[] as OpenAPIExample[])(
|
||||||
|
'should not generate a csv example',
|
||||||
|
(sample: OpenAPIExample) => {
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const examples = generateCsvExample({
|
||||||
|
parser,
|
||||||
|
schema: spec.components.schemas.test,
|
||||||
|
sample,
|
||||||
|
samplerOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(examples.length).toEqual(0);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
74
src/utils/__tests__/fixtures/csv-compatible-schema.json
Normal file
74
src/utils/__tests__/fixtures/csv-compatible-schema.json
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
{
|
||||||
|
"openapi": "3.0.0",
|
||||||
|
"info": {
|
||||||
|
"version": "1.0",
|
||||||
|
"title": "Foo"
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"test": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/Category1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/Category2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test2": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/Category1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/Category2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test3": {
|
||||||
|
"$ref": "#/components/schemas/Category1"
|
||||||
|
},
|
||||||
|
"Category1": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Competitor": {
|
||||||
|
"example": "google.com",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"2023-02-02": {
|
||||||
|
"example": "0.356263",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"2023-02-03": {
|
||||||
|
"example": "0.1251661",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Category2": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Competitor": {
|
||||||
|
"example": "facebook.com",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"2023-02-02": {
|
||||||
|
"example": "0.74324",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"2023-02-03": {
|
||||||
|
"example": "0.73542",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
121
src/utils/csv.ts
Normal file
121
src/utils/csv.ts
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
import * as Sampler from 'openapi-sampler';
|
||||||
|
import { OpenAPIExample, OpenAPISchema } from '../types';
|
||||||
|
import { Example } from '../types/example';
|
||||||
|
import { MergedOpenAPISchema, OpenAPIParser } from '../services';
|
||||||
|
|
||||||
|
const MAX_ITEM_DEPTH = 1;
|
||||||
|
|
||||||
|
interface CsvExampleProps {
|
||||||
|
parser: OpenAPIParser;
|
||||||
|
schema: OpenAPISchema;
|
||||||
|
sample: OpenAPIExample;
|
||||||
|
samplerOptions: object;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasSameHeaders = (headers: string, sample: OpenAPIExample) =>
|
||||||
|
Object.keys(sample).every(key => headers.includes(key));
|
||||||
|
|
||||||
|
const getCsvRows = (sample: OpenAPIExample): string => {
|
||||||
|
const headers = Object.keys(sample?.[0] ?? sample).join(',');
|
||||||
|
// Ensure the schema has deterministic headers
|
||||||
|
const hasValidHeaders = (Array.isArray(sample) ? sample : [sample]).every(row =>
|
||||||
|
hasSameHeaders(headers, row),
|
||||||
|
);
|
||||||
|
if (!hasValidHeaders) return '';
|
||||||
|
|
||||||
|
let values: string;
|
||||||
|
|
||||||
|
if (Array.isArray(sample)) {
|
||||||
|
values = sample.map(Object.values).join('\n');
|
||||||
|
} else {
|
||||||
|
values = Object.values(sample).join(',');
|
||||||
|
}
|
||||||
|
return headers + '\n' + values;
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanUpExamples = (examples: Example[]): Example[] =>
|
||||||
|
examples.filter(({ exampleValue }) => exampleValue);
|
||||||
|
|
||||||
|
export const generateCsvExample = ({
|
||||||
|
parser,
|
||||||
|
schema,
|
||||||
|
sample,
|
||||||
|
samplerOptions,
|
||||||
|
}: CsvExampleProps): Example[] => {
|
||||||
|
let examples: Example[] = [];
|
||||||
|
let depthCount = 0;
|
||||||
|
let exampleCount = 1;
|
||||||
|
const isValidSample = (Array.isArray(sample) ? sample : [sample]).every(sampleItem =>
|
||||||
|
Object.values(sampleItem).every(
|
||||||
|
value => typeof value !== 'object' && typeof value !== 'undefined',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const processSamplesWithSchema = (subSchema: OpenAPISchema) => {
|
||||||
|
if (!subSchema) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subItems = subSchema.items as OpenAPISchema;
|
||||||
|
if (subSchema.type === 'array' && subItems && depthCount < MAX_ITEM_DEPTH) {
|
||||||
|
depthCount++;
|
||||||
|
processSamplesWithSchema(subItems);
|
||||||
|
}
|
||||||
|
const metadata = {
|
||||||
|
exampleDescription: subSchema.description || schema.description || '',
|
||||||
|
exampleSummary: subSchema.title || schema.title || 'Example CSV',
|
||||||
|
};
|
||||||
|
if (subSchema.allOf) {
|
||||||
|
const resolved: OpenAPISchema = {
|
||||||
|
...schema,
|
||||||
|
items: parser.deref(subSchema.allOf as MergedOpenAPISchema).resolved,
|
||||||
|
};
|
||||||
|
const sampleData = Sampler.sample(
|
||||||
|
resolved as any,
|
||||||
|
samplerOptions,
|
||||||
|
parser.spec,
|
||||||
|
) as OpenAPIExample;
|
||||||
|
|
||||||
|
const csvRows = getCsvRows(sampleData);
|
||||||
|
examples.push({
|
||||||
|
exampleId: `Example ${exampleCount++}`,
|
||||||
|
exampleValue: csvRows,
|
||||||
|
...metadata,
|
||||||
|
});
|
||||||
|
} else if (subSchema.oneOf) {
|
||||||
|
const oneOfExamples = subSchema.oneOf.map(oneOfSchema => {
|
||||||
|
const { resolved } = parser.deref(oneOfSchema as MergedOpenAPISchema);
|
||||||
|
const sampleData = Sampler.sample(
|
||||||
|
resolved as any,
|
||||||
|
samplerOptions,
|
||||||
|
parser.spec,
|
||||||
|
) as OpenAPIExample;
|
||||||
|
const csvRows = getCsvRows(sampleData);
|
||||||
|
const currentMetadata = {
|
||||||
|
exampleDescription: oneOfSchema.description || metadata.exampleDescription,
|
||||||
|
exampleSummary: oneOfSchema.title || metadata.exampleSummary,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
exampleId: `Example ${exampleCount++}`,
|
||||||
|
exampleValue: csvRows,
|
||||||
|
...currentMetadata,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
examples = [...examples, ...oneOfExamples];
|
||||||
|
} else if (subSchema.$ref) {
|
||||||
|
const csvRows = getCsvRows(sample);
|
||||||
|
examples.push({
|
||||||
|
exampleId: `Example ${exampleCount++}`,
|
||||||
|
exampleValue: csvRows,
|
||||||
|
...metadata,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isValidSample) {
|
||||||
|
processSamplesWithSchema(schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleanUpExamples(examples);
|
||||||
|
};
|
|
@ -168,10 +168,6 @@ export function isFormUrlEncoded(contentType: string): boolean {
|
||||||
return contentType === 'application/x-www-form-urlencoded';
|
return contentType === 'application/x-www-form-urlencoded';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isXml = (contentType: string): boolean => {
|
|
||||||
return contentType === 'application/xml';
|
|
||||||
};
|
|
||||||
|
|
||||||
function delimitedEncodeField(fieldVal: any, fieldName: string, delimiter: string): string {
|
function delimitedEncodeField(fieldVal: any, fieldName: string, delimiter: string): string {
|
||||||
if (isArray(fieldVal)) {
|
if (isArray(fieldVal)) {
|
||||||
return fieldVal.map(v => v.toString()).join(delimiter);
|
return fieldVal.map(v => v.toString()).join(delimiter);
|
||||||
|
|
|
@ -1,28 +1,22 @@
|
||||||
import { MergedOpenAPISchema } from '../services';
|
import { MergedOpenAPISchema } from '../services';
|
||||||
import { OpenAPISchema } from '../types';
|
import { OpenAPISchema } from '../types';
|
||||||
import { json2xml } from './jsonToXml';
|
import { json2xml } from './jsonToXml';
|
||||||
|
import { Example } from '../types/example';
|
||||||
|
|
||||||
export interface ConfigAccessOptions {
|
export interface ConfigAccessOptions {
|
||||||
includeReadOnly?: boolean;
|
includeReadOnly?: boolean;
|
||||||
includeWriteOnly?: boolean;
|
includeWriteOnly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExampleConfig extends ConfigAccessOptions {
|
interface ExampleConfig extends ConfigAccessOptions {
|
||||||
includeDeprecated?: boolean;
|
includeDeprecated?: boolean;
|
||||||
useXmlTagForProp?: boolean;
|
useXmlTagForProp?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenerateExampleProps extends ConfigAccessOptions {
|
interface GenerateExampleProps extends ConfigAccessOptions {
|
||||||
schema: MergedOpenAPISchema | undefined;
|
schema: MergedOpenAPISchema | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FinalExamples {
|
|
||||||
exampleDescription: string;
|
|
||||||
exampleId: string;
|
|
||||||
exampleSummary: string;
|
|
||||||
exampleValue: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mergePropertyExamples = (
|
const mergePropertyExamples = (
|
||||||
obj: { [x: string]: any },
|
obj: { [x: string]: any },
|
||||||
propertyName: string,
|
propertyName: string,
|
||||||
|
@ -482,8 +476,8 @@ export const generateXmlExample = ({
|
||||||
includeReadOnly = true,
|
includeReadOnly = true,
|
||||||
includeWriteOnly = true,
|
includeWriteOnly = true,
|
||||||
schema,
|
schema,
|
||||||
}: GenerateExampleProps): FinalExamples[] => {
|
}: GenerateExampleProps): Example[] => {
|
||||||
const finalExamples: FinalExamples[] = [];
|
const finalExamples: Example[] = [];
|
||||||
|
|
||||||
if (!schema) return finalExamples;
|
if (!schema) return finalExamples;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user