Skip to main content

car_integrations/
lib.rs

1//! Account-bound integrations for Common Agent Runtime: Calendar, Contacts,
2//! Mail.
3//!
4//! The logical capabilities are the same on every OS; the backends are
5//! not. v1 defines the release contract — stable return shapes that carry
6//! explicit `available` + `backend` fields so downstream apps can branch
7//! cleanly while backends light up incrementally.
8//!
9//! # Backends (target)
10//!
11//! | OS      | Calendar                  | Contacts                  | Mail                         |
12//! |---------|---------------------------|---------------------------|------------------------------|
13//! | macOS   | EventKit (EKEventStore)   | Contacts.framework         | IMAP/SMTP + AppleScript hint |
14//! | Windows | MS Graph + Outlook MAPI   | Windows.Contacts + Graph   | MS Graph + MAPI              |
15//! | Linux   | Evolution DS + CalDAV     | Evolution DS + CardDAV     | Evolution DS + IMAP/SMTP     |
16//!
17//! v1 wires the API only. Backends return Unavailable-with-reason so the
18//! release surface is stable before any one adapter is complete.
19//!
20//! # Dependencies (and honest gaps)
21//!
22//! Full operation needs:
23//! - **car-secrets** — where credentials and tokens live
24//! - **car-accounts** — to know which account the call should be bound to
25//! - **car-permissions** — to preflight OS consent before the side effect
26//!
27//! These are wired as runtime deps but not yet composed inside any
28//! backend implementation.
29
30use serde::{Deserialize, Serialize};
31use thiserror::Error;
32
33pub mod calendar;
34pub mod contacts;
35pub mod mail;
36
37/// Availability envelope — returned from every list-ish method so callers
38/// can branch on `available` instead of assuming a populated list.
39///
40/// **Flatten caution.** This struct is `#[serde(flatten)]`ed into every
41/// listing payload (see `CalendarListing`, `ContactListing`, etc.). Do not
42/// add fields with names that collide with payload fields. `#[non_exhaustive]`
43/// to keep future additions additive.
44#[derive(Debug, Clone, Serialize, Deserialize)]
45#[non_exhaustive]
46pub struct Availability {
47    /// True if the backend returned real data.
48    pub available: bool,
49    /// Backend that was attempted (`"eventkit"`, `"msgraph"`, `"eds"`, ...).
50    pub backend: String,
51    /// When `available` is false, human-readable explanation.
52    pub reason: Option<String>,
53}
54
55impl Availability {
56    pub fn pending(backend: &'static str, reason: impl Into<String>) -> Self {
57        Self {
58            available: false,
59            backend: backend.to_string(),
60            reason: Some(reason.into()),
61        }
62    }
63}
64
65#[derive(Debug, Error)]
66pub enum IntegrationError {
67    #[error("backend not available: {0}")]
68    Unavailable(String),
69    #[error("integration backend error: {0}")]
70    Backend(String),
71    #[error("not yet implemented: {0}")]
72    NotImplemented(&'static str),
73}