linear_tools/
http.rs

1use anyhow::{Result, anyhow};
2use cynic::http::ReqwestExt;
3use reqwest::Client;
4
5pub struct LinearClient {
6    client: Client,
7    url: String,
8    api_key: String,
9}
10
11impl LinearClient {
12    pub fn new(api_key: Option<String>) -> Result<Self> {
13        let api_key = match api_key.or_else(|| std::env::var("LINEAR_API_KEY").ok()) {
14            Some(k) if !k.is_empty() => k,
15            _ => return Err(anyhow!("LINEAR_API_KEY environment variable is not set")),
16        };
17
18        let url = std::env::var("LINEAR_GRAPHQL_URL")
19            .ok()
20            .filter(|u| !u.is_empty())
21            .unwrap_or_else(|| "https://api.linear.app/graphql".to_string());
22
23        let client = Client::builder().user_agent("linear-tools/0.1.0").build()?;
24
25        Ok(Self {
26            client,
27            url,
28            api_key,
29        })
30    }
31
32    pub async fn run<Q, V>(&self, op: cynic::Operation<Q, V>) -> Result<cynic::GraphQlResponse<Q>>
33    where
34        Q: serde::de::DeserializeOwned + 'static,
35        V: serde::Serialize,
36    {
37        let mut req = self
38            .client
39            .post(&self.url)
40            .header("Content-Type", "application/json");
41
42        // Auto-detect auth header type:
43        // - Personal API key: "lin_api_*" => raw Authorization header
44        // - OAuth2 token: anything else => Bearer token
45        if self.api_key.starts_with("lin_api_") {
46            req = req.header("Authorization", &self.api_key);
47        } else {
48            req = req.bearer_auth(&self.api_key);
49        }
50
51        let result = req.run_graphql(op).await;
52        result.map_err(|e| anyhow!(e))
53    }
54}