diff --git a/oxide-api/src/Api.ts b/oxide-api/src/Api.ts index 19b085e..0f3d77a 100644 --- a/oxide-api/src/Api.ts +++ b/oxide-api/src/Api.ts @@ -5174,6 +5174,10 @@ export class Api extends HttpClient { method: "POST", body, query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -5235,6 +5239,13 @@ export class Api extends HttpClient { path: `/v1/certificates`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -5249,6 +5260,10 @@ export class Api extends HttpClient { path: `/v1/certificates`, method: "POST", body, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -5262,6 +5277,10 @@ export class Api extends HttpClient { return this.request({ path: `/v1/certificates/${path.certificate}`, method: "GET", + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -5289,6 +5308,13 @@ export class Api extends HttpClient { path: `/v1/disks`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -5304,6 +5330,10 @@ export class Api extends HttpClient { method: "POST", body, query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -5321,6 +5351,10 @@ export class Api extends HttpClient { path: `/v1/disks/${path.disk}`, method: "GET", query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -5444,6 +5478,12 @@ export class Api extends HttpClient { path: `/v1/disks/${path.disk}/metrics/${path.metric}`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.timestamp = new Date(o.timestamp); + return o; + }); + }, ...params, }); }, @@ -5458,6 +5498,13 @@ export class Api extends HttpClient { path: `/v1/floating-ips`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -5476,6 +5523,10 @@ export class Api extends HttpClient { method: "POST", body, query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -5493,6 +5544,10 @@ export class Api extends HttpClient { path: `/v1/floating-ips/${path.floatingIp}`, method: "GET", query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -5516,6 +5571,10 @@ export class Api extends HttpClient { method: "PUT", body, query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -5559,6 +5618,10 @@ export class Api extends HttpClient { method: "POST", body, query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -5579,6 +5642,10 @@ export class Api extends HttpClient { path: `/v1/floating-ips/${path.floatingIp}/detach`, method: "POST", query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -5620,6 +5687,13 @@ export class Api extends HttpClient { path: `/v1/images`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -5638,6 +5712,10 @@ export class Api extends HttpClient { method: "POST", body, query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -5655,6 +5733,10 @@ export class Api extends HttpClient { path: `/v1/images/${path.image}`, method: "GET", query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -5689,6 +5771,10 @@ export class Api extends HttpClient { path: `/v1/images/${path.image}/demote`, method: "POST", query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -5706,6 +5792,10 @@ export class Api extends HttpClient { path: `/v1/images/${path.image}/promote`, method: "POST", query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -5720,6 +5810,14 @@ export class Api extends HttpClient { path: `/v1/instances`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + o.time_run_state_updated = new Date(o.time_run_state_updated); + return o; + }); + }, ...params, }); }, @@ -5738,6 +5836,11 @@ export class Api extends HttpClient { method: "POST", body, query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + o.time_run_state_updated = new Date(o.time_run_state_updated); + }, ...params, }); }, @@ -5755,6 +5858,11 @@ export class Api extends HttpClient { path: `/v1/instances/${path.instance}`, method: "GET", query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + o.time_run_state_updated = new Date(o.time_run_state_updated); + }, ...params, }); }, @@ -5792,6 +5900,13 @@ export class Api extends HttpClient { path: `/v1/instances/${path.instance}/disks`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -5815,6 +5930,10 @@ export class Api extends HttpClient { method: "POST", body, query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -5838,6 +5957,10 @@ export class Api extends HttpClient { method: "POST", body, query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -5918,6 +6041,11 @@ export class Api extends HttpClient { path: `/v1/instances/${path.instance}/reboot`, method: "POST", query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + o.time_run_state_updated = new Date(o.time_run_state_updated); + }, ...params, }); }, @@ -5958,6 +6086,13 @@ export class Api extends HttpClient { path: `/v1/instances/${path.instance}/ssh-public-keys`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -5975,6 +6110,11 @@ export class Api extends HttpClient { path: `/v1/instances/${path.instance}/start`, method: "POST", query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + o.time_run_state_updated = new Date(o.time_run_state_updated); + }, ...params, }); }, @@ -5992,6 +6132,11 @@ export class Api extends HttpClient { path: `/v1/instances/${path.instance}/stop`, method: "POST", query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + o.time_run_state_updated = new Date(o.time_run_state_updated); + }, ...params, }); }, @@ -6006,6 +6151,13 @@ export class Api extends HttpClient { path: `/v1/ip-pools`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -6019,6 +6171,10 @@ export class Api extends HttpClient { return this.request({ path: `/v1/ip-pools/${path.pool}`, method: "GET", + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -6084,6 +6240,13 @@ export class Api extends HttpClient { path: `/v1/me/ssh-keys`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -6098,6 +6261,10 @@ export class Api extends HttpClient { path: `/v1/me/ssh-keys`, method: "POST", body, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -6111,6 +6278,10 @@ export class Api extends HttpClient { return this.request({ path: `/v1/me/ssh-keys/${path.sshKey}`, method: "GET", + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -6141,6 +6312,12 @@ export class Api extends HttpClient { path: `/v1/metrics/${path.metricName}`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.timestamp = new Date(o.timestamp); + return o; + }); + }, ...params, }); }, @@ -6155,6 +6332,13 @@ export class Api extends HttpClient { path: `/v1/network-interfaces`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -6176,6 +6360,10 @@ export class Api extends HttpClient { method: "POST", body, query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -6196,6 +6384,10 @@ export class Api extends HttpClient { path: `/v1/network-interfaces/${path.interface}`, method: "GET", query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -6219,6 +6411,10 @@ export class Api extends HttpClient { method: "PUT", body, query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -6287,6 +6483,13 @@ export class Api extends HttpClient { path: `/v1/projects`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -6301,6 +6504,10 @@ export class Api extends HttpClient { path: `/v1/projects`, method: "POST", body, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -6314,6 +6521,10 @@ export class Api extends HttpClient { return this.request({ path: `/v1/projects/${path.project}`, method: "GET", + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -6328,6 +6539,10 @@ export class Api extends HttpClient { path: `/v1/projects/${path.project}`, method: "PUT", body, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -6385,6 +6600,13 @@ export class Api extends HttpClient { path: `/v1/snapshots`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -6403,6 +6625,10 @@ export class Api extends HttpClient { method: "POST", body, query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -6420,6 +6646,10 @@ export class Api extends HttpClient { path: `/v1/snapshots/${path.snapshot}`, method: "GET", query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -6451,6 +6681,13 @@ export class Api extends HttpClient { path: `/v1/system/hardware/disks`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -6464,6 +6701,10 @@ export class Api extends HttpClient { return this.request({ path: `/v1/system/hardware/disks/${path.diskId}`, method: "GET", + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -6478,6 +6719,13 @@ export class Api extends HttpClient { path: `/v1/system/hardware/racks`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -6491,6 +6739,10 @@ export class Api extends HttpClient { return this.request({ path: `/v1/system/hardware/racks/${path.rackId}`, method: "GET", + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -6505,6 +6757,13 @@ export class Api extends HttpClient { path: `/v1/system/hardware/sleds`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -6532,6 +6791,10 @@ export class Api extends HttpClient { return this.request({ path: `/v1/system/hardware/sleds/${path.sledId}`, method: "GET", + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -6552,6 +6815,13 @@ export class Api extends HttpClient { path: `/v1/system/hardware/sleds/${path.sledId}/disks`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -6572,6 +6842,13 @@ export class Api extends HttpClient { path: `/v1/system/hardware/sleds/${path.sledId}/instances`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -6697,6 +6974,13 @@ export class Api extends HttpClient { path: `/v1/system/hardware/switches`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -6710,6 +6994,10 @@ export class Api extends HttpClient { return this.request({ path: `/v1/system/hardware/switches/${path.switchId}`, method: "GET", + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -6724,6 +7012,13 @@ export class Api extends HttpClient { path: `/v1/system/identity-providers`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -6806,6 +7101,10 @@ export class Api extends HttpClient { method: "POST", body, query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -6826,6 +7125,10 @@ export class Api extends HttpClient { path: `/v1/system/identity-providers/saml/${path.provider}`, method: "GET", query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -6840,6 +7143,13 @@ export class Api extends HttpClient { path: `/v1/system/ip-pools`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -6854,6 +7164,10 @@ export class Api extends HttpClient { path: `/v1/system/ip-pools`, method: "POST", body, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -6867,6 +7181,10 @@ export class Api extends HttpClient { return this.request({ path: `/v1/system/ip-pools/${path.pool}`, method: "GET", + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -6881,6 +7199,10 @@ export class Api extends HttpClient { path: `/v1/system/ip-pools/${path.pool}`, method: "PUT", body, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -6914,6 +7236,12 @@ export class Api extends HttpClient { path: `/v1/system/ip-pools/${path.pool}/ranges`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + return o; + }); + }, ...params, }); }, @@ -6928,6 +7256,9 @@ export class Api extends HttpClient { path: `/v1/system/ip-pools/${path.pool}/ranges/add`, method: "POST", body, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + }, ...params, }); }, @@ -7026,6 +7357,10 @@ export class Api extends HttpClient { return this.request({ path: `/v1/system/ip-pools-service`, method: "GET", + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -7040,6 +7375,12 @@ export class Api extends HttpClient { path: `/v1/system/ip-pools-service/ranges`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + return o; + }); + }, ...params, }); }, @@ -7054,6 +7395,9 @@ export class Api extends HttpClient { path: `/v1/system/ip-pools-service/ranges/add`, method: "POST", body, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + }, ...params, }); }, @@ -7085,6 +7429,12 @@ export class Api extends HttpClient { path: `/v1/system/metrics/${path.metricName}`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.timestamp = new Date(o.timestamp); + return o; + }); + }, ...params, }); }, @@ -7099,6 +7449,13 @@ export class Api extends HttpClient { path: `/v1/system/networking/address-lot`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -7156,6 +7513,10 @@ export class Api extends HttpClient { return this.request({ path: `/v1/system/networking/allow-list`, method: "GET", + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -7170,6 +7531,10 @@ export class Api extends HttpClient { path: `/v1/system/networking/allow-list`, method: "PUT", body, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -7222,6 +7587,13 @@ export class Api extends HttpClient { path: `/v1/system/networking/bgp`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -7236,6 +7608,10 @@ export class Api extends HttpClient { path: `/v1/system/networking/bgp`, method: "POST", body, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -7278,6 +7654,10 @@ export class Api extends HttpClient { path: `/v1/system/networking/bgp-announce-set`, method: "PUT", body, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -7407,6 +7787,13 @@ export class Api extends HttpClient { path: `/v1/system/networking/switch-port-settings`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -7527,6 +7914,13 @@ export class Api extends HttpClient { path: `/v1/system/silos`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -7538,6 +7932,10 @@ export class Api extends HttpClient { path: `/v1/system/silos`, method: "POST", body, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -7551,6 +7949,10 @@ export class Api extends HttpClient { return this.request({ path: `/v1/system/silos/${path.silo}`, method: "GET", + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -7581,6 +7983,13 @@ export class Api extends HttpClient { path: `/v1/system/silos/${path.silo}/ip-pools`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -7686,6 +8095,13 @@ export class Api extends HttpClient { path: `/v1/system/users-builtin`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -7699,6 +8115,10 @@ export class Api extends HttpClient { return this.request({ path: `/v1/system/users-builtin/${path.user}`, method: "GET", + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -7754,6 +8174,12 @@ export class Api extends HttpClient { path: `/v1/timeseries/schema`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.created = new Date(o.created); + return o; + }); + }, ...params, }); }, @@ -7792,6 +8218,13 @@ export class Api extends HttpClient { path: `/v1/vpc-firewall-rules`, method: "GET", query, + transformResponse: (o) => { + o.rules = o.rules.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -7813,6 +8246,13 @@ export class Api extends HttpClient { method: "PUT", body, query, + transformResponse: (o) => { + o.rules = o.rules.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -7827,6 +8267,13 @@ export class Api extends HttpClient { path: `/v1/vpc-router-routes`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -7845,6 +8292,10 @@ export class Api extends HttpClient { method: "POST", body, query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -7865,6 +8316,10 @@ export class Api extends HttpClient { path: `/v1/vpc-router-routes/${path.route}`, method: "GET", query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -7888,6 +8343,10 @@ export class Api extends HttpClient { method: "PUT", body, query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -7922,6 +8381,13 @@ export class Api extends HttpClient { path: `/v1/vpc-routers`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -7940,6 +8406,10 @@ export class Api extends HttpClient { method: "POST", body, query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -7957,6 +8427,10 @@ export class Api extends HttpClient { path: `/v1/vpc-routers/${path.router}`, method: "GET", query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -7980,6 +8454,10 @@ export class Api extends HttpClient { method: "PUT", body, query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -8014,6 +8492,13 @@ export class Api extends HttpClient { path: `/v1/vpc-subnets`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -8032,6 +8517,10 @@ export class Api extends HttpClient { method: "POST", body, query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -8049,6 +8538,10 @@ export class Api extends HttpClient { path: `/v1/vpc-subnets/${path.subnet}`, method: "GET", query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -8072,6 +8565,10 @@ export class Api extends HttpClient { method: "PUT", body, query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -8112,6 +8609,13 @@ export class Api extends HttpClient { path: `/v1/vpc-subnets/${path.subnet}/network-interfaces`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -8126,6 +8630,13 @@ export class Api extends HttpClient { path: `/v1/vpcs`, method: "GET", query, + transformResponse: (o) => { + o.items = o.items.map((o: any) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + return o; + }); + }, ...params, }); }, @@ -8141,6 +8652,10 @@ export class Api extends HttpClient { method: "POST", body, query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -8158,6 +8673,10 @@ export class Api extends HttpClient { path: `/v1/vpcs/${path.vpc}`, method: "GET", query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, @@ -8181,6 +8700,10 @@ export class Api extends HttpClient { method: "PUT", body, query, + transformResponse: (o) => { + o.time_created = new Date(o.time_created); + o.time_modified = new Date(o.time_modified); + }, ...params, }); }, diff --git a/oxide-api/src/http-client.ts b/oxide-api/src/http-client.ts index b820c3b..00d8c2b 100644 --- a/oxide-api/src/http-client.ts +++ b/oxide-api/src/http-client.ts @@ -6,7 +6,7 @@ * Copyright Oxide Computer Company */ -import { camelToSnake, processResponseBody, snakeify, isNotNull } from "./util"; +import { camelToSnake, snakeifyKeys, isNotNull, camelifyKeys } from "./util"; /** Success responses from the API */ export type ApiSuccess = { @@ -62,6 +62,8 @@ function encodeQueryParam(key: string, value: unknown) { export async function handleResponse( response: Response, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + transformResponse?: (o: any) => void, ): Promise> { const respText = await response.text(); @@ -70,8 +72,13 @@ export async function handleResponse( try { // don't bother trying to parse empty responses like 204s // TODO: is empty object what we want here? - respJson = - respText.length > 0 ? processResponseBody(JSON.parse(respText)) : {}; + if (respText.length > 0) { + respJson = JSON.parse(respText); + transformResponse?.(respJson); // no assignment because this mutates the object + respJson = camelifyKeys(respJson); + } else { + respJson = {}; + } } catch (e) { return { type: "client_error", @@ -115,6 +122,8 @@ export interface FullParams extends FetchParams { body?: unknown; host?: string; method?: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + transformResponse?: (o: any) => void; } export interface ApiConfig { @@ -148,14 +157,15 @@ export class HttpClient { path, query, host, + transformResponse, ...fetchParams }: FullParams): Promise> { const url = (host || this.host) + path + toQueryString(query); const init = { ...mergeParams(this.baseParams, fetchParams), - body: JSON.stringify(snakeify(body), replacer), + body: JSON.stringify(snakeifyKeys(body), replacer), }; - return handleResponse(await fetch(url, init)); + return handleResponse(await fetch(url, init), transformResponse); } } diff --git a/oxide-api/src/util.ts b/oxide-api/src/util.ts index a4438bd..5848e34 100644 --- a/oxide-api/src/util.ts +++ b/oxide-api/src/util.ts @@ -45,18 +45,9 @@ export const mapObj = return newObj; }; -export const parseIfDate = (k: string | undefined, v: unknown) => { - if (typeof v === "string" && (k?.startsWith("time_") || k === "timestamp")) { - const d = new Date(v); - if (isNaN(d.getTime())) return v; - return d; - } - return v; -}; +export const snakeifyKeys = mapObj(camelToSnake); -export const snakeify = mapObj(camelToSnake); - -export const processResponseBody = mapObj(snakeToCamel, parseIfDate); +export const camelifyKeys = mapObj(snakeToCamel); export function isNotNull(value: T): value is NonNullable { return value != null; diff --git a/oxide-openapi-gen-ts/src/client/api.test.ts b/oxide-openapi-gen-ts/src/client/api.test.ts new file mode 100644 index 0000000..2a81ab3 --- /dev/null +++ b/oxide-openapi-gen-ts/src/client/api.test.ts @@ -0,0 +1,88 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright Oxide Computer Company + */ +import { describe, expect, it } from "vitest"; +import { genTransformResponse } from "./api"; +import { type OpenAPIV3 } from "openapi-types"; + +const projectSchema = { + description: "View of a Project", + type: "object", + properties: { + description: { + description: "human-readable free-form text about a resource", + type: "string", + }, + id: { + description: + "unique, immutable, system-controlled identifier for each resource", + type: "string", + format: "uuid", + }, + name: { + description: + "unique, mutable, user-controlled identifier for each resource", + allOf: [Array], + }, + time_created: { + description: "timestamp when this resource was created", + type: "string", + format: "date-time", + }, + time_modified: { + description: "timestamp when this resource was last modified", + type: "string", + format: "date-time", + }, + }, + required: ["description", "id", "name", "time_created", "time_modified"], +}; + +// TODO: add array and nested object properties +const noTransforms = { + description: "View of a Project", + type: "object", + properties: { + description: { + description: "human-readable free-form text about a resource", + type: "string", + }, + id: { + description: + "unique, immutable, system-controlled identifier for each resource", + type: "string", + format: "uuid", + }, + name: { + description: + "unique, mutable, user-controlled identifier for each resource", + allOf: [Array], + }, + }, + required: ["description", "id", "name"], +}; + +const spec: OpenAPIV3.Document = { + openapi: "", + info: { title: "", version: "" }, + paths: {}, +}; + +describe("generateTransformFunction", () => { + it("handles timestamps at top level", () => { + expect(genTransformResponse(spec, projectSchema)).toMatchInlineSnapshot(` + "(o) => { + o.time_created = new Date(o.time_created) + o.time_modified = new Date(o.time_modified) + }" + `); + }); + + it("returns null when there are no transforms to make", () => { + expect(genTransformResponse(spec, noTransforms)).toEqual(undefined); + }); +}); diff --git a/oxide-openapi-gen-ts/src/client/api.ts b/oxide-openapi-gen-ts/src/client/api.ts index c24ac74..2c0c55e 100644 --- a/oxide-openapi-gen-ts/src/client/api.ts +++ b/oxide-openapi-gen-ts/src/client/api.ts @@ -22,7 +22,7 @@ import { snakeToPascal, } from "../util"; import { initIO } from "../io"; -import type { Schema } from "../schema/base"; +import { refToSchemaName, type Schema } from "../schema/base"; import { contentRef, docComment, @@ -276,6 +276,18 @@ export function generateApi(spec: OpenAPIV3.Document, destDir: string) { if (queryParams.length > 0) { w(" query,"); } + + if (successType) { + // insert transformResponse if necessary + const schema = spec.components!.schemas![successType]; + if (schema && "properties" in schema && schema.properties) { + const transformResponse = genTransformResponse(spec, schema); + if (transformResponse) { + w0("transformResponse: " + genTransformResponse(spec, schema) + ","); + } + } + } + w(` ...params, }) },`); @@ -336,3 +348,52 @@ export function generateApi(spec: OpenAPIV3.Document, destDir: string) { export default Api;`); out.end(); } + +// TODO: special case for the common transform function that just does the +// created and modified timestamps, could save a lot of lines +export function genTransformResponse( + spec: OpenAPIV3.Document, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + schema: any +): string | undefined { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + function recurse(schema: any, path: string): string | undefined { + if (schema.type === "object") { + const properties = Object.entries(schema.properties || {}) + .map(([key, propSchema]) => { + const propPath = path ? `${path}.${key}` : key; + const transformCode = recurse(propSchema, propPath); + return transformCode ? `o.${key} = ${transformCode}` : undefined; + }) + .filter((x) => x); + if (properties.length === 0) return undefined; + + return properties.join("\n"); + } else if (schema.type === "array") { + const transformCode = recurse(schema.items, ""); + return transformCode + ? `o.${path}.map((o: any) => { + ${transformCode} + return o +})` + : undefined; + } else if (schema.type === "string") { + if (schema.format === "date-time") { + return `new Date(o.${path})`; + } else if (schema.format === "uint128") { + return `BigInt(o.${path})`; + } + } else if ("$ref" in schema) { + const schemaName = refToSchemaName(schema.$ref); + const _schema = spec.components!.schemas![schemaName]; + return recurse(_schema, path); + } + return undefined; + } + + const transformCode = recurse(schema, ""); + if (!transformCode) return undefined; + return `(o) => { +${transformCode} +}`; +} diff --git a/oxide-openapi-gen-ts/src/client/msw-handlers.ts b/oxide-openapi-gen-ts/src/client/msw-handlers.ts index a28a90d..30c4320 100644 --- a/oxide-openapi-gen-ts/src/client/msw-handlers.ts +++ b/oxide-openapi-gen-ts/src/client/msw-handlers.ts @@ -43,7 +43,7 @@ export function generateMSWHandlers(spec: OpenAPIV3.Document, destDir: string) { import type { SnakeCasedPropertiesDeep as Snakify, Promisable } from "type-fest"; import { type ZodSchema } from "zod"; import type * as Api from "./Api"; - import { snakeify } from "./util"; + import { snakeifyKeys } from "./util"; import * as schema from "./validate"; type HandlerResult = Json | StrictResponse>; @@ -90,10 +90,10 @@ export function generateMSWHandlers(spec: OpenAPIV3.Document, destDir: string) { ? `body: Json,` : ""; const pathParams = conf.parameters?.filter( - (param) => "name" in param && param.schema && param.in === "path", + (param) => "name" in param && param.schema && param.in === "path" ); const queryParams = conf.parameters?.filter( - (param) => "name" in param && param.schema && param.in === "query", + (param) => "name" in param && param.schema && param.in === "query" ); const pathParamsType = pathParams?.length ? `path: Api.${snakeToPascal(opId)}PathParams,` @@ -159,7 +159,7 @@ export function generateMSWHandlers(spec: OpenAPIV3.Document, destDir: string) { let body = undefined if (bodySchema) { const rawBody = await req.json() - const result = bodySchema.transform(snakeify).safeParse(rawBody); + const result = bodySchema.transform(snakeifyKeys).safeParse(rawBody); if (!result.success) { const message = 'Zod error for body: ' + JSON.stringify(result.error) return json({ error_code: 'InvalidRequest', message }, { status: 400 }) @@ -222,8 +222,8 @@ export function generateMSWHandlers(spec: OpenAPIV3.Document, destDir: string) { w( `http.${method}('${formatPath( - path, - )}', handler(handlers['${handler}'], ${paramSchema}, ${bodySchema})),`, + path + )}', handler(handlers['${handler}'], ${paramSchema}, ${bodySchema})),` ); } w(`]}`); 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..4a01945 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 @@ -52,9 +52,11 @@ describe("handleResponse", () => { expect(response.headers.get("Content-Type")).toBe("application/json"); }); - it("parses dates and converts to camel case", async () => { + it("parses dates and applies transformResponse", async () => { const resp = json({ time_created: "2022-05-01" }); - const { response, ...rest } = await handleResponse(resp); + const { response, ...rest } = await handleResponse(resp, (o) => { + o.time_created = new Date(o.time_created); + }); expect(rest).toMatchObject({ type: "success", data: { @@ -64,7 +66,7 @@ describe("handleResponse", () => { expect(response.headers.get("Content-Type")).toBe("application/json"); }); - it("leaves unparseable dates alone", async () => { + it("doesn't try to parse dates if there is no transformResponse", async () => { const resp = json({ time_created: "abc" }); const { response, ...rest } = await handleResponse(resp); expect(rest).toMatchObject({ diff --git a/oxide-openapi-gen-ts/src/client/static/http-client.ts b/oxide-openapi-gen-ts/src/client/static/http-client.ts index 4cf3a72..5fcc332 100644 --- a/oxide-openapi-gen-ts/src/client/static/http-client.ts +++ b/oxide-openapi-gen-ts/src/client/static/http-client.ts @@ -6,7 +6,7 @@ * Copyright Oxide Computer Company */ -import { camelToSnake, processResponseBody, snakeify, isNotNull } from "./util"; +import { camelToSnake, snakeifyKeys, isNotNull, camelifyKeys } from "./util"; /** Success responses from the API */ export type ApiSuccess = { @@ -61,7 +61,9 @@ function encodeQueryParam(key: string, value: unknown) { } export async function handleResponse( - response: Response + response: Response, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + transformResponse?: (o: any) => void ): Promise> { const respText = await response.text(); @@ -70,8 +72,13 @@ export async function handleResponse( try { // don't bother trying to parse empty responses like 204s // TODO: is empty object what we want here? - respJson = - respText.length > 0 ? processResponseBody(JSON.parse(respText)) : {}; + if (respText.length > 0) { + respJson = JSON.parse(respText); + transformResponse?.(respJson); // no assignment because this mutates the object + respJson = camelifyKeys(respJson); + } else { + respJson = {}; + } } catch (e) { return { type: "client_error", @@ -115,6 +122,8 @@ export interface FullParams extends FetchParams { body?: unknown; host?: string; method?: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + transformResponse?: (o: any) => void; } export interface ApiConfig { @@ -148,14 +157,15 @@ export class HttpClient { path, query, host, + transformResponse, ...fetchParams }: FullParams): Promise> { const url = (host || this.host) + path + toQueryString(query); const init = { ...mergeParams(this.baseParams, fetchParams), - body: JSON.stringify(snakeify(body), replacer), + body: JSON.stringify(snakeifyKeys(body), replacer), }; - return handleResponse(await fetch(url, init)); + return handleResponse(await fetch(url, init), transformResponse); } } 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..8de8995 100644 --- a/oxide-openapi-gen-ts/src/client/static/util.test.ts +++ b/oxide-openapi-gen-ts/src/client/static/util.test.ts @@ -7,12 +7,11 @@ */ import { + camelifyKeys, camelToSnake, isObjectOrArray, mapObj, - parseIfDate, - processResponseBody, - snakeify, + snakeifyKeys, snakeToCamel, uniqueItems, } from "./util"; @@ -60,8 +59,8 @@ describe("mapObj", () => { }); }); -test("processResponseBody", () => { - expect(processResponseBody({})).toEqual({}); +test("camelifyKeys", () => { + expect(camelifyKeys({})).toEqual({}); const date = new Date(); const dateStr = date.toISOString(); @@ -70,45 +69,14 @@ test("processResponseBody", () => { another_prop: "abc", time_created: dateStr, }; - expect(processResponseBody(resp)).toMatchObject({ + expect(camelifyKeys(resp)).toMatchObject({ id: "big-uuid", anotherProp: "abc", - timeCreated: expect.any(Date), + timeCreated: dateStr, }); }); -describe("parseIfDate", () => { - it("passes through non-date values", () => { - expect(parseIfDate("abc", 123)).toEqual(123); - expect(parseIfDate("abc", "def")).toEqual("def"); - }); - - 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", () => { +test("snakeifyKeys", () => { const obj = { id: "vpc-id", timeCreated: new Date(Date.UTC(2021, 0, 1)).toISOString(), @@ -119,7 +87,7 @@ test("snakeify", () => { weAreSerious: "xyz", }, }; - expect(snakeify(obj)).toMatchInlineSnapshot(` + expect(snakeifyKeys(obj)).toMatchInlineSnapshot(` { "id": "vpc-id", "nested_obj": { diff --git a/oxide-openapi-gen-ts/src/client/static/util.ts b/oxide-openapi-gen-ts/src/client/static/util.ts index 21cf097..e9014ee 100644 --- a/oxide-openapi-gen-ts/src/client/static/util.ts +++ b/oxide-openapi-gen-ts/src/client/static/util.ts @@ -45,18 +45,9 @@ export const mapObj = return newObj; }; -export const parseIfDate = (k: string | undefined, v: unknown) => { - if (typeof v === "string" && (k?.startsWith("time_") || k === "timestamp")) { - const d = new Date(v); - if (isNaN(d.getTime())) return v; - return d; - } - return v; -}; +export const snakeifyKeys = mapObj(camelToSnake); -export const snakeify = mapObj(camelToSnake); - -export const processResponseBody = mapObj(snakeToCamel, parseIfDate); +export const camelifyKeys = mapObj(snakeToCamel); export function isNotNull(value: T): value is NonNullable { return value != null; diff --git a/oxide-openapi-gen-ts/src/client/zodValidators.ts b/oxide-openapi-gen-ts/src/client/zodValidators.ts index 51ba3f8..c983816 100644 --- a/oxide-openapi-gen-ts/src/client/zodValidators.ts +++ b/oxide-openapi-gen-ts/src/client/zodValidators.ts @@ -38,7 +38,7 @@ export function generateZodValidators( */ import { z, ZodType } from 'zod'; - import { processResponseBody, uniqueItems } from './util'; + import { camelifyKeys, uniqueItems } from './util'; /** * Zod only supports string enums at the moment. A previous issue was opened @@ -62,7 +62,7 @@ export function generateZodValidators( docComment(extractDoc(schema), schemaNames, io); } - w0(`export const ${schemaName} = z.preprocess(processResponseBody,`); + w0(`export const ${schemaName} = z.preprocess(camelifyKeys,`); schemaToZod(schema, io); w(")\n"); } @@ -76,9 +76,7 @@ export function generateZodValidators( const opName = snakeToPascal(conf.operationId); const params = conf.parameters; - w( - `export const ${opName}Params = z.preprocess(processResponseBody, z.object({` - ); + w(`export const ${opName}Params = z.preprocess(camelifyKeys, z.object({`); w(" path: z.object({"); for (const param of params || []) {