picturarium/src/request.ts

71 lines
2 KiB
TypeScript

import { z } from "zod"
import { Result } from "./result"
// === Request Errors ===
export type RequestError =
| { tag: "networkError"; error: unknown }
| { tag: "httpError"; status: number; statusText: string }
| { tag: "jsonParseError"; error: unknown }
| { tag: "invalidResponse"; error: z.ZodError }
export const RequestError = {
toString(error: RequestError): string {
switch (error.tag) {
case "networkError":
return "Network error. Check your connection and try again."
case "httpError":
return `Server returned ${String(error.status)} ${error.statusText}.`
case "jsonParseError":
return "The server returned an invalid response."
case "invalidResponse":
return "The image data had an unexpected format."
}
},
}
// === Generic safe fetch functions ===
export async function fetchJsonSafe(url: string): Promise<Result<unknown, RequestError>> {
let response: Response
try {
response = await fetch(url)
} catch (error: unknown) {
return Result.err({ tag: "networkError", error })
}
if (!response.ok) {
return Result.err({
tag: "httpError",
status: response.status,
statusText: response.statusText,
})
} else {
try {
const json: unknown = await response.json()
return Result.ok(json)
} catch (error: unknown) {
return Result.err({ tag: "jsonParseError", error })
}
}
}
// Assumes `f` is some sort of a zod parsing function that may throw `z.ZodError`
export async function fetchJsonSafeWith<A>(
url: string,
f: (json: unknown) => A,
): Promise<Result<A, RequestError>> {
const result = await fetchJsonSafe(url)
switch (result.tag) {
case "error":
return Result.err(result.error)
case "ok":
try {
return Result.ok(f(result.value))
} catch (error: unknown) {
if (error instanceof z.ZodError) {
return Result.err({ tag: "invalidResponse", error })
} else {
throw error
}
}
}
}