#pragma once
#include <cstdint>
#include <map>
#include <optional>
#include <stdexcept>
#include <stop_token>
#include <string>
#include <vector>
namespace torlinkc {
inline constexpr const char* kUserAgent = "torlinkc (+https://git.kristoffersson.info/repos/Torlinkc)";
struct HttpResponse {
int status = 0;
std::string body;
std::map<std::string, std::string> headers; // lowercase keys
bool ok() const { return status >= 200 && status < 300; }
std::optional<std::string> header(const std::string& lowercaseName) const {
auto it = headers.find(lowercaseName);
if (it == headers.end()) return std::nullopt;
return it->second;
}
};
class HttpError : public std::runtime_error {
public:
HttpError(int status, const std::string& message) : std::runtime_error(message), status(status) {}
int status;
};
struct FetchOptions {
int retries = 5;
int baseMs = 500;
int capMs = 20000;
// Extra "Name: value" request headers (User-Agent is always sent and
// shouldn't be repeated here).
std::vector<std::string> headers;
// Default-constructed stop_token is never stop_requested(), so passing
// nothing here behaves exactly as it did before cancellation existed.
// Checked before each attempt, wired into libcurl's transfer-progress
// callback to abort mid-request, and used to cut a backoff sleep short --
// this is what makes a Phase 3 SearchAggregator able to actually cancel a
// stale search instead of just abandoning it and blocking on join() until
// curl's own timeout elapses.
std::stop_token stopToken;
};
// Parses a Retry-After header value (either delay-seconds or an HTTP-date)
// into a millisecond delay. Ported from util/net.ts::parseRetryAfter.
std::optional<std::int64_t> parseRetryAfter(const std::string& value);
// Exponential backoff with full jitter, honoring an explicit Retry-After
// floor when present. Ported from util/net.ts::backoffDelay.
std::int64_t backoffDelay(int attempt, int baseMs, int capMs, std::optional<std::int64_t> retryAfterMs = std::nullopt);
// A GET request with retry/backoff for transient failures, matching
// util/net.ts::fetchResilient's policy: retries on 408/425/429/500/502/503/504,
// honors Retry-After, and treats a 503 from ddos-guard/cloudflare as
// immediately fatal (retrying never helps against those). Phase 1 has no
// cancellation model yet, so there is no AbortSignal equivalent.
HttpResponse fetchResilient(const std::string& url, const FetchOptions& opts = {});
} // namespace torlinkc