Skip to main content

rusmes_jmap/methods/
mod.rs

1//! JMAP method handlers
2
3pub mod email;
4pub mod email_advanced;
5pub mod identity;
6pub mod mailbox;
7pub mod search_snippet;
8pub mod submission;
9pub mod thread;
10pub mod vacation;
11
12use crate::types::{JmapError, JmapErrorType, JmapMethodCall, JmapMethodResponse};
13use rusmes_storage::backends::filesystem::FilesystemBackend;
14use rusmes_storage::StorageBackend;
15use std::path::PathBuf;
16use std::sync::Arc;
17
18/// Dispatch JMAP method call
19#[allow(clippy::too_many_arguments)]
20pub async fn dispatch_method(
21    call: JmapMethodCall,
22    capabilities: &[String],
23) -> anyhow::Result<JmapMethodResponse> {
24    let method_name = &call.0;
25    let call_id = &call.2;
26
27    // Validate method requires proper capability
28    if let Err(error) = validate_method_capability(method_name, capabilities) {
29        return Ok(JmapMethodResponse(
30            "error".to_string(),
31            serde_json::to_value(error)?,
32            call_id.clone(),
33        ));
34    }
35
36    // Get storage backend from configured path
37    let backend = Arc::new(FilesystemBackend::new(PathBuf::from("/tmp/rusmes/mail")));
38    let message_store = backend.message_store();
39
40    // Dispatch to the appropriate handler
41    match method_name.as_str() {
42        // Email methods
43        "Email/get" => {
44            let request = serde_json::from_value(call.1)?;
45            let response = email::email_get(request, message_store.as_ref()).await?;
46            Ok(JmapMethodResponse(
47                "Email/get".to_string(),
48                serde_json::to_value(response)?,
49                call_id.clone(),
50            ))
51        }
52        "Email/set" => {
53            let request = serde_json::from_value(call.1)?;
54            let response = email::email_set(request, message_store.as_ref()).await?;
55            Ok(JmapMethodResponse(
56                "Email/set".to_string(),
57                serde_json::to_value(response)?,
58                call_id.clone(),
59            ))
60        }
61        "Email/query" => {
62            let request = serde_json::from_value(call.1)?;
63            let response = email::email_query(request, message_store.as_ref()).await?;
64            Ok(JmapMethodResponse(
65                "Email/query".to_string(),
66                serde_json::to_value(response)?,
67                call_id.clone(),
68            ))
69        }
70        "Email/changes" => {
71            let request = serde_json::from_value(call.1)?;
72            let response = email_advanced::email_changes(request, message_store.as_ref()).await?;
73            Ok(JmapMethodResponse(
74                "Email/changes".to_string(),
75                serde_json::to_value(response)?,
76                call_id.clone(),
77            ))
78        }
79        "Email/queryChanges" => {
80            let request = serde_json::from_value(call.1)?;
81            let response =
82                email_advanced::email_query_changes(request, message_store.as_ref()).await?;
83            Ok(JmapMethodResponse(
84                "Email/queryChanges".to_string(),
85                serde_json::to_value(response)?,
86                call_id.clone(),
87            ))
88        }
89        "Email/copy" => {
90            let request = serde_json::from_value(call.1)?;
91            let response = email_advanced::email_copy(request, message_store.as_ref()).await?;
92            Ok(JmapMethodResponse(
93                "Email/copy".to_string(),
94                serde_json::to_value(response)?,
95                call_id.clone(),
96            ))
97        }
98        "Email/import" => {
99            let request = serde_json::from_value(call.1)?;
100            let response = email_advanced::email_import(request, message_store.as_ref()).await?;
101            Ok(JmapMethodResponse(
102                "Email/import".to_string(),
103                serde_json::to_value(response)?,
104                call_id.clone(),
105            ))
106        }
107        "Email/parse" => {
108            let request = serde_json::from_value(call.1)?;
109            let response = email_advanced::email_parse(request, message_store.as_ref()).await?;
110            Ok(JmapMethodResponse(
111                "Email/parse".to_string(),
112                serde_json::to_value(response)?,
113                call_id.clone(),
114            ))
115        }
116
117        // EmailSubmission methods
118        "EmailSubmission/get" => {
119            let request = serde_json::from_value(call.1)?;
120            let response =
121                submission::email_submission_get(request, message_store.as_ref()).await?;
122            Ok(JmapMethodResponse(
123                "EmailSubmission/get".to_string(),
124                serde_json::to_value(response)?,
125                call_id.clone(),
126            ))
127        }
128        "EmailSubmission/set" => {
129            let request = serde_json::from_value(call.1)?;
130            let response =
131                submission::email_submission_set(request, message_store.as_ref()).await?;
132            Ok(JmapMethodResponse(
133                "EmailSubmission/set".to_string(),
134                serde_json::to_value(response)?,
135                call_id.clone(),
136            ))
137        }
138        "EmailSubmission/query" => {
139            let request = serde_json::from_value(call.1)?;
140            let response =
141                submission::email_submission_query(request, message_store.as_ref()).await?;
142            Ok(JmapMethodResponse(
143                "EmailSubmission/query".to_string(),
144                serde_json::to_value(response)?,
145                call_id.clone(),
146            ))
147        }
148        "EmailSubmission/changes" => {
149            let request = serde_json::from_value(call.1)?;
150            let response =
151                submission::email_submission_changes(request, message_store.as_ref()).await?;
152            Ok(JmapMethodResponse(
153                "EmailSubmission/changes".to_string(),
154                serde_json::to_value(response)?,
155                call_id.clone(),
156            ))
157        }
158
159        // Mailbox methods
160        "Mailbox/get" => {
161            let request = serde_json::from_value(call.1)?;
162            let response = mailbox::mailbox_get(request, message_store.as_ref()).await?;
163            Ok(JmapMethodResponse(
164                "Mailbox/get".to_string(),
165                serde_json::to_value(response)?,
166                call_id.clone(),
167            ))
168        }
169        "Mailbox/set" => {
170            let request = serde_json::from_value(call.1)?;
171            let response = mailbox::mailbox_set(request, message_store.as_ref()).await?;
172            Ok(JmapMethodResponse(
173                "Mailbox/set".to_string(),
174                serde_json::to_value(response)?,
175                call_id.clone(),
176            ))
177        }
178        "Mailbox/query" => {
179            let request = serde_json::from_value(call.1)?;
180            let response = mailbox::mailbox_query(request, message_store.as_ref()).await?;
181            Ok(JmapMethodResponse(
182                "Mailbox/query".to_string(),
183                serde_json::to_value(response)?,
184                call_id.clone(),
185            ))
186        }
187        "Mailbox/changes" => {
188            let request = serde_json::from_value(call.1)?;
189            let response = mailbox::mailbox_changes(request, message_store.as_ref()).await?;
190            Ok(JmapMethodResponse(
191                "Mailbox/changes".to_string(),
192                serde_json::to_value(response)?,
193                call_id.clone(),
194            ))
195        }
196        "Mailbox/queryChanges" => {
197            let request = serde_json::from_value(call.1)?;
198            let response = mailbox::mailbox_query_changes(request, message_store.as_ref()).await?;
199            Ok(JmapMethodResponse(
200                "Mailbox/queryChanges".to_string(),
201                serde_json::to_value(response)?,
202                call_id.clone(),
203            ))
204        }
205
206        // Thread methods
207        "Thread/get" => {
208            let request = serde_json::from_value(call.1)?;
209            let response = thread::thread_get(request, message_store.as_ref()).await?;
210            Ok(JmapMethodResponse(
211                "Thread/get".to_string(),
212                serde_json::to_value(response)?,
213                call_id.clone(),
214            ))
215        }
216        "Thread/changes" => {
217            let request = serde_json::from_value(call.1)?;
218            let response = thread::thread_changes(request, message_store.as_ref()).await?;
219            Ok(JmapMethodResponse(
220                "Thread/changes".to_string(),
221                serde_json::to_value(response)?,
222                call_id.clone(),
223            ))
224        }
225
226        // SearchSnippet methods
227        "SearchSnippet/get" => {
228            let request = serde_json::from_value(call.1)?;
229            let response =
230                search_snippet::search_snippet_get(request, message_store.as_ref()).await?;
231            Ok(JmapMethodResponse(
232                "SearchSnippet/get".to_string(),
233                serde_json::to_value(response)?,
234                call_id.clone(),
235            ))
236        }
237
238        // Identity methods
239        "Identity/get" => {
240            let request = serde_json::from_value(call.1)?;
241            let response = identity::identity_get(request, message_store.as_ref()).await?;
242            Ok(JmapMethodResponse(
243                "Identity/get".to_string(),
244                serde_json::to_value(response)?,
245                call_id.clone(),
246            ))
247        }
248        "Identity/set" => {
249            let request = serde_json::from_value(call.1)?;
250            let response = identity::identity_set(request, message_store.as_ref()).await?;
251            Ok(JmapMethodResponse(
252                "Identity/set".to_string(),
253                serde_json::to_value(response)?,
254                call_id.clone(),
255            ))
256        }
257        "Identity/changes" => {
258            let request = serde_json::from_value(call.1)?;
259            let response = identity::identity_changes(request, message_store.as_ref()).await?;
260            Ok(JmapMethodResponse(
261                "Identity/changes".to_string(),
262                serde_json::to_value(response)?,
263                call_id.clone(),
264            ))
265        }
266
267        // VacationResponse methods
268        "VacationResponse/get" => {
269            let request = serde_json::from_value(call.1)?;
270            let response = vacation::vacation_response_get(request, message_store.as_ref()).await?;
271            Ok(JmapMethodResponse(
272                "VacationResponse/get".to_string(),
273                serde_json::to_value(response)?,
274                call_id.clone(),
275            ))
276        }
277        "VacationResponse/set" => {
278            let request = serde_json::from_value(call.1)?;
279            let response = vacation::vacation_response_set(request, message_store.as_ref()).await?;
280            Ok(JmapMethodResponse(
281                "VacationResponse/set".to_string(),
282                serde_json::to_value(response)?,
283                call_id.clone(),
284            ))
285        }
286
287        _ => {
288            // Return unknownMethod error
289            Ok(JmapMethodResponse(
290                "error".to_string(),
291                serde_json::to_value(
292                    JmapError::new(JmapErrorType::UnknownMethod)
293                        .with_detail(format!("Unknown method: {}", method_name)),
294                )?,
295                call_id.clone(),
296            ))
297        }
298    }
299}
300
301/// Validate that the method is supported by the declared capabilities
302fn validate_method_capability(method_name: &str, capabilities: &[String]) -> Result<(), JmapError> {
303    let required_capability = match method_name {
304        m if m.starts_with("Email/") => "urn:ietf:params:jmap:mail",
305        m if m.starts_with("Mailbox/") => "urn:ietf:params:jmap:mail",
306        m if m.starts_with("Thread/") => "urn:ietf:params:jmap:mail",
307        m if m.starts_with("SearchSnippet/") => "urn:ietf:params:jmap:mail",
308        m if m.starts_with("EmailSubmission/") => "urn:ietf:params:jmap:submission",
309        m if m.starts_with("Identity/") => "urn:ietf:params:jmap:submission",
310        m if m.starts_with("VacationResponse/") => "urn:ietf:params:jmap:vacationresponse",
311        _ => {
312            // Core methods don't require additional capabilities beyond core
313            return Ok(());
314        }
315    };
316
317    if !capabilities.iter().any(|cap| cap == required_capability) {
318        return Err(
319            JmapError::new(JmapErrorType::UnknownMethod).with_detail(format!(
320                "Method '{}' requires capability '{}' which was not declared in 'using'",
321                method_name, required_capability
322            )),
323        );
324    }
325
326    Ok(())
327}