Skip to content

Commit 310e589

Browse files
author
Luis Fernando Planella Gonzalez
committed
Nullable in query parameters not correct
Fixes #293
1 parent 0ffea9b commit 310e589

File tree

5 files changed

+45
-40
lines changed

5 files changed

+45
-40
lines changed

lib/gen-utils.ts

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -167,28 +167,17 @@ export function escapeId(name: string) {
167167
}
168168

169169
/**
170-
* Returns the TypeScript type for the given type and options
170+
* Appends | null to the given type
171171
*/
172-
export function tsType(schemaOrRef: SchemaOrRef | undefined, options: Options, openApi: OpenAPIObject, container?: Model): string {
173-
if (!schemaOrRef) {
174-
// No schema
175-
return 'any';
176-
}
177-
if (schemaOrRef.$ref) {
178-
// A reference
179-
const resolved = resolveRef(openApi, schemaOrRef.$ref);
180-
const nullable = !!(resolved && (resolved as SchemaObject).nullable);
181-
const prefix = nullable ? 'null | ' : '';
182-
const name = simpleName(schemaOrRef.$ref);
183-
if (container && container.name === name) {
184-
// When referencing the same container, use its type name
185-
return prefix + container.typeName;
186-
} else {
187-
return prefix + qualifiedName(name, options);
188-
}
172+
function maybeAppendNull(type: string, nullable: boolean) {
173+
if (` ${type} `.includes('null') || !nullable) {
174+
// The type itself already includes null
175+
return type;
189176
}
190-
const schema = schemaOrRef as SchemaObject;
177+
return (type.includes(' ') ? `(${type})` : type) + (nullable ? ' | null' : '');
178+
}
191179

180+
function rawTsType(schema: SchemaObject, options: Options, openApi: OpenAPIObject, container?: Model): string {
192181
// An union of types
193182
const union = schema.oneOf || schema.anyOf || [];
194183
if (union.length > 0) {
@@ -204,10 +193,7 @@ export function tsType(schemaOrRef: SchemaOrRef | undefined, options: Options, o
204193
// An array
205194
if (type === 'array' || schema.items) {
206195
const items = schema.items || {};
207-
let itemsType = tsType(items, options, openApi, container);
208-
if ((items as any)['nullable'] && !itemsType.includes(' | null')) {
209-
itemsType += ' | null';
210-
}
196+
const itemsType = tsType(items, options, openApi, container);
211197
return `Array<${itemsType}>`;
212198
}
213199

@@ -244,10 +230,7 @@ export function tsType(schemaOrRef: SchemaOrRef | undefined, options: Options, o
244230
if (!propRequired) {
245231
result += '?';
246232
}
247-
let propertyType = tsType(property, options, openApi, container);
248-
if ((property as SchemaObject).nullable) {
249-
propertyType = `${propertyType} | null`;
250-
}
233+
const propertyType = tsType(property, options, openApi, container);
251234
result += `: ${propertyType};\n`;
252235
}
253236
if (schema.additionalProperties) {
@@ -277,10 +260,33 @@ export function tsType(schemaOrRef: SchemaOrRef | undefined, options: Options, o
277260
return 'Blob';
278261
}
279262

280-
// A simple type
263+
// A simple type (integer doesn't exist as type in JS, use number instead)
281264
return type === 'integer' ? 'number' : type;
282265
}
283266

267+
/**
268+
* Returns the TypeScript type for the given type and options
269+
*/
270+
export function tsType(schemaOrRef: SchemaOrRef | undefined, options: Options, openApi: OpenAPIObject, container?: Model): string {
271+
if (!schemaOrRef) {
272+
// No schema
273+
return 'any';
274+
}
275+
276+
if (schemaOrRef.$ref) {
277+
// A reference
278+
const resolved = resolveRef(openApi, schemaOrRef.$ref) as SchemaObject;
279+
const name = simpleName(schemaOrRef.$ref);
280+
// When referencing the same container, use its type name
281+
return maybeAppendNull((container && container.name === name) ? container.typeName : qualifiedName(name, options), !!resolved.nullable);
282+
}
283+
284+
// Resolve the actual type (maybe nullable)
285+
const schema = schemaOrRef as SchemaObject;
286+
const type = rawTsType(schema, options, openApi, container);
287+
return maybeAppendNull(type, !!schema.nullable);
288+
}
289+
284290
/**
285291
* Resolves a reference
286292
* @param ref The reference name, such as #/components/schemas/Name, or just Name

lib/property.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ export class Property {
2121
openApi: OpenAPIObject) {
2222

2323
this.type = tsType(this.schema, options, openApi, model);
24-
if ((schema as SchemaObject)?.nullable && !this.type.startsWith('null | ')) {
25-
this.type = 'null | ' + this.type;
26-
}
2724
this.identifier = escapeId(this.name);
2825
const description = (schema as SchemaObject).description || '';
2926
this.tsComments = tsComments(description, 1, (schema as SchemaObject).deprecated);

test/all-operations.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,10 @@
7979
{
8080
"name": "get2",
8181
"description": "GET param 2",
82+
"required": true,
8283
"schema": {
83-
"type": "integer"
84+
"type": "integer",
85+
"nullable": true
8486
}
8587
},
8688
{

test/all-operations.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ describe('Generation tests using all-operations.json', () => {
214214
expect(params[2].type).toBe('RefString');
215215
expect(params[2].in).toBe('query');
216216
expect(params[3].name).toBe('get2');
217-
expect(params[3].type).toBe('number');
217+
expect(params[3].type).toBe('number | null');
218218
expect(params[3].in).toBe('query');
219219
expect(params[4].name).toBe('get3');
220220
expect(params[4].var).toBe('get3');

test/all-types.spec.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ describe('Generation tests using all-types.json', () => {
9797
expect(decl.name).toBe('NullableObject');
9898
// There's no support for additional properties in typescript-parser. Check as text.
9999
const text = ts.substring(decl.start || 0, decl.end || ts.length);
100-
expect(text).toContain('= {\n\'name\'?: string;\n}');
100+
expect(text).toContain('= ({\n\'name\'?: string;\n})');
101101
done();
102102
});
103103
});
@@ -285,14 +285,14 @@ describe('Generation tests using all-types.json', () => {
285285
expect(decl.name).toBe('AdditionalProperties');
286286
expect(decl.properties.length).toBe(3);
287287
expect(decl.properties[0].name).toBe('age');
288-
expect(decl.properties[0].type).toBe('null | number');
288+
expect(decl.properties[0].type).toBe('number | null');
289289
expect(decl.properties[1].name).toBe('description');
290290
expect(decl.properties[1].type).toBe('string');
291291
expect(decl.properties[2].name).toBe('name');
292292
expect(decl.properties[2].type).toBe('string');
293293
expect(decl.properties[2].isOptional).toBeFalse();
294294
const text = ts.substring(decl.start || 0, decl.end || ts.length);
295-
expect(text).toContain('[key: string]: ABRefObject | null | number | string | undefined;');
295+
expect(text).toContain('[key: string]: ABRefObject | number | null | string | undefined;');
296296
done();
297297
});
298298
});
@@ -310,13 +310,13 @@ describe('Generation tests using all-types.json', () => {
310310
expect(decl.name).toBe('Nullables');
311311
expect(decl.properties.length).toBe(3);
312312
expect(decl.properties[0].name).toBe('inlinedNullableObject');
313-
expect(decl.properties[0].type).withContext('inlinedNullableObject property').toBe('null | {\n\'someProperty\': string;\n}');
313+
expect(decl.properties[0].type).withContext('inlinedNullableObject property').toBe('({\n\'someProperty\': string;\n}) | null');
314314
expect(decl.properties[0].isOptional).toBeFalse();
315315
expect(decl.properties[1].name).toBe('nullableObject');
316-
expect(decl.properties[1].type).withContext('nullableObject property').toBe('null | NullableObject');
316+
expect(decl.properties[1].type).withContext('nullableObject property').toBe('NullableObject | null');
317317
expect(decl.properties[1].isOptional).toBeFalse();
318318
expect(decl.properties[2].name).toBe('withNullableProperty');
319-
expect(decl.properties[2].type).withContext('withNullableProperty property').toBe('{\n\'someProperty\': null | NullableObject;\n}');
319+
expect(decl.properties[2].type).withContext('withNullableProperty property').toBe('{\n\'someProperty\': NullableObject | null;\n}');
320320
expect(decl.properties[2].isOptional).toBeFalse();
321321
done();
322322
});
@@ -410,7 +410,7 @@ describe('Generation tests using all-types.json', () => {
410410
assertProperty('booleanProp', 'boolean');
411411
assertProperty('anyProp', 'any');
412412

413-
assertProperty('nullableObject', 'null | NullableObject');
413+
assertProperty('nullableObject', 'NullableObject | null');
414414
assertProperty('refEnumProp', 'RefEnum', true);
415415
assertProperty('refObjectProp', 'ABRefObject', true);
416416
assertProperty('unionProp', 'Union');

0 commit comments

Comments
 (0)