From 3783dffc4bb23059aa20ac8d74dc3238484422e7 Mon Sep 17 00:00:00 2001 From: v8tenko Date: Wed, 29 Nov 2023 16:56:43 +0300 Subject: [PATCH] feat: new featueres --- src/__snapshots__/examples/array.test.ts.snap | 231 ++++++++++++++++++ .../base.test.ts.snap} | 0 src/__tests__/examples/array.test.ts | 100 ++++++++ .../base.test.ts} | 2 +- src/includer/index.ts | 21 +- src/includer/models.ts | 1 + src/includer/traverse/tables.ts | 45 +++- src/includer/ui/main.ts | 6 +- 8 files changed, 389 insertions(+), 17 deletions(-) create mode 100644 src/__snapshots__/examples/array.test.ts.snap rename src/__snapshots__/{example.test.ts.snap => examples/base.test.ts.snap} (100%) create mode 100644 src/__tests__/examples/array.test.ts rename src/__tests__/{example.test.ts => examples/base.test.ts} (97%) diff --git a/src/__snapshots__/examples/array.test.ts.snap b/src/__snapshots__/examples/array.test.ts.snap new file mode 100644 index 0000000..49684a4 --- /dev/null +++ b/src/__snapshots__/examples/array.test.ts.snap @@ -0,0 +1,231 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`openapi project with examples renders example field 1`] = ` +"
+ +# example.array + +## Request + +
+ +
+ +POST {.openapi__method} + + +\`\`\` +http://localhost:8080/test +\`\`\` + + +
+ +
+ +Generated server url{.openapi__request__description} + +#### Body + +{% cut "application/json" %} + + +\`\`\`json +[ + { + "test": 1 + }, + { + "test": 2 + } +] +\`\`\` + + +{% endcut %} + + +any[] + +## Responses + +
+ +## 200 OK + +Base 200 response + +#### Body + +{% cut "application/json" %} + + +\`\`\`json +{} +\`\`\` + + +{% endcut %} + + +
+ + +
" +`; + +exports[`openapi project with examples renders infered example 1`] = ` +"
+ +# example.array + +## Request + +
+ +
+ +POST {.openapi__method} + + +\`\`\` +http://localhost:8080/test +\`\`\` + + +
+ +
+ +Generated server url{.openapi__request__description} + +#### Body + +{% cut "application/json" %} + + +\`\`\`json +[ + { + "name": "string" + } +] +\`\`\` + + +{% endcut %} + + +[Cat](#cat)[] + +### Cat + +#||| **Name** | **Type** | **Description** || + +|| name | string | |||# + +## Responses + +
+ +## 200 OK + +Base 200 response + +#### Body + +{% cut "application/json" %} + + +\`\`\`json +{} +\`\`\` + + +{% endcut %} + + +
+ + +
" +`; + +exports[`openapi project with examples renders nested arrays exmaple 1`] = ` +"
+ +# example.array + +## Request + +
+ +
+ +POST {.openapi__method} + + +\`\`\` +http://localhost:8080/test +\`\`\` + + +
+ +
+ +Generated server url{.openapi__request__description} + +#### Body + +{% cut "application/json" %} + + +\`\`\`json +[ + [ + { + "name": "string" + } + ] +] +\`\`\` + + +{% endcut %} + + +[Cat](#cat)[][] + +### Cat + +#||| **Name** | **Type** | **Description** || + +|| name | string | |||# + +## Responses + +
+ +## 200 OK + +Base 200 response + +#### Body + +{% cut "application/json" %} + + +\`\`\`json +{} +\`\`\` + + +{% endcut %} + + +
+ + +
" +`; diff --git a/src/__snapshots__/example.test.ts.snap b/src/__snapshots__/examples/base.test.ts.snap similarity index 100% rename from src/__snapshots__/example.test.ts.snap rename to src/__snapshots__/examples/base.test.ts.snap diff --git a/src/__tests__/examples/array.test.ts b/src/__tests__/examples/array.test.ts new file mode 100644 index 0000000..d480aa8 --- /dev/null +++ b/src/__tests__/examples/array.test.ts @@ -0,0 +1,100 @@ +import {DocumentBuilder, run} from '../__helpers__/run'; + +const name = 'example.array'; +describe('openapi project with examples', () => { + it('renders example field', async () => { + const spec = new DocumentBuilder(name) + .request({ + schema: { + example: [ + { + test: 1, + }, + { + test: 2, + }, + ], + type: 'array', + items: {}, + }, + }) + .response(200, { + description: 'Base 200 response', + schema: { + type: 'object', + }, + }) + .build(); + + const fs = await run(spec); + + const page = fs.match(name); + + expect(page).toMatchSnapshot(); + }); + + it('renders infered example', async () => { + const spec = new DocumentBuilder(name) + .request({ + schema: { + type: 'array', + items: DocumentBuilder.ref('Cat'), + }, + }) + .response(200, { + description: 'Base 200 response', + schema: { + type: 'object', + }, + }) + .component('Cat', { + type: 'object', + properties: { + name: { + type: 'string', + }, + }, + }) + .build(); + + const fs = await run(spec); + + const page = fs.match(name); + + expect(page).toMatchSnapshot(); + }); + + it('renders nested arrays exmaple', async () => { + const spec = new DocumentBuilder(name) + .request({ + schema: { + type: 'array', + items: { + type: 'array', + items: DocumentBuilder.ref('Cat'), + }, + }, + }) + .response(200, { + description: 'Base 200 response', + schema: { + type: 'object', + }, + }) + .component('Cat', { + type: 'object', + properties: { + name: { + type: 'string', + }, + }, + }) + .build(); + + const fs = await run(spec); + + const page = fs.match(name); + + expect(page).toMatchSnapshot(); + }); +}); diff --git a/src/__tests__/example.test.ts b/src/__tests__/examples/base.test.ts similarity index 97% rename from src/__tests__/example.test.ts rename to src/__tests__/examples/base.test.ts index 034da66..eb1302f 100644 --- a/src/__tests__/example.test.ts +++ b/src/__tests__/examples/base.test.ts @@ -1,4 +1,4 @@ -import {DocumentBuilder, run} from './__helpers__/run'; +import {DocumentBuilder, run} from '../__helpers__/run'; const name = 'example'; describe('openapi project with examples', () => { diff --git a/src/includer/index.ts b/src/includer/index.ts index 4e74220..b93c8b1 100644 --- a/src/includer/index.ts +++ b/src/includer/index.ts @@ -164,14 +164,20 @@ async function generateToc(params: GenerateTocParams): Promise { items: [], }; - section.items = endpointsOfTag.map((endpoint) => handleEndpointRender(endpoint, id)); - const custom = ArgvService.tag(tag.name); + const customId = custom?.alias || id; + + section.items = endpointsOfTag.map((endpoint) => handleEndpointRender(endpoint, customId)); const customLeadingPageName = custom?.name || leadingPageName; if (!custom?.hidden) { - addLeadingPage(section, leadingPageMode, customLeadingPageName, join(id, 'index.md')); + addLeadingPage( + section, + leadingPageMode, + customLeadingPageName, + join(customId, 'index.md'), + ); } toc.items.push(section); @@ -270,12 +276,13 @@ async function generateContent(params: GenerateContentParams): Promise { spec.tags.forEach((tag, id) => { const {endpoints} = tag; + const custom = ArgvService.tag(tag.name); + const customId = custom?.alias || id; + endpoints.forEach((endpoint) => { - results.push(handleEndpointIncluder(endpoint, join(writePath, id), sandbox)); + results.push(handleEndpointIncluder(endpoint, join(writePath, customId), sandbox)); }); - const custom = ArgvService.tag(tag.name); - if (custom?.hidden) { return; } @@ -285,7 +292,7 @@ async function generateContent(params: GenerateContentParams): Promise { : generators.section(tag); results.push({ - path: join(writePath, id, 'index.md'), + path: join(writePath, customId, 'index.md'), content, }); }); diff --git a/src/includer/models.ts b/src/includer/models.ts index 3bec21c..5723925 100644 --- a/src/includer/models.ts +++ b/src/includer/models.ts @@ -281,6 +281,7 @@ export type CustomTag = { hidden?: boolean; name?: string; path?: string; + alias?: string; }; export type OpenApiIncluderParams = { diff --git a/src/includer/traverse/tables.ts b/src/includer/traverse/tables.ts index 61857bb..ee0c101 100644 --- a/src/includer/traverse/tables.ts +++ b/src/includer/traverse/tables.ts @@ -37,6 +37,15 @@ export function tableFromSchema(schema: OpenJSONSchema): TableFromSchemaResult { return {content, tableRefs: []}; } + if (schema.type === 'array') { + const {type, ref} = prepareTableRowData(schema); + + return { + content: type, + tableRefs: ref ? [ref] : [], + }; + } + const {rows, refs} = prepareObjectSchemaTable(schema); let content = rows.length ? table([['Name', 'Type', 'Description'], ...rows]) : ''; @@ -135,8 +144,8 @@ export function prepareTableRowData( const inner = prepareTableRowData(value.items, key, parentRef); const innerDescription = inner.ref - ? description - : concatNewLine(description, inner.description); + ? concatNewLine(description, inner.description) + : description; if (RefsService.isRuntimeAllowed() && inner.runtimeRef) { RefsService.runtime(inner.runtimeRef, value.items); @@ -178,20 +187,20 @@ export function prepareTableRowData( function prepareComplexDescription(baseDescription: string, value: OpenJSONSchema): string { let description = baseDescription; const enumValues = value.enum?.map((s) => `\`${s}\``).join(', '); - if (enumValues) { + if (typeof enumValues !== 'undefined') { description = concatNewLine( description, `Enum: ${enumValues}`, ); } - if (value.default) { + if (typeof value.default !== 'undefined') { description = concatNewLine( description, `Default: \`${value.default}\``, ); } - if (value.example) { + if (typeof value.example !== 'undefined') { description = concatNewLine( description, `Example: \`${value.example}\``, @@ -247,13 +256,29 @@ function findNonNullOneOfElement(schema: OpenJSONSchema): OpenJSONSchema { throw new Error(`Unable to create sample element: \n ${stringify(schema, null, 2)}`); } -export function prepareSampleObject(schema: OpenJSONSchema, callstack: OpenJSONSchema[] = []) { +export function prepareSampleObject( + schema: OpenJSONSchema, + callstack: OpenJSONSchema[] = [], +): Object | Array { const result: {[key: string]: unknown} = {}; if (schema.example) { return schema.example; } + if (schema.type === 'array') { + if (Array.isArray(schema.items) || typeof schema.items !== 'object') { + throw new Error( + `Unable to create sample element for ${stringify( + schema, + null, + 4, + )}.\n You can pass only one scheme to items`, + ); + } + return [prepareSampleObject(schema.items)]; + } + const merged = findNonNullOneOfElement(RefsService.merge(schema)); Object.entries(merged.properties || {}).forEach(([key, value]) => { @@ -302,7 +327,13 @@ function prepareSampleElement( return prepareSampleObject(schema, downCallstack); case 'array': if (!schema.items || schema.items === true || Array.isArray(schema.items)) { - throw Error(`Unsupported array items for ${key}`); + throw new Error( + `Unable to create sample element for ${stringify( + schema, + null, + 4, + )}.\n You can pass only one scheme to items`, + ); } return [ prepareSampleElement(key, schema.items, isRequired(key, schema), downCallstack), diff --git a/src/includer/ui/main.ts b/src/includer/ui/main.ts index 9737a21..f4f7e17 100644 --- a/src/includer/ui/main.ts +++ b/src/includer/ui/main.ts @@ -2,7 +2,7 @@ import ArgvService from '../services/argv'; import stringify from 'json-stringify-safe'; -import {sep} from 'path'; +import {join} from 'path'; import {block, body, code, cut, link, list, mono, page, title} from '.'; @@ -79,7 +79,9 @@ function sections({tags, endpoints}: Specification) { return undefined; } - return link(name, id + sep + 'index.md'); + const customId = custom?.alias || id; + + return link(name, join(customId, 'index.md')); }) .filter(Boolean) as string[];