use anyhow::{bail, Context, Result};
use reqwest::{Client, RequestBuilder, Response, StatusCode};
use serde_json::Value;
use crate::keyring_store;
pub fn build_client() -> Result<(Client, String)> {
let token = if let Ok(key) = std::env::var("TRANC_API_KEY") {
key
} else {
keyring_store::get_token()
.context("failed to read token from keyring")?
.ok_or_else(|| {
anyhow::anyhow!(
"not authenticated — run `tranc auth login` first, \
or set TRANC_API_KEY"
)
})?
};
let client = Client::builder()
.user_agent(concat!("tranc-cli/", env!("CARGO_PKG_VERSION")))
.build()
.context("failed to build HTTP client")?;
Ok((client, token))
}
pub async fn get_json(rb: RequestBuilder) -> Result<Value> {
let resp: Response = rb.send().await.context("HTTP request failed")?;
parse_response(resp).await
}
pub async fn post_json(rb: RequestBuilder) -> Result<Value> {
let resp: Response = rb.send().await.context("HTTP request failed")?;
parse_response(resp).await
}
async fn parse_response(resp: Response) -> Result<Value> {
let status = resp.status();
let body = resp.text().await.context("failed to read response body")?;
if status == StatusCode::UNAUTHORIZED {
bail!(
"authentication failed (401) — run `tranc auth login` to refresh your token, \
or set TRANC_API_KEY"
);
}
if !status.is_success() {
let detail = serde_json::from_str::<Value>(&body)
.ok()
.and_then(|v| v.get("error").or(v.get("message")).cloned())
.map(|v| v.to_string())
.unwrap_or_else(|| body.clone());
bail!("API error {status}: {detail}");
}
serde_json::from_str(&body).context("failed to parse JSON response")
}