Skip to main content

io_jmap/rfc8620/
error.rs

1//! JMAP error objects (RFC 8620): the method-level error returned in a
2//! failed method response (§3.6.1) and the per-object `Foo/set` error
3//! (§5.3).
4
5use core::{error::Error, fmt};
6
7use alloc::{string::String, vec::Vec};
8
9use serde::{Deserialize, Serialize};
10
11/// A JMAP method-level error (RFC 8620 §3.6.1).
12///
13/// NOTE: variants keep the struct shape even with a single field, the
14/// internally-tagged serde representation of the wire object requires
15/// it.
16#[derive(Clone, Debug, Serialize, Deserialize)]
17#[serde(tag = "type", rename_all = "camelCase")]
18pub enum JmapMethodError {
19    /// An unexpected or unknown error occurred during the method call.
20    ServerFail {
21        /// Optional human-readable detail.
22        description: Option<String>,
23    },
24    /// Some, but not all, expected changes were applied.
25    ServerPartialFail,
26    /// The server is currently unable to run the method.
27    ServerUnavailable {
28        /// Optional human-readable detail.
29        description: Option<String>,
30    },
31    /// The method requires a capability the request did not declare.
32    UnknownCapability {
33        /// Optional human-readable detail.
34        description: Option<String>,
35    },
36    /// The request body was not valid JSON.
37    NotJson {
38        /// Optional human-readable detail.
39        description: Option<String>,
40    },
41    /// The request parsed as JSON but is not a valid Request object.
42    NotRequest {
43        /// Optional human-readable detail.
44        description: Option<String>,
45    },
46    /// A server-defined limit was exceeded.
47    Limit {
48        /// Optional human-readable detail.
49        description: Option<String>,
50        /// The name of the exceeded limit.
51        limit: String,
52    },
53    /// One of the method arguments is invalid.
54    InvalidArguments {
55        /// Optional human-readable detail.
56        description: Option<String>,
57    },
58    /// Access denied for this method call (RFC 8620 §3.6.2), e.g. requesting
59    /// the `url` or `keys` properties in `PushSubscription/get` (§7.2.1).
60    Forbidden {
61        /// Optional human-readable detail.
62        description: Option<String>,
63    },
64    /// The total request size exceeds the server limit.
65    RequestTooLarge,
66    /// The referenced object does not exist.
67    NotFound,
68    /// A `Foo/set` update patch is invalid.
69    InvalidPatch {
70        /// Optional human-readable detail.
71        description: Option<String>,
72    },
73    /// The object will be destroyed by this request, so it cannot be
74    /// updated.
75    WillDestroy {
76        /// Optional human-readable detail.
77        description: Option<String>,
78    },
79    /// One or more object properties are invalid.
80    InvalidProperties {
81        /// Optional human-readable detail.
82        description: Option<String>,
83        /// The invalid property names.
84        #[serde(default)]
85        properties: Vec<String>,
86    },
87    /// The type is a singleton, objects cannot be created or destroyed.
88    Singleton,
89    /// The method name is not known by the server.
90    UnknownMethod {
91        /// Optional human-readable detail.
92        description: Option<String>,
93    },
94    /// Server can no longer compute changes from `sinceState` (RFC 8620 §5.2):
95    /// callers MUST fall back to `Foo/get` and resume from the returned state.
96    CannotCalculateChanges {
97        /// Optional human-readable detail.
98        description: Option<String>,
99    },
100    /// Any error type this library does not know about.
101    #[serde(other)]
102    Unknown,
103}
104
105impl fmt::Display for JmapMethodError {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        match self {
108            Self::ServerFail { description } => {
109                write!(f, "JMAP serverFail")?;
110                if let Some(d) = description {
111                    write!(f, ": {d}")?;
112                }
113                Ok(())
114            }
115            Self::ServerPartialFail => write!(f, "JMAP serverPartialFail"),
116            Self::ServerUnavailable { description } => {
117                write!(f, "JMAP serverUnavailable")?;
118                if let Some(d) = description {
119                    write!(f, ": {d}")?;
120                }
121                Ok(())
122            }
123            Self::UnknownCapability { description } => {
124                write!(f, "JMAP unknownCapability")?;
125                if let Some(d) = description {
126                    write!(f, ": {d}")?;
127                }
128                Ok(())
129            }
130            Self::NotJson { description } => {
131                write!(f, "JMAP notJson")?;
132                if let Some(d) = description {
133                    write!(f, ": {d}")?;
134                }
135                Ok(())
136            }
137            Self::NotRequest { description } => {
138                write!(f, "JMAP notRequest")?;
139                if let Some(d) = description {
140                    write!(f, ": {d}")?;
141                }
142                Ok(())
143            }
144            Self::Limit { description, limit } => {
145                write!(f, "JMAP limit ({limit})")?;
146                if let Some(d) = description {
147                    write!(f, ": {d}")?;
148                }
149                Ok(())
150            }
151            Self::InvalidArguments { description } => {
152                write!(f, "JMAP invalidArguments")?;
153                if let Some(d) = description {
154                    write!(f, ": {d}")?;
155                }
156                Ok(())
157            }
158            Self::Forbidden { description } => {
159                write!(f, "JMAP forbidden")?;
160                if let Some(d) = description {
161                    write!(f, ": {d}")?;
162                }
163                Ok(())
164            }
165            Self::RequestTooLarge => write!(f, "JMAP requestTooLarge"),
166            Self::NotFound => write!(f, "JMAP notFound"),
167            Self::InvalidPatch { description } => {
168                write!(f, "JMAP invalidPatch")?;
169                if let Some(d) = description {
170                    write!(f, ": {d}")?;
171                }
172                Ok(())
173            }
174            Self::WillDestroy { description } => {
175                write!(f, "JMAP willDestroy")?;
176                if let Some(d) = description {
177                    write!(f, ": {d}")?;
178                }
179                Ok(())
180            }
181            Self::InvalidProperties {
182                description,
183                properties,
184            } => {
185                write!(f, "JMAP invalidProperties")?;
186                if !properties.is_empty() {
187                    write!(f, " [{}]", properties.join(", "))?;
188                }
189                if let Some(d) = description {
190                    write!(f, ": {d}")?;
191                }
192                Ok(())
193            }
194            Self::Singleton => write!(f, "JMAP singleton"),
195            Self::UnknownMethod { description } => {
196                write!(f, "JMAP unknownMethod")?;
197                if let Some(d) = description {
198                    write!(f, ": {d}")?;
199                }
200                Ok(())
201            }
202            Self::CannotCalculateChanges { description } => {
203                write!(f, "JMAP cannotCalculateChanges")?;
204                if let Some(d) = description {
205                    write!(f, ": {d}")?;
206                }
207                Ok(())
208            }
209            Self::Unknown => write!(f, "JMAP unknown error"),
210        }
211    }
212}
213
214impl Error for JmapMethodError {}
215
216/// Per-object error returned in `Foo/set` responses (RFC 8620 §5.3).
217#[derive(Clone, Debug, Deserialize)]
218#[serde(rename_all = "camelCase")]
219pub struct JmapSetError {
220    /// The wire error type (`invalidProperties`, `forbidden`, …).
221    pub r#type: String,
222    /// Optional human-readable detail.
223    pub description: Option<String>,
224    /// The properties the error relates to, when applicable.
225    #[serde(default)]
226    pub properties: Vec<String>,
227}