leash_sdk/integrations/mod.rs
1//! Typed integration namespaces — Gmail, Calendar, Drive, Linear, plus the
2//! generic [`Provider`] escape hatch for providers without typed wrappers yet
3//! (Slack, GitHub, HubSpot, …).
4//!
5//! Mirrors `leash.integrations` in the TS / Python / Go SDKs. Every call
6//! routes through `crate::transport::Transport` which enforces the platform
7//! contract (X-API-Key + Cookie only, never `Authorization: Bearer`).
8
9mod calendar;
10mod drive;
11mod gmail;
12mod linear;
13pub mod types;
14
15pub use calendar::Calendar;
16pub use drive::Drive;
17pub use gmail::Gmail;
18pub use linear::Linear;
19
20use crate::errors::Result;
21use crate::transport::Transport;
22
23/// `leash.integrations()` — typed providers plus a generic escape hatch.
24#[derive(Debug, Clone)]
25pub struct Integrations {
26 transport: Transport,
27}
28
29impl Integrations {
30 pub(crate) fn new(transport: Transport) -> Self {
31 Self { transport }
32 }
33
34 /// Gmail provider.
35 pub fn gmail(&self) -> Gmail {
36 Gmail::new(self.transport.clone())
37 }
38
39 /// Google Calendar provider (canonical name).
40 pub fn calendar(&self) -> Calendar {
41 Calendar::new(self.transport.clone())
42 }
43
44 /// Alias of [`Self::calendar`] matching the TS / Python long-form name.
45 pub fn google_calendar(&self) -> Calendar {
46 self.calendar()
47 }
48
49 /// Google Drive provider (canonical name).
50 pub fn drive(&self) -> Drive {
51 Drive::new(self.transport.clone())
52 }
53
54 /// Alias of [`Self::drive`] matching the TS / Python long-form name.
55 pub fn google_drive(&self) -> Drive {
56 self.drive()
57 }
58
59 /// Linear provider.
60 pub fn linear(&self) -> Linear {
61 Linear::new(self.transport.clone())
62 }
63
64 /// Generic escape hatch — call any provider action by name.
65 ///
66 /// Use this for providers without typed wrappers yet (Slack, GitHub,
67 /// HubSpot, Jira, Gong, Slite, BigQuery, custom MCP servers, …).
68 ///
69 /// ```no_run
70 /// # async fn example(client: &leash_sdk::Leash) -> leash_sdk::Result<()> {
71 /// let res = client
72 /// .integrations()
73 /// .provider("slack")
74 /// .call("post_message", serde_json::json!({ "channel": "#general", "text": "hi" }))
75 /// .await?;
76 /// # let _ = res;
77 /// # Ok(()) }
78 /// ```
79 pub fn provider(&self, name: impl Into<String>) -> Provider {
80 Provider {
81 transport: self.transport.clone(),
82 name: name.into(),
83 }
84 }
85}
86
87/// Generic provider invoker — no typed shape, just a pass-through of action +
88/// body. Returned by [`Integrations::provider`].
89#[derive(Debug, Clone)]
90pub struct Provider {
91 transport: Transport,
92 name: String,
93}
94
95impl Provider {
96 /// Provider id this caller is bound to.
97 pub fn name(&self) -> &str {
98 &self.name
99 }
100
101 /// POST to `/api/integrations/{name}/{action}` with the given body and
102 /// return the raw JSON `data` field (or the raw response when there's no
103 /// envelope).
104 pub async fn call(&self, action: &str, body: serde_json::Value) -> Result<serde_json::Value> {
105 self.transport.integrations_call(&self.name, action, &body).await
106 }
107}