From f01372afac62c3c91a61653b61ac8c0750f5a66c Mon Sep 17 00:00:00 2001 From: David Crespo Date: Fri, 21 Jul 2023 13:41:23 -0500 Subject: [PATCH] clean up HttpClient, rename baseUrl to host to match CLI --- client/Api.ts | 301 ++++++++++++++++++------------------- client/http-client.ts | 134 ++++++++--------- generator/client/api.ts | 4 +- static/http-client.test.ts | 36 ++++- static/http-client.ts | 138 +++++++++-------- tsconfig.json | 2 +- 6 files changed, 319 insertions(+), 296 deletions(-) diff --git a/client/Api.ts b/client/Api.ts index e89c9fd..9ff827d 100644 --- a/client/Api.ts +++ b/client/Api.ts @@ -8,7 +8,7 @@ * Copyright Oxide Computer Company */ -import type { RequestParams } from "./http-client"; +import type { FetchParams } from "./http-client"; import { HttpClient, toQueryString } from "./http-client"; export type { ApiConfig, @@ -3143,7 +3143,7 @@ export class Api extends HttpClient { /** * Start an OAuth 2.0 Device Authorization Grant */ - deviceAuthRequest: (_: EmptyObj, params: RequestParams = {}) => { + deviceAuthRequest: (_: EmptyObj, params: FetchParams = {}) => { return this.request({ path: `/device/auth`, method: "POST", @@ -3155,7 +3155,7 @@ export class Api extends HttpClient { */ deviceAuthConfirm: ( { body }: { body: DeviceAuthVerify }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/device/confirm`, @@ -3167,7 +3167,7 @@ export class Api extends HttpClient { /** * Request a device access token */ - deviceAccessToken: (_: EmptyObj, params: RequestParams = {}) => { + deviceAccessToken: (_: EmptyObj, params: FetchParams = {}) => { return this.request({ path: `/device/token`, method: "POST", @@ -3179,7 +3179,7 @@ export class Api extends HttpClient { */ loginSaml: ( { path }: { path: LoginSamlPathParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/login/${path.siloName}/saml/${path.providerName}`, @@ -3192,7 +3192,7 @@ export class Api extends HttpClient { */ certificateList: ( { query = {} }: { query?: CertificateListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/certificates`, @@ -3206,7 +3206,7 @@ export class Api extends HttpClient { */ certificateCreate: ( { body }: { body: CertificateCreate }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/certificates`, @@ -3220,7 +3220,7 @@ export class Api extends HttpClient { */ certificateView: ( { path }: { path: CertificateViewPathParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/certificates/${path.certificate}`, @@ -3233,7 +3233,7 @@ export class Api extends HttpClient { */ certificateDelete: ( { path }: { path: CertificateDeletePathParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/certificates/${path.certificate}`, @@ -3246,7 +3246,7 @@ export class Api extends HttpClient { */ diskList: ( { query = {} }: { query?: DiskListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/disks`, @@ -3260,7 +3260,7 @@ export class Api extends HttpClient { */ diskCreate: ( { query, body }: { query?: DiskCreateQueryParams; body: DiskCreate }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/disks`, @@ -3278,7 +3278,7 @@ export class Api extends HttpClient { path, query = {}, }: { path: DiskViewPathParams; query?: DiskViewQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/disks/${path.disk}`, @@ -3295,7 +3295,7 @@ export class Api extends HttpClient { path, query = {}, }: { path: DiskDeletePathParams; query?: DiskDeleteQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/disks/${path.disk}`, @@ -3317,7 +3317,7 @@ export class Api extends HttpClient { query?: DiskBulkWriteImportQueryParams; body: ImportBlocksBulkWrite; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/disks/${path.disk}/bulk-write`, @@ -3338,7 +3338,7 @@ export class Api extends HttpClient { path: DiskBulkWriteImportStartPathParams; query?: DiskBulkWriteImportStartQueryParams; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/disks/${path.disk}/bulk-write-start`, @@ -3358,7 +3358,7 @@ export class Api extends HttpClient { path: DiskBulkWriteImportStopPathParams; query?: DiskBulkWriteImportStopQueryParams; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/disks/${path.disk}/bulk-write-stop`, @@ -3380,7 +3380,7 @@ export class Api extends HttpClient { query?: DiskFinalizeImportQueryParams; body: FinalizeDisk; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/disks/${path.disk}/finalize`, @@ -3403,7 +3403,7 @@ export class Api extends HttpClient { query?: DiskImportBlocksFromUrlQueryParams; body: ImportBlocksFromUrl; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/disks/${path.disk}/import`, @@ -3424,7 +3424,7 @@ export class Api extends HttpClient { path: DiskMetricsListPathParams; query?: DiskMetricsListQueryParams; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/disks/${path.disk}/metrics/${path.metric}`, @@ -3438,7 +3438,7 @@ export class Api extends HttpClient { */ groupList: ( { query = {} }: { query?: GroupListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/groups`, @@ -3452,7 +3452,7 @@ export class Api extends HttpClient { */ groupView: ( { path }: { path: GroupViewPathParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/groups/${path.groupId}`, @@ -3465,7 +3465,7 @@ export class Api extends HttpClient { */ imageList: ( { query = {} }: { query?: ImageListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/images`, @@ -3482,7 +3482,7 @@ export class Api extends HttpClient { query = {}, body, }: { query?: ImageCreateQueryParams; body: ImageCreate }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/images`, @@ -3500,7 +3500,7 @@ export class Api extends HttpClient { path, query = {}, }: { path: ImageViewPathParams; query?: ImageViewQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/images/${path.image}`, @@ -3517,7 +3517,7 @@ export class Api extends HttpClient { path, query = {}, }: { path: ImageDeletePathParams; query?: ImageDeleteQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/images/${path.image}`, @@ -3534,7 +3534,7 @@ export class Api extends HttpClient { path, query, }: { path: ImageDemotePathParams; query?: ImageDemoteQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/images/${path.image}/demote`, @@ -3551,7 +3551,7 @@ export class Api extends HttpClient { path, query = {}, }: { path: ImagePromotePathParams; query?: ImagePromoteQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/images/${path.image}/promote`, @@ -3565,7 +3565,7 @@ export class Api extends HttpClient { */ instanceList: ( { query = {} }: { query?: InstanceListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/instances`, @@ -3582,7 +3582,7 @@ export class Api extends HttpClient { query, body, }: { query?: InstanceCreateQueryParams; body: InstanceCreate }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/instances`, @@ -3600,7 +3600,7 @@ export class Api extends HttpClient { path, query = {}, }: { path: InstanceViewPathParams; query?: InstanceViewQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/instances/${path.instance}`, @@ -3617,7 +3617,7 @@ export class Api extends HttpClient { path, query = {}, }: { path: InstanceDeletePathParams; query?: InstanceDeleteQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/instances/${path.instance}`, @@ -3637,7 +3637,7 @@ export class Api extends HttpClient { path: InstanceDiskListPathParams; query?: InstanceDiskListQueryParams; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/instances/${path.instance}/disks`, @@ -3659,7 +3659,7 @@ export class Api extends HttpClient { query?: InstanceDiskAttachQueryParams; body: DiskPath; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/instances/${path.instance}/disks/attach`, @@ -3682,7 +3682,7 @@ export class Api extends HttpClient { query?: InstanceDiskDetachQueryParams; body: DiskPath; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/instances/${path.instance}/disks/detach`, @@ -3703,7 +3703,7 @@ export class Api extends HttpClient { path: InstanceExternalIpListPathParams; query?: InstanceExternalIpListQueryParams; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/instances/${path.instance}/external-ips`, @@ -3725,7 +3725,7 @@ export class Api extends HttpClient { query?: InstanceMigrateQueryParams; body: InstanceMigrate; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/instances/${path.instance}/migrate`, @@ -3743,7 +3743,7 @@ export class Api extends HttpClient { path, query = {}, }: { path: InstanceRebootPathParams; query?: InstanceRebootQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/instances/${path.instance}/reboot`, @@ -3763,7 +3763,7 @@ export class Api extends HttpClient { path: InstanceSerialConsolePathParams; query?: InstanceSerialConsoleQueryParams; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/instances/${path.instance}/serial-console`, @@ -3780,7 +3780,7 @@ export class Api extends HttpClient { path, query = {}, }: { path: InstanceStartPathParams; query?: InstanceStartQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/instances/${path.instance}/start`, @@ -3797,7 +3797,7 @@ export class Api extends HttpClient { path, query = {}, }: { path: InstanceStopPathParams; query?: InstanceStopQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/instances/${path.instance}/stop`, @@ -3811,7 +3811,7 @@ export class Api extends HttpClient { */ projectIpPoolList: ( { query = {} }: { query?: ProjectIpPoolListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/ip-pools`, @@ -3831,7 +3831,7 @@ export class Api extends HttpClient { path: ProjectIpPoolViewPathParams; query?: ProjectIpPoolViewQueryParams; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/ip-pools/${path.pool}`, @@ -3848,7 +3848,7 @@ export class Api extends HttpClient { path, body, }: { path: LoginLocalPathParams; body: UsernamePasswordCredentials }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/login/${path.siloName}/local`, @@ -3860,7 +3860,7 @@ export class Api extends HttpClient { /** * Log user out of web console by deleting session on client and server */ - logout: (_: EmptyObj, params: RequestParams = {}) => { + logout: (_: EmptyObj, params: FetchParams = {}) => { return this.request({ path: `/v1/logout`, method: "POST", @@ -3870,7 +3870,7 @@ export class Api extends HttpClient { /** * Fetch the user associated with the current session */ - currentUserView: (_: EmptyObj, params: RequestParams = {}) => { + currentUserView: (_: EmptyObj, params: FetchParams = {}) => { return this.request({ path: `/v1/me`, method: "GET", @@ -3882,7 +3882,7 @@ export class Api extends HttpClient { */ currentUserGroups: ( { query = {} }: { query?: CurrentUserGroupsQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/me/groups`, @@ -3896,7 +3896,7 @@ export class Api extends HttpClient { */ currentUserSshKeyList: ( { query = {} }: { query?: CurrentUserSshKeyListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/me/ssh-keys`, @@ -3910,7 +3910,7 @@ export class Api extends HttpClient { */ currentUserSshKeyCreate: ( { body }: { body: SshKeyCreate }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/me/ssh-keys`, @@ -3924,7 +3924,7 @@ export class Api extends HttpClient { */ currentUserSshKeyView: ( { path }: { path: CurrentUserSshKeyViewPathParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/me/ssh-keys/${path.sshKey}`, @@ -3937,7 +3937,7 @@ export class Api extends HttpClient { */ currentUserSshKeyDelete: ( { path }: { path: CurrentUserSshKeyDeletePathParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/me/ssh-keys/${path.sshKey}`, @@ -3953,7 +3953,7 @@ export class Api extends HttpClient { path, query = {}, }: { path: SiloMetricPathParams; query?: SiloMetricQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/metrics/${path.metricName}`, @@ -3967,7 +3967,7 @@ export class Api extends HttpClient { */ instanceNetworkInterfaceList: ( { query = {} }: { query?: InstanceNetworkInterfaceListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/network-interfaces`, @@ -3987,7 +3987,7 @@ export class Api extends HttpClient { query?: InstanceNetworkInterfaceCreateQueryParams; body: InstanceNetworkInterfaceCreate; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/network-interfaces`, @@ -4008,7 +4008,7 @@ export class Api extends HttpClient { path: InstanceNetworkInterfaceViewPathParams; query?: InstanceNetworkInterfaceViewQueryParams; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/network-interfaces/${path.interface}`, @@ -4030,7 +4030,7 @@ export class Api extends HttpClient { query?: InstanceNetworkInterfaceUpdateQueryParams; body: InstanceNetworkInterfaceUpdate; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/network-interfaces/${path.interface}`, @@ -4051,7 +4051,7 @@ export class Api extends HttpClient { path: InstanceNetworkInterfaceDeletePathParams; query?: InstanceNetworkInterfaceDeleteQueryParams; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/network-interfaces/${path.interface}`, @@ -4063,7 +4063,7 @@ export class Api extends HttpClient { /** * Fetch the current silo's IAM policy */ - policyView: (_: EmptyObj, params: RequestParams = {}) => { + policyView: (_: EmptyObj, params: FetchParams = {}) => { return this.request({ path: `/v1/policy`, method: "GET", @@ -4075,7 +4075,7 @@ export class Api extends HttpClient { */ policyUpdate: ( { body }: { body: SiloRolePolicy }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/policy`, @@ -4089,7 +4089,7 @@ export class Api extends HttpClient { */ projectList: ( { query = {} }: { query?: ProjectListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/projects`, @@ -4103,7 +4103,7 @@ export class Api extends HttpClient { */ projectCreate: ( { body }: { body: ProjectCreate }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/projects`, @@ -4117,7 +4117,7 @@ export class Api extends HttpClient { */ projectView: ( { path }: { path: ProjectViewPathParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/projects/${path.project}`, @@ -4130,7 +4130,7 @@ export class Api extends HttpClient { */ projectUpdate: ( { path, body }: { path: ProjectUpdatePathParams; body: ProjectUpdate }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/projects/${path.project}`, @@ -4144,7 +4144,7 @@ export class Api extends HttpClient { */ projectDelete: ( { path }: { path: ProjectDeletePathParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/projects/${path.project}`, @@ -4157,7 +4157,7 @@ export class Api extends HttpClient { */ projectPolicyView: ( { path }: { path: ProjectPolicyViewPathParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/projects/${path.project}/policy`, @@ -4173,7 +4173,7 @@ export class Api extends HttpClient { path, body, }: { path: ProjectPolicyUpdatePathParams; body: ProjectRolePolicy }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/projects/${path.project}/policy`, @@ -4187,7 +4187,7 @@ export class Api extends HttpClient { */ snapshotList: ( { query = {} }: { query?: SnapshotListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/snapshots`, @@ -4204,7 +4204,7 @@ export class Api extends HttpClient { query, body, }: { query?: SnapshotCreateQueryParams; body: SnapshotCreate }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/snapshots`, @@ -4222,7 +4222,7 @@ export class Api extends HttpClient { path, query = {}, }: { path: SnapshotViewPathParams; query?: SnapshotViewQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/snapshots/${path.snapshot}`, @@ -4239,7 +4239,7 @@ export class Api extends HttpClient { path, query = {}, }: { path: SnapshotDeletePathParams; query?: SnapshotDeleteQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/snapshots/${path.snapshot}`, @@ -4253,7 +4253,7 @@ export class Api extends HttpClient { */ physicalDiskList: ( { query = {} }: { query?: PhysicalDiskListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/hardware/disks`, @@ -4267,7 +4267,7 @@ export class Api extends HttpClient { */ rackList: ( { query = {} }: { query?: RackListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/hardware/racks`, @@ -4281,7 +4281,7 @@ export class Api extends HttpClient { */ rackView: ( { path }: { path: RackViewPathParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/hardware/racks/${path.rackId}`, @@ -4294,7 +4294,7 @@ export class Api extends HttpClient { */ sledList: ( { query = {} }: { query?: SledListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/hardware/sleds`, @@ -4308,7 +4308,7 @@ export class Api extends HttpClient { */ sledView: ( { path }: { path: SledViewPathParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/hardware/sleds/${path.sledId}`, @@ -4327,7 +4327,7 @@ export class Api extends HttpClient { path: SledPhysicalDiskListPathParams; query?: SledPhysicalDiskListQueryParams; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/hardware/sleds/${path.sledId}/disks`, @@ -4347,7 +4347,7 @@ export class Api extends HttpClient { path: SledInstanceListPathParams; query?: SledInstanceListQueryParams; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/hardware/sleds/${path.sledId}/instances`, @@ -4361,7 +4361,7 @@ export class Api extends HttpClient { */ networkingSwitchPortList: ( { query = {} }: { query?: NetworkingSwitchPortListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/hardware/switch-port`, @@ -4383,7 +4383,7 @@ export class Api extends HttpClient { query?: NetworkingSwitchPortApplySettingsQueryParams; body: SwitchPortApplySettings; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/hardware/switch-port/${path.port}/settings`, @@ -4404,7 +4404,7 @@ export class Api extends HttpClient { path: NetworkingSwitchPortClearSettingsPathParams; query?: NetworkingSwitchPortClearSettingsQueryParams; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/hardware/switch-port/${path.port}/settings`, @@ -4418,7 +4418,7 @@ export class Api extends HttpClient { */ switchList: ( { query = {} }: { query?: SwitchListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/hardware/switches`, @@ -4432,7 +4432,7 @@ export class Api extends HttpClient { */ switchView: ( { path }: { path: SwitchViewPathParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/hardware/switches/${path.switchId}`, @@ -4445,7 +4445,7 @@ export class Api extends HttpClient { */ siloIdentityProviderList: ( { query = {} }: { query?: SiloIdentityProviderListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/identity-providers`, @@ -4462,7 +4462,7 @@ export class Api extends HttpClient { query, body, }: { query?: LocalIdpUserCreateQueryParams; body: UserCreate }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/identity-providers/local/users`, @@ -4483,7 +4483,7 @@ export class Api extends HttpClient { path: LocalIdpUserDeletePathParams; query?: LocalIdpUserDeleteQueryParams; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/identity-providers/local/users/${path.userId}`, @@ -4505,7 +4505,7 @@ export class Api extends HttpClient { query?: LocalIdpUserSetPasswordQueryParams; body: UserPassword; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/identity-providers/local/users/${path.userId}/set-password`, @@ -4526,7 +4526,7 @@ export class Api extends HttpClient { query?: SamlIdentityProviderCreateQueryParams; body: SamlIdentityProviderCreate; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/identity-providers/saml`, @@ -4547,7 +4547,7 @@ export class Api extends HttpClient { path: SamlIdentityProviderViewPathParams; query?: SamlIdentityProviderViewQueryParams; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/identity-providers/saml/${path.provider}`, @@ -4561,7 +4561,7 @@ export class Api extends HttpClient { */ ipPoolList: ( { query = {} }: { query?: IpPoolListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/ip-pools`, @@ -4575,7 +4575,7 @@ export class Api extends HttpClient { */ ipPoolCreate: ( { body }: { body: IpPoolCreate }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/ip-pools`, @@ -4589,7 +4589,7 @@ export class Api extends HttpClient { */ ipPoolView: ( { path }: { path: IpPoolViewPathParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/ip-pools/${path.pool}`, @@ -4602,7 +4602,7 @@ export class Api extends HttpClient { */ ipPoolUpdate: ( { path, body }: { path: IpPoolUpdatePathParams; body: IpPoolUpdate }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/ip-pools/${path.pool}`, @@ -4616,7 +4616,7 @@ export class Api extends HttpClient { */ ipPoolDelete: ( { path }: { path: IpPoolDeletePathParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/ip-pools/${path.pool}`, @@ -4635,7 +4635,7 @@ export class Api extends HttpClient { path: IpPoolRangeListPathParams; query?: IpPoolRangeListQueryParams; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/ip-pools/${path.pool}/ranges`, @@ -4649,7 +4649,7 @@ export class Api extends HttpClient { */ ipPoolRangeAdd: ( { path, body }: { path: IpPoolRangeAddPathParams; body: IpRange }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/ip-pools/${path.pool}/ranges/add`, @@ -4663,7 +4663,7 @@ export class Api extends HttpClient { */ ipPoolRangeRemove: ( { path, body }: { path: IpPoolRangeRemovePathParams; body: IpRange }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/ip-pools/${path.pool}/ranges/remove`, @@ -4675,7 +4675,7 @@ export class Api extends HttpClient { /** * Fetch the IP pool used for Oxide services */ - ipPoolServiceView: (_: EmptyObj, params: RequestParams = {}) => { + ipPoolServiceView: (_: EmptyObj, params: FetchParams = {}) => { return this.request({ path: `/v1/system/ip-pools-service`, method: "GET", @@ -4687,7 +4687,7 @@ export class Api extends HttpClient { */ ipPoolServiceRangeList: ( { query = {} }: { query?: IpPoolServiceRangeListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/ip-pools-service/ranges`, @@ -4701,7 +4701,7 @@ export class Api extends HttpClient { */ ipPoolServiceRangeAdd: ( { body }: { body: IpRange }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/ip-pools-service/ranges/add`, @@ -4715,7 +4715,7 @@ export class Api extends HttpClient { */ ipPoolServiceRangeRemove: ( { body }: { body: IpRange }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/ip-pools-service/ranges/remove`, @@ -4732,7 +4732,7 @@ export class Api extends HttpClient { path, query = {}, }: { path: SystemMetricPathParams; query?: SystemMetricQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/metrics/${path.metricName}`, @@ -4746,7 +4746,7 @@ export class Api extends HttpClient { */ networkingAddressLotList: ( { query = {} }: { query?: NetworkingAddressLotListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/networking/address-lot`, @@ -4760,7 +4760,7 @@ export class Api extends HttpClient { */ networkingAddressLotCreate: ( { body }: { body: AddressLotCreate }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/networking/address-lot`, @@ -4774,7 +4774,7 @@ export class Api extends HttpClient { */ networkingAddressLotDelete: ( { path }: { path: NetworkingAddressLotDeletePathParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/networking/address-lot/${path.addressLot}`, @@ -4793,7 +4793,7 @@ export class Api extends HttpClient { path: NetworkingAddressLotBlockListPathParams; query?: NetworkingAddressLotBlockListQueryParams; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/networking/address-lot/${path.addressLot}/blocks`, @@ -4807,7 +4807,7 @@ export class Api extends HttpClient { */ networkingLoopbackAddressList: ( { query = {} }: { query?: NetworkingLoopbackAddressListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/networking/loopback-address`, @@ -4821,7 +4821,7 @@ export class Api extends HttpClient { */ networkingLoopbackAddressCreate: ( { body }: { body: LoopbackAddressCreate }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/networking/loopback-address`, @@ -4835,7 +4835,7 @@ export class Api extends HttpClient { */ networkingLoopbackAddressDelete: ( { path }: { path: NetworkingLoopbackAddressDeletePathParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/networking/loopback-address/${path.rackId}/${path.switchLocation}/${path.address}/${path.subnetMask}`, @@ -4848,7 +4848,7 @@ export class Api extends HttpClient { */ networkingSwitchPortSettingsList: ( { query = {} }: { query?: NetworkingSwitchPortSettingsListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/networking/switch-port-settings`, @@ -4862,7 +4862,7 @@ export class Api extends HttpClient { */ networkingSwitchPortSettingsCreate: ( { body }: { body: SwitchPortSettingsCreate }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/networking/switch-port-settings`, @@ -4876,7 +4876,7 @@ export class Api extends HttpClient { */ networkingSwitchPortSettingsDelete: ( { query = {} }: { query?: NetworkingSwitchPortSettingsDeleteQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/networking/switch-port-settings`, @@ -4890,7 +4890,7 @@ export class Api extends HttpClient { */ networkingSwitchPortSettingsView: ( { path }: { path: NetworkingSwitchPortSettingsViewPathParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/networking/switch-port-settings/${path.port}`, @@ -4901,7 +4901,7 @@ export class Api extends HttpClient { /** * Fetch the top-level IAM policy */ - systemPolicyView: (_: EmptyObj, params: RequestParams = {}) => { + systemPolicyView: (_: EmptyObj, params: FetchParams = {}) => { return this.request({ path: `/v1/system/policy`, method: "GET", @@ -4913,7 +4913,7 @@ export class Api extends HttpClient { */ systemPolicyUpdate: ( { body }: { body: FleetRolePolicy }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/policy`, @@ -4927,7 +4927,7 @@ export class Api extends HttpClient { */ roleList: ( { query = {} }: { query?: RoleListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/roles`, @@ -4941,7 +4941,7 @@ export class Api extends HttpClient { */ roleView: ( { path }: { path: RoleViewPathParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/roles/${path.roleName}`, @@ -4954,7 +4954,7 @@ export class Api extends HttpClient { */ siloList: ( { query = {} }: { query?: SiloListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/silos`, @@ -4966,10 +4966,7 @@ export class Api extends HttpClient { /** * Create a silo */ - siloCreate: ( - { body }: { body: SiloCreate }, - params: RequestParams = {} - ) => { + siloCreate: ({ body }: { body: SiloCreate }, params: FetchParams = {}) => { return this.request({ path: `/v1/system/silos`, method: "POST", @@ -4982,7 +4979,7 @@ export class Api extends HttpClient { */ siloView: ( { path }: { path: SiloViewPathParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/silos/${path.silo}`, @@ -4995,7 +4992,7 @@ export class Api extends HttpClient { */ siloDelete: ( { path }: { path: SiloDeletePathParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/silos/${path.silo}`, @@ -5008,7 +5005,7 @@ export class Api extends HttpClient { */ siloPolicyView: ( { path }: { path: SiloPolicyViewPathParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/silos/${path.silo}/policy`, @@ -5024,7 +5021,7 @@ export class Api extends HttpClient { path, body, }: { path: SiloPolicyUpdatePathParams; body: SiloRolePolicy }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/silos/${path.silo}/policy`, @@ -5038,7 +5035,7 @@ export class Api extends HttpClient { */ siloUserList: ( { query = {} }: { query?: SiloUserListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/users`, @@ -5055,7 +5052,7 @@ export class Api extends HttpClient { path, query, }: { path: SiloUserViewPathParams; query?: SiloUserViewQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/users/${path.userId}`, @@ -5069,7 +5066,7 @@ export class Api extends HttpClient { */ userBuiltinList: ( { query = {} }: { query?: UserBuiltinListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/users-builtin`, @@ -5083,7 +5080,7 @@ export class Api extends HttpClient { */ userBuiltinView: ( { path }: { path: UserBuiltinViewPathParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/system/users-builtin/${path.user}`, @@ -5096,7 +5093,7 @@ export class Api extends HttpClient { */ userList: ( { query = {} }: { query?: UserListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/users`, @@ -5110,7 +5107,7 @@ export class Api extends HttpClient { */ vpcFirewallRulesView: ( { query }: { query?: VpcFirewallRulesViewQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/vpc-firewall-rules`, @@ -5130,7 +5127,7 @@ export class Api extends HttpClient { query?: VpcFirewallRulesUpdateQueryParams; body: VpcFirewallRuleUpdateParams; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/vpc-firewall-rules`, @@ -5145,7 +5142,7 @@ export class Api extends HttpClient { */ vpcRouterRouteList: ( { query = {} }: { query?: VpcRouterRouteListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/vpc-router-routes`, @@ -5162,7 +5159,7 @@ export class Api extends HttpClient { query, body, }: { query?: VpcRouterRouteCreateQueryParams; body: RouterRouteCreate }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/vpc-router-routes`, @@ -5183,7 +5180,7 @@ export class Api extends HttpClient { path: VpcRouterRouteViewPathParams; query?: VpcRouterRouteViewQueryParams; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/vpc-router-routes/${path.route}`, @@ -5205,7 +5202,7 @@ export class Api extends HttpClient { query?: VpcRouterRouteUpdateQueryParams; body: RouterRouteUpdate; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/vpc-router-routes/${path.route}`, @@ -5226,7 +5223,7 @@ export class Api extends HttpClient { path: VpcRouterRouteDeletePathParams; query?: VpcRouterRouteDeleteQueryParams; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/vpc-router-routes/${path.route}`, @@ -5240,7 +5237,7 @@ export class Api extends HttpClient { */ vpcRouterList: ( { query = {} }: { query?: VpcRouterListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/vpc-routers`, @@ -5257,7 +5254,7 @@ export class Api extends HttpClient { query, body, }: { query?: VpcRouterCreateQueryParams; body: VpcRouterCreate }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/vpc-routers`, @@ -5275,7 +5272,7 @@ export class Api extends HttpClient { path, query = {}, }: { path: VpcRouterViewPathParams; query?: VpcRouterViewQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/vpc-routers/${path.router}`, @@ -5297,7 +5294,7 @@ export class Api extends HttpClient { query?: VpcRouterUpdateQueryParams; body: VpcRouterUpdate; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/vpc-routers/${path.router}`, @@ -5318,7 +5315,7 @@ export class Api extends HttpClient { path: VpcRouterDeletePathParams; query?: VpcRouterDeleteQueryParams; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/vpc-routers/${path.router}`, @@ -5332,7 +5329,7 @@ export class Api extends HttpClient { */ vpcSubnetList: ( { query = {} }: { query?: VpcSubnetListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/vpc-subnets`, @@ -5349,7 +5346,7 @@ export class Api extends HttpClient { query, body, }: { query?: VpcSubnetCreateQueryParams; body: VpcSubnetCreate }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/vpc-subnets`, @@ -5367,7 +5364,7 @@ export class Api extends HttpClient { path, query = {}, }: { path: VpcSubnetViewPathParams; query?: VpcSubnetViewQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/vpc-subnets/${path.subnet}`, @@ -5389,7 +5386,7 @@ export class Api extends HttpClient { query?: VpcSubnetUpdateQueryParams; body: VpcSubnetUpdate; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/vpc-subnets/${path.subnet}`, @@ -5410,7 +5407,7 @@ export class Api extends HttpClient { path: VpcSubnetDeletePathParams; query?: VpcSubnetDeleteQueryParams; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/vpc-subnets/${path.subnet}`, @@ -5430,7 +5427,7 @@ export class Api extends HttpClient { path: VpcSubnetListNetworkInterfacesPathParams; query?: VpcSubnetListNetworkInterfacesQueryParams; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/vpc-subnets/${path.subnet}/network-interfaces`, @@ -5444,7 +5441,7 @@ export class Api extends HttpClient { */ vpcList: ( { query = {} }: { query?: VpcListQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/vpcs`, @@ -5458,7 +5455,7 @@ export class Api extends HttpClient { */ vpcCreate: ( { query, body }: { query?: VpcCreateQueryParams; body: VpcCreate }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/vpcs`, @@ -5476,7 +5473,7 @@ export class Api extends HttpClient { path, query = {}, }: { path: VpcViewPathParams; query?: VpcViewQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/vpcs/${path.vpc}`, @@ -5498,7 +5495,7 @@ export class Api extends HttpClient { query?: VpcUpdateQueryParams; body: VpcUpdate; }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/vpcs/${path.vpc}`, @@ -5516,7 +5513,7 @@ export class Api extends HttpClient { path, query = {}, }: { path: VpcDeletePathParams; query?: VpcDeleteQueryParams }, - params: RequestParams = {} + params: FetchParams = {} ) => { return this.request({ path: `/v1/vpcs/${path.vpc}`, diff --git a/client/http-client.ts b/client/http-client.ts index 8354ef0..f84d32b 100644 --- a/client/http-client.ts +++ b/client/http-client.ts @@ -8,26 +8,6 @@ import { camelToSnake, processResponseBody, snakeify, isNotNull } from "./util"; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type QueryParamsType = Record; - -export interface FullRequestParams extends Omit { - path: string; - query?: QueryParamsType; - body?: unknown; - baseUrl?: string; -} - -export type RequestParams = Omit< - FullRequestParams, - "body" | "method" | "query" | "path" ->; - -export interface ApiConfig { - baseUrl?: string; - baseApiParams?: Omit; -} - /** Success responses from the API */ export type ApiSuccess = { type: "success"; @@ -83,19 +63,6 @@ function encodeQueryParam(key: string, value: unknown) { )}`; } -/** Query params with null values filtered out. `"?"` included. */ -export function toQueryString(rawQuery?: QueryParamsType): string { - const qs = Object.entries(rawQuery || {}) - .filter(([_key, value]) => isNotNull(value)) - .map(([key, value]) => - Array.isArray(value) - ? value.map((item) => encodeQueryParam(key, item)).join("&") - : encodeQueryParam(key, value) - ) - .join("&"); - return qs ? "?" + qs : ""; -} - export async function handleResponse( response: Response ): Promise> { @@ -135,51 +102,78 @@ export async function handleResponse( }; } -export class HttpClient { - public baseUrl = ""; +// has to be any. the particular query params types don't like unknown +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type QueryParams = Record; - private baseApiParams: RequestParams = { - credentials: "same-origin", - headers: {}, - redirect: "follow", - referrerPolicy: "no-referrer", - }; +/** + * Params that get passed to `fetch`. This ends up as an optional second + * argument to each generated request method. Properties are a subset of + * `RequestInit`. + */ +export interface FetchParams extends Omit {} - constructor(apiConfig: ApiConfig = {}) { - Object.assign(this, apiConfig); - } +/** All arguments to `request()` */ +export interface FullParams extends FetchParams { + path: string; + query?: QueryParams; + body?: unknown; + host?: string; + method?: string; +} - private mergeRequestParams(params: RequestParams): RequestParams { - return { - ...this.baseApiParams, - ...params, - headers: { - ...this.baseApiParams.headers, - ...params.headers, - }, - }; +export interface ApiConfig { + host?: string; + baseParams?: FetchParams; +} + +export class HttpClient { + host: string; + baseParams: FetchParams; + + constructor({ host = "", baseParams = {} }: ApiConfig = {}) { + this.host = host; + this.baseParams = mergeParams( + { headers: { "Content-Type": "application/json" } }, + baseParams + ); } - public request = async ({ + public async request({ body, path, query, - baseUrl, - ...params - }: FullRequestParams): Promise> => { - const requestParams = this.mergeRequestParams(params); - - const url = (baseUrl || this.baseUrl || "") + path + toQueryString(query); - - const response = await fetch(url, { - ...requestParams, - headers: { - "Content-Type": "application/json", - ...requestParams.headers, - }, + host, + ...fetchParams + }: FullParams): Promise> { + const url = (host || this.host) + path + toQueryString(query); + const init = { + ...mergeParams(this.baseParams, fetchParams), body: JSON.stringify(snakeify(body), replacer), - }); + }; + return handleResponse(await fetch(url, init)); + } +} - return handleResponse(response); - }; +export function mergeParams(a: FetchParams, b: FetchParams): FetchParams { + // calling `new Headers()` normalizes `HeadersInit`, which could be a Headers + // object, a plain object, or an array of tuples + const headers = new Headers(a.headers); + for (const [key, value] of new Headers(b.headers).entries()) { + headers.set(key, value); + } + return { ...a, ...b, headers }; +} + +/** Query params with null values filtered out. `"?"` included. */ +export function toQueryString(rawQuery?: QueryParams): string { + const qs = Object.entries(rawQuery || {}) + .filter(([_key, value]) => isNotNull(value)) + .map(([key, value]) => + Array.isArray(value) + ? value.map((item) => encodeQueryParam(key, item)).join("&") + : encodeQueryParam(key, value) + ) + .join("&"); + return qs ? "?" + qs : ""; } diff --git a/generator/client/api.ts b/generator/client/api.ts index 8e5d731..62ac1e8 100644 --- a/generator/client/api.ts +++ b/generator/client/api.ts @@ -79,7 +79,7 @@ export function generateApi(spec: OpenAPIV3.Document) { copy("./static/util.ts"); copy("./static/http-client.ts"); - w(`import type { RequestParams } from './http-client' + w(`import type { FetchParams } from './http-client' import { HttpClient, toQueryString } from './http-client'`); w(`export type { @@ -256,7 +256,7 @@ export function generateApi(spec: OpenAPIV3.Document) { w("_: EmptyObj,"); } - w(`params: RequestParams = {}) => { + w(`params: FetchParams = {}) => { return this.request<${successType}>({ path: ${pathToTemplateStr(path)}, method: "${method.toUpperCase()}",`); diff --git a/static/http-client.test.ts b/static/http-client.test.ts index 3d48d38..ed6ccbe 100644 --- a/static/http-client.test.ts +++ b/static/http-client.test.ts @@ -8,7 +8,7 @@ * Copyright Oxide Computer Company */ -import { handleResponse } from "./http-client"; +import { handleResponse, mergeParams } from "./http-client"; import { describe, expect, it } from "vitest"; const headers = { "Content-Type": "application/json" }; @@ -73,3 +73,37 @@ describe("handleResponse", () => { expect(result.headers.get("Content-Type")).toBe("application/json"); }); }); + +describe("mergeParams", () => { + it("handles empty objects", () => { + expect(mergeParams({}, {})).toEqual({ headers: new Headers() }); + }); + + it("merges headers of different formats", () => { + const obj = { headers: { a: "b" } }; + const headers = { headers: new Headers({ c: "d" }) }; + const tuples = { headers: [["e", "f"]] as HeadersInit }; + + expect(mergeParams(obj, headers)).toEqual({ + headers: new Headers({ a: "b", c: "d" }), + }); + expect(mergeParams(obj, tuples)).toEqual({ + headers: new Headers({ a: "b", e: "f" }), + }); + expect(mergeParams(tuples, headers)).toEqual({ + headers: new Headers({ e: "f", c: "d" }), + }); + }); + + it("second arg takes precendence in case of overlap", () => { + expect( + mergeParams( + { redirect: "follow", headers: { "Content-Type": "x" } }, + { redirect: "error", headers: { "Content-Type": "y" } } + ) + ).toEqual({ + redirect: "error", + headers: new Headers({ "Content-Type": "y" }), + }); + }); +}); diff --git a/static/http-client.ts b/static/http-client.ts index 8354ef0..4a87f1d 100644 --- a/static/http-client.ts +++ b/static/http-client.ts @@ -8,26 +8,6 @@ import { camelToSnake, processResponseBody, snakeify, isNotNull } from "./util"; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type QueryParamsType = Record; - -export interface FullRequestParams extends Omit { - path: string; - query?: QueryParamsType; - body?: unknown; - baseUrl?: string; -} - -export type RequestParams = Omit< - FullRequestParams, - "body" | "method" | "query" | "path" ->; - -export interface ApiConfig { - baseUrl?: string; - baseApiParams?: Omit; -} - /** Success responses from the API */ export type ApiSuccess = { type: "success"; @@ -83,19 +63,6 @@ function encodeQueryParam(key: string, value: unknown) { )}`; } -/** Query params with null values filtered out. `"?"` included. */ -export function toQueryString(rawQuery?: QueryParamsType): string { - const qs = Object.entries(rawQuery || {}) - .filter(([_key, value]) => isNotNull(value)) - .map(([key, value]) => - Array.isArray(value) - ? value.map((item) => encodeQueryParam(key, item)).join("&") - : encodeQueryParam(key, value) - ) - .join("&"); - return qs ? "?" + qs : ""; -} - export async function handleResponse( response: Response ): Promise> { @@ -135,51 +102,82 @@ export async function handleResponse( }; } -export class HttpClient { - public baseUrl = ""; +// has to be any. the particular query params types don't like unknown +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type QueryParams = Record; - private baseApiParams: RequestParams = { - credentials: "same-origin", - headers: {}, - redirect: "follow", - referrerPolicy: "no-referrer", - }; +/** + * Params that get passed to `fetch`. This ends up as an optional second + * argument to each generated request method. Properties are a subset of + * `RequestInit`. + */ +export interface FetchParams extends Omit {} - constructor(apiConfig: ApiConfig = {}) { - Object.assign(this, apiConfig); - } +/** All arguments to `request()` */ +export interface FullParams extends FetchParams { + path: string; + query?: QueryParams; + body?: unknown; + host?: string; + method?: string; +} - private mergeRequestParams(params: RequestParams): RequestParams { - return { - ...this.baseApiParams, - ...params, - headers: { - ...this.baseApiParams.headers, - ...params.headers, - }, - }; +export interface ApiConfig { + /** + * No host means requests will be sent to the current host. This is used in + * the web console. + */ + host?: string; + baseParams?: FetchParams; +} + +export class HttpClient { + host: string; + baseParams: FetchParams; + + constructor({ host = "", baseParams = {} }: ApiConfig = {}) { + this.host = host; + this.baseParams = mergeParams( + { headers: { "Content-Type": "application/json" } }, + baseParams + ); } - public request = async ({ + public async request({ body, path, query, - baseUrl, - ...params - }: FullRequestParams): Promise> => { - const requestParams = this.mergeRequestParams(params); - - const url = (baseUrl || this.baseUrl || "") + path + toQueryString(query); - - const response = await fetch(url, { - ...requestParams, - headers: { - "Content-Type": "application/json", - ...requestParams.headers, - }, + host, + ...fetchParams + }: FullParams): Promise> { + const url = (host || this.host) + path + toQueryString(query); + const init = { + ...mergeParams(this.baseParams, fetchParams), body: JSON.stringify(snakeify(body), replacer), - }); + }; + return handleResponse(await fetch(url, init)); + } +} - return handleResponse(response); - }; +export function mergeParams(a: FetchParams, b: FetchParams): FetchParams { + // calling `new Headers()` normalizes `HeadersInit`, which could be a Headers + // object, a plain object, or an array of tuples + const headers = new Headers(a.headers); + for (const [key, value] of new Headers(b.headers).entries()) { + headers.set(key, value); + } + return { ...a, ...b, headers }; +} + +/** Query params with null values filtered out. `"?"` included. */ +export function toQueryString(rawQuery?: QueryParams): string { + const qs = Object.entries(rawQuery || {}) + .filter(([_key, value]) => isNotNull(value)) + .map(([key, value]) => + Array.isArray(value) + ? value.map((item) => encodeQueryParam(key, item)).join("&") + : encodeQueryParam(key, value) + ) + .join("&"); + return qs ? "?" + qs : ""; } diff --git a/tsconfig.json b/tsconfig.json index 344be4c..7e49dea 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ "allowSyntheticDefaultImports": true, "esModuleInterop": true, "experimentalDecorators": true, - "lib": ["es2019", "dom"], + "lib": ["es2019", "dom", "DOM.Iterable"], "module": "es2020", "moduleResolution": "node", "resolveJsonModule": true,