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}