parse_rs/session.rs
1use reqwest::Method;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4
5use crate::error::ParseError;
6use crate::object::{deserialize_string_to_option_parse_date, deserialize_string_to_parse_date};
7use crate::types::ParseDate;
8use crate::Parse;
9
10/// Represents a Parse Server Session object, detailing an active user session.
11///
12/// This struct includes standard fields for a session such as `objectId`, `sessionToken`,
13/// `createdAt`, `updatedAt`, `expiresAt`, and information about the associated `user`.
14/// It also captures `installationId` if the session originated from a specific installation,
15/// and `createdWith` which describes how the session was initiated (e.g., login, signup).
16///
17/// The `user` field is a `serde_json::Value` because its content can vary. If the query
18/// fetching the session includes `include=user`, this field will contain the full `ParseUser` object.
19/// Otherwise, it might be a pointer or a more minimal representation.
20#[derive(Debug, Serialize, Deserialize, Clone)]
21pub struct ParseSession {
22 #[serde(rename = "objectId")]
23 pub object_id: String,
24
25 #[serde(deserialize_with = "deserialize_string_to_parse_date")]
26 #[serde(rename = "createdAt")]
27 pub created_at: ParseDate,
28
29 #[serde(default)] // Default will be Option::None
30 #[serde(deserialize_with = "deserialize_string_to_option_parse_date")]
31 #[serde(rename = "updatedAt")]
32 pub updated_at: Option<ParseDate>,
33
34 /// The user associated with this session. Can be a full `ParseUser` object if `include=user` is used.
35 pub user: Value, // When 'include=user' is used, this will be the full User object
36
37 #[serde(rename = "sessionToken")]
38 pub session_token: String,
39
40 #[serde(rename = "installationId")]
41 pub installation_id: Option<String>,
42
43 #[serde(rename = "expiresAt")]
44 #[serde(default)] // Default will be Option::None
45 pub expires_at: Option<ParseDate>,
46
47 pub restricted: Option<bool>, // Typically false for normal sessions, true for restricted ones
48
49 #[serde(rename = "createdWith")]
50 pub created_with: Option<Value>, // e.g., {"action": "login", "authProvider": "password"}
51
52 // Catch all for other fields
53 #[serde(flatten)]
54 pub other_fields: std::collections::HashMap<String, Value>,
55}
56
57/// Represents the successful response from a session update operation.
58///
59/// When a session is updated via the API (e.g., using `ParseSessionHandle::update_by_object_id`),
60/// the server typically responds with the `updatedAt` timestamp of the modified session.
61#[derive(Debug, Serialize, Deserialize, Clone)]
62#[serde(rename_all = "camelCase")]
63pub struct SessionUpdateResponse {
64 /// The timestamp indicating when the session was last updated.
65 pub updated_at: String,
66}
67
68/// Represents the response structure when fetching multiple sessions.
69///
70/// This struct is used to deserialize the JSON array of session objects returned by endpoints
71/// like `/parse/sessions` when queried with the Master Key.
72#[derive(Debug, Deserialize, Clone)]
73pub struct GetAllSessionsResponse {
74 /// A vector containing the [`ParseSession`](crate::session::ParseSession) objects retrieved.
75 pub results: Vec<ParseSession>,
76 // Optionally, add `count: Option<i64>` if count is requested and needed.
77}
78
79/// Provides methods for interacting with Parse Server sessions.
80///
81/// An instance of `ParseSessionHandle` is obtained by calling the [`session()`](crate::Parse::session)
82/// method on a `Parse` instance. It allows for operations such as retrieving the current session's details,
83/// fetching specific sessions by ID (Master Key required), deleting sessions (Master Key required),
84/// updating sessions (Master Key required), and listing all sessions (Master Key required).
85///
86/// This handle operates in the context of the `Parse` it was created from, using its configuration
87/// (server URL, app ID, keys) for API requests.
88pub struct ParseSessionHandle<'a> {
89 client: &'a Parse,
90}
91
92impl<'a> ParseSessionHandle<'a> {
93 /// Creates a new `ParseSessionHandle`.
94 ///
95 /// This constructor is typically called by `Parse::session()`.
96 ///
97 /// # Arguments
98 ///
99 /// * `client`: A reference to the `Parse` instance that this handle will operate upon.
100 pub fn new(client: &'a Parse) -> Self {
101 ParseSessionHandle { client }
102 }
103
104 /// Retrieves the current user's session details.
105 ///
106 /// This method makes a GET request to the `/sessions/me` endpoint. It requires an active
107 /// session token to be configured in the `Parse` (typically set after a successful
108 /// login or signup). The server then returns the full details of the session associated
109 /// with that token.
110 ///
111 /// If no session token is available in the client, this method will return a
112 /// `ParseError::SessionTokenMissing` error without making a network request.
113 ///
114 /// # Returns
115 ///
116 /// A `Result` containing the [`ParseSession`](crate::session::ParseSession) object for the current session
117 /// if successful, or a `ParseError` if the request fails (e.g., session token invalid/expired,
118 /// network issue, or no session token present).
119 ///
120 /// # Examples
121 ///
122 /// ```rust,no_run
123 /// use parse_rs::{Parse, ParseError, user::LoginRequest};
124 ///
125 /// # #[tokio::main]
126 /// # async fn main() -> Result<(), ParseError> {
127 /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
128 /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
129 /// # let mut client = Parse::new(&server_url, &app_id, None, None, None)?;
130 ///
131 /// // Assume a user has logged in, so client.session_token() is Some(...)
132 /// // let login_details = LoginRequest { username: "test_user", password: "password123" };
133 /// // client.user().login(&login_details).await?;
134 ///
135 /// if client.is_authenticated() {
136 /// match client.session().me().await {
137 /// Ok(current_session) => {
138 /// println!("Current session details retrieved for user: {:?}", current_session.user);
139 /// println!("Session token: {}", current_session.session_token);
140 /// println!("Expires at: {:?}", current_session.expires_at);
141 /// // The session token in current_session should match client.session_token()
142 /// assert_eq!(Some(current_session.session_token.as_str()), client.session_token());
143 /// }
144 /// Err(e) => eprintln!("Failed to get current session details: {}", e),
145 /// }
146 /// } else {
147 /// println!("No user is currently authenticated to get session details.");
148 /// }
149 /// # Ok(())
150 /// # }
151 /// ```
152 // Corresponds to GET /parse/sessions/me
153 pub async fn me(&self) -> Result<ParseSession, ParseError> {
154 if self.client.session_token.is_none() {
155 return Err(ParseError::SessionTokenMissing);
156 }
157 // GET /sessions/me does not take a body and uses the client's current session token for auth.
158 self.client
159 ._request(Method::GET, "sessions/me", None::<&Value>, false, None) // false for use_master_key, None for explicit session_token (uses client's)
160 .await
161 }
162
163 /// Retrieves a specific session by its `objectId`.
164 ///
165 /// This method makes a GET request to the `/sessions/:objectId` endpoint. It requires the
166 /// Master Key to be configured in the `Parse` for authorization, as accessing arbitrary
167 /// session objects is a privileged operation.
168 ///
169 /// # Arguments
170 ///
171 /// * `object_id`: A string slice representing the `objectId` of the session to retrieve.
172 ///
173 /// # Returns
174 ///
175 /// A `Result` containing the [`ParseSession`](crate::session::ParseSession) object if found and the Master Key is valid,
176 /// or a `ParseError` if the session is not found, the Master Key is missing or invalid, or any
177 /// other error occurs (e.g., network issue).
178 ///
179 /// # Examples
180 ///
181 /// ```rust,no_run
182 /// use parse_rs::{Parse, ParseError};
183 ///
184 /// # #[tokio::main]
185 /// # async fn main() -> Result<(), ParseError> {
186 /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
187 /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
188 /// # let master_key = std::env::var("PARSE_MASTER_KEY").unwrap_or_else(|_| "myMasterKey".to_string());
189 /// // Ensure the client is initialized with the Master Key for this operation
190 /// let client = Parse::new(&server_url, &app_id, None, None, Some(&master_key))?;
191 ///
192 /// let session_object_id_to_fetch = "someValidSessionObjectId"; // Replace with an actual session objectId
193 ///
194 /// match client.session().get_by_object_id(session_object_id_to_fetch).await {
195 /// Ok(session) => {
196 /// println!("Successfully fetched session with objectId: {}", session.object_id);
197 /// println!("Associated user: {:?}", session.user);
198 /// println!("Session token: {}", session.session_token);
199 /// }
200 /// Err(e) => {
201 /// eprintln!("Failed to get session by objectId '{}': {}", session_object_id_to_fetch, e);
202 /// // This could be due to various reasons: session not found, master key invalid, network error, etc.
203 /// // e.g., if master key is missing or wrong, ParseError::MasterKeyMissingOrInvalid might be returned by the server (as unauthorized).
204 /// // e.g., if session_object_id_to_fetch does not exist, ParseError::ObjectNotFound might be returned.
205 /// }
206 /// }
207 /// # Ok(())
208 /// # }
209 /// ```
210 pub async fn get_by_object_id(&self, object_id: &str) -> Result<ParseSession, ParseError> {
211 let endpoint = format!("sessions/{}", object_id);
212 self.client
213 ._request(Method::GET, &endpoint, None::<&Value>, true, None) // true for use_master_key
214 .await
215 }
216
217 /// Deletes a specific session by its `objectId`.
218 ///
219 /// This method makes a DELETE request to the `/sessions/:objectId` endpoint. It requires the
220 /// Master Key to be configured in the `Parse` for authorization, as deleting arbitrary
221 /// session objects is a privileged operation. Successfully deleting a session effectively
222 /// invalidates that session token, forcing the user to log in again.
223 ///
224 /// # Arguments
225 ///
226 /// * `object_id`: A string slice representing the `objectId` of the session to delete.
227 ///
228 /// # Returns
229 ///
230 /// A `Result` containing `()` (an empty tuple) if the session is successfully deleted.
231 /// Returns a `ParseError` if the session is not found, the Master Key is missing or invalid,
232 /// or any other error occurs (e.g., network issue).
233 ///
234 /// # Examples
235 ///
236 /// ```rust,no_run
237 /// use parse_rs::{Parse, ParseError};
238 ///
239 /// # #[tokio::main]
240 /// # async fn main() -> Result<(), ParseError> {
241 /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
242 /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
243 /// # let master_key = std::env::var("PARSE_MASTER_KEY").unwrap_or_else(|_| "myMasterKey".to_string());
244 /// // Ensure the client is initialized with the Master Key for this operation
245 /// let client = Parse::new(&server_url, &app_id, None, None, Some(&master_key))?;
246 ///
247 /// let session_object_id_to_delete = "someSessionObjectIdToDelete"; // Replace with an actual session objectId
248 ///
249 /// match client.session().delete_by_object_id(session_object_id_to_delete).await {
250 /// Ok(_) => {
251 /// println!("Successfully deleted session with objectId: {}", session_object_id_to_delete);
252 /// }
253 /// Err(e) => {
254 /// eprintln!("Failed to delete session by objectId '{}': {}", session_object_id_to_delete, e);
255 /// // Common errors: ParseError::ObjectNotFound if the session doesn't exist,
256 /// // ParseError::MasterKeyMissingOrInvalid (or generic unauthorized) if master key is wrong.
257 /// }
258 /// }
259 /// # Ok(())
260 /// # }
261 /// ```
262 pub async fn delete_by_object_id(&self, object_id: &str) -> Result<(), ParseError> {
263 let endpoint = format!("sessions/{}", object_id);
264 // Expect serde_json::Value to consume the empty {} response, then map to Ok(()).
265 // The master key is required for this operation.
266 let _: Value = self
267 .client
268 ._request(Method::DELETE, &endpoint, None::<&Value>, true, None) // true for use_master_key
269 .await?;
270 Ok(())
271 }
272
273 /// Updates a specific session by its `objectId`.
274 ///
275 /// This method makes a PUT request to the `/sessions/:objectId` endpoint, allowing modifications
276 /// to the specified session object. It requires the Master Key to be configured in the `Parse`
277 /// for authorization. The `session_data` argument should be a serializable struct or map
278 /// containing the fields to be updated on the session object.
279 ///
280 /// Note: Not all fields on a session object are typically mutable. Consult the Parse Server
281 /// documentation for details on which fields can be updated.
282 ///
283 /// # Type Parameters
284 ///
285 /// * `T`: The type of the `session_data` argument. This type must implement `Serialize`, `Send`, and `Sync`.
286 /// It can be a custom struct representing the updatable fields or a `serde_json::Value` for dynamic updates.
287 ///
288 /// # Arguments
289 ///
290 /// * `object_id`: A string slice representing the `objectId` of the session to update.
291 /// * `session_data`: A reference to the data containing the fields to update.
292 ///
293 /// # Returns
294 ///
295 /// A `Result` containing a [`SessionUpdateResponse`](crate::session::SessionUpdateResponse) (which typically includes the `updatedAt` timestamp)
296 /// if the session is successfully updated. Returns a `ParseError` if the session is not found,
297 /// the Master Key is missing or invalid, the update data is invalid, or any other error occurs.
298 ///
299 /// # Examples
300 ///
301 /// ```rust,no_run
302 /// use parse_rs::{Parse, ParseError, session::SessionUpdateResponse};
303 /// use serde_json::json; // For creating a JSON value to update
304 ///
305 /// # #[tokio::main]
306 /// # async fn main() -> Result<(), ParseError> {
307 /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
308 /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
309 /// # let master_key = std::env::var("PARSE_MASTER_KEY").unwrap_or_else(|_| "myMasterKey".to_string());
310 /// // Client must be initialized with the Master Key
311 /// let client = Parse::new(&server_url, &app_id, None, None, Some(&master_key))?;
312 ///
313 /// let session_object_id_to_update = "someSessionObjectIdToUpdate"; // Replace with an actual session objectId
314 ///
315 /// // Example: Update a custom field on the session. Parse Server must be configured to allow this.
316 /// // Let's assume we want to add/update a field `customData: { "notes": "privileged update" }`
317 /// let update_payload = json!({
318 /// "customData": {
319 /// "notes": "privileged update by master key"
320 /// }
321 /// });
322 ///
323 /// match client.session().update_by_object_id(session_object_id_to_update, &update_payload).await {
324 /// Ok(response) => {
325 /// println!(
326 /// "Successfully updated session '{}'. New updatedAt: {}",
327 /// session_object_id_to_update,
328 /// response.updated_at
329 /// );
330 /// }
331 /// Err(e) => {
332 /// eprintln!(
333 /// "Failed to update session by objectId '{}': {}",
334 /// session_object_id_to_update, e
335 /// );
336 /// // Common errors: ParseError::ObjectNotFound, ParseError::MasterKeyMissingOrInvalid, or invalid update payload.
337 /// }
338 /// }
339 /// # Ok(())
340 /// # }
341 /// ```
342 pub async fn update_by_object_id<T: Serialize + Send + Sync>(
343 &self,
344 object_id: &str,
345 session_data: &T,
346 ) -> Result<SessionUpdateResponse, ParseError> {
347 let endpoint = format!("sessions/{}", object_id);
348 self.client
349 ._request(Method::PUT, &endpoint, Some(session_data), true, None) // true for use_master_key
350 .await
351 }
352
353 /// Retrieves multiple sessions, optionally filtered and paginated using a query string.
354 ///
355 /// This method makes a GET request to the `/sessions` endpoint. It requires the Master Key
356 /// to be configured in the `Parse` for authorization, as listing all sessions is a
357 /// highly privileged operation.
358 ///
359 /// The `query_string` argument allows for server-side filtering, pagination, ordering, and
360 /// inclusion of related data (like the `user` object via `include=user`).
361 ///
362 /// # Arguments
363 ///
364 /// * `query_string`: An optional string slice representing the URL-encoded query parameters.
365 /// For example: `"limit=10&skip=20&include=user&where={\"user\":{\"$inQuery\":{\"where\":{\"username\":\"test_user\"},\"className\":\"_User\"}}}"`
366 /// If `None`, all sessions (up to the server's default limit) are requested.
367 ///
368 /// # Returns
369 ///
370 /// A `Result` containing a `Vec<ParseSession>` if the request is successful. The vector will
371 /// contain the session objects matching the query. Returns a `ParseError` if the Master Key
372 /// is missing or invalid, the query string is malformed, or any other error occurs.
373 ///
374 /// # Examples
375 ///
376 /// ```rust,no_run
377 /// use parse_rs::{Parse, ParseError};
378 ///
379 /// # #[tokio::main]
380 /// # async fn main() -> Result<(), ParseError> {
381 /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
382 /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
383 /// # let master_key = std::env::var("PARSE_MASTER_KEY").unwrap_or_else(|_| "myMasterKey".to_string());
384 /// // Client must be initialized with the Master Key
385 /// let client = Parse::new(&server_url, &app_id, None, None, Some(&master_key))?;
386 ///
387 /// // Example 1: Get all sessions (respecting server default limit) and include user data
388 /// match client.session().get_all_sessions(Some("include=user")).await {
389 /// Ok(sessions) => {
390 /// println!("Successfully retrieved {} sessions (with user data):", sessions.len());
391 /// for session in sessions {
392 /// println!(" Session ID: {}, User: {:?}", session.object_id, session.user);
393 /// }
394 /// }
395 /// Err(e) => eprintln!("Failed to get all sessions with user data: {}", e),
396 /// }
397 ///
398 /// // Example 2: Get the first 5 sessions, ordered by creation date descending
399 /// let query_params = "limit=5&order=-createdAt";
400 /// match client.session().get_all_sessions(Some(query_params)).await {
401 /// Ok(sessions) => {
402 /// println!("\nSuccessfully retrieved {} sessions (first 5, newest first):", sessions.len());
403 /// for session in sessions {
404 /// println!(" Session ID: {}, Created At: {:?}", session.object_id, session.created_at);
405 /// }
406 /// }
407 /// Err(e) => eprintln!("Failed to get paginated/ordered sessions: {}", e),
408 /// }
409 ///
410 /// // Example 3: Get sessions without any query parameters (server defaults apply)
411 /// match client.session().get_all_sessions(None).await {
412 /// Ok(sessions) => {
413 /// println!("\nSuccessfully retrieved {} sessions (server defaults):", sessions.len());
414 /// }
415 /// Err(e) => eprintln!("Failed to get sessions with no query: {}", e),
416 /// }
417 /// # Ok(())
418 /// # }
419 /// ```
420 pub async fn get_all_sessions(
421 &self,
422 query_string: Option<&str>,
423 ) -> Result<Vec<ParseSession>, ParseError> {
424 #[derive(Deserialize, Debug)]
425 struct SessionsResponse<T> {
426 results: Vec<T>,
427 }
428
429 let endpoint = match query_string {
430 Some(qs) => format!("sessions?{}", qs),
431 None => "sessions".to_string(),
432 };
433 let response: SessionsResponse<ParseSession> = self
434 .client
435 ._request(Method::GET, &endpoint, None::<&Value>, true, None) // true for use_master_key
436 .await?;
437 Ok(response.results)
438 }
439}