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        mode: Option<SearchMode>,
133        explain: bool,
134    },
135    SyncNow {
136        account_id: Option<AccountId>,
137    },
138    GetSyncStatus {
139        account_id: AccountId,
140    },
141    SetFlags {
142        message_id: MessageId,
143        flags: MessageFlags,
144    },
145    Count {
146        query: String,
147        mode: Option<SearchMode>,
148    },
149    GetHeaders {
150        message_id: MessageId,
151    },
152    ListSavedSearches,
153    ListSubscriptions {
154        limit: u32,
155    },
156    GetSemanticStatus,
157    EnableSemantic {
158        enabled: bool,
159    },
160    InstallSemanticProfile {
161        profile: SemanticProfile,
162    },
163    UseSemanticProfile {
164        profile: SemanticProfile,
165    },
166    ReindexSemantic,
167    CreateSavedSearch {
168        name: String,
169        query: String,
170        search_mode: SearchMode,
171    },
172    DeleteSavedSearch {
173        name: String,
174    },
175    RunSavedSearch {
176        name: String,
177        limit: u32,
178    },
179    // Mutations (Phase 2)
180    Mutation(MutationCommand),
181    Unsubscribe {
182        message_id: MessageId,
183    },
184    Snooze {
185        message_id: MessageId,
186        wake_at: chrono::DateTime<chrono::Utc>,
187    },
188    Unsnooze {
189        message_id: MessageId,
190    },
191    ListSnoozed,
192    // Compose (Phase 2)
193    PrepareReply {
194        message_id: MessageId,
195        reply_all: bool,
196    },
197    PrepareForward {
198        message_id: MessageId,
199    },
200    SendDraft {
201        draft: Draft,
202    },
203    /// Save draft to the mail server (e.g. Gmail Drafts folder).
204    SaveDraftToServer {
205        draft: Draft,
206    },
207    ListDrafts,
208
209    // Export (Phase 3)
210    ExportThread {
211        thread_id: ThreadId,
212        format: ExportFormat,
213    },
214    ExportSearch {
215        query: String,
216        format: ExportFormat,
217    },
218
219    GetStatus,
220    Ping,
221    Shutdown,
222}
223
224/// Mutation commands for modifying messages.
225#[derive(Debug, Clone, Serialize, Deserialize)]
226#[serde(tag = "mutation")]
227pub enum MutationCommand {
228    Archive {
229        message_ids: Vec<MessageId>,
230    },
231    Trash {
232        message_ids: Vec<MessageId>,
233    },
234    Spam {
235        message_ids: Vec<MessageId>,
236    },
237    Star {
238        message_ids: Vec<MessageId>,
239        starred: bool,
240    },
241    SetRead {
242        message_ids: Vec<MessageId>,
243        read: bool,
244    },
245    ModifyLabels {
246        message_ids: Vec<MessageId>,
247        add: Vec<String>,
248        remove: Vec<String>,
249    },
250    Move {
251        message_ids: Vec<MessageId>,
252        target_label: String,
253    },
254}
255
256/// Reply context returned by PrepareReply.
257#[derive(Debug, Clone, Serialize, Deserialize)]
258pub struct ReplyContext {
259    pub in_reply_to: String,
260    pub references: Vec<String>,
261    pub reply_to: String,
262    pub cc: String,
263    pub subject: String,
264    pub from: String,
265    pub thread_context: String,
266}
267
268/// Forward context returned by PrepareForward.
269#[derive(Debug, Clone, Serialize, Deserialize)]
270pub struct ForwardContext {
271    pub subject: String,
272    pub from: String,
273    pub forwarded_content: String,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
277#[serde(tag = "status")]
278#[allow(clippy::large_enum_variant)]
279pub enum Response {
280    Ok { data: ResponseData },
281    Error { message: String },
282}
283
284#[derive(Debug, Clone, Serialize, Deserialize)]
285#[serde(tag = "kind")]
286#[allow(clippy::large_enum_variant)]
287pub enum ResponseData {
288    Envelopes {
289        envelopes: Vec<Envelope>,
290    },
291    Envelope {
292        envelope: Envelope,
293    },
294    Body {
295        body: MessageBody,
296    },
297    AttachmentFile {
298        file: AttachmentFile,
299    },
300    Bodies {
301        bodies: Vec<MessageBody>,
302    },
303    Thread {
304        thread: Thread,
305        messages: Vec<Envelope>,
306    },
307    Labels {
308        labels: Vec<Label>,
309    },
310    Label {
311        label: Label,
312    },
313    Rules {
314        rules: Vec<serde_json::Value>,
315    },
316    RuleData {
317        rule: serde_json::Value,
318    },
319    Accounts {
320        accounts: Vec<AccountSummaryData>,
321    },
322    AccountsConfig {
323        accounts: Vec<AccountConfigData>,
324    },
325    AccountOperation {
326        result: AccountOperationResult,
327    },
328    RuleFormData {
329        form: RuleFormData,
330    },
331    RuleDryRun {
332        results: Vec<serde_json::Value>,
333    },
334    EventLogEntries {
335        entries: Vec<EventLogEntry>,
336    },
337    LogLines {
338        lines: Vec<String>,
339    },
340    DoctorReport {
341        report: DoctorReport,
342    },
343    BugReport {
344        content: String,
345    },
346    RuleHistory {
347        entries: Vec<serde_json::Value>,
348    },
349    SearchResults {
350        results: Vec<SearchResultItem>,
351    },
352    SyncStatus {
353        sync: AccountSyncStatus,
354    },
355    Count {
356        count: u32,
357    },
358    Headers {
359        headers: Vec<(String, String)>,
360    },
361    SavedSearches {
362        searches: Vec<mxr_core::types::SavedSearch>,
363    },
364    Subscriptions {
365        subscriptions: Vec<mxr_core::types::SubscriptionSummary>,
366    },
367    SemanticStatus {
368        snapshot: SemanticStatusSnapshot,
369    },
370    SavedSearchData {
371        search: mxr_core::types::SavedSearch,
372    },
373    Status {
374        uptime_secs: u64,
375        accounts: Vec<String>,
376        total_messages: u32,
377        daemon_pid: Option<u32>,
378        sync_statuses: Vec<AccountSyncStatus>,
379    },
380    ReplyContext {
381        context: ReplyContext,
382    },
383    ForwardContext {
384        context: ForwardContext,
385    },
386    Drafts {
387        drafts: Vec<Draft>,
388    },
389    SnoozedMessages {
390        snoozed: Vec<Snoozed>,
391    },
392    ExportResult {
393        content: String,
394    },
395    Pong,
396    Ack,
397}
398
399#[derive(Debug, Clone, Serialize, Deserialize)]
400pub struct SearchResultItem {
401    pub message_id: MessageId,
402    pub account_id: AccountId,
403    pub thread_id: ThreadId,
404    pub score: f32,
405    pub mode: SearchMode,
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize)]
409pub struct AttachmentFile {
410    pub attachment_id: AttachmentId,
411    pub filename: String,
412    pub path: String,
413}
414
415#[derive(Debug, Clone, Serialize, Deserialize)]
416pub struct EventLogEntry {
417    pub timestamp: i64,
418    pub level: String,
419    pub category: String,
420    pub account_id: Option<AccountId>,
421    pub message_id: Option<String>,
422    pub rule_id: Option<String>,
423    pub summary: String,
424    pub details: Option<String>,
425}
426
427#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
428pub struct AccountSyncStatus {
429    pub account_id: AccountId,
430    pub account_name: String,
431    pub last_attempt_at: Option<String>,
432    pub last_success_at: Option<String>,
433    pub last_error: Option<String>,
434    pub failure_class: Option<String>,
435    pub consecutive_failures: u32,
436    pub backoff_until: Option<String>,
437    pub sync_in_progress: bool,
438    pub current_cursor_summary: Option<String>,
439    pub last_synced_count: u32,
440    pub healthy: bool,
441}
442
443#[derive(Debug, Clone, Serialize, Deserialize)]
444pub struct DoctorReport {
445    pub healthy: bool,
446    pub data_dir_exists: bool,
447    pub database_exists: bool,
448    pub index_exists: bool,
449    pub socket_exists: bool,
450    pub socket_reachable: bool,
451    pub stale_socket: bool,
452    pub daemon_running: bool,
453    pub daemon_pid: Option<u32>,
454    pub index_lock_held: bool,
455    pub index_lock_error: Option<String>,
456    pub database_path: String,
457    pub database_size_bytes: u64,
458    pub index_path: String,
459    pub index_size_bytes: u64,
460    pub log_path: String,
461    pub log_size_bytes: u64,
462    pub sync_statuses: Vec<AccountSyncStatus>,
463    pub recent_sync_events: Vec<EventLogEntry>,
464    pub recent_error_logs: Vec<String>,
465    pub recommended_next_steps: Vec<String>,
466}
467
468#[derive(Debug, Clone, Serialize, Deserialize)]
469pub struct RuleFormData {
470    pub id: Option<String>,
471    pub name: String,
472    pub condition: String,
473    pub action: String,
474    pub priority: i32,
475    pub enabled: bool,
476}
477
478#[derive(Debug, Clone, Serialize, Deserialize)]
479pub struct AccountConfigData {
480    pub key: String,
481    pub name: String,
482    pub email: String,
483    pub sync: Option<AccountSyncConfigData>,
484    pub send: Option<AccountSendConfigData>,
485    pub is_default: bool,
486}
487
488#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
489#[serde(rename_all = "snake_case")]
490pub enum AccountSourceData {
491    Runtime,
492    Config,
493    Both,
494}
495
496#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
497#[serde(rename_all = "snake_case")]
498pub enum AccountEditModeData {
499    Full,
500    RuntimeOnly,
501}
502
503#[derive(Debug, Clone, Serialize, Deserialize)]
504pub struct AccountSummaryData {
505    pub account_id: AccountId,
506    pub key: Option<String>,
507    pub name: String,
508    pub email: String,
509    pub provider_kind: String,
510    pub sync_kind: Option<String>,
511    pub send_kind: Option<String>,
512    pub enabled: bool,
513    pub is_default: bool,
514    pub source: AccountSourceData,
515    pub editable: AccountEditModeData,
516    pub sync: Option<AccountSyncConfigData>,
517    pub send: Option<AccountSendConfigData>,
518}
519
520#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
521#[serde(rename_all = "snake_case")]
522pub enum GmailCredentialSourceData {
523    #[default]
524    Bundled,
525    Custom,
526}
527
528#[derive(Debug, Clone, Serialize, Deserialize)]
529#[serde(tag = "type", rename_all = "snake_case")]
530pub enum AccountSyncConfigData {
531    Gmail {
532        #[serde(default)]
533        credential_source: GmailCredentialSourceData,
534        client_id: String,
535        client_secret: Option<String>,
536        token_ref: String,
537    },
538    Imap {
539        host: String,
540        port: u16,
541        username: String,
542        password_ref: String,
543        password: Option<String>,
544        use_tls: bool,
545    },
546}
547
548#[derive(Debug, Clone, Serialize, Deserialize)]
549pub struct AccountOperationStep {
550    pub ok: bool,
551    pub detail: String,
552}
553
554#[derive(Debug, Clone, Serialize, Deserialize)]
555pub struct AccountOperationResult {
556    pub ok: bool,
557    pub summary: String,
558    pub save: Option<AccountOperationStep>,
559    pub auth: Option<AccountOperationStep>,
560    pub sync: Option<AccountOperationStep>,
561    pub send: Option<AccountOperationStep>,
562}
563
564#[derive(Debug, Clone, Serialize, Deserialize)]
565#[serde(tag = "type", rename_all = "snake_case")]
566pub enum AccountSendConfigData {
567    Gmail,
568    Smtp {
569        host: String,
570        port: u16,
571        username: String,
572        password_ref: String,
573        password: Option<String>,
574        use_tls: bool,
575    },
576}
577
578#[derive(Debug, Clone, Serialize, Deserialize)]
579#[serde(tag = "event")]
580pub enum DaemonEvent {
581    SyncCompleted {
582        account_id: AccountId,
583        messages_synced: u32,
584    },
585    SyncError {
586        account_id: AccountId,
587        error: String,
588    },
589    NewMessages {
590        envelopes: Vec<Envelope>,
591    },
592    MessageUnsnoozed {
593        message_id: MessageId,
594    },
595    LabelCountsUpdated {
596        counts: Vec<LabelCount>,
597    },
598}
599
600#[derive(Debug, Clone, Serialize, Deserialize)]
601pub struct LabelCount {
602    pub label_id: LabelId,
603    pub unread_count: u32,
604    pub total_count: u32,
605}