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> { 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( url: string, f: (json: unknown) => A, ): Promise> { 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 } } } }