Skip to content

Commit d8c2d1e

Browse files
committed
feat: 解析nullable属性
1 parent 210bf0d commit d8c2d1e

File tree

5 files changed

+132
-72
lines changed

5 files changed

+132
-72
lines changed

src/lib/parse-parameters.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { OpenAPIV3 } from 'openapi-types';
22
import { refToObject } from './ref-to-object';
33
import { generateComments } from './generate-comments';
4-
import { parseSchemaType } from './parse-schema';
4+
import { parseSchema } from './parse-schema';
55

66
export const parseParameters = (
77
docs: OpenAPIV3.Document,
@@ -17,7 +17,7 @@ export const parseParameters = (
1717
const types = parameters
1818
.map((parameter) => {
1919
if (!parameter.schema) return '';
20-
return `${generateComments(parameter)}${parameter.name}${parameter.required ? '' : '?'}: ${parseSchemaType(docs, parameter.schema)}
20+
return `${generateComments(parameter)}${parameter.name}${parameter.required ? '' : '?'}: ${parseSchema(docs, parameter.schema)}
2121
`;
2222
})
2323
.filter(Boolean);

src/lib/parse-request-body.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { OpenAPIV3 } from 'openapi-types';
2-
import { parseSchemaType } from './parse-schema';
2+
import { parseSchema } from './parse-schema';
33
import { refToObject } from './ref-to-object';
44

55
export const parseRequestBody = (
@@ -15,7 +15,7 @@ export const parseRequestBody = (
1515
);
1616
const types = contentTypes.map((contentType) => {
1717
const { schema } = requestBody.content[contentType]!;
18-
return parseSchemaType(docs, schema!);
18+
return parseSchema(docs, schema!);
1919
});
2020
return {
2121
contentTypes: contentTypes.filter((item) => item !== '*/*'),

src/lib/parse-response.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { OpenAPIV3 } from 'openapi-types';
2-
import { parseSchemaType } from './parse-schema';
2+
import { parseSchema } from './parse-schema';
33
import { refToObject } from './ref-to-object';
44

55
export const parseResponse = (
@@ -18,7 +18,7 @@ export const parseResponse = (
1818
.flatMap((item) => Object.values(item.content || {}))
1919
.map((item) => item.schema && refToObject(docs, item.schema))
2020
.filter(Boolean)
21-
.map((schema) => parseSchemaType(docs, schema || {}));
21+
.map((schema) => parseSchema(docs, schema || {}));
2222

2323
return {
2424
responseTypes: [...new Set(contentTypes)],

src/lib/parse-schema.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,36 @@ import type { OpenAPIV3 } from 'openapi-types';
22
import { refToObject } from './ref-to-object';
33
import { generateComments } from './generate-comments';
44

5-
export const parseSchemaType = (
5+
export const parseSchema = (
66
docs: OpenAPIV3.Document,
77
schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject,
88
): string => {
99
const parsed = refToObject(docs, schema);
10+
const nullable = parsed.nullable ? ' | null' : '';
1011

1112
switch (parsed.type) {
1213
case 'array':
13-
return `${parseSchemaType(docs, parsed.items)}[]`;
14+
return `(${parseSchema(docs, parsed.items)})[]${nullable}`;
1415
case 'boolean':
15-
return 'boolean';
16+
return `boolean${nullable}`;
1617
case 'integer':
1718
case 'number':
18-
return 'number';
19+
return `number${nullable}`;
1920
case 'object':
2021
const requiredProperties = parsed.required || [];
2122
const properties = Object.entries(parsed.properties || {}).map(([key, schema]) => {
2223
const schemaObj = refToObject(docs, schema);
23-
return `${generateComments(schemaObj)}${key}${requiredProperties.includes(key) ? '' : '?'}: ${parseSchemaType(docs, schemaObj)}`;
24+
return `${generateComments(schemaObj)}${key}${requiredProperties.includes(key) ? '' : '?'}: ${parseSchema(docs, schemaObj)}`;
2425
});
25-
return `{ ${properties.join(';')} }`;
26+
return `{ ${properties.join(';')} }${nullable}`;
2627
case 'string':
27-
if (parsed.format === 'binary') return 'Blob';
28-
return 'string';
28+
if (parsed.format === 'binary') return `Blob${nullable}`;
29+
return `string${nullable}`;
2930
}
3031

3132
if (parsed.oneOf) {
32-
return parsed.oneOf.map((schema) => parseSchemaType(docs, schema)).join(' | ');
33+
return parsed.oneOf.map((schema) => parseSchema(docs, schema)).join(' | ');
3334
}
3435

35-
return 'never';
36+
return 'unknown';
3637
};

test/lib/parse-schema.test.ts

Lines changed: 115 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,137 @@
1-
import { expect, test } from 'vitest';
2-
import { parseSchemaType } from '../../src/lib/parse-schema';
1+
import { describe, expect, test } from 'vitest';
2+
import { parseSchema } from '../../src/lib/parse-schema';
33
import { getBasicDocument } from '../mocks/get-basic-document';
44

55
const docs = getBasicDocument();
66

7-
test('字符串', () => {
8-
const type = parseSchemaType(docs, { type: 'string' });
9-
expect(type).toMatchInlineSnapshot(`"string"`);
10-
});
7+
describe('常规', () => {
8+
test('数字', () => {
9+
expect(parseSchema(docs, { type: 'number' })).toMatchInlineSnapshot(`"number"`);
10+
expect(parseSchema(docs, { type: 'integer' })).toMatchInlineSnapshot(`"number"`);
11+
});
1112

12-
test('数字', () => {
13-
expect(parseSchemaType(docs, { type: 'number' })).toMatchInlineSnapshot(`"number"`);
14-
expect(parseSchemaType(docs, { type: 'integer' })).toMatchInlineSnapshot(`"number"`);
15-
});
13+
test('字符串', () => {
14+
const type = parseSchema(docs, { type: 'string' });
15+
expect(type).toMatchInlineSnapshot(`"string"`);
16+
});
1617

17-
test('布尔', () => {
18-
const type = parseSchemaType(docs, { type: 'boolean' });
19-
expect(type).toMatchInlineSnapshot(`"boolean"`);
20-
});
18+
test('布尔', () => {
19+
const type = parseSchema(docs, { type: 'boolean' });
20+
expect(type).toMatchInlineSnapshot(`"boolean"`);
21+
});
2122

22-
test('数组', () => {
23-
const type = parseSchemaType(docs, { type: 'array', items: { type: 'string' } });
24-
expect(type).toMatchInlineSnapshot(`"string[]"`);
25-
});
23+
test('数组', () => {
24+
const type = parseSchema(docs, { type: 'array', items: { type: 'string' } });
25+
expect(type).toMatchInlineSnapshot(`"(string)[]"`);
26+
});
2627

27-
test('对象数组', () => {
28-
const type = parseSchemaType(docs, {
29-
type: 'array',
30-
items: { type: 'object', properties: { foo: { type: 'string' } }, required: ['foo'] },
28+
test('对象数组', () => {
29+
const type = parseSchema(docs, {
30+
type: 'array',
31+
items: {
32+
type: 'object',
33+
properties: { foo: { type: 'string' } },
34+
required: ['foo'],
35+
},
36+
});
37+
expect(type).toMatchInlineSnapshot(`"({ foo: string })[]"`);
3138
});
32-
expect(type).toMatchInlineSnapshot(`"{ foo: string }[]"`);
33-
});
3439

35-
test('对象', () => {
36-
const type = parseSchemaType(docs, {
37-
type: 'object',
38-
properties: { foo: { type: 'string' } },
40+
test('对象', () => {
41+
const type = parseSchema(docs, {
42+
type: 'object',
43+
properties: { foo: { type: 'string' } },
44+
});
45+
expect(type).toMatchInlineSnapshot(`"{ foo?: string }"`);
3946
});
40-
expect(type).toMatchInlineSnapshot(`"{ foo?: string }"`);
41-
});
4247

43-
test('对象属性包含描述', () => {
44-
const type = parseSchemaType(docs, {
45-
type: 'object',
46-
properties: { foo: { type: 'string', description: 'foo=bar' } },
47-
});
48-
expect(type).toMatchInlineSnapshot(`
49-
"{
50-
/**
51-
* foo=bar
52-
*/
53-
foo?: string }"
54-
`);
55-
});
48+
test('对象属性包含描述', () => {
49+
const type = parseSchema(docs, {
50+
type: 'object',
51+
properties: { foo: { type: 'string', description: 'foo=bar' } },
52+
});
53+
expect(type).toMatchInlineSnapshot(`
54+
"{
55+
/**
56+
* foo=bar
57+
*/
58+
foo?: string }"
59+
`);
60+
});
5661

57-
test('空对象', () => {
58-
const type = parseSchemaType(docs, { type: 'object' });
59-
expect(type).toMatchInlineSnapshot(`"{ }"`);
60-
});
62+
test('空对象', () => {
63+
const type = parseSchema(docs, { type: 'object' });
64+
expect(type).toMatchInlineSnapshot(`"{ }"`);
65+
});
66+
67+
test('文件', () => {
68+
const type = parseSchema(docs, { type: 'string', format: 'binary' });
69+
expect(type).toMatchInlineSnapshot(`"Blob"`);
70+
});
6171

62-
test('文件', () => {
63-
const type = parseSchemaType(docs, { type: 'string', format: 'binary' });
64-
expect(type).toMatchInlineSnapshot(`"Blob"`);
72+
test('oneOf', () => {
73+
const type = parseSchema(docs, {
74+
oneOf: [{ type: 'string' }, { type: 'boolean' }, { type: 'integer' }],
75+
});
76+
expect(type).toMatchInlineSnapshot(`"string | boolean | number"`);
77+
});
6578
});
6679

67-
test('oneOf', () => {
68-
const type = parseSchemaType(docs, {
69-
oneOf: [{ type: 'string' }, { type: 'boolean' }, { type: 'integer' }],
80+
describe('nullable', () => {
81+
test('数字', () => {
82+
expect(parseSchema(docs, { type: 'number', nullable: true })).toMatchInlineSnapshot(
83+
`"number | null"`,
84+
);
85+
});
86+
87+
test('字符串', () => {
88+
expect(parseSchema(docs, { type: 'string', nullable: true })).toMatchInlineSnapshot(
89+
`"string | null"`,
90+
);
91+
});
92+
93+
test('布尔', () => {
94+
expect(parseSchema(docs, { type: 'boolean', nullable: true })).toMatchInlineSnapshot(
95+
`"boolean | null"`,
96+
);
97+
});
98+
99+
test('数组', () => {
100+
expect(
101+
parseSchema(docs, { type: 'array', items: { type: 'string' }, nullable: true }),
102+
).toMatchInlineSnapshot(`"(string)[] | null"`);
103+
});
104+
105+
test('数组内', () => {
106+
expect(
107+
parseSchema(docs, { type: 'array', items: { type: 'string', nullable: true } }),
108+
).toMatchInlineSnapshot(`"(string | null)[]"`);
109+
});
110+
111+
test('对象', () => {
112+
expect(parseSchema(docs, { type: 'object', nullable: true })).toMatchInlineSnapshot(
113+
`"{ } | null"`,
114+
);
115+
});
116+
117+
test('对象内', () => {
118+
expect(
119+
parseSchema(docs, {
120+
type: 'object',
121+
properties: { foo: { type: 'string', nullable: true } },
122+
}),
123+
).toMatchInlineSnapshot(`"{ foo?: string | null }"`);
124+
});
125+
126+
test('二进制', () => {
127+
expect(
128+
parseSchema(docs, { type: 'string', format: 'binary', nullable: true }),
129+
).toMatchInlineSnapshot(`"Blob | null"`);
70130
});
71-
expect(type).toMatchInlineSnapshot(`"string | boolean | number"`);
72131
});
73132

74133
test('未知类型', () => {
75134
// @ts-expect-error
76-
const type = parseSchemaType(docs, { type: 'any' });
77-
expect(type).toMatchInlineSnapshot(`"never"`);
135+
const type = parseSchema(docs, { type: 'any' });
136+
expect(type).toMatchInlineSnapshot(`"unknown"`);
78137
});

0 commit comments

Comments
 (0)