Skip to main content

leash_sdk/integrations/
types.rs

1//! Wire types for integration responses. Mirrors the shapes in
2//! `leash-sdk-ts/src/integrations/types.ts` and the Linear types in
3//! `leash-sdk-ts/src/integrations/providers/linear.ts`.
4//!
5//! Field names and JSON renames follow the platform wire format (camelCase),
6//! so `serde_json` round-trips through these types are zero-effort.
7
8#![allow(missing_docs)]
9
10
11use std::collections::HashMap;
12
13use serde::{Deserialize, Serialize};
14
15// ---------------------------------------------------------------------------
16// Gmail
17// ---------------------------------------------------------------------------
18
19/// A single Gmail message reference.
20#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
21pub struct GmailMessage {
22    #[serde(default)]
23    pub id: String,
24    #[serde(rename = "threadId", default)]
25    pub thread_id: String,
26}
27
28/// Paginated Gmail message list.
29#[derive(Debug, Clone, Serialize, Deserialize, Default)]
30pub struct GmailMessageList {
31    #[serde(default)]
32    pub messages: Vec<GmailMessage>,
33    #[serde(rename = "nextPageToken", default, skip_serializing_if = "Option::is_none")]
34    pub next_page_token: Option<String>,
35    #[serde(rename = "resultSizeEstimate", default, skip_serializing_if = "Option::is_none")]
36    pub result_size_estimate: Option<u64>,
37}
38
39/// A single Gmail label.
40#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
41pub struct GmailLabel {
42    #[serde(default)]
43    pub id: String,
44    #[serde(default)]
45    pub name: String,
46    #[serde(default, rename = "type")]
47    pub label_type: String,
48}
49
50/// Gmail label list.
51#[derive(Debug, Clone, Serialize, Deserialize, Default)]
52pub struct GmailLabelList {
53    #[serde(default)]
54    pub labels: Vec<GmailLabel>,
55}
56
57/// Parameters for `gmail().list_messages()`.
58#[derive(Debug, Clone, Serialize, Default)]
59pub struct GmailListParams {
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub query: Option<String>,
62    #[serde(rename = "maxResults", skip_serializing_if = "Option::is_none")]
63    pub max_results: Option<u32>,
64    #[serde(rename = "labelIds", skip_serializing_if = "Option::is_none")]
65    pub label_ids: Option<Vec<String>>,
66    #[serde(rename = "pageToken", skip_serializing_if = "Option::is_none")]
67    pub page_token: Option<String>,
68}
69
70/// Parameters for `gmail().send_message()`.
71#[derive(Debug, Clone, Serialize, Default)]
72pub struct GmailSendMessageParams {
73    pub to: String,
74    pub subject: String,
75    pub body: String,
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub cc: Option<String>,
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub bcc: Option<String>,
80}
81
82/// `format` selector for `gmail().get_message()`.
83#[derive(Debug, Clone, Copy)]
84pub enum GmailMessageFormat {
85    Full,
86    Metadata,
87    Minimal,
88    Raw,
89}
90
91impl GmailMessageFormat {
92    pub fn as_str(self) -> &'static str {
93        match self {
94            Self::Full => "full",
95            Self::Metadata => "metadata",
96            Self::Minimal => "minimal",
97            Self::Raw => "raw",
98        }
99    }
100}
101
102// ---------------------------------------------------------------------------
103// Calendar
104// ---------------------------------------------------------------------------
105
106/// Entry in the user's calendar list.
107#[derive(Debug, Clone, Serialize, Deserialize, Default)]
108pub struct CalendarListEntry {
109    #[serde(default)]
110    pub id: String,
111    #[serde(default, skip_serializing_if = "String::is_empty")]
112    pub summary: String,
113    #[serde(default, skip_serializing_if = "String::is_empty")]
114    pub description: String,
115    #[serde(rename = "timeZone", default, skip_serializing_if = "String::is_empty")]
116    pub time_zone: String,
117    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
118    pub primary: bool,
119    #[serde(rename = "backgroundColor", default, skip_serializing_if = "String::is_empty")]
120    pub background_color: String,
121    #[serde(rename = "foregroundColor", default, skip_serializing_if = "String::is_empty")]
122    pub foreground_color: String,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize, Default)]
126pub struct CalendarList {
127    #[serde(default)]
128    pub calendars: Vec<CalendarListEntry>,
129}
130
131/// Google-style date/time descriptor. Exactly one of `date_time` (timed) or
132/// `date` (all-day) should be populated.
133#[derive(Debug, Clone, Serialize, Deserialize, Default)]
134pub struct EventDateTime {
135    #[serde(rename = "dateTime", default, skip_serializing_if = "Option::is_none")]
136    pub date_time: Option<String>,
137    #[serde(default, skip_serializing_if = "Option::is_none")]
138    pub date: Option<String>,
139    #[serde(rename = "timeZone", default, skip_serializing_if = "Option::is_none")]
140    pub time_zone: Option<String>,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize, Default)]
144pub struct CalendarAttendee {
145    pub email: String,
146    #[serde(rename = "responseStatus", default, skip_serializing_if = "Option::is_none")]
147    pub response_status: Option<String>,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize, Default)]
151pub struct CalendarEvent {
152    #[serde(default, skip_serializing_if = "String::is_empty")]
153    pub id: String,
154    #[serde(default, skip_serializing_if = "String::is_empty")]
155    pub summary: String,
156    #[serde(default, skip_serializing_if = "String::is_empty")]
157    pub description: String,
158    #[serde(default, skip_serializing_if = "String::is_empty")]
159    pub location: String,
160    #[serde(default)]
161    pub start: EventDateTime,
162    #[serde(default)]
163    pub end: EventDateTime,
164    #[serde(default, skip_serializing_if = "Vec::is_empty")]
165    pub attendees: Vec<CalendarAttendee>,
166    #[serde(default, skip_serializing_if = "String::is_empty")]
167    pub status: String,
168    #[serde(rename = "htmlLink", default, skip_serializing_if = "String::is_empty")]
169    pub html_link: String,
170    #[serde(default, skip_serializing_if = "String::is_empty")]
171    pub created: String,
172    #[serde(default, skip_serializing_if = "String::is_empty")]
173    pub updated: String,
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize, Default)]
177pub struct CalendarEventList {
178    #[serde(default)]
179    pub events: Vec<CalendarEvent>,
180    #[serde(rename = "nextPageToken", default, skip_serializing_if = "Option::is_none")]
181    pub next_page_token: Option<String>,
182}
183
184#[derive(Debug, Clone, Serialize, Default)]
185pub struct CalendarListEventsParams {
186    #[serde(rename = "calendarId", skip_serializing_if = "Option::is_none")]
187    pub calendar_id: Option<String>,
188    #[serde(rename = "timeMin", skip_serializing_if = "Option::is_none")]
189    pub time_min: Option<String>,
190    #[serde(rename = "timeMax", skip_serializing_if = "Option::is_none")]
191    pub time_max: Option<String>,
192    #[serde(rename = "maxResults", skip_serializing_if = "Option::is_none")]
193    pub max_results: Option<u32>,
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub query: Option<String>,
196    #[serde(rename = "singleEvents", skip_serializing_if = "Option::is_none")]
197    pub single_events: Option<bool>,
198    #[serde(rename = "orderBy", skip_serializing_if = "Option::is_none")]
199    pub order_by: Option<String>,
200}
201
202#[derive(Debug, Clone, Serialize, Default)]
203pub struct CalendarCreateEventParams {
204    #[serde(rename = "calendarId", skip_serializing_if = "Option::is_none")]
205    pub calendar_id: Option<String>,
206    pub summary: String,
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub description: Option<String>,
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub location: Option<String>,
211    pub start: EventDateTime,
212    pub end: EventDateTime,
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub attendees: Option<Vec<CalendarAttendee>>,
215}
216
217// ---------------------------------------------------------------------------
218// Drive
219// ---------------------------------------------------------------------------
220
221#[derive(Debug, Clone, Serialize, Deserialize, Default)]
222pub struct DriveFile {
223    #[serde(default)]
224    pub id: String,
225    #[serde(default)]
226    pub name: String,
227    #[serde(rename = "mimeType", default)]
228    pub mime_type: String,
229    #[serde(default, skip_serializing_if = "Option::is_none")]
230    pub size: Option<String>,
231    #[serde(rename = "createdTime", default, skip_serializing_if = "Option::is_none")]
232    pub created_time: Option<String>,
233    #[serde(rename = "modifiedTime", default, skip_serializing_if = "Option::is_none")]
234    pub modified_time: Option<String>,
235    #[serde(default, skip_serializing_if = "Vec::is_empty")]
236    pub parents: Vec<String>,
237    #[serde(rename = "webViewLink", default, skip_serializing_if = "Option::is_none")]
238    pub web_view_link: Option<String>,
239    #[serde(rename = "webContentLink", default, skip_serializing_if = "Option::is_none")]
240    pub web_content_link: Option<String>,
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize, Default)]
244pub struct DriveFileList {
245    #[serde(default)]
246    pub files: Vec<DriveFile>,
247    #[serde(rename = "nextPageToken", default, skip_serializing_if = "Option::is_none")]
248    pub next_page_token: Option<String>,
249}
250
251#[derive(Debug, Clone, Serialize, Default)]
252pub struct DriveListFilesParams {
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub query: Option<String>,
255    #[serde(rename = "maxResults", skip_serializing_if = "Option::is_none")]
256    pub max_results: Option<u32>,
257    #[serde(rename = "folderId", skip_serializing_if = "Option::is_none")]
258    pub folder_id: Option<String>,
259}
260
261#[derive(Debug, Clone, Serialize, Default)]
262pub struct DriveUploadFileParams {
263    pub name: String,
264    pub content: String,
265    #[serde(rename = "mimeType")]
266    pub mime_type: String,
267    #[serde(rename = "parentId", skip_serializing_if = "Option::is_none")]
268    pub parent_id: Option<String>,
269}
270
271// ---------------------------------------------------------------------------
272// Linear
273// ---------------------------------------------------------------------------
274
275#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
276pub struct LinearUserRef {
277    #[serde(default, skip_serializing_if = "String::is_empty")]
278    pub id: String,
279    #[serde(default, skip_serializing_if = "String::is_empty")]
280    pub name: String,
281    #[serde(default, skip_serializing_if = "String::is_empty")]
282    pub email: String,
283    #[serde(rename = "displayName", default, skip_serializing_if = "String::is_empty")]
284    pub display_name: String,
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize, Default)]
288pub struct LinearStateRef {
289    #[serde(default)]
290    pub id: String,
291    #[serde(default)]
292    pub name: String,
293    #[serde(default, rename = "type")]
294    pub state_type: String,
295    #[serde(default, skip_serializing_if = "String::is_empty")]
296    pub color: String,
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize, Default)]
300pub struct LinearTeamRef {
301    #[serde(default)]
302    pub id: String,
303    #[serde(default)]
304    pub key: String,
305    #[serde(default)]
306    pub name: String,
307}
308
309#[derive(Debug, Clone, Serialize, Deserialize, Default)]
310pub struct LinearIssue {
311    #[serde(default)]
312    pub id: String,
313    #[serde(default, skip_serializing_if = "String::is_empty")]
314    pub identifier: String,
315    #[serde(default)]
316    pub title: String,
317    #[serde(default, skip_serializing_if = "String::is_empty")]
318    pub description: String,
319    #[serde(default, skip_serializing_if = "Option::is_none")]
320    pub priority: Option<i32>,
321    #[serde(rename = "createdAt", default, skip_serializing_if = "String::is_empty")]
322    pub created_at: String,
323    #[serde(rename = "updatedAt", default, skip_serializing_if = "String::is_empty")]
324    pub updated_at: String,
325    #[serde(default, skip_serializing_if = "String::is_empty")]
326    pub url: String,
327    #[serde(default, skip_serializing_if = "Option::is_none")]
328    pub assignee: Option<LinearUserRef>,
329    #[serde(default, skip_serializing_if = "Option::is_none")]
330    pub state: Option<LinearStateRef>,
331    #[serde(default, skip_serializing_if = "Option::is_none")]
332    pub team: Option<LinearTeamRef>,
333    #[serde(rename = "labelIds", default, skip_serializing_if = "Vec::is_empty")]
334    pub label_ids: Vec<String>,
335    #[serde(rename = "projectId", default, skip_serializing_if = "String::is_empty")]
336    pub project_id: String,
337}
338
339#[derive(Debug, Clone, Serialize, Deserialize, Default)]
340pub struct LinearComment {
341    #[serde(default)]
342    pub id: String,
343    #[serde(default)]
344    pub body: String,
345    #[serde(rename = "issueId", default)]
346    pub issue_id: String,
347    #[serde(default, skip_serializing_if = "Option::is_none")]
348    pub user: Option<LinearUserRef>,
349    #[serde(rename = "createdAt", default, skip_serializing_if = "String::is_empty")]
350    pub created_at: String,
351    #[serde(rename = "updatedAt", default, skip_serializing_if = "String::is_empty")]
352    pub updated_at: String,
353    #[serde(default, skip_serializing_if = "String::is_empty")]
354    pub url: String,
355}
356
357#[derive(Debug, Clone, Serialize, Deserialize, Default)]
358pub struct LinearTeam {
359    #[serde(default)]
360    pub id: String,
361    #[serde(default)]
362    pub key: String,
363    #[serde(default)]
364    pub name: String,
365    #[serde(default, skip_serializing_if = "String::is_empty")]
366    pub description: String,
367    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
368    pub private: bool,
369    #[serde(default, skip_serializing_if = "String::is_empty")]
370    pub icon: String,
371    #[serde(default, skip_serializing_if = "String::is_empty")]
372    pub color: String,
373}
374
375#[derive(Debug, Clone, Serialize, Deserialize, Default)]
376pub struct LinearProject {
377    #[serde(default)]
378    pub id: String,
379    #[serde(default)]
380    pub name: String,
381    #[serde(default, skip_serializing_if = "String::is_empty")]
382    pub description: String,
383    #[serde(default, skip_serializing_if = "String::is_empty")]
384    pub state: String,
385    #[serde(rename = "targetDate", default, skip_serializing_if = "String::is_empty")]
386    pub target_date: String,
387    #[serde(rename = "startDate", default, skip_serializing_if = "String::is_empty")]
388    pub start_date: String,
389    #[serde(default, skip_serializing_if = "String::is_empty")]
390    pub url: String,
391    #[serde(rename = "teamIds", default, skip_serializing_if = "Vec::is_empty")]
392    pub team_ids: Vec<String>,
393    #[serde(default, skip_serializing_if = "Option::is_none")]
394    pub progress: Option<f64>,
395}
396
397#[derive(Debug, Clone, Serialize, Default)]
398pub struct LinearListIssuesFilter {
399    #[serde(rename = "teamId", skip_serializing_if = "Option::is_none")]
400    pub team_id: Option<String>,
401    #[serde(rename = "assigneeId", skip_serializing_if = "Option::is_none")]
402    pub assignee_id: Option<String>,
403    #[serde(rename = "stateType", skip_serializing_if = "Option::is_none")]
404    pub state_type: Option<String>,
405    #[serde(skip_serializing_if = "Option::is_none")]
406    pub limit: Option<u32>,
407    #[serde(skip_serializing_if = "Option::is_none")]
408    pub cursor: Option<String>,
409}
410
411#[derive(Debug, Clone, Serialize, Deserialize, Default)]
412pub struct LinearListIssuesResult {
413    #[serde(default)]
414    pub issues: Vec<LinearIssue>,
415    #[serde(default, skip_serializing_if = "Option::is_none")]
416    pub cursor: Option<String>,
417}
418
419#[derive(Debug, Clone, Serialize, Default)]
420pub struct LinearCreateIssueInput {
421    #[serde(rename = "teamId")]
422    pub team_id: String,
423    pub title: String,
424    #[serde(skip_serializing_if = "Option::is_none")]
425    pub description: Option<String>,
426    #[serde(rename = "assigneeId", skip_serializing_if = "Option::is_none")]
427    pub assignee_id: Option<String>,
428    #[serde(skip_serializing_if = "Option::is_none")]
429    pub priority: Option<i32>,
430    #[serde(rename = "labelIds", skip_serializing_if = "Option::is_none")]
431    pub label_ids: Option<Vec<String>>,
432}
433
434#[derive(Debug, Clone, Serialize, Default)]
435pub struct LinearUpdateIssuePatch {
436    #[serde(rename = "teamId", skip_serializing_if = "Option::is_none")]
437    pub team_id: Option<String>,
438    #[serde(skip_serializing_if = "Option::is_none")]
439    pub title: Option<String>,
440    #[serde(skip_serializing_if = "Option::is_none")]
441    pub description: Option<String>,
442    #[serde(rename = "assigneeId", skip_serializing_if = "Option::is_none")]
443    pub assignee_id: Option<String>,
444    #[serde(skip_serializing_if = "Option::is_none")]
445    pub priority: Option<i32>,
446    #[serde(rename = "labelIds", skip_serializing_if = "Option::is_none")]
447    pub label_ids: Option<Vec<String>>,
448}
449
450#[derive(Debug, Clone, Serialize, Default)]
451pub struct LinearListProjectsFilter {
452    #[serde(rename = "teamId", skip_serializing_if = "Option::is_none")]
453    pub team_id: Option<String>,
454}
455
456// ---------------------------------------------------------------------------
457// Shared utility — connection status (kept for compatibility w/ env-fetch test paths)
458// ---------------------------------------------------------------------------
459
460#[derive(Debug, Clone, Serialize, Deserialize, Default)]
461pub struct ConnectionStatus {
462    #[serde(rename = "providerId", default)]
463    pub provider_id: String,
464    #[serde(default)]
465    pub status: String,
466    #[serde(default, skip_serializing_if = "Option::is_none")]
467    pub email: Option<String>,
468    #[serde(rename = "expiresAt", default, skip_serializing_if = "Option::is_none")]
469    pub expires_at: Option<String>,
470}
471
472/// Header bag alias used by the custom MCP server config shape.
473pub type Headers = HashMap<String, String>;