Skip to content

Commit 922e593

Browse files
authored
[typescript-fetch] guard required date properties against null (#24509)
A required date or date-time property was deserialized with an unguarded new Date(json[...]), turning a null value into 1970-01-01 and an absent value into Invalid Date, which then throws RangeError on serialization. Pass a nullish value through unchanged in both directions, as the template already does for every other required property type.
1 parent 0cfa3c5 commit 922e593

6 files changed

Lines changed: 88 additions & 10 deletions

File tree

modules/openapi-generator/src/main/resources/typescript-fetch/modelGeneric.mustache

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,10 @@ export function {{classname}}FromJSONTyped(json: any, ignoreDiscriminator: boole
9898
{{/isArray}}
9999
{{^isArray}}
100100
{{#isDateType}}
101-
'{{name}}': {{^required}}{{#isNullable}}json['{{baseName}}'] === undefined ? undefined : json['{{baseName}}'] === null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? undefined : {{/isNullable}}{{/required}}({{#required}}{{#isNullable}}json['{{baseName}}'] == null ? null : {{/isNullable}}{{/required}}new Date(json['{{baseName}}'])),
101+
'{{name}}': {{^required}}{{#isNullable}}json['{{baseName}}'] === undefined ? undefined : json['{{baseName}}'] === null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? undefined : {{/isNullable}}{{/required}}({{#required}}{{#isNullable}}json['{{baseName}}'] == null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? json['{{baseName}}'] : {{/isNullable}}{{/required}}new Date(json['{{baseName}}'])),
102102
{{/isDateType}}
103103
{{#isDateTimeType}}
104-
'{{name}}': {{^required}}{{#isNullable}}json['{{baseName}}'] === undefined ? undefined : json['{{baseName}}'] === null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? undefined : {{/isNullable}}{{/required}}({{#required}}{{#isNullable}}json['{{baseName}}'] == null ? null : {{/isNullable}}{{/required}}new Date(json['{{baseName}}'])),
104+
'{{name}}': {{^required}}{{#isNullable}}json['{{baseName}}'] === undefined ? undefined : json['{{baseName}}'] === null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? undefined : {{/isNullable}}{{/required}}({{#required}}{{#isNullable}}json['{{baseName}}'] == null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? json['{{baseName}}'] : {{/isNullable}}{{/required}}new Date(json['{{baseName}}'])),
105105
{{/isDateTimeType}}
106106
{{^isDateType}}
107107
{{^isDateTimeType}}
@@ -173,10 +173,10 @@ export function {{classname}}ToJSONTyped(value?: {{#hasReadOnly}}Omit<{{classnam
173173
{{^isReadOnly}}
174174
{{#isPrimitiveType}}
175175
{{#isDateType}}
176-
'{{baseName}}': {{^required}}value['{{name}}'] == null ? value['{{name}}'] : {{/required}}{{#isNullable}}{{#required}}value['{{name}}'] == null ? value['{{name}}'] : {{/required}}{{/isNullable}}value['{{name}}'].toISOString().substring(0,10),
176+
'{{baseName}}': value['{{name}}'] == null ? value['{{name}}'] : value['{{name}}'].toISOString().substring(0,10),
177177
{{/isDateType}}
178178
{{#isDateTimeType}}
179-
'{{baseName}}': {{^required}}value['{{name}}'] == null ? value['{{name}}'] : {{/required}}{{#isNullable}}{{#required}}value['{{name}}'] == null ? value['{{name}}'] : {{/required}}{{/isNullable}}value['{{name}}'].toISOString(),
179+
'{{baseName}}': value['{{name}}'] == null ? value['{{name}}'] : value['{{name}}'].toISOString(),
180180
{{/isDateTimeType}}
181181
{{#isArray}}
182182
'{{baseName}}': {{#uniqueItems}}{{^required}}value['{{name}}'] == null ? undefined : {{/required}}{{#required}}{{#isNullable}}value['{{name}}'] == null ? null : {{/isNullable}}{{/required}}Array.from(value['{{name}}'] as Set<any>){{/uniqueItems}}{{^uniqueItems}}value['{{name}}']{{/uniqueItems}},

modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,39 @@ public void testIssue19572_OmitUsesCorrectPropertyNameInApi() throws Exception {
756756
TestUtils.assertFileContains(modelPath, " financingOption?: Omit<FinancingOption, 'id'|'createdAt'|'updatedAt'|'foo'|'bar'>;");
757757
}
758758

759+
@Test(description = "Verify required date and date-time properties are null-guarded on serialization and deserialization")
760+
public void testRequiredDatesAreNullGuarded() throws Exception {
761+
File output = generate(
762+
Collections.emptyMap(),
763+
"src/test/resources/3_0/typescript-fetch/required-date.yaml"
764+
);
765+
766+
Path modelPath = Paths.get(output + "/models/Event.ts");
767+
TestUtils.assertFileExists(modelPath);
768+
769+
TestUtils.assertFileContains(modelPath,
770+
"'requiredDate': (json['requiredDate'] == null ? json['requiredDate'] : new Date(json['requiredDate'])),");
771+
TestUtils.assertFileContains(modelPath,
772+
"'requiredDateTime': (json['requiredDateTime'] == null ? json['requiredDateTime'] : new Date(json['requiredDateTime'])),");
773+
TestUtils.assertFileContains(modelPath,
774+
"'requiredNullableDate': (json['requiredNullableDate'] == null ? null : new Date(json['requiredNullableDate'])),");
775+
TestUtils.assertFileContains(modelPath,
776+
"'requiredNullableDateTime': (json['requiredNullableDateTime'] == null ? null : new Date(json['requiredNullableDateTime'])),");
777+
TestUtils.assertFileContains(modelPath,
778+
"'optionalDate': json['optionalDate'] == null ? undefined : (new Date(json['optionalDate'])),");
779+
TestUtils.assertFileContains(modelPath,
780+
"'optionalDateTime': json['optionalDateTime'] == null ? undefined : (new Date(json['optionalDateTime'])),");
781+
782+
TestUtils.assertFileContains(modelPath,
783+
"'requiredDate': value['requiredDate'] == null ? value['requiredDate'] : value['requiredDate'].toISOString().substring(0,10),");
784+
TestUtils.assertFileContains(modelPath,
785+
"'requiredDateTime': value['requiredDateTime'] == null ? value['requiredDateTime'] : value['requiredDateTime'].toISOString(),");
786+
TestUtils.assertFileContains(modelPath,
787+
"'requiredNullableDate': value['requiredNullableDate'] == null ? value['requiredNullableDate'] : value['requiredNullableDate'].toISOString().substring(0,10),");
788+
TestUtils.assertFileContains(modelPath,
789+
"'optionalDateTime': value['optionalDateTime'] == null ? value['optionalDateTime'] : value['optionalDateTime'].toISOString(),");
790+
}
791+
759792
private static File generate(
760793
Map<String, Object> properties
761794
) throws IOException {
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
openapi: 3.0.3
2+
info:
3+
title: Required date properties
4+
version: 1.0.0
5+
paths:
6+
/event:
7+
get:
8+
operationId: getEvent
9+
responses:
10+
'200':
11+
description: successful operation
12+
content:
13+
application/json:
14+
schema:
15+
$ref: '#/components/schemas/Event'
16+
components:
17+
schemas:
18+
Event:
19+
type: object
20+
required:
21+
- requiredDate
22+
- requiredDateTime
23+
- requiredNullableDate
24+
- requiredNullableDateTime
25+
properties:
26+
requiredDate:
27+
type: string
28+
format: date
29+
requiredDateTime:
30+
type: string
31+
format: date-time
32+
requiredNullableDate:
33+
type: string
34+
format: date
35+
nullable: true
36+
requiredNullableDateTime:
37+
type: string
38+
format: date-time
39+
nullable: true
40+
optionalDate:
41+
type: string
42+
format: date
43+
optionalDateTime:
44+
type: string
45+
format: date-time

samples/client/petstore/typescript-fetch/builds/default-v3.0/models/FormatTest.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export function FormatTestFromJSONTyped(json: any, ignoreDiscriminator: boolean)
116116
'string': json['string'] == null ? undefined : json['string'],
117117
'_byte': json['byte'],
118118
'binary': json['binary'] == null ? undefined : json['binary'],
119-
'date': (new Date(json['date'])),
119+
'date': (json['date'] == null ? json['date'] : new Date(json['date'])),
120120
'dateTime': json['dateTime'] == null ? undefined : (new Date(json['dateTime'])),
121121
'uuid': json['uuid'] == null ? undefined : json['uuid'],
122122
'password': json['password'],
@@ -146,7 +146,7 @@ export function FormatTestToJSONTyped(value?: FormatTest | null, ignoreDiscrimin
146146
'string': value['string'],
147147
'byte': value['_byte'],
148148
'binary': value['binary'],
149-
'date': value['date'].toISOString().substring(0,10),
149+
'date': value['date'] == null ? value['date'] : value['date'].toISOString().substring(0,10),
150150
'dateTime': value['dateTime'] == null ? value['dateTime'] : value['dateTime'].toISOString(),
151151
'uuid': value['uuid'],
152152
'password': value['password'],

samples/client/petstore/typescript-fetch/builds/kebab-case/models/format-test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export function FormatTestFromJSONTyped(json: any, ignoreDiscriminator: boolean)
116116
'string': json['string'] == null ? undefined : json['string'],
117117
'_byte': json['byte'],
118118
'binary': json['binary'] == null ? undefined : json['binary'],
119-
'date': (new Date(json['date'])),
119+
'date': (json['date'] == null ? json['date'] : new Date(json['date'])),
120120
'dateTime': json['dateTime'] == null ? undefined : (new Date(json['dateTime'])),
121121
'uuid': json['uuid'] == null ? undefined : json['uuid'],
122122
'password': json['password'],
@@ -146,7 +146,7 @@ export function FormatTestToJSONTyped(value?: FormatTest | null, ignoreDiscrimin
146146
'string': value['string'],
147147
'byte': value['_byte'],
148148
'binary': value['binary'],
149-
'date': value['date'].toISOString().substring(0,10),
149+
'date': value['date'] == null ? value['date'] : value['date'].toISOString().substring(0,10),
150150
'dateTime': value['dateTime'] == null ? value['dateTime'] : value['dateTime'].toISOString(),
151151
'uuid': value['uuid'],
152152
'password': value['password'],

samples/client/petstore/typescript-fetch/builds/snakecase-discriminator/models/FormatTest.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export function FormatTestFromJSONTyped(json: any, ignoreDiscriminator: boolean)
116116
'string': json['string'] == null ? undefined : json['string'],
117117
'_byte': json['byte'],
118118
'binary': json['binary'] == null ? undefined : json['binary'],
119-
'date': (new Date(json['date'])),
119+
'date': (json['date'] == null ? json['date'] : new Date(json['date'])),
120120
'dateTime': json['dateTime'] == null ? undefined : (new Date(json['dateTime'])),
121121
'uuid': json['uuid'] == null ? undefined : json['uuid'],
122122
'password': json['password'],
@@ -146,7 +146,7 @@ export function FormatTestToJSONTyped(value?: FormatTest | null, ignoreDiscrimin
146146
'string': value['string'],
147147
'byte': value['_byte'],
148148
'binary': value['binary'],
149-
'date': value['date'].toISOString().substring(0,10),
149+
'date': value['date'] == null ? value['date'] : value['date'].toISOString().substring(0,10),
150150
'dateTime': value['dateTime'] == null ? value['dateTime'] : value['dateTime'].toISOString(),
151151
'uuid': value['uuid'],
152152
'password': value['password'],

0 commit comments

Comments
 (0)