From 804d2c1f2dfcd2f85aab9aec6aa361928af09238 Mon Sep 17 00:00:00 2001 From: lideming Date: Tue, 16 Jan 2024 17:42:25 +0800 Subject: [PATCH] update HttpApiClient --- src/database/HttpApiClient.ts | 99 +++++++++++++++++++++++++++-------- src/database/HttpApiServer.ts | 11 ++-- tests/http_api.test.ts | 11 ++-- 3 files changed, 93 insertions(+), 28 deletions(-) diff --git a/src/database/HttpApiClient.ts b/src/database/HttpApiClient.ts index c994595..ff461b3 100644 --- a/src/database/HttpApiClient.ts +++ b/src/database/HttpApiClient.ts @@ -14,24 +14,49 @@ export class ClientDatabase { return this.getSet(name, type as any) as any; } + async createKvSet(name: string): Promise { + return await this.createSet(name, "kv"); + } + + async createDocSet(name: string): Promise { + return await this.createSet(name, "doc"); + } + getSet(name: string, type?: "kv"): ClientKvSet; getSet(name: string, type: "doc"): ClientDocSet; getSet(name: string, type: "kv" | "doc" = "kv") { if (type === "kv" || type === undefined) { - return new ClientKvSet(this.httpClient, name); + return this.getKvSet(name); } else if (type === "doc") { - return new ClientDocSet(this.httpClient, name); + return this.getDocSet(name); } else { throw new Error("wrong type " + type); } } + getKvSet(name: string) { + return new ClientKvSet(this.httpClient, name); + } + + getDocSet(name: string) { + return new ClientDocSet(this.httpClient, name); + } + async deleteSet(name: string, type: "kv" | "doc"): Promise { return await this.httpClient.request( "DELETE", url`/sets/${type}:${name}`, ); } + + async deleteKvSet(name: string): Promise { + return await this.deleteSet(name, "kv"); + } + + async deleteDocSet(name: string): Promise { + return await this.deleteSet(name, "doc"); + } + async getObjects(): Promise< { name: string; @@ -51,8 +76,12 @@ export class ClientDatabase { export class ClientKvSet { constructor(readonly httpClient: HttpClient, readonly name: string) {} - exists(): Promise { - throw new Error("Method not implemented."); + async exists(): Promise { + const { status } = await this.httpClient.requestReturnStatus( + "HEAD", + url`/sets/kv:${this.name}`, + ); + return status == 200; } async get(key: SetKeyType): Promise { return await this.httpClient.request( @@ -60,18 +89,19 @@ export class ClientKvSet { url`/sets/kv:${this.name}/${JSON.stringify(key)}`, ); } - async set(key: SetKeyType, value: SetValueType): Promise { - await this.httpClient.request( + async set(key: SetKeyType, value: SetValueType): Promise { + return await this.httpClient.request( "PUT", url`/sets/kv:${this.name}/${JSON.stringify(key)}`, value, ); } - async delete(key: SetKeyType): Promise { - await this.httpClient.request( + async delete(key: SetKeyType): Promise { + const { status } = await this.httpClient.requestReturnStatus( "DELETE", url`/sets/kv:${this.name}/${JSON.stringify(key)}`, ); + return status == 200; } async getAll(): Promise< { @@ -105,10 +135,13 @@ export class ClientKvSet { export class ClientDocSet { constructor(readonly httpClient: HttpClient, readonly name: string) {} - get setId() { - return `doc:${this.name}`; + async exists(): Promise { + const { status } = await this.httpClient.requestReturnStatus( + "HEAD", + url`/sets/doc:${this.name}`, + ); + return status == 200; } - async getCount(): Promise { return await this.httpClient.request( "GET", @@ -130,16 +163,17 @@ export class ClientDocSet { } async upsert(doc: any): Promise { return await this.httpClient.request( - "POST", - url`/sets/doc:${this.name}/${JSON.stringify(doc.id)}?insert`, + "PUT", + url`/sets/doc:${this.name}/${JSON.stringify(doc.id)}`, doc, ); } async delete(id: unknown): Promise { - return await this.httpClient.request( + const { status } = await this.httpClient.requestReturnStatus( "DELETE", url`/sets/doc:${this.name}/${JSON.stringify(id)}`, ); + return status == 200; } async getIds(): Promise { return await this.httpClient.request( @@ -175,6 +209,22 @@ export class HttpClient { method: "GET" | "POST" | "PUT" | "DELETE", url: string, body?: any, + ) { + const { status, body: resBody } = await this.requestReturnStatus( + method, + url, + body, + ); + if (status < 200 || status >= 300) { + throw new HTTPError(status, resBody); + } + return resBody; + } + + async requestReturnStatus( + method: "GET" | "POST" | "PUT" | "DELETE" | "HEAD", + url: string, + body?: any, ) { const { fetch = defaultFetch, baseUrl = "", token } = this.options; const headers: any = {}; @@ -190,14 +240,19 @@ export class HttpClient { if (resp.headers.get("content-type")?.startsWith("application/json")) { json = await resp.json(); } - if (!resp.ok) { - if (json) { - throw new Error(`API: ${json.error}`); - } else { - throw new Error(`HTTP status (${resp.status}) ${await resp.text()}`); - } - } - return json; + return { + status: resp.status, + body: json !== undefined ? json : await resp.text(), + }; + } +} + +class HTTPError extends Error { + constructor(readonly httpStatus: number, readonly httpBody: any) { + const bodyString = typeof httpBody == "object" + ? JSON.stringify(httpBody) + : httpBody; + super(`HTTP status (${httpStatus}): ${bodyString}`); } } diff --git a/src/database/HttpApiServer.ts b/src/database/HttpApiServer.ts index e266b25..0634202 100644 --- a/src/database/HttpApiServer.ts +++ b/src/database/HttpApiServer.ts @@ -91,6 +91,12 @@ export class HttpApiHanlder { } else if (req.method == "DELETE") { // Delete a set return await this.db.deleteSet(setname, settype); + } else if (req.method == "HEAD") { + // Check if the set exists + // this.getSet() throws 404 if the set not found + //@ts-ignore + await this.getSet(setname, settype); + return; } } if (settype == "kv") { @@ -104,8 +110,7 @@ export class HttpApiHanlder { } else if (req.method == "PUT") { // Set a key-value pair const set = await this.getSet(setname, settype); - await set.set(key, await req.json()); - return; + return await set.set(key, await req.json()); } else if (req.method == "DELETE") { // Delete a key const set = await this.getSet(setname, settype); @@ -215,7 +220,7 @@ export class HttpApiHanlder { private async getSet(name: string, type: "kv"): Promise; private async getSet(name: string, type: "doc"): Promise; - private async getSet(name: string, type: string) { + private async getSet(name: string, type: "kv" | "doc") { const set = this.db.getSet(name, type as any) as any; if (!await set.exists()) throw new ApiError(404, `set not found`); return set; diff --git a/tests/http_api.test.ts b/tests/http_api.test.ts index 1fd387f..331ecda 100644 --- a/tests/http_api.test.ts +++ b/tests/http_api.test.ts @@ -122,13 +122,18 @@ for (const RUN_IN_WORKER of [false, true]) { testOptions, async () => { await testApi("POST", "/sets/kv:test"); - await testApi("PUT", `/sets/kv:test/${jsonUri("key1")}`, "value1"); + await testApi("PUT", `/sets/kv:test/${jsonUri("key1")}`, "value1", true); await testApi("GET", `/sets/kv:test/${jsonUri("key1")}`, "value1"); await testApi("GET", `/sets/kv:test/?count`, 1); - await testApi("PUT", `/sets/kv:test/${jsonUri(123)}`, 456); + await testApi("PUT", `/sets/kv:test/${jsonUri(123)}`, 456, true); await testApi("GET", `/sets/kv:test/${jsonUri(123)}`, 456); await testApi("GET", `/sets/kv:test/?count`, 2); - await testApi("PUT", `/sets/kv:test/${jsonUri("with/ & %")}`, "okay?"); + await testApi( + "PUT", + `/sets/kv:test/${jsonUri("with/ & %")}`, + "okay?", + true, + ); await testApi("GET", `/sets/kv:test/${jsonUri("with/ & %")}`, "okay?"); await testApi("GET", `/sets/kv:test/?count`, 3); await testApi("DELETE", `/sets/kv:test/${jsonUri("with/ & %")}`);