Skip to main content

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}