import type { CodeTab } from "./code-tabs"; import { DOCS_BASE_URL } from "./nav-config"; /** * Generates equivalent raw-HTTP samples for a Fetcha API call in the eight languages * shown across the docs. Samples use only standard libraries (plus `requests` in Python) * so they work without the SDKs, which are not yet published. */ type Json = null | boolean | number | string | Json[] | { [k: string]: Json }; export interface ApiCall { method: "GET" | "POST" | "DELETE"; path: string; body?: Record; /** Adds an Idempotency-Key header (sessions create). */ idempotencyKey?: string; /** Short trailing statement per language showing how to read the response. */ after?: Partial>; } export type Lang = "curl" | "javascript" | "python" | "go" | "php" | "ruby" | "java" | "csharp"; export const LANG_LABEL: Record = { curl: "cURL", javascript: "JavaScript", python: "Python", go: "Go", php: "PHP", ruby: "Ruby", java: "Java", csharp: "C#", }; const ORDER: Lang[] = ["curl", "javascript", "python", "go", "php", "ruby", "java", "csharp"]; /* ------------------------------------------------------------------------------------------ */ /* Literal renderers */ /* ------------------------------------------------------------------------------------------ */ const IND = " "; function q(s: string): string { return JSON.stringify(s); } function jsonLiteral(v: Json, depth = 0): string { return JSON.stringify(v, null, 2) .split("\n") .map((l, i) => (i === 0 ? l : IND.repeat(depth) + l)) .join("\n"); } /** JS object literal (unquoted keys where valid). */ function jsLiteral(v: Json, depth = 0): string { const pad = IND.repeat(depth + 1); const end = IND.repeat(depth); if (v === null) return "null"; if (Array.isArray(v)) return v.length ? `[${v.map((x) => jsLiteral(x, depth)).join(", ")}]` : "[]"; if (typeof v === "object") { const entries = Object.entries(v); if (!entries.length) return "{}"; return `{\n${entries.map(([k, x]) => `${pad}${/^[a-zA-Z_$][\w$]*$/.test(k) ? k : q(k)}: ${jsLiteral(x, depth + 1)}`).join(",\n")},\n${end}}`; } return JSON.stringify(v); } function pyLiteral(v: Json, depth = 0): string { const pad = IND.repeat(depth + 1); const end = IND.repeat(depth); if (v === null) return "None"; if (v === true) return "True"; if (v === false) return "False"; if (Array.isArray(v)) return `[${v.map((x) => pyLiteral(x, depth)).join(", ")}]`; if (typeof v === "object") { const entries = Object.entries(v); if (!entries.length) return "{}"; return `{\n${entries.map(([k, x]) => `${pad}${q(k)}: ${pyLiteral(x, depth + 1)}`).join(",\n")},\n${end}}`; } return JSON.stringify(v); } function rubyLiteral(v: Json, depth = 0): string { const pad = IND.repeat(depth + 1); const end = IND.repeat(depth); if (v === null) return "nil"; if (Array.isArray(v)) return `[${v.map((x) => rubyLiteral(x, depth)).join(", ")}]`; if (typeof v === "object") { const entries = Object.entries(v); if (!entries.length) return "{}"; return `{\n${entries.map(([k, x]) => `${pad}${q(k)} => ${rubyLiteral(x, depth + 1)}`).join(",\n")}\n${end}}`; } return JSON.stringify(v); } function phpLiteral(v: Json, depth = 0): string { const pad = IND.repeat(depth + 1); const end = IND.repeat(depth); if (v === null) return "null"; if (Array.isArray(v)) return `[${v.map((x) => phpLiteral(x, depth)).join(", ")}]`; if (typeof v === "object") { const entries = Object.entries(v); if (!entries.length) return "[]"; return `[\n${entries.map(([k, x]) => `${pad}${phpStr(k)} => ${phpLiteral(x, depth + 1)}`).join(",\n")},\n${end}]`; } if (typeof v === "string") return phpStr(v); return String(v); } function phpStr(s: string): string { return `'${s.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`; } function goLiteral(v: Json, depth = 0): string { const pad = IND.repeat(depth + 1); const end = IND.repeat(depth); if (v === null) return "nil"; if (Array.isArray(v)) return `[]any{${v.map((x) => goLiteral(x, depth)).join(", ")}}`; if (typeof v === "object") { const entries = Object.entries(v); if (!entries.length) return "map[string]any{}"; return `map[string]any{\n${entries.map(([k, x]) => `${pad}${q(k)}: ${goLiteral(x, depth + 1)}`).join(",\n")},\n${end}}`; } return JSON.stringify(v); } function indentBlock(s: string, spaces: number): string { const pad = " ".repeat(spaces); return s .split("\n") .map((l, i) => (i === 0 ? l : pad + l)) .join("\n"); } /* ------------------------------------------------------------------------------------------ */ /* Generators */ /* ------------------------------------------------------------------------------------------ */ function curl(c: ApiCall): string { const lines = [`curl ${DOCS_BASE_URL}${c.path} \\`]; if (c.method !== "GET") lines.push(` -X ${c.method} \\`); lines.push(` -H "Authorization: Bearer $FETCHA_API_KEY" \\`); if (c.idempotencyKey) lines.push(` -H "Idempotency-Key: ${c.idempotencyKey}" \\`); if (c.body) { lines.push(` -H "Content-Type: application/json" \\`); lines.push(` -d '${JSON.stringify(c.body, null, 2).replace(/'/g, "'\\''")}'`); } else { lines[lines.length - 1] = lines[lines.length - 1]!.replace(/ \\$/, ""); } return lines.join("\n"); } function javascript(c: ApiCall): string { const headers = [` Authorization: \`Bearer \${process.env.FETCHA_API_KEY}\`,`]; if (c.body) headers.push(` "Content-Type": "application/json",`); if (c.idempotencyKey) headers.push(` "Idempotency-Key": ${q(c.idempotencyKey)},`); const parts = [`const res = await fetch(${q(`${DOCS_BASE_URL}${c.path}`)}, {`, ` method: ${q(c.method)},`, ` headers: {`, ...headers, ` },`]; if (c.body) parts.push(` body: JSON.stringify(${indentBlock(jsLiteral(c.body), 2)}),`); parts.push(`});`, ``, `const data = await res.json();`); parts.push(`if (!res.ok) throw new Error(\`\${data.error.code}: \${data.error.message} (\${data.error.request_id})\`);`); parts.push(c.after?.javascript ?? `console.log(data);`); return parts.join("\n"); } function python(c: ApiCall): string { const lines = [`import os`, `import requests`, ``]; const headers = [` "Authorization": f"Bearer {os.environ['FETCHA_API_KEY']}",`]; if (c.body) headers.push(` "Content-Type": "application/json",`); if (c.idempotencyKey) headers.push(` "Idempotency-Key": ${q(c.idempotencyKey)},`); lines.push(`res = requests.${c.method.toLowerCase()}(`, ` ${q(`${DOCS_BASE_URL}${c.path}`)},`, ` headers={`, ...headers, ` },`); if (c.body) lines.push(` json=${indentBlock(pyLiteral(c.body), 4)},`); lines.push(`)`, `data = res.json()`, `if res.status_code >= 400:`, ` err = data["error"]`, ` raise RuntimeError(f"{err['code']}: {err['message']} ({err['request_id']})")`); lines.push(c.after?.python ?? `print(data)`); return lines.join("\n"); } function go(c: ApiCall): string { const hasBody = Boolean(c.body); const imports = [`"encoding/json"`, `"fmt"`, `"net/http"`, `"os"`]; if (hasBody) imports.splice(0, 0, `"bytes"`); const lines = [`package main`, ``, `import (`, ...imports.map((i) => `\t${i}`), `)`, ``, `func main() {`]; if (hasBody) { lines.push(`\tpayload, _ := json.Marshal(${indentBlock(goLiteral(c.body as Json), 0).replace(/\n/g, "\n\t")})`); lines.push(`\treq, _ := http.NewRequest(${q(c.method)}, ${q(`${DOCS_BASE_URL}${c.path}`)}, bytes.NewReader(payload))`); lines.push(`\treq.Header.Set("Content-Type", "application/json")`); } else { lines.push(`\treq, _ := http.NewRequest(${q(c.method)}, ${q(`${DOCS_BASE_URL}${c.path}`)}, nil)`); } lines.push(`\treq.Header.Set("Authorization", "Bearer "+os.Getenv("FETCHA_API_KEY"))`); if (c.idempotencyKey) lines.push(`\treq.Header.Set("Idempotency-Key", ${q(c.idempotencyKey)})`); lines.push( ``, `\tres, err := http.DefaultClient.Do(req)`, `\tif err != nil {`, `\t\tpanic(err)`, `\t}`, `\tdefer res.Body.Close()`, ``, `\tvar data map[string]any`, `\tjson.NewDecoder(res.Body).Decode(&data)`, `\tif res.StatusCode >= 400 {`, `\t\tfmt.Println("error:", data["error"])`, `\t\treturn`, `\t}`, c.after?.go ?? `\tfmt.Println(data)`, `}`, ); return lines.join("\n"); } function php(c: ApiCall): string { const lines = [` true,`, ` CURLOPT_CUSTOMREQUEST => ${phpStr(c.method)},`, ` CURLOPT_HTTPHEADER => [`, ...headers, ` ],`); if (c.body) lines.push(` CURLOPT_POSTFIELDS => json_encode(${indentBlock(phpLiteral(c.body), 2)}),`); lines.push(`]);`, ``, `$raw = curl_exec($ch);`, `$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);`, `curl_close($ch);`, ``, `$data = json_decode($raw, true);`, `if ($status >= 400) {`, ` throw new RuntimeException($data['error']['code'] . ': ' . $data['error']['message'] . ' (' . $data['error']['request_id'] . ')');`, `}`); lines.push(c.after?.php ?? `print_r($data);`); return lines.join("\n"); } function ruby(c: ApiCall): string { const klass = c.method === "GET" ? "Get" : c.method === "POST" ? "Post" : "Delete"; const lines = [`require "net/http"`, `require "json"`, ``, `uri = URI(${q(`${DOCS_BASE_URL}${c.path}`)})`, `req = Net::HTTP::${klass}.new(uri)`, `req["Authorization"] = "Bearer #{ENV.fetch("FETCHA_API_KEY")}"`]; if (c.body) { lines.push(`req["Content-Type"] = "application/json"`); } if (c.idempotencyKey) lines.push(`req["Idempotency-Key"] = ${q(c.idempotencyKey)}`); if (c.body) lines.push(`req.body = ${indentBlock(rubyLiteral(c.body), 0)}.to_json`); lines.push(``, `res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }`, `data = JSON.parse(res.body)`, `if res.code.to_i >= 400`, ` err = data["error"]`, ` raise "#{err["code"]}: #{err["message"]} (#{err["request_id"]})"`, `end`); lines.push(c.after?.ruby ?? `puts data`); return lines.join("\n"); } function java(c: ApiCall): string { const lines = [`import java.net.URI;`, `import java.net.http.HttpClient;`, `import java.net.http.HttpRequest;`, `import java.net.http.HttpResponse;`, ``, `public class FetchaExample {`, ` public static void main(String[] args) throws Exception {`, ` String apiKey = System.getenv("FETCHA_API_KEY");`]; if (c.body) { lines.push(` String body = """`, ...jsonLiteral(c.body).split("\n").map((l) => ` ${l}`), ` """;`); } lines.push(``, ` HttpRequest.Builder builder = HttpRequest.newBuilder()`, ` .uri(URI.create(${q(`${DOCS_BASE_URL}${c.path}`)}))`, ` .header("Authorization", "Bearer " + apiKey)`); if (c.body) lines.push(` .header("Content-Type", "application/json")`); if (c.idempotencyKey) lines.push(` .header("Idempotency-Key", ${q(c.idempotencyKey)})`); if (c.method === "GET") lines.push(` .GET();`); else if (c.method === "DELETE") lines.push(` .DELETE();`); else lines.push(` .POST(HttpRequest.BodyPublishers.ofString(body));`); lines.push( ``, ` HttpResponse res = HttpClient.newHttpClient()`, ` .send(builder.build(), HttpResponse.BodyHandlers.ofString());`, ``, ` // Parse res.body() with your JSON library (Jackson, Gson, ...).`, ` // Errors have the shape {"error": {"code", "message", "request_id"}}.`, ` System.out.println(res.statusCode());`, c.after?.java ?? ` System.out.println(res.body());`, ` }`, `}`, ); return lines.join("\n"); } function csharp(c: ApiCall): string { const lines = [`using System.Net.Http;`, `using System.Net.Http.Headers;`, `using System.Text;`, `using System.Text.Json;`, ``, `var apiKey = Environment.GetEnvironmentVariable("FETCHA_API_KEY");`, `using var http = new HttpClient();`, `http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);`]; if (c.idempotencyKey) lines.push(`http.DefaultRequestHeaders.Add("Idempotency-Key", ${q(c.idempotencyKey)});`); lines.push(``); if (c.body) { lines.push(`var body = """`, ...jsonLiteral(c.body).split("\n").map((l) => ` ${l}`), ` """;`, `var content = new StringContent(body, Encoding.UTF8, "application/json");`); } const url = q(`${DOCS_BASE_URL}${c.path}`); if (c.method === "GET") lines.push(`var res = await http.GetAsync(${url});`); else if (c.method === "DELETE") lines.push(`var res = await http.DeleteAsync(${url});`); else lines.push(`var res = await http.PostAsync(${url}, content);`); lines.push( `var json = await res.Content.ReadAsStringAsync();`, `using var doc = JsonDocument.Parse(json);`, ``, `if (!res.IsSuccessStatusCode)`, `{`, ` var err = doc.RootElement.GetProperty("error");`, ` throw new Exception($"{err.GetProperty("code")}: {err.GetProperty("message")} ({err.GetProperty("request_id")})");`, `}`, c.after?.csharp ?? `Console.WriteLine(doc.RootElement);`, ); return lines.join("\n"); } const GEN: Record string> = { curl, javascript, python, go, php, ruby, java, csharp }; const CODE_LANG: Record = { curl: "bash", javascript: "javascript", python: "python", go: "go", php: "php", ruby: "ruby", java: "java", csharp: "csharp", }; /** Build the eight-language tab set for an API call. */ export function apiTabs(call: ApiCall, langs: Lang[] = ORDER): CodeTab[] { return langs.map((l) => ({ label: LANG_LABEL[l], lang: CODE_LANG[l], code: GEN[l](call) })); } /** Convenience for the most common case: POST /v1/fetch with a body. */ export function fetchTabs(body: Record, after?: ApiCall["after"], langs?: Lang[]): CodeTab[] { return apiTabs({ method: "POST", path: "/v1/fetch", body, after }, langs); }