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
11pub fn extract_data<Q>(resp: cynic::GraphQlResponse<Q>) -> Result<Q> {
13 if let Some(errors) = resp.errors
14 && !errors.is_empty()
15 {
16 let mut parts = Vec::new();
17 for e in errors {
18 let path = e.path.unwrap_or_default();
19 let path_str = if path.is_empty() {
20 String::new()
21 } else {
22 let p = path
23 .into_iter()
24 .map(|v| match v {
25 cynic::GraphQlErrorPathSegment::Field(f) => f,
26 cynic::GraphQlErrorPathSegment::Index(i) => i.to_string(),
27 })
28 .collect::<Vec<_>>()
29 .join(".");
30 format!(" (path: {})", p)
31 };
32 parts.push(format!("{}{}", e.message, path_str));
33 }
34 return Err(anyhow!(
35 "GraphQL errors from Linear:\n- {}",
36 parts.join("\n- ")
37 ));
38 }
39
40 match resp.data {
41 Some(data) => Ok(data),
42 None => Err(anyhow!("No data returned from Linear")),
43 }
44}
45
46impl LinearClient {
47 pub fn new(api_key: Option<String>) -> Result<Self> {
48 let api_key = match api_key.or_else(|| std::env::var("LINEAR_API_KEY").ok()) {
49 Some(k) if !k.is_empty() => k,
50 _ => return Err(anyhow!("LINEAR_API_KEY environment variable is not set")),
51 };
52
53 let url = std::env::var("LINEAR_GRAPHQL_URL")
54 .ok()
55 .filter(|u| !u.is_empty())
56 .unwrap_or_else(|| "https://api.linear.app/graphql".to_string());
57
58 let client = Client::builder().user_agent("linear-tools/0.1.0").build()?;
59
60 Ok(Self {
61 client,
62 url,
63 api_key,
64 })
65 }
66
67 pub async fn run<Q, V>(&self, op: cynic::Operation<Q, V>) -> Result<cynic::GraphQlResponse<Q>>
68 where
69 Q: serde::de::DeserializeOwned + 'static,
70 V: serde::Serialize,
71 {
72 let mut req = self
73 .client
74 .post(&self.url)
75 .header("Content-Type", "application/json");
76
77 if self.api_key.starts_with("lin_api_") {
81 req = req.header("Authorization", &self.api_key);
82 } else {
83 req = req.bearer_auth(&self.api_key);
84 }
85
86 let result = req.run_graphql(op).await;
87 result.map_err(|e| anyhow!(e))
88 }
89}