Skip to main content

jmap_mail_types/
identity.rs

1//! RFC 8621 §6 Identity object.
2//!
3//! Provides [`Identity`] — stores information about an email address or domain
4//! the user may send from.
5
6use crate::email::EmailAddress;
7use jmap_types::Id;
8use serde::{Deserialize, Serialize};
9
10/// An RFC 8621 §6 Identity object.
11///
12/// Stores information about an email address or domain the user may send from.
13#[non_exhaustive]
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "camelCase")]
16pub struct Identity {
17    /// The id of the Identity (immutable; server-set).
18    pub id: Id,
19    /// The "From" name the client SHOULD use when creating a new Email
20    /// from this Identity.  Defaults to `""`.
21    #[serde(default)]
22    pub name: String,
23    /// The "From" email address the client MUST use (immutable).
24    pub email: String,
25    /// The Reply-To value the client SHOULD set.  `null` if not set.
26    pub reply_to: Option<Vec<EmailAddress>>,
27    /// The Bcc value the client SHOULD set.  `null` if not set.
28    pub bcc: Option<Vec<EmailAddress>>,
29    /// Plaintext signature.  Defaults to `""`.
30    #[serde(default)]
31    pub text_signature: String,
32    /// HTML snippet signature.  Defaults to `""`.
33    #[serde(default)]
34    pub html_signature: String,
35    /// Whether the user may delete this Identity (server-set).
36    pub may_delete: bool,
37    /// Catch-all for vendor / site / private extension fields not covered
38    /// by the typed fields above. Preserves unknown fields across
39    /// deserialize/serialize round-trip per workspace extras-preservation
40    /// policy (see workspace AGENTS.md).
41    #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
42    pub extra: serde_json::Map<String, serde_json::Value>,
43}
44
45impl Identity {
46    /// Construct an [`Identity`] from its three identifying fields.
47    ///
48    /// `name`, `text_signature`, and `html_signature` default to `""`.
49    /// `reply_to` and `bcc` default to `None`.
50    pub fn new(id: Id, email: impl Into<String>, may_delete: bool) -> Self {
51        Self {
52            id,
53            email: email.into(),
54            may_delete,
55            name: String::new(),
56            reply_to: None,
57            bcc: None,
58            text_signature: String::new(),
59            html_signature: String::new(),
60            extra: serde_json::Map::new(),
61        }
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use serde_json::json;
69
70    // ── Extras-preservation policy tests (JMAP-lbdy.2) ───────────────────
71
72    /// `Identity.extra` captures vendor fields and preserves them across
73    /// deserialize/serialize round-trip.
74    #[test]
75    fn identity_preserves_vendor_extras() {
76        let raw = json!({
77            "id": "i1",
78            "name": "Alice",
79            "email": "alice@example.com",
80            "textSignature": "-- Alice",
81            "htmlSignature": "<p>-- Alice</p>",
82            "mayDelete": true,
83            "acmeCorpDepartment": "engineering"
84        });
85        let id: Identity = serde_json::from_value(raw).unwrap();
86        assert_eq!(
87            id.extra.get("acmeCorpDepartment").and_then(|v| v.as_str()),
88            Some("engineering")
89        );
90        let back = serde_json::to_value(&id).unwrap();
91        assert_eq!(back["acmeCorpDepartment"], "engineering");
92    }
93}