Skip to content

Commit 7f2bd94

Browse files
committed
feat: recursively make table from nested json for response json
1 parent bb6b22d commit 7f2bd94

7 files changed

+272
-23
lines changed

src/formatters/table-from-object.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
type TableArgType<T> = {
2+
headers: string[]
3+
dataIndex: string[]
4+
rows: T[]
5+
}
6+
7+
export const tableFromObject = <T extends { [key: string]: string }>({
8+
headers,
9+
dataIndex,
10+
rows
11+
}: TableArgType<T>): string => {
12+
const headerRow = headers.join(' | ')
13+
const headerSeparator = headers.map(() => '---').join(' | ')
14+
const bodyRows = rows
15+
.map(row => {
16+
return dataIndex.map(index => row[index]).join(' | ')
17+
})
18+
.join('\n')
19+
20+
return `${headerRow}\n${headerSeparator}\n${bodyRows}`
21+
}

src/formatters/table.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
type Table = (arg: { headers: string[]; rows: string[][] }) => string
1+
type TableArgType<T> = {
2+
headers: string[]
3+
rows: T[][]
4+
}
25

3-
export const table: Table = ({ headers, rows }) => {
6+
export const table = <T>({ headers, rows }: TableArgType<T>): string => {
47
const headerRow = headers.join(' | ')
58
const headerSeparator = headers.map(() => '---').join(' | ')
69
const bodyRows = rows.map(row => row.join(' | ')).join('\n')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export type FlattenedSchemaPropertyItem = {
2+
property: string
3+
type: string
4+
required: string
5+
description: string
6+
example: string
7+
}

src/utils/format-single-api-endpoint-as-markdown.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ export const formatSingleApiEndpointAsMarkdown: FormatSingleApiEndpointAsMarkdow
3232
const requestBodyMarkdown = requestBodyFormatter({
3333
endpointDetailData
3434
})
35-
const responseMarkdown = responseFormatter(responses)
35+
const responseMarkdown = responseFormatter(
36+
responses,
37+
baseApiEndpoint?.responses
38+
)
3639

3740
const generatedMarkdown = `
3841
---

src/utils/get-flattened-schema.ts

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { FlattenedSchemaPropertyItem } from '@/types/flattened-schema-property-item'
2+
import { SingleReponseObj } from '@/utils/json-to-markdown-table'
3+
4+
const recursivelyFormatNestedObjects = (
5+
schema: SingleReponseObj['schema'],
6+
rows: FlattenedSchemaPropertyItem[] = [],
7+
iteration = 0,
8+
previousPropertyPrefix = '',
9+
fromArray?: boolean
10+
) => {
11+
console.log('iteration: ', iteration)
12+
console.table(rows)
13+
console.log('schema', schema)
14+
const isInitialIteration = iteration === 0
15+
const conditionalPropertyPrefix = isInitialIteration ? '' : '.'
16+
if (schema.type === 'array') {
17+
console.log('schema.type detected as: ' + schema.type)
18+
// console.log('schema', schema)
19+
const row: FlattenedSchemaPropertyItem = {
20+
property: `${previousPropertyPrefix}${conditionalPropertyPrefix}[]`,
21+
type: schema.type ?? '',
22+
required: '-',
23+
description: schema.description ?? '',
24+
example: schema.example ?? ''
25+
}
26+
27+
rows.push(row)
28+
29+
if (schema.items.properties) {
30+
console.log('schema.items.properties detected as: ' + schema.items.type)
31+
recursivelyFormatNestedObjects(
32+
schema.items,
33+
rows,
34+
iteration + 1,
35+
row.property,
36+
true
37+
)
38+
} else {
39+
return rows
40+
}
41+
} else {
42+
console.log('schema.type detected as non-array: ' + schema.type)
43+
44+
// console.log('rows', rows)
45+
// console.log('--schema', schema)
46+
if (schema.properties) {
47+
for (const [propertyName, propertyMetadata] of Object.entries(
48+
schema.properties
49+
)) {
50+
console.log('checking propertyName: ' + propertyName)
51+
const arrayPrefix = propertyMetadata.type === 'array' ? '.[]' : ''
52+
const row: FlattenedSchemaPropertyItem = {
53+
property: `${previousPropertyPrefix}${conditionalPropertyPrefix}${propertyName}`,
54+
type: propertyMetadata.type ?? '',
55+
required: schema.required?.includes(propertyName) ? 'Yes' : 'No',
56+
description: propertyMetadata.description ?? '',
57+
example: propertyMetadata.example ?? ''
58+
}
59+
60+
rows.push(row)
61+
console.table(rows)
62+
if (propertyMetadata.properties) {
63+
recursivelyFormatNestedObjects(propertyMetadata, rows, iteration + 1)
64+
}
65+
if (propertyMetadata.type === 'array') {
66+
recursivelyFormatNestedObjects(
67+
propertyMetadata.items,
68+
rows,
69+
iteration + 1,
70+
row.property + arrayPrefix,
71+
true
72+
)
73+
}
74+
}
75+
} else {
76+
return rows
77+
}
78+
}
79+
console.log('==right before return rows==')
80+
console.table(rows)
81+
return rows
82+
}
83+
84+
export const getflattenedSchema = (
85+
singleResponseObjSchema: SingleReponseObj['schema']
86+
): FlattenedSchemaPropertyItem[] => {
87+
const initialRows: FlattenedSchemaPropertyItem[] = []
88+
const rows = recursivelyFormatNestedObjects(
89+
singleResponseObjSchema,
90+
initialRows,
91+
0
92+
)
93+
// console.log('rows', rows)
94+
return rows
95+
}

src/utils/json-to-markdown-table.ts

+71-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
// Utility function to format JSON as Markdown code block
2-
3-
import { table } from '@/formatters/table'
1+
import { tableFromObject } from '@/formatters/table-from-object'
42
import { Markdown } from '@/utils/markdown/markdown'
53
import { OpenAPIV3 } from 'openapi-types'
64

@@ -59,22 +57,81 @@ export type SingleReponseObj = {
5957
schema: SchemaObject
6058
}
6159

60+
type JsonRowItem = {
61+
property: string
62+
type: string
63+
required: string
64+
description: string
65+
example: string
66+
}
67+
68+
const recursivelyFormatNestedObjects = (
69+
schema: SchemaObject,
70+
fromArray?: boolean
71+
): JsonRowItem[] => {
72+
const rows: JsonRowItem[] = []
73+
74+
if (schema.type === 'array') {
75+
// console.log('schema', schema)
76+
const row: JsonRowItem = {
77+
property: `[]`,
78+
type: schema.type ?? '',
79+
required: '-',
80+
description: schema.description ?? '',
81+
example: schema.example ?? ''
82+
}
83+
84+
rows.push(row)
85+
86+
if (schema.items.properties) {
87+
const nestedRows = recursivelyFormatNestedObjects(schema.items, true)
88+
rows.push(...nestedRows)
89+
}
90+
} else {
91+
if (schema.properties) {
92+
for (const [propertyName, propertyMetadata] of Object.entries(
93+
schema.properties
94+
)) {
95+
const row: JsonRowItem = {
96+
property: `${fromArray ? '[].' : ''}${propertyName}`,
97+
type: propertyMetadata.type ?? '',
98+
required: schema.required?.includes(propertyName) ? 'Yes' : 'No',
99+
description: propertyMetadata.description ?? '',
100+
example: propertyMetadata.example ?? ''
101+
}
102+
103+
rows.push(row)
104+
105+
if (propertyMetadata.properties) {
106+
const nestedRows = recursivelyFormatNestedObjects(propertyMetadata)
107+
rows.push(...nestedRows)
108+
}
109+
}
110+
}
111+
}
112+
113+
return rows
114+
}
115+
62116
export function jsonToMarkdownTable(obj: SingleReponseObj): string {
63117
const mdc = new Markdown()
64118

65119
const headers = ['Property', 'Type', 'Required', 'Description', 'Example']
66120

67-
const rows = Object.entries(obj.schema.properties ?? {}).map(
68-
([propertyName, propertyMetadata]) => [
69-
propertyName,
70-
propertyMetadata.type,
71-
obj.schema.required?.includes(propertyName) ? 'Yes' : 'No',
72-
propertyMetadata.description ?? '',
73-
propertyMetadata.example ?? ''
74-
]
75-
)
76-
77-
const tableMarkdown = table({ headers, rows })
121+
// const rows = Object.entries(obj.schema.properties ?? {}).map(
122+
// ([propertyName, propertyMetadata]) => [
123+
// propertyName,
124+
// propertyMetadata.type,
125+
// obj.schema.required?.includes(propertyName) ? 'Yes' : 'No',
126+
// propertyMetadata.description ?? '',
127+
// propertyMetadata.example ?? ''
128+
// ]
129+
// )
130+
131+
const rows = recursivelyFormatNestedObjects(obj.schema)
132+
const dataIndex = headers.map(header => header.toLowerCase())
133+
134+
const tableMarkdown = tableFromObject({ headers, rows, dataIndex })
78135

79136
mdc.appendToNewLine(tableMarkdown)
80137

Original file line numberDiff line numberDiff line change
@@ -1,10 +1,35 @@
1+
import { tableFromObject } from '@/formatters/table-from-object'
2+
import { FlattenedSchemaPropertyItem } from '@/types/flattened-schema-property-item'
3+
import { getflattenedSchema } from '@/utils/get-flattened-schema'
14
import { SchemaObject } from '@/utils/json-to-markdown'
2-
import { jsonToMarkdownTable } from '@/utils/json-to-markdown-table'
5+
import { Markdown } from '@/utils/markdown/markdown'
36
import { OpenAPIV3 } from 'openapi-types'
47

5-
type ResponseFormatter = (responses: OpenAPIV3.ResponsesObject) => string
8+
type ResponseFormatter = (
9+
responses: OpenAPIV3.ResponsesObject,
10+
baseResponse?: OpenAPIV3.ResponsesObject
11+
) => string
612

7-
export const responseFormatter: ResponseFormatter = responses => {
13+
export const responseFormatter: ResponseFormatter = (
14+
responses,
15+
baseResponse
16+
) => {
17+
let flattenedResponse: FlattenedSchemaPropertyItem[] = []
18+
let flattenedBaseResponse: FlattenedSchemaPropertyItem[] = []
19+
20+
if (baseResponse) {
21+
const successBaseReponse = (baseResponse['200'] ??
22+
baseResponse['201']) as OpenAPIV3.ResponseObject // all refs have been resolved in main.ts
23+
24+
const successBaseResponseContent =
25+
successBaseReponse?.content?.['application/json'] ??
26+
successBaseReponse?.content?.['text/plain']
27+
28+
const successBaseResponseContentSchema =
29+
successBaseResponseContent?.schema as SchemaObject
30+
31+
flattenedBaseResponse = getflattenedSchema(successBaseResponseContentSchema)
32+
}
833
const successResponse = (responses['200'] ??
934
responses['201']) as OpenAPIV3.ResponseObject // all refs have been resolved in main.ts
1035

@@ -15,9 +40,47 @@ export const responseFormatter: ResponseFormatter = responses => {
1540
const successResponseContentSchema =
1641
successResponseContent?.schema as SchemaObject
1742

18-
const responseMarkdown = successResponseContent?.schema
19-
? jsonToMarkdownTable({ schema: successResponseContentSchema })
20-
: ''
43+
flattenedResponse = getflattenedSchema(successResponseContentSchema)
44+
45+
// const responseMarkdown = successResponseContent?.schema
46+
// ? jsonToMarkdownTable({ schema: successResponseContentSchema })
47+
// : ''
48+
49+
const mdc = new Markdown()
50+
51+
const headers = ['Property', 'Type', 'Required', 'Description', 'Example']
52+
53+
// const rows = Object.entries(obj.schema.properties ?? {}).map(
54+
// ([propertyName, propertyMetadata]) => [
55+
// propertyName,
56+
// propertyMetadata.type,
57+
// obj.schema.required?.includes(propertyName) ? 'Yes' : 'No',
58+
// propertyMetadata.description ?? '',
59+
// propertyMetadata.example ?? ''
60+
// ]
61+
// )
62+
const baseFlattenedSchemaPropertyList = flattenedBaseResponse?.map(
63+
row => row.property
64+
)
65+
const rowsFromNewBody = flattenedResponse.map(row => {
66+
const hasBeenAdded = !baseFlattenedSchemaPropertyList.includes(row.property)
67+
68+
return {
69+
property: `${hasBeenAdded ? '✅' : ''} ${row.property}`,
70+
type: row.type,
71+
required: row.required,
72+
description: row.description,
73+
example: row.example
74+
}
75+
})
76+
const rows = [...rowsFromNewBody]
77+
const dataIndex = headers.map(header => header.toLowerCase())
78+
79+
const tableMarkdown = tableFromObject({ headers, rows, dataIndex })
80+
81+
mdc.appendToNewLine(tableMarkdown)
82+
83+
const responseMarkdown = successResponseContent?.schema ? mdc.toString() : ''
2184

2285
return responseMarkdown
2386
}

0 commit comments

Comments
 (0)