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