From c3930f1985cc5dd134e495e0854b95232e7c729a Mon Sep 17 00:00:00 2001 From: David Crespo Date: Sat, 22 Nov 2025 13:29:23 -0600 Subject: [PATCH] move HttpClient into Api --- oxide-api/src/Api.ts | 59 ++++++++++++++++--- oxide-api/src/http-client.ts | 48 +-------------- oxide-openapi-gen-ts/src/client/api.ts | 52 ++++++++++++++-- .../src/client/static/http-client.ts | 48 +-------------- 4 files changed, 103 insertions(+), 104 deletions(-) diff --git a/oxide-api/src/Api.ts b/oxide-api/src/Api.ts index b7b8146..c281b78 100644 --- a/oxide-api/src/Api.ts +++ b/oxide-api/src/Api.ts @@ -8,15 +8,16 @@ /* eslint-disable */ -import type { FetchParams } from "./http-client"; -import { HttpClient, toQueryString } from "./http-client"; - -export type { - ApiConfig, - ApiResult, - ErrorBody, - ErrorResult, +import type { FetchParams, FullParams, ApiResult } from "./http-client"; +import { + dateReplacer, + handleResponse, + mergeParams, + toQueryString, } from "./http-client"; +import { snakeify } from "./util"; + +export type { ApiResult, ErrorBody, ErrorResult } from "./http-client"; /** * An IPv4 subnet @@ -6668,7 +6669,47 @@ export interface WebhookSecretsDeletePathParams { } type EmptyObj = Record; -export class Api extends HttpClient { +export interface ApiConfig { + /** + * No host means requests will be sent to the current host. This is used in + * the web console. + */ + host?: string; + token?: string; + baseParams?: FetchParams; +} + +export class Api { + host: string; + token?: string; + baseParams: FetchParams; + + constructor({ host = "", baseParams = {}, token }: ApiConfig = {}) { + this.host = host; + this.token = token; + + const headers = new Headers({ "Content-Type": "application/json" }); + if (token) { + headers.append("Authorization", `Bearer ${token}`); + } + this.baseParams = mergeParams({ headers }, baseParams); + } + + public async request({ + body, + path, + query, + host, + ...fetchParams + }: FullParams): Promise> { + const url = (host || this.host) + path + toQueryString(query); + const init = { + ...mergeParams(this.baseParams, fetchParams), + body: JSON.stringify(snakeify(body), dateReplacer), + }; + return handleResponse(await fetch(url, init)); + } + methods = { /** * Start an OAuth 2.0 Device Authorization Grant diff --git a/oxide-api/src/http-client.ts b/oxide-api/src/http-client.ts index b820c3b..13838da 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, processResponseBody, isNotNull } from "./util"; /** Success responses from the API */ export type ApiSuccess = { @@ -47,7 +47,7 @@ export type ApiResult = ApiSuccess | ErrorResult; * body and query params. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any -function replacer(_key: string, value: any) { +export function dateReplacer(_key: string, value: any) { if (value instanceof Date) { return value.toISOString(); } @@ -56,7 +56,7 @@ function replacer(_key: string, value: any) { function encodeQueryParam(key: string, value: unknown) { return `${encodeURIComponent(camelToSnake(key))}=${encodeURIComponent( - replacer(key, value), + dateReplacer(key, value), )}`; } @@ -117,48 +117,6 @@ export interface FullParams extends FetchParams { method?: string; } -export interface ApiConfig { - /** - * No host means requests will be sent to the current host. This is used in - * the web console. - */ - host?: string; - token?: string; - baseParams?: FetchParams; -} - -export class HttpClient { - host: string; - token?: string; - baseParams: FetchParams; - - constructor({ host = "", baseParams = {}, token }: ApiConfig = {}) { - this.host = host; - this.token = token; - - const headers = new Headers({ "Content-Type": "application/json" }); - if (token) { - headers.append("Authorization", `Bearer ${token}`); - } - this.baseParams = mergeParams({ headers }, baseParams); - } - - public async request({ - body, - path, - query, - 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)); - } -} - 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 diff --git a/oxide-openapi-gen-ts/src/client/api.ts b/oxide-openapi-gen-ts/src/client/api.ts index 5eb3dfc..917737a 100644 --- a/oxide-openapi-gen-ts/src/client/api.ts +++ b/oxide-openapi-gen-ts/src/client/api.ts @@ -126,10 +126,11 @@ export function generateApi(spec: OpenAPIV3.Document, destDir: string) { w(`/* eslint-disable */ - import type { FetchParams } from './http-client' - import { HttpClient, toQueryString } from './http-client' + import type { FetchParams, FullParams, ApiResult } from "./http-client"; + import { dateReplacer, handleResponse, mergeParams, toQueryString } from './http-client' + import { snakeify } from './util' - export type { ApiConfig, ApiResult, ErrorBody, ErrorResult, } from './http-client' + export type { ApiResult, ErrorBody, ErrorResult } from './http-client' `); const schemaNames = getSortedSchemas(spec); @@ -169,8 +170,49 @@ export function generateApi(spec: OpenAPIV3.Document, destDir: string) { w("type EmptyObj = Record;"); - w(`export class Api extends HttpClient { - methods = {`); + w(`export interface ApiConfig { + /** + * No host means requests will be sent to the current host. This is used in + * the web console. + */ + host?: string; + token?: string; + baseParams?: FetchParams; + } + + export class Api { + host: string; + token?: string; + baseParams: FetchParams; + + + constructor({ host = "", baseParams = {}, token }: ApiConfig = {}) { + this.host = host; + this.token = token; + + const headers = new Headers({ "Content-Type": "application/json" }); + if (token) { + headers.append("Authorization", \`Bearer \${token}\`); + } + this.baseParams = mergeParams({ headers }, baseParams); + } + + public async request({ + body, + path, + query, + host, + ...fetchParams + }: FullParams): Promise> { + const url = (host || this.host) + path + toQueryString(query); + const init = { + ...mergeParams(this.baseParams, fetchParams), + body: JSON.stringify(snakeify(body), dateReplacer), + }; + return handleResponse(await fetch(url, init)); + } + + methods = {`); for (const { conf, opId, method, path } of iterPathConfig(spec.paths)) { // websockets handled in the next loop 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..121ec35 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, processResponseBody, isNotNull } from "./util"; /** Success responses from the API */ export type ApiSuccess = { @@ -47,7 +47,7 @@ export type ApiResult = ApiSuccess | ErrorResult; * body and query params. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any -function replacer(_key: string, value: any) { +export function dateReplacer(_key: string, value: any) { if (value instanceof Date) { return value.toISOString(); } @@ -56,7 +56,7 @@ function replacer(_key: string, value: any) { function encodeQueryParam(key: string, value: unknown) { return `${encodeURIComponent(camelToSnake(key))}=${encodeURIComponent( - replacer(key, value) + dateReplacer(key, value) )}`; } @@ -117,48 +117,6 @@ export interface FullParams extends FetchParams { method?: string; } -export interface ApiConfig { - /** - * No host means requests will be sent to the current host. This is used in - * the web console. - */ - host?: string; - token?: string; - baseParams?: FetchParams; -} - -export class HttpClient { - host: string; - token?: string; - baseParams: FetchParams; - - constructor({ host = "", baseParams = {}, token }: ApiConfig = {}) { - this.host = host; - this.token = token; - - const headers = new Headers({ "Content-Type": "application/json" }); - if (token) { - headers.append("Authorization", `Bearer ${token}`); - } - this.baseParams = mergeParams({ headers }, baseParams); - } - - public async request({ - body, - path, - query, - 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)); - } -} - 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