Skip to main content

mxr_protocol/
types.rs

1use mxr_core::id::*;
2use mxr_core::types::*;
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct IpcMessage {
7    pub id: u64,
8    pub payload: IpcPayload,
9}
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
12#[serde(tag = "type")]
13#[allow(clippy::large_enum_variant)]
14pub enum IpcPayload {
15    Request(Request),
16    Response(Response),
17    Event(DaemonEvent),
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21#[serde(tag = "cmd")]
22pub enum Request {
23    ListEnvelopes {
24        label_id: Option<LabelId>,
25        account_id: Option<AccountId>,
26        limit: u32,
27        offset: u32,
28    },
29    ListEnvelopesByIds {
30        message_ids: Vec<MessageId>,
31    },
32    GetEnvelope {
33        message_id: MessageId,
34    },
35    GetBody {
36        message_id: MessageId,
37    },
38    DownloadAttachment {
39        message_id: MessageId,
40        attachment_id: AttachmentId,
41    },
42    OpenAttachment {
43        message_id: MessageId,
44        attachment_id: AttachmentId,
45    },
46    ListBodies {
47        message_ids: Vec<MessageId>,
48    },
49    GetThread {
50        thread_id: ThreadId,
51    },
52    ListLabels {
53        account_id: Option<AccountId>,
54    },
55    CreateLabel {
56        name: String,
57        color: Option<String>,
58        account_id: Option<AccountId>,
59    },
60    DeleteLabel {
61        name: String,
62        account_id: Option<AccountId>,
63    },
64    RenameLabel {
65        old: String,
66        new: String,
67        account_id: Option<AccountId>,
68    },
69    ListRules,
70    ListAccounts,
71    ListAccountsConfig,
72    AuthorizeAccountConfig {
73        account: AccountConfigData,
74        reauthorize: bool,
75    },
76    UpsertAccountConfig {
77        account: AccountConfigData,
78    },
79    SetDefaultAccount {
80        key: String,
81    },
82    TestAccountConfig {
83        account: AccountConfigData,
84    },
85    GetRule {
86        rule: String,
87    },
88    GetRuleForm {
89        rule: String,
90    },
91    UpsertRule {
92        rule: serde_json::Value,
93    },
94    UpsertRuleForm {
95        existing_rule: Option<String>,
96        name: String,
97        condition: String,
98        action: String,
99        priority: i32,
100        enabled: bool,
101    },
102    DeleteRule {
103        rule: String,
104    },
105    DryRunRules {
106        rule: Option<String>,
107        all: bool,
108        after: Option<String>,
109    },
110    ListEvents {
111        limit: u32,
112        level: Option<String>,
113        category: Option<String>,
114    },
115    GetLogs {
116        limit: u32,
117        level: Option<String>,
118    },
119    GetDoctorReport,
120    GenerateBugReport {
121        verbose: bool,
122        full_logs: bool,
123        since: Option<String>,
124    },
125    ListRuleHistory {
126        rule: Option<String>,
127        limit: u32,
128    },
129    Search {
130        query: String,
131        limit: u32,
132    },
133    SyncNow {
134        account_id: Option<AccountId>,
135    },
136    GetSyncStatus {
137        account_id: AccountId,
138    },
139    SetFlags {
140        message_id: MessageId,
141        flags: MessageFlags,
142    },
143    Count {
144        query: String,
145    },
146    GetHeaders {
147        message_id: MessageId,
148    },
149    ListSavedSearches,
150    ListSubscriptions {
151        limit: u32,
152    },
153    CreateSavedSearch {
154        name: String,
155        query: String,
156    },
157    DeleteSavedSearch {
158        name: String,
159    },
160    RunSavedSearch {
161        name: String,
162        limit: u32,
163    },
164    // Mutations (Phase 2)
165    Mutation(MutationCommand),
166    Unsubscribe {
167        message_id: MessageId,
168    },
169    Snooze {
170        message_id: MessageId,
171        wake_at: chrono::DateTime<chrono::Utc>,
172    },
173    Unsnooze {
174        message_id: MessageId,
175    },
176    ListSnoozed,
177    // Compose (Phase 2)
178    PrepareReply {
179        message_id: MessageId,
180        reply_all: bool,
181    },
182    PrepareForward {
183        message_id: MessageId,
184    },
185    SendDraft {
186        draft: Draft,
187    },
188    /// Save draft to the mail server (e.g. Gmail Drafts folder).
189    SaveDraftToServer {
190        draft: Draft,
191    },
192    ListDrafts,
193
194    // Export (Phase 3)
195    ExportThread {
196        thread_id: ThreadId,
197        format: ExportFormat,
198    },
199    ExportSearch {
200        query: String,
201        format: ExportFormat,
202    },
203
204    GetStatus,
205    Ping,
206    Shutdown,
207}
208
209/// Mutation commands for modifying messages.
210#[derive(Debug, Clone, Serialize, Deserialize)]
211#[serde(tag = "mutation")]
212pub enum MutationCommand {
213    Archive {
214        message_ids: Vec<MessageId>,
215    },
216    Trash {
217        message_ids: Vec<MessageId>,
218    },
219    Spam {
220        message_ids: Vec<MessageId>,
221    },
222    Star {
223        message_ids: Vec<MessageId>,
224        starred: bool,
225    },
226    SetRead {
227        message_ids: Vec<MessageId>,
228        read: bool,
229    },
230    ModifyLabels {
231        message_ids: Vec<MessageId>,
232        add: Vec<String>,
233        remove: Vec<String>,
234    },
235    Move {
236        message_ids: Vec<MessageId>,
237        target_label: String,
238    },
239}
240
241/// Reply context returned by PrepareReply.
242#[derive(Debug, Clone, Serialize, Deserialize)]
243pub struct ReplyContext {
244    pub in_reply_to: String,
245    pub references: Vec<String>,
246    pub reply_to: String,
247    pub cc: String,
248    pub subject: String,
249    pub from: String,
250    pub thread_context: String,
251}
252
253/// Forward context returned by PrepareForward.
254#[derive(Debug, Clone, Serialize, Deserialize)]
255pub struct ForwardContext {
256    pub subject: String,
257    pub from: String,
258    pub forwarded_content: String,
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize)]
262#[serde(tag = "status")]
263#[allow(clippy::large_enum_variant)]
264pub enum Response {
265    Ok { data: ResponseData },
266    Error { message: String },
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize)]
270#[serde(tag = "kind")]
271#[allow(clippy::large_enum_variant)]
272pub enum ResponseData {
273    Envelopes {
274        envelopes: Vec<Envelope>,
275    },
276    Envelope {
277        envelope: Envelope,
278    },
279    Body {
280        body: MessageBody,
281    },
282    AttachmentFile {
283        file: AttachmentFile,
284    },
285    Bodies {
286        bodies: Vec<MessageBody>,
287    },
288    Thread {
289        thread: Thread,
290        messages: Vec<Envelope>,
291    },
292    Labels {
293        labels: Vec<Label>,
294    },
295    Label {
296        label: Label,
297    },
298    Rules {
299        rules: Vec<serde_json::Value>,
300    },
301    RuleData {
302        rule: serde_json::Value,
303    },
304    Accounts {
305        accounts: Vec<AccountSummaryData>,
306    },
307    AccountsConfig {
308        accounts: Vec<AccountConfigData>,
309    },
310    AccountOperation {
311        result: AccountOperationResult,
312    },
313    RuleFormData {
314        form: RuleFormData,
315    },
316    RuleDryRun {
317        results: Vec<serde_json::Value>,
318    },
319    EventLogEntries {
320        entries: Vec<EventLogEntry>,
321    },
322    LogLines {
323        lines: Vec<String>,
324    },
325    DoctorReport {
326        report: DoctorReport,
327    },
328    BugReport {
329        content: String,
330    },
331    RuleHistory {
332        entries: Vec<serde_json::Value>,
333    },
334    SearchResults {
335        results: Vec<SearchResultItem>,
336    },
337    SyncStatus {
338        sync: AccountSyncStatus,
339    },
340    Count {
341        count: u32,
342    },
343    Headers {
344        headers: Vec<(String, String)>,
345    },
346    SavedSearches {
347        searches: Vec<mxr_core::types::SavedSearch>,
348    },
349    Subscriptions {
350        subscriptions: Vec<mxr_core::types::SubscriptionSummary>,
351    },
352    SavedSearchData {
353        search: mxr_core::types::SavedSearch,
354    },
355    Status {
356        uptime_secs: u64,
357        accounts: Vec<String>,
358        total_messages: u32,
359        daemon_pid: Option<u32>,
360        sync_statuses: Vec<AccountSyncStatus>,
361    },
362    ReplyContext {
363        context: ReplyContext,
364    },
365    ForwardContext {
366        context: ForwardContext,
367    },
368    Drafts {
369        drafts: Vec<Draft>,
370    },
371    SnoozedMessages {
372        snoozed: Vec<Snoozed>,
373    },
374    ExportResult {
375        content: String,
376    },
377    Pong,
378    Ack,
379}
380
381#[derive(Debug, Clone, Serialize, Deserialize)]
382pub struct SearchResultItem {
383    pub message_id: MessageId,
384    pub account_id: AccountId,
385    pub thread_id: ThreadId,
386    pub score: f32,
387}
388
389#[derive(Debug, Clone, Serialize, Deserialize)]
390pub struct AttachmentFile {
391    pub attachment_id: AttachmentId,
392    pub filename: String,
393    pub path: String,
394}
395
396#[derive(Debug, Clone, Serialize, Deserialize)]
397pub struct EventLogEntry {
398    pub timestamp: i64,
399    pub level: String,
400    pub category: String,
401    pub account_id: Option<AccountId>,
402    pub message_id: Option<String>,
403    pub rule_id: Option<String>,
404    pub summary: String,
405    pub details: Option<String>,
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
409pub struct AccountSyncStatus {
410    pub account_id: AccountId,
411    pub account_name: String,
412    pub last_attempt_at: Option<String>,
413    pub last_success_at: Option<String>,
414    pub last_error: Option<String>,
415    pub failure_class: Option<String>,
416    pub consecutive_failures: u32,
417    pub backoff_until: Option<String>,
418    pub sync_in_progress: bool,
419    pub current_cursor_summary: Option<String>,
420    pub last_synced_count: u32,
421    pub healthy: bool,
422}
423
424#[derive(Debug, Clone, Serialize, Deserialize)]
425pub struct DoctorReport {
426    pub healthy: bool,
427    pub data_dir_exists: bool,
428    pub database_exists: bool,
429    pub index_exists: bool,
430    pub socket_exists: bool,
431    pub socket_reachable: bool,
432    pub stale_socket: bool,
433    pub daemon_running: bool,
434    pub daemon_pid: Option<u32>,
435    pub index_lock_held: bool,
436    pub index_lock_error: Option<String>,
437    pub database_path: String,
438    pub database_size_bytes: u64,
439    pub index_path: String,
440    pub index_size_bytes: u64,
441    pub log_path: String,
442    pub log_size_bytes: u64,
443    pub sync_statuses: Vec<AccountSyncStatus>,
444    pub recent_sync_events: Vec<EventLogEntry>,
445    pub recent_error_logs: Vec<String>,
446    pub recommended_next_steps: Vec<String>,
447}
448
449#[derive(Debug, Clone, Serialize, Deserialize)]
450pub struct RuleFormData {
451    pub id: Option<String>,
452    pub name: String,
453    pub condition: String,
454    pub action: String,
455    pub priority: i32,
456    pub enabled: bool,
457}
458
459#[derive(Debug, Clone, Serialize, Deserialize)]
460pub struct AccountConfigData {
461    pub key: String,
462    pub name: String,
463    pub email: String,
464    pub sync: Option<AccountSyncConfigData>,
465    pub send: Option<AccountSendConfigData>,
466    pub is_default: bool,
467}
468
469#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
470#[serde(rename_all = "snake_case")]
471pub enum AccountSourceData {
472    Runtime,
473    Config,
474    Both,
475}
476
477#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
478#[serde(rename_all = "snake_case")]
479pub enum AccountEditModeData {
480    Full,
481    RuntimeOnly,
482}
483
484#[derive(Debug, Clone, Serialize, Deserialize)]
485pub struct AccountSummaryData {
486    pub account_id: AccountId,
487    pub key: Option<String>,
488    pub name: String,
489    pub email: String,
490    pub provider_kind: String,
491    pub sync_kind: Option<String>,
492    pub send_kind: Option<String>,
493    pub enabled: bool,
494    pub is_default: bool,
495    pub source: AccountSourceData,
496    pub editable: AccountEditModeData,
497    pub sync: Option<AccountSyncConfigData>,
498    pub send: Option<AccountSendConfigData>,
499}
500
501#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
502#[serde(rename_all = "snake_case")]
503pub enum GmailCredentialSourceData {
504    #[default]
505    Bundled,
506    Custom,
507}
508
509#[derive(Debug, Clone, Serialize, Deserialize)]
510#[serde(tag = "type", rename_all = "snake_case")]
511pub enum AccountSyncConfigData {
512    Gmail {
513        #[serde(default)]
514        credential_source: GmailCredentialSourceData,
515        client_id: String,
516        client_secret: Option<String>,
517        token_ref: String,
518    },
519    Imap {
520        host: String,
521        port: u16,
522        username: String,
523        password_ref: String,
524        password: Option<String>,
525        use_tls: bool,
526    },
527}
528
529#[derive(Debug, Clone, Serialize, Deserialize)]
530pub struct AccountOperationStep {
531    pub ok: bool,
532    pub detail: String,
533}
534
535#[derive(Debug, Clone, Serialize, Deserialize)]
536pub struct AccountOperationResult {
537    pub ok: bool,
538    pub summary: String,
539    pub save: Option<AccountOperationStep>,
540    pub auth: Option<AccountOperationStep>,
541    pub sync: Option<AccountOperationStep>,
542    pub send: Option<AccountOperationStep>,
543}
544
545#[derive(Debug, Clone, Serialize, Deserialize)]
546#[serde(tag = "type", rename_all = "snake_case")]
547pub enum AccountSendConfigData {
548    Gmail,
549    Smtp {
550        host: String,
551        port: u16,
552        username: String,
553        password_ref: String,
554        password: Option<String>,
555        use_tls: bool,
556    },
557}
558
559#[derive(Debug, Clone, Serialize, Deserialize)]
560#[serde(tag = "event")]
561pub enum DaemonEvent {
562    SyncCompleted {
563        account_id: AccountId,
564        messages_synced: u32,
565    },
566    SyncError {
567        account_id: AccountId,
568        error: String,
569    },
570    NewMessages {
571        envelopes: Vec<Envelope>,
572    },
573    MessageUnsnoozed {
574        message_id: MessageId,
575    },
576    LabelCountsUpdated {
577        counts: Vec<LabelCount>,
578    },
579}
580
581#[derive(Debug, Clone, Serialize, Deserialize)]
582pub struct LabelCount {
583    pub label_id: LabelId,
584    pub unread_count: u32,
585    pub total_count: u32,
586}