diff --git a/oxide-api/src/util.ts b/oxide-api/src/util.ts index a4438bd..a20a595 100644 --- a/oxide-api/src/util.ts +++ b/oxide-api/src/util.ts @@ -31,7 +31,7 @@ export const isObjectOrArray = (o: unknown) => export const mapObj = ( kf: (k: string) => string, - vf: (k: string | undefined, v: unknown) => unknown = (k, v) => v, + vf: (k: string | undefined, v: unknown) => unknown = (_k, v) => v, ) => (o: unknown): unknown => { if (!isObjectOrArray(o)) return o; @@ -45,8 +45,14 @@ export const mapObj = return newObj; }; -export const parseIfDate = (k: string | undefined, v: unknown) => { - if (typeof v === "string" && (k?.startsWith("time_") || k === "timestamp")) { +const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,6})?Z$/; + +/** + * Use regex to recognize a likely date and then attempt to parse it. Original + * value is passed through if either step fails. + */ +export const parseIfDate = (_k: string | undefined, v: unknown) => { + if (typeof v === "string" && isoDateRegex.test(v)) { const d = new Date(v); if (isNaN(d.getTime())) return v; return d; diff --git a/oxide-openapi-gen-ts/src/client/static/http-client.test.ts b/oxide-openapi-gen-ts/src/client/static/http-client.test.ts index ae75d50..ac69052 100644 --- a/oxide-openapi-gen-ts/src/client/static/http-client.test.ts +++ b/oxide-openapi-gen-ts/src/client/static/http-client.test.ts @@ -53,12 +53,12 @@ describe("handleResponse", () => { }); it("parses dates and converts to camel case", async () => { - const resp = json({ time_created: "2022-05-01" }); + const resp = json({ time_created: "2022-05-01T02:03:04Z" }); const { response, ...rest } = await handleResponse(resp); expect(rest).toMatchObject({ type: "success", data: { - timeCreated: new Date(Date.UTC(2022, 4, 1)), + timeCreated: new Date(Date.UTC(2022, 4, 1, 2, 3, 4)), }, }); expect(response.headers.get("Content-Type")).toBe("application/json"); diff --git a/oxide-openapi-gen-ts/src/client/static/util.test.ts b/oxide-openapi-gen-ts/src/client/static/util.test.ts index 9a1f1ff..fd57224 100644 --- a/oxide-openapi-gen-ts/src/client/static/util.test.ts +++ b/oxide-openapi-gen-ts/src/client/static/util.test.ts @@ -46,7 +46,7 @@ test("isObjectOrArray", () => { describe("mapObj", () => { const fn = mapObj( (k) => k + "_", - (k, v) => (typeof v === "number" ? v * 2 : v) + (_k, v) => (typeof v === "number" ? v * 2 : v) ); it("leaves non-objects alone", () => { @@ -78,34 +78,24 @@ test("processResponseBody", () => { }); describe("parseIfDate", () => { - it("passes through non-date values", () => { - expect(parseIfDate("abc", 123)).toEqual(123); - expect(parseIfDate("abc", "def")).toEqual("def"); + it.each([ + 123, + "def", + // missing 0 ↓ here + "2021-05-03T05:3:05Z", + ])("passes through non-date value %s", (v) => { + expect(parseIfDate("abc", v)).toEqual(v); + expect(parseIfDate("time_created", v)).toEqual(v); }); - const timestamp = 1643092429315; - const dateStr = new Date(timestamp).toISOString(); + it("parses values that pass the regex for dates", () => { + const timestamp = 1643092429315; + const dateStr = new Date(timestamp).toISOString(); - it("doesn't parse dates if key doesn't start with time_", () => { - expect(parseIfDate("abc", dateStr)).toEqual(dateStr); - }); - - it("parses dates if key starts with time_", () => { const value = parseIfDate("time_whatever", dateStr); expect(value).toBeInstanceOf(Date); expect((value as Date).getTime()).toEqual(timestamp); }); - - it("parses dates if key = 'timestamp'", () => { - const value = parseIfDate("timestamp", dateStr); - expect(value).toBeInstanceOf(Date); - expect((value as Date).getTime()).toEqual(timestamp); - }); - - it("passes through values that fail to parse as dates", () => { - const value = parseIfDate("time_whatever", "blah"); - expect(value).toEqual("blah"); - }); }); test("snakeify", () => { diff --git a/oxide-openapi-gen-ts/src/client/static/util.ts b/oxide-openapi-gen-ts/src/client/static/util.ts index 21cf097..b83caaa 100644 --- a/oxide-openapi-gen-ts/src/client/static/util.ts +++ b/oxide-openapi-gen-ts/src/client/static/util.ts @@ -31,7 +31,7 @@ export const isObjectOrArray = (o: unknown) => export const mapObj = ( kf: (k: string) => string, - vf: (k: string | undefined, v: unknown) => unknown = (k, v) => v + vf: (k: string | undefined, v: unknown) => unknown = (_k, v) => v ) => (o: unknown): unknown => { if (!isObjectOrArray(o)) return o; @@ -45,8 +45,14 @@ export const mapObj = return newObj; }; -export const parseIfDate = (k: string | undefined, v: unknown) => { - if (typeof v === "string" && (k?.startsWith("time_") || k === "timestamp")) { +const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,6})?Z$/; + +/** + * Use regex to recognize a likely date and then attempt to parse it. Original + * value is passed through if either step fails. + */ +export const parseIfDate = (_k: string | undefined, v: unknown) => { + if (typeof v === "string" && isoDateRegex.test(v)) { const d = new Date(v); if (isNaN(d.getTime())) return v; return d;