jmap_chat_client/methods/space_invite.rs
1//! JMAP Chat — SpaceInvite/* method implementations on SessionClient.
2//!
3//! Spec: JMAP Chat draft §4.17 (SpaceInvite/get, /changes, /set).
4
5use jmap_types::{Id, State};
6
7use super::{ChangesResponse, GetResponse, SetResponse, SpaceInviteCreateInput};
8
9impl super::SessionClient {
10 /// Fetch SpaceInvite objects by IDs (JMAP Chat §4.17 SpaceInvite/get).
11 ///
12 /// If `ids` is `None`, returns all SpaceInvite objects for the account.
13 /// Pass `properties: None` to return all fields.
14 ///
15 /// # Errors
16 ///
17 /// - [`ClientError::InvalidSession`](jmap_base_client::ClientError::InvalidSession)
18 /// if the bound session has no primary account for
19 /// `urn:ietf:params:jmap:chat`.
20 /// - Any transport / protocol variant returned by
21 /// [`JmapClient::call`](jmap_base_client::JmapClient::call):
22 /// [`Http`](jmap_base_client::ClientError::Http),
23 /// [`Parse`](jmap_base_client::ClientError::Parse),
24 /// [`AuthFailed`](jmap_base_client::ClientError::AuthFailed),
25 /// [`MethodError`](jmap_base_client::ClientError::MethodError)
26 /// (wraps RFC 8620 §3.6.2 method-level errors such as
27 /// `accountNotFound`, `invalidArguments`, `serverFail`),
28 /// [`MethodNotFound`](jmap_base_client::ClientError::MethodNotFound),
29 /// [`ResponseTooLarge`](jmap_base_client::ClientError::ResponseTooLarge),
30 /// or
31 /// [`UnexpectedResponse`](jmap_base_client::ClientError::UnexpectedResponse).
32 pub async fn space_invite_get(
33 &self,
34 ids: Option<&[Id]>,
35 properties: Option<&[&str]>,
36 ) -> Result<GetResponse<jmap_chat_types::SpaceInvite>, jmap_base_client::ClientError> {
37 let (api_url, account_id) = self.session_parts()?;
38 // Omit `ids` / `properties` when None — see the matching comment on
39 // `chat_get` for the rationale (consistent with set/changes/query).
40 let mut args = serde_json::json!({ "accountId": account_id });
41 if let Some(id_slice) = ids {
42 args["ids"] = serde_json::to_value(id_slice)
43 .map_err(jmap_base_client::ClientError::from_parse)?;
44 }
45 if let Some(props) = properties {
46 args["properties"] =
47 serde_json::to_value(props).map_err(jmap_base_client::ClientError::from_parse)?;
48 }
49 let req = super::build_request("SpaceInvite/get", args, super::USING_CHAT);
50 let resp = self.call_internal(api_url, &req).await?;
51 jmap_base_client::extract_response(&resp, super::CALL_ID)
52 }
53
54 /// Fetch changes to SpaceInvite objects since `since_state` (RFC 8620 §5.2 / SpaceInvite/changes).
55 ///
56 /// If `has_more_changes` is true in the response, call again with `new_state`
57 /// as `since_state` until the flag is false.
58 ///
59 /// # Errors
60 ///
61 /// - [`ClientError::InvalidArgument`](jmap_base_client::ClientError::InvalidArgument)
62 /// if `since_state` is the empty string (defence-in-depth
63 /// empty-state guard).
64 /// - [`ClientError::InvalidSession`](jmap_base_client::ClientError::InvalidSession)
65 /// if the bound session has no primary account for
66 /// `urn:ietf:params:jmap:chat`.
67 /// - Any transport / protocol variant returned by
68 /// [`JmapClient::call`](jmap_base_client::JmapClient::call) — see
69 /// the matching error list on [`Self::space_invite_get`].
70 pub async fn space_invite_changes(
71 &self,
72 since_state: &State,
73 max_changes: Option<u64>,
74 ) -> Result<ChangesResponse, jmap_base_client::ClientError> {
75 // Defence-in-depth: see `chat_changes`.
76 if since_state.as_ref().is_empty() {
77 return Err(jmap_base_client::ClientError::InvalidArgument(
78 "space_invite_changes: since_state may not be empty".into(),
79 ));
80 }
81 let (api_url, account_id) = self.session_parts()?;
82 let mut args = serde_json::json!({
83 "accountId": account_id,
84 "sinceState": since_state,
85 });
86 if let Some(mc) = max_changes {
87 args["maxChanges"] = mc.into();
88 }
89 let req = super::build_request("SpaceInvite/changes", args, super::USING_CHAT);
90 let resp = self.call_internal(api_url, &req).await?;
91 jmap_base_client::extract_response(&resp, super::CALL_ID)
92 }
93
94 /// Create a SpaceInvite (RFC 8620 §5.3 / SpaceInvite/set create).
95 ///
96 /// When `input.client_id` is `None`, a ULID is generated automatically.
97 ///
98 /// # Errors
99 ///
100 /// - [`ClientError::InvalidSession`](jmap_base_client::ClientError::InvalidSession)
101 /// if the bound session has no primary account for
102 /// `urn:ietf:params:jmap:chat`.
103 /// - Any transport / protocol variant returned by
104 /// [`JmapClient::call`](jmap_base_client::JmapClient::call) — see
105 /// the matching error list on [`Self::space_invite_get`]. /set
106 /// create errors (e.g. `invalidProperties`, `forbidden`) appear
107 /// in [`SetResponse::not_created`] rather than as [`Err`].
108 pub async fn space_invite_create(
109 &self,
110 input: &SpaceInviteCreateInput<'_>,
111 ) -> Result<SetResponse, jmap_base_client::ClientError> {
112 let (api_url, account_id) = self.session_parts()?;
113 let mut obj = serde_json::json!({ "spaceId": input.space_id });
114 if let Some(ch) = input.default_channel_id {
115 obj["defaultChannelId"] = ch.as_ref().into();
116 }
117 if let Some(ea) = input.expires_at {
118 obj["expiresAt"] = ea.as_ref().into();
119 }
120 if let Some(mu) = input.max_uses {
121 obj["maxUses"] = mu.into();
122 }
123 let client_id = super::resolve_client_id(input.client_id);
124 let args = serde_json::json!({
125 "accountId": account_id,
126 "create": { client_id: obj },
127 });
128 let req = super::build_request("SpaceInvite/set", args, super::USING_CHAT);
129 let resp = self.call_internal(api_url, &req).await?;
130 jmap_base_client::extract_response(&resp, super::CALL_ID)
131 }
132
133 /// Destroy SpaceInvite objects (RFC 8620 §5.3 / SpaceInvite/set destroy).
134 ///
135 /// `ids` must be non-empty; the guard fires before any network call.
136 ///
137 /// # Errors
138 ///
139 /// - [`ClientError::InvalidArgument`](jmap_base_client::ClientError::InvalidArgument)
140 /// if `ids` is empty (caller-precondition guard).
141 /// - [`ClientError::InvalidSession`](jmap_base_client::ClientError::InvalidSession)
142 /// if the bound session has no primary account for
143 /// `urn:ietf:params:jmap:chat`.
144 /// - Any transport / protocol variant returned by
145 /// [`JmapClient::call`](jmap_base_client::JmapClient::call) — see
146 /// the matching error list on [`Self::space_invite_get`]. /set
147 /// destroy errors appear in [`SetResponse::not_destroyed`]
148 /// rather than as [`Err`].
149 pub async fn space_invite_destroy(
150 &self,
151 ids: &[Id],
152 ) -> Result<SetResponse, jmap_base_client::ClientError> {
153 if ids.is_empty() {
154 return Err(jmap_base_client::ClientError::InvalidArgument(
155 "space_invite_destroy: ids may not be empty".into(),
156 ));
157 }
158 let (api_url, account_id) = self.session_parts()?;
159 let args = serde_json::json!({
160 "accountId": account_id,
161 "destroy": ids,
162 });
163 let req = super::build_request("SpaceInvite/set", args, super::USING_CHAT);
164 let resp = self.call_internal(api_url, &req).await?;
165 jmap_base_client::extract_response(&resp, super::CALL_ID)
166 }
167}