Skip to main content

jmap_chat_client/
types.rs

1//! JMAP Chat client-side auxiliary types.
2//!
3//! This module contains types used in client-facing APIs that are not part of
4//! the wire-format types defined in `jmap-chat-types`.
5
6use serde::Serialize;
7
8// ---------------------------------------------------------------------------
9// ContactPresenceFilter
10// ---------------------------------------------------------------------------
11
12/// Presence filter for `ChatContact/query` operations.
13///
14/// Mirrors [`jmap_chat_types::Presence`] but omits `Other`, which has no
15/// defined filter semantics and must never be sent to the server.
16///
17/// Use [`TryFrom<jmap_chat_types::Presence>`] to convert a deserialized
18/// presence value into a filter value (fails if `Other`).
19#[non_exhaustive]
20#[derive(Debug, Clone, PartialEq, Serialize)]
21#[serde(rename_all = "lowercase")]
22pub enum ContactPresenceFilter {
23    /// Filter to contacts currently online.
24    Online,
25    /// Filter to contacts marked away.
26    Away,
27    /// Filter to contacts marked busy.
28    Busy,
29    /// Filter to contacts marked invisible.
30    Invisible,
31    /// Filter to contacts currently offline.
32    Offline,
33}
34
35impl TryFrom<jmap_chat_types::Presence> for ContactPresenceFilter {
36    /// Conversion fails when `p` is `Presence::Other`.
37    type Error = ();
38    fn try_from(p: jmap_chat_types::Presence) -> Result<Self, ()> {
39        match p {
40            jmap_chat_types::Presence::Online => Ok(ContactPresenceFilter::Online),
41            jmap_chat_types::Presence::Away => Ok(ContactPresenceFilter::Away),
42            jmap_chat_types::Presence::Busy => Ok(ContactPresenceFilter::Busy),
43            jmap_chat_types::Presence::Invisible => Ok(ContactPresenceFilter::Invisible),
44            jmap_chat_types::Presence::Offline => Ok(ContactPresenceFilter::Offline),
45            _ => Err(()),
46        }
47    }
48}
49
50// ---------------------------------------------------------------------------
51// QuotaScope
52// ---------------------------------------------------------------------------
53
54/// RFC 9425 §3.1 Scope — the set of accounts the quota limit applies to.
55///
56/// Wire strings: `"account"`, `"domain"`, `"global"`.
57/// `Other(String)` preserves any unrecognized value for lossless round-trip.
58#[non_exhaustive]
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub enum QuotaScope {
61    /// Quota applies to this account only.
62    Account,
63    /// Quota applies to all accounts sharing this domain.
64    Domain,
65    /// Quota applies to all accounts on the server.
66    Global,
67    /// Catch-all for any unrecognized wire value from a future spec version.
68    /// The original wire value is preserved for lossless round-trip.
69    Other(String),
70}
71
72impl QuotaScope {
73    /// The canonical wire string for this quota scope.
74    pub fn as_str(&self) -> &str {
75        match self {
76            Self::Account => "account",
77            Self::Domain => "domain",
78            Self::Global => "global",
79            Self::Other(s) => s.as_str(),
80        }
81    }
82}
83
84impl std::fmt::Display for QuotaScope {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        f.write_str(self.as_str())
87    }
88}
89
90impl serde::Serialize for QuotaScope {
91    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
92        s.serialize_str(self.as_str())
93    }
94}
95
96impl<'de> serde::Deserialize<'de> for QuotaScope {
97    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
98        let raw = String::deserialize(d)?;
99        Ok(match raw.as_str() {
100            "account" => QuotaScope::Account,
101            "domain" => QuotaScope::Domain,
102            "global" => QuotaScope::Global,
103            _ => QuotaScope::Other(raw),
104        })
105    }
106}
107
108// ---------------------------------------------------------------------------
109// ChatMemberRole
110// ---------------------------------------------------------------------------
111
112/// Role of a participant in a group Chat.
113///
114/// The spec defines two well-known values: `"admin"` and `"member"`.
115/// `Unknown(String)` preserves any unrecognized value for lossless round-trip.
116///
117/// Wire strings: `"admin"`, `"member"`.
118#[non_exhaustive]
119#[derive(Debug, Clone, PartialEq)]
120pub enum ChatMemberRole {
121    /// Group or channel administrator with management permissions.
122    Admin,
123    /// Regular member.
124    Member,
125    /// Catch-all for any unrecognized wire value from a future spec version.
126    Unknown(String),
127}
128
129impl ChatMemberRole {
130    /// The canonical wire string for this role.
131    pub fn as_str(&self) -> &str {
132        match self {
133            Self::Admin => "admin",
134            Self::Member => "member",
135            Self::Unknown(s) => s.as_str(),
136        }
137    }
138}
139
140impl std::fmt::Display for ChatMemberRole {
141    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142        f.write_str(self.as_str())
143    }
144}
145
146impl serde::Serialize for ChatMemberRole {
147    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
148        s.serialize_str(self.as_str())
149    }
150}
151
152impl<'de> serde::Deserialize<'de> for ChatMemberRole {
153    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
154        let raw = String::deserialize(d)?;
155        Ok(match raw.as_str() {
156            "admin" => ChatMemberRole::Admin,
157            "member" => ChatMemberRole::Member,
158            _ => ChatMemberRole::Unknown(raw),
159        })
160    }
161}
162
163// ---------------------------------------------------------------------------
164// BodyType
165// ---------------------------------------------------------------------------
166
167/// MIME type for a message body.
168///
169/// The spec defines three well-known values. `Unknown(String)` preserves any
170/// unrecognized MIME type for lossless round-trip.
171///
172/// Wire strings: `"text/plain"`, `"text/markdown"`, `"application/jmap-chat-rich"`.
173#[non_exhaustive]
174#[derive(Debug, Clone, PartialEq)]
175pub enum BodyType {
176    /// `"text/plain"` — unformatted UTF-8 text.
177    Plain,
178    /// `"text/markdown"` — CommonMark-formatted text.
179    Markdown,
180    /// `"application/jmap-chat-rich"` — structured rich text (spans array).
181    Rich,
182    /// Any unrecognized MIME type string, preserved as-is.
183    Unknown(String),
184}
185
186impl BodyType {
187    /// The canonical MIME type string for this body type.
188    pub fn as_str(&self) -> &str {
189        match self {
190            Self::Plain => "text/plain",
191            Self::Markdown => "text/markdown",
192            Self::Rich => "application/jmap-chat-rich",
193            Self::Unknown(s) => s.as_str(),
194        }
195    }
196}
197
198impl std::fmt::Display for BodyType {
199    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200        f.write_str(self.as_str())
201    }
202}
203
204impl serde::Serialize for BodyType {
205    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
206        s.serialize_str(self.as_str())
207    }
208}
209
210impl<'de> serde::Deserialize<'de> for BodyType {
211    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
212        let raw = String::deserialize(d)?;
213        Ok(match raw.as_str() {
214            "text/plain" => BodyType::Plain,
215            "text/markdown" => BodyType::Markdown,
216            "application/jmap-chat-rich" => BodyType::Rich,
217            _ => BodyType::Unknown(raw),
218        })
219    }
220}