TypeScript 97.5%
SQL 1.4%
Python 0.8%
1import type { CodeTab } from "./code-tabs";2import { DOCS_BASE_URL } from "./nav-config";34/**5 * Generates equivalent raw-HTTP samples for a Fetcha API call in the eight languages6 * shown across the docs. Samples use only standard libraries (plus `requests` in Python)7 * so they work without the SDKs, which are not yet published.8 */910type Json = null | boolean | number | string | Json[] | { [k: string]: Json };1112export interface ApiCall {13 method: "GET" | "POST" | "DELETE";14 path: string;15 body?: Record<string, Json>;16 /** Adds an Idempotency-Key header (sessions create). */17 idempotencyKey?: string;18 /** Short trailing statement per language showing how to read the response. */19 after?: Partial<Record<Lang, string>>;20}2122export type Lang = "curl" | "javascript" | "python" | "go" | "php" | "ruby" | "java" | "csharp";2324export const LANG_LABEL: Record<Lang, string> = {25 curl: "cURL",26 javascript: "JavaScript",27 python: "Python",28 go: "Go",29 php: "PHP",30 ruby: "Ruby",31 java: "Java",32 csharp: "C#",33};3435const ORDER: Lang[] = ["curl", "javascript", "python", "go", "php", "ruby", "java", "csharp"];3637/* ------------------------------------------------------------------------------------------ */38/* Literal renderers */39/* ------------------------------------------------------------------------------------------ */4041const IND = " ";4243function q(s: string): string {44 return JSON.stringify(s);45}4647function jsonLiteral(v: Json, depth = 0): string {48 return JSON.stringify(v, null, 2)49 .split("\n")50 .map((l, i) => (i === 0 ? l : IND.repeat(depth) + l))51 .join("\n");52}5354/** JS object literal (unquoted keys where valid). */55function jsLiteral(v: Json, depth = 0): string {56 const pad = IND.repeat(depth + 1);57 const end = IND.repeat(depth);58 if (v === null) return "null";59 if (Array.isArray(v)) return v.length ? `[${v.map((x) => jsLiteral(x, depth)).join(", ")}]` : "[]";60 if (typeof v === "object") {61 const entries = Object.entries(v);62 if (!entries.length) return "{}";63 return `{\n${entries.map(([k, x]) => `${pad}${/^[a-zA-Z_$][\w$]*$/.test(k) ? k : q(k)}: ${jsLiteral(x, depth + 1)}`).join(",\n")},\n${end}}`;64 }65 return JSON.stringify(v);66}6768function pyLiteral(v: Json, depth = 0): string {69 const pad = IND.repeat(depth + 1);70 const end = IND.repeat(depth);71 if (v === null) return "None";72 if (v === true) return "True";73 if (v === false) return "False";74 if (Array.isArray(v)) return `[${v.map((x) => pyLiteral(x, depth)).join(", ")}]`;75 if (typeof v === "object") {76 const entries = Object.entries(v);77 if (!entries.length) return "{}";78 return `{\n${entries.map(([k, x]) => `${pad}${q(k)}: ${pyLiteral(x, depth + 1)}`).join(",\n")},\n${end}}`;79 }80 return JSON.stringify(v);81}8283function rubyLiteral(v: Json, depth = 0): string {84 const pad = IND.repeat(depth + 1);85 const end = IND.repeat(depth);86 if (v === null) return "nil";87 if (Array.isArray(v)) return `[${v.map((x) => rubyLiteral(x, depth)).join(", ")}]`;88 if (typeof v === "object") {89 const entries = Object.entries(v);90 if (!entries.length) return "{}";91 return `{\n${entries.map(([k, x]) => `${pad}${q(k)} => ${rubyLiteral(x, depth + 1)}`).join(",\n")}\n${end}}`;92 }93 return JSON.stringify(v);94}9596function phpLiteral(v: Json, depth = 0): string {97 const pad = IND.repeat(depth + 1);98 const end = IND.repeat(depth);99 if (v === null) return "null";100 if (Array.isArray(v)) return `[${v.map((x) => phpLiteral(x, depth)).join(", ")}]`;101 if (typeof v === "object") {102 const entries = Object.entries(v);103 if (!entries.length) return "[]";104 return `[\n${entries.map(([k, x]) => `${pad}${phpStr(k)} => ${phpLiteral(x, depth + 1)}`).join(",\n")},\n${end}]`;105 }106 if (typeof v === "string") return phpStr(v);107 return String(v);108}109110function phpStr(s: string): string {111 return `'${s.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;112}113114function goLiteral(v: Json, depth = 0): string {115 const pad = IND.repeat(depth + 1);116 const end = IND.repeat(depth);117 if (v === null) return "nil";118 if (Array.isArray(v)) return `[]any{${v.map((x) => goLiteral(x, depth)).join(", ")}}`;119 if (typeof v === "object") {120 const entries = Object.entries(v);121 if (!entries.length) return "map[string]any{}";122 return `map[string]any{\n${entries.map(([k, x]) => `${pad}${q(k)}: ${goLiteral(x, depth + 1)}`).join(",\n")},\n${end}}`;123 }124 return JSON.stringify(v);125}126127function indentBlock(s: string, spaces: number): string {128 const pad = " ".repeat(spaces);129 return s130 .split("\n")131 .map((l, i) => (i === 0 ? l : pad + l))132 .join("\n");133}134135/* ------------------------------------------------------------------------------------------ */136/* Generators */137/* ------------------------------------------------------------------------------------------ */138139function curl(c: ApiCall): string {140 const lines = [`curl ${DOCS_BASE_URL}${c.path} \\`];141 if (c.method !== "GET") lines.push(` -X ${c.method} \\`);142 lines.push(` -H "Authorization: Bearer $FETCHA_API_KEY" \\`);143 if (c.idempotencyKey) lines.push(` -H "Idempotency-Key: ${c.idempotencyKey}" \\`);144 if (c.body) {145 lines.push(` -H "Content-Type: application/json" \\`);146 lines.push(` -d '${JSON.stringify(c.body, null, 2).replace(/'/g, "'\\''")}'`);147 } else {148 lines[lines.length - 1] = lines[lines.length - 1]!.replace(/ \\$/, "");149 }150 return lines.join("\n");151}152153function javascript(c: ApiCall): string {154 const headers = [` Authorization: \`Bearer \${process.env.FETCHA_API_KEY}\`,`];155 if (c.body) headers.push(` "Content-Type": "application/json",`);156 if (c.idempotencyKey) headers.push(` "Idempotency-Key": ${q(c.idempotencyKey)},`);157 const parts = [`const res = await fetch(${q(`${DOCS_BASE_URL}${c.path}`)}, {`, ` method: ${q(c.method)},`, ` headers: {`, ...headers, ` },`];158 if (c.body) parts.push(` body: JSON.stringify(${indentBlock(jsLiteral(c.body), 2)}),`);159 parts.push(`});`, ``, `const data = await res.json();`);160 parts.push(`if (!res.ok) throw new Error(\`\${data.error.code}: \${data.error.message} (\${data.error.request_id})\`);`);161 parts.push(c.after?.javascript ?? `console.log(data);`);162 return parts.join("\n");163}164165function python(c: ApiCall): string {166 const lines = [`import os`, `import requests`, ``];167 const headers = [` "Authorization": f"Bearer {os.environ['FETCHA_API_KEY']}",`];168 if (c.body) headers.push(` "Content-Type": "application/json",`);169 if (c.idempotencyKey) headers.push(` "Idempotency-Key": ${q(c.idempotencyKey)},`);170 lines.push(`res = requests.${c.method.toLowerCase()}(`, ` ${q(`${DOCS_BASE_URL}${c.path}`)},`, ` headers={`, ...headers, ` },`);171 if (c.body) lines.push(` json=${indentBlock(pyLiteral(c.body), 4)},`);172 lines.push(`)`, `data = res.json()`, `if res.status_code >= 400:`, ` err = data["error"]`, ` raise RuntimeError(f"{err['code']}: {err['message']} ({err['request_id']})")`);173 lines.push(c.after?.python ?? `print(data)`);174 return lines.join("\n");175}176177function go(c: ApiCall): string {178 const hasBody = Boolean(c.body);179 const imports = [`"encoding/json"`, `"fmt"`, `"net/http"`, `"os"`];180 if (hasBody) imports.splice(0, 0, `"bytes"`);181 const lines = [`package main`, ``, `import (`, ...imports.map((i) => `\t${i}`), `)`, ``, `func main() {`];182 if (hasBody) {183 lines.push(`\tpayload, _ := json.Marshal(${indentBlock(goLiteral(c.body as Json), 0).replace(/\n/g, "\n\t")})`);184 lines.push(`\treq, _ := http.NewRequest(${q(c.method)}, ${q(`${DOCS_BASE_URL}${c.path}`)}, bytes.NewReader(payload))`);185 lines.push(`\treq.Header.Set("Content-Type", "application/json")`);186 } else {187 lines.push(`\treq, _ := http.NewRequest(${q(c.method)}, ${q(`${DOCS_BASE_URL}${c.path}`)}, nil)`);188 }189 lines.push(`\treq.Header.Set("Authorization", "Bearer "+os.Getenv("FETCHA_API_KEY"))`);190 if (c.idempotencyKey) lines.push(`\treq.Header.Set("Idempotency-Key", ${q(c.idempotencyKey)})`);191 lines.push(192 ``,193 `\tres, err := http.DefaultClient.Do(req)`,194 `\tif err != nil {`,195 `\t\tpanic(err)`,196 `\t}`,197 `\tdefer res.Body.Close()`,198 ``,199 `\tvar data map[string]any`,200 `\tjson.NewDecoder(res.Body).Decode(&data)`,201 `\tif res.StatusCode >= 400 {`,202 `\t\tfmt.Println("error:", data["error"])`,203 `\t\treturn`,204 `\t}`,205 c.after?.go ?? `\tfmt.Println(data)`,206 `}`,207 );208 return lines.join("\n");209}210211function php(c: ApiCall): string {212 const lines = [`<?php`, ``, `$ch = curl_init(${phpStr(`${DOCS_BASE_URL}${c.path}`)});`];213 const headers = [` 'Authorization: Bearer ' . getenv('FETCHA_API_KEY'),`];214 if (c.body) headers.push(` 'Content-Type: application/json',`);215 if (c.idempotencyKey) headers.push(` ${phpStr(`Idempotency-Key: ${c.idempotencyKey}`)},`);216 lines.push(`curl_setopt_array($ch, [`, ` CURLOPT_RETURNTRANSFER => true,`, ` CURLOPT_CUSTOMREQUEST => ${phpStr(c.method)},`, ` CURLOPT_HTTPHEADER => [`, ...headers, ` ],`);217 if (c.body) lines.push(` CURLOPT_POSTFIELDS => json_encode(${indentBlock(phpLiteral(c.body), 2)}),`);218 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'] . ')');`, `}`);219 lines.push(c.after?.php ?? `print_r($data);`);220 return lines.join("\n");221}222223function ruby(c: ApiCall): string {224 const klass = c.method === "GET" ? "Get" : c.method === "POST" ? "Post" : "Delete";225 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")}"`];226 if (c.body) {227 lines.push(`req["Content-Type"] = "application/json"`);228 }229 if (c.idempotencyKey) lines.push(`req["Idempotency-Key"] = ${q(c.idempotencyKey)}`);230 if (c.body) lines.push(`req.body = ${indentBlock(rubyLiteral(c.body), 0)}.to_json`);231 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`);232 lines.push(c.after?.ruby ?? `puts data`);233 return lines.join("\n");234}235236function java(c: ApiCall): string {237 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");`];238 if (c.body) {239 lines.push(` String body = """`, ...jsonLiteral(c.body).split("\n").map((l) => ` ${l}`), ` """;`);240 }241 lines.push(``, ` HttpRequest.Builder builder = HttpRequest.newBuilder()`, ` .uri(URI.create(${q(`${DOCS_BASE_URL}${c.path}`)}))`, ` .header("Authorization", "Bearer " + apiKey)`);242 if (c.body) lines.push(` .header("Content-Type", "application/json")`);243 if (c.idempotencyKey) lines.push(` .header("Idempotency-Key", ${q(c.idempotencyKey)})`);244 if (c.method === "GET") lines.push(` .GET();`);245 else if (c.method === "DELETE") lines.push(` .DELETE();`);246 else lines.push(` .POST(HttpRequest.BodyPublishers.ofString(body));`);247 lines.push(248 ``,249 ` HttpResponse<String> res = HttpClient.newHttpClient()`,250 ` .send(builder.build(), HttpResponse.BodyHandlers.ofString());`,251 ``,252 ` // Parse res.body() with your JSON library (Jackson, Gson, ...).`,253 ` // Errors have the shape {"error": {"code", "message", "request_id"}}.`,254 ` System.out.println(res.statusCode());`,255 c.after?.java ?? ` System.out.println(res.body());`,256 ` }`,257 `}`,258 );259 return lines.join("\n");260}261262function csharp(c: ApiCall): string {263 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);`];264 if (c.idempotencyKey) lines.push(`http.DefaultRequestHeaders.Add("Idempotency-Key", ${q(c.idempotencyKey)});`);265 lines.push(``);266 if (c.body) {267 lines.push(`var body = """`, ...jsonLiteral(c.body).split("\n").map((l) => ` ${l}`), ` """;`, `var content = new StringContent(body, Encoding.UTF8, "application/json");`);268 }269 const url = q(`${DOCS_BASE_URL}${c.path}`);270 if (c.method === "GET") lines.push(`var res = await http.GetAsync(${url});`);271 else if (c.method === "DELETE") lines.push(`var res = await http.DeleteAsync(${url});`);272 else lines.push(`var res = await http.PostAsync(${url}, content);`);273 lines.push(274 `var json = await res.Content.ReadAsStringAsync();`,275 `using var doc = JsonDocument.Parse(json);`,276 ``,277 `if (!res.IsSuccessStatusCode)`,278 `{`,279 ` var err = doc.RootElement.GetProperty("error");`,280 ` throw new Exception($"{err.GetProperty("code")}: {err.GetProperty("message")} ({err.GetProperty("request_id")})");`,281 `}`,282 c.after?.csharp ?? `Console.WriteLine(doc.RootElement);`,283 );284 return lines.join("\n");285}286287const GEN: Record<Lang, (c: ApiCall) => string> = { curl, javascript, python, go, php, ruby, java, csharp };288289const CODE_LANG: Record<Lang, CodeTab["lang"]> = {290 curl: "bash",291 javascript: "javascript",292 python: "python",293 go: "go",294 php: "php",295 ruby: "ruby",296 java: "java",297 csharp: "csharp",298};299300/** Build the eight-language tab set for an API call. */301export function apiTabs(call: ApiCall, langs: Lang[] = ORDER): CodeTab[] {302 return langs.map((l) => ({ label: LANG_LABEL[l], lang: CODE_LANG[l], code: GEN[l](call) }));303}304305/** Convenience for the most common case: POST /v1/fetch with a body. */306export function fetchTabs(body: Record<string, Json>, after?: ApiCall["after"], langs?: Lang[]): CodeTab[] {307 return apiTabs({ method: "POST", path: "/v1/fetch", body, after }, langs);308}309