Skip to content

Commit d7cb7d8

Browse files
committed
fix(javascript): reject malformed date strings
1 parent 2399eed commit d7cb7d8

4 files changed

Lines changed: 55 additions & 1 deletion

File tree

packages/quicktype-core/src/language/JavaScript/JavaScriptRenderer.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,10 +445,15 @@ ${hasArrayConstraints ? ' if ((typ.min !== undefined && val.length < typ.
445445
if (val === null) {
446446
return null;
447447
}
448+
if (!(val instanceof Date) && (typeof val !== "string" || !/^[0-9]{4}-(?:0[1-9]|1[0-2])-(?:[0-2][0-9]|3[01])(?:T(?:[01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](?:[.][0-9]+)?(?:Z|[+-](?:[01][0-9]|2[0-3]):[0-5][0-9]))?$/i.test(val)))
449+
return invalidValue(l("Date"), val, key, parent);
448450
const d = new Date(val);
449451
if (isNaN(d.valueOf())) {
450452
return invalidValue(l("Date"), val, key, parent);
451453
}
454+
const date = typeof val === "string" ? val.slice(0, 10) : null;
455+
if (date !== null && new Date(date + "T00:00:00Z").toISOString().slice(0, 10) !== date)
456+
return invalidValue(l("Date"), val, key, parent);
452457
return d;
453458
}
454459

packages/quicktype-core/src/language/JavaScriptPropTypes/JavaScriptPropTypesRenderer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ export class JavaScriptPropTypesRenderer extends ConvenienceRenderer {
209209
return '(props, name) => props[name] == null || /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i.test(props[name]) ? null : new Error("Expected UUID")';
210210
}
211211
if (transformedStringType.kind === "date-time") {
212-
return '(props, name) => props[name] == null || typeof props[name] === "string" && !Number.isNaN(Date.parse(props[name])) ? null : new Error("Expected date-time")';
212+
return '(props, name) => props[name] == null || typeof props[name] === "string" && /^(\\d{4}-(?:0[1-9]|1[0-2])-(?:[0-2]\\d|3[01]))(?:T(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|[+-](?:[01]\\d|2[0-3]):[0-5]\\d))?$/i.test(props[name]) && !Number.isNaN(Date.parse(`${props[name].slice(0, 10)}T00:00:00Z`)) && new Date(Date.parse(`${props[name].slice(0, 10)}T00:00:00Z`)).toISOString().slice(0, 10) === props[name].slice(0, 10) ? null : new Error("Expected date-time")';
213213
}
214214
return "PropTypes.string";
215215
},
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"date": "1985-04-12",
3+
"time": "23:20:50.52Z",
4+
"date-time": "2023-02-29T12:00:00Z",
5+
"union-array": ["1985-04-12", "23:20:50.52Z"],
6+
"complex-union-array": ["2018-08-13T21:31:01+00:10", "foo", 123]
7+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { InputData, JSONSchemaInput, quicktype } from "quicktype-core";
2+
import { expect, test } from "vitest";
3+
4+
interface GeneratedConverters {
5+
topLevelToJson: (value: { when: Date }) => string;
6+
}
7+
8+
async function converters(): Promise<GeneratedConverters> {
9+
const schemaInput = new JSONSchemaInput(undefined);
10+
await schemaInput.addSource({
11+
name: "TopLevel",
12+
schema: JSON.stringify({
13+
type: "object",
14+
properties: {
15+
when: { type: "string", format: "date-time" },
16+
},
17+
required: ["when"],
18+
}),
19+
});
20+
const inputData = new InputData();
21+
inputData.addInput(schemaInput);
22+
const result = await quicktype({ inputData, lang: "javascript" });
23+
const generatedModule: { exports: Partial<GeneratedConverters> } = {
24+
exports: {},
25+
};
26+
new Function("exports", "module", result.lines.join("\n"))(
27+
generatedModule.exports,
28+
generatedModule,
29+
);
30+
return generatedModule.exports as GeneratedConverters;
31+
}
32+
33+
test("JavaScript converter validates Date instances", async () => {
34+
const { topLevelToJson } = await converters();
35+
36+
expect(
37+
JSON.parse(topLevelToJson({ when: new Date("2024-02-29T00:00:00Z") })),
38+
).toEqual({ when: "2024-02-29T00:00:00.000Z" });
39+
expect(() => topLevelToJson({ when: new Date(Number.NaN) })).toThrow(
40+
"Expected Date",
41+
);
42+
});

0 commit comments

Comments
 (0)