parse_rs/
user.rs

1// src/user.rs
2
3use crate::object::{deserialize_string_to_option_parse_date, deserialize_string_to_parse_date};
4use crate::types::ParseDate;
5use crate::ParseError;
6use reqwest::Method;
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9
10/// Represents a Parse Server User object.
11///
12/// This struct contains standard fields for a user, such as `objectId`, `username`, `email`,
13/// `emailVerified`, `sessionToken`, `createdAt`, and `updatedAt`.
14/// It is used to deserialize user data received from the Parse Server and can also be
15/// (though less commonly for `ParseUser` itself) used for creating or updating user objects.
16#[derive(Debug, Serialize, Deserialize, Clone)]
17pub struct ParseUser {
18    #[serde(rename = "objectId")]
19    pub object_id: Option<String>,
20    pub username: String,
21    pub email: Option<String>,
22    #[serde(rename = "emailVerified")]
23    pub email_verified: Option<bool>,
24    #[serde(rename = "sessionToken")]
25    pub session_token: Option<String>,
26    #[serde(
27        rename = "createdAt",
28        deserialize_with = "deserialize_string_to_option_parse_date",
29        skip_serializing_if = "Option::is_none"
30    )]
31    pub created_at: Option<ParseDate>,
32    #[serde(
33        rename = "updatedAt",
34        deserialize_with = "deserialize_string_to_option_parse_date",
35        skip_serializing_if = "Option::is_none"
36    )]
37    pub updated_at: Option<ParseDate>,
38}
39
40// New struct for signup response
41/// Represents the successful response from a user signup operation.
42///
43/// It includes the `objectId` of the newly created user, their `sessionToken`,
44/// and the `createdAt` timestamp.
45#[derive(Debug, Deserialize, Clone)]
46pub struct SignupResponse {
47    #[serde(rename = "objectId")]
48    pub object_id: String,
49    #[serde(rename = "sessionToken")]
50    pub session_token: String,
51    #[serde(
52        rename = "createdAt",
53        deserialize_with = "deserialize_string_to_parse_date"
54    )]
55    pub created_at: ParseDate,
56}
57
58// Request body for user signup
59/// Represents the data required to sign up a new user.
60///
61/// This struct is typically serialized and sent as the body of a signup request.
62/// It includes `username`, `password`, and an optional `email`.
63/// Additional fields can be included by using a more generic type like `ParseObject` or `HashMap<String, Value>`
64/// with the `signup` method if the server is configured to accept them.
65#[derive(Serialize, Debug)]
66pub struct SignupRequest<'a> {
67    pub username: &'a str,
68    pub password: &'a str,
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub email: Option<&'a str>,
71}
72
73// Request body for user login
74/// Represents the data required to log in an existing user.
75///
76/// This struct is typically serialized and sent as the body of a login request.
77/// It includes `username` and `password`.
78#[derive(Serialize, Debug)]
79pub struct LoginRequest<'a> {
80    pub username: &'a str,
81    pub password: &'a str,
82}
83
84// Request body for password reset request
85/// Represents the data required to request a password reset for a user.
86///
87/// This struct is typically serialized and sent as the body of a password reset request.
88/// It includes the `email` address of the user requesting the reset.
89#[derive(Serialize, Debug)]
90pub struct PasswordResetRequest<'a> {
91    pub email: &'a str,
92}
93
94/// Provides methods for managing user authentication and user-specific operations.
95///
96/// An instance of `ParseUserHandle` is obtained by calling the [`user()`](crate::Parse::user)
97/// method on a `Parse` instance. It allows for operations such as signing up new users,
98/// logging in existing users, fetching the current user's details, logging out, and requesting password resets.
99///
100/// The handle maintains a mutable reference to the `Parse` to update its session state (e.g., `session_token`)
101/// upon successful login or signup, and to clear it on logout.
102pub struct ParseUserHandle<'a> {
103    client: &'a mut crate::Parse,
104}
105
106impl ParseUserHandle<'_> {
107    /// Creates a new `ParseUserHandle`.
108    ///
109    /// This constructor is typically called by `Parse::user()`.
110    ///
111    /// # Arguments
112    ///
113    /// * `client`: A mutable reference to the `Parse` instance that this handle will operate upon.
114    pub fn new(client: &mut crate::Parse) -> ParseUserHandle<'_> {
115        ParseUserHandle { client }
116    }
117
118    // User management methods
119    /// Signs up a new user with the Parse Server.
120    ///
121    /// This method sends the provided user data to the `/users` endpoint. Upon successful signup,
122    /// the Parse Server returns the new user's `objectId`, a `sessionToken`, and `createdAt` timestamp.
123    /// The `sessionToken` is automatically stored in the `Parse` instance, making the new user
124    /// the current authenticated user for subsequent requests.
125    ///
126    /// # Type Parameters
127    ///
128    /// * `T`: The type of the `user_data` argument. This type must implement `Serialize`, `Send`, and `Sync`.
129    ///   Commonly, this will be [`SignupRequest`](crate::user::SignupRequest) for standard username/password/email signups,
130    ///   but can also be a `ParseObject` or a `HashMap<String, Value>` if you need to include additional
131    ///   custom fields during signup (assuming your Parse Server is configured to allow this).
132    ///
133    /// # Arguments
134    ///
135    /// * `user_data`: A reference to the data for the new user. This typically includes `username` and `password`,
136    ///   and can optionally include `email` and other custom fields.
137    ///
138    /// # Returns
139    ///
140    /// A `Result` containing a [`SignupResponse`](crate::user::SignupResponse) if the signup is successful,
141    /// or a `ParseError` if the signup fails (e.g., username taken, invalid data, network issue).
142    ///
143    /// # Examples
144    ///
145    /// ```rust,no_run
146    /// use parse_rs::{Parse, ParseError, user::SignupRequest};
147    /// use serde_json::Value;
148    /// use std::collections::HashMap;
149    ///
150    /// # #[tokio::main]
151    /// # async fn main() -> Result<(), ParseError> {
152    /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
153    /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
154    /// # let mut client = Parse::new(&server_url, &app_id, None, None, None)?;
155    ///
156    /// // Example 1: Using SignupRequest for standard fields
157    /// let signup_details = SignupRequest {
158    ///     username: "new_user_1",
159    ///     password: "securePassword123",
160    ///     email: Some("user1@example.com"),
161    /// };
162    ///
163    /// match client.user().signup(&signup_details).await {
164    ///     Ok(response) => {
165    ///         println!("User '{}' signed up successfully! ObjectId: {}, Session Token: {}",
166    ///                  signup_details.username, response.object_id, response.session_token);
167    ///         assert_eq!(client.session_token(), Some(response.session_token.as_str()));
168    ///     }
169    ///     Err(e) => eprintln!("Signup failed for user '{}': {}", signup_details.username, e),
170    /// }
171    ///
172    /// // Example 2: Using HashMap for additional custom fields (if server allows)
173    /// let mut custom_signup_data = HashMap::new();
174    /// custom_signup_data.insert("username".to_string(), Value::String("new_user_2".to_string()));
175    /// custom_signup_data.insert("password".to_string(), Value::String("anotherSecurePass456".to_string()));
176    /// custom_signup_data.insert("email".to_string(), Value::String("user2@example.com".to_string()));
177    /// custom_signup_data.insert("customField".to_string(), Value::String("customValue".to_string()));
178    /// custom_signup_data.insert("age".to_string(), Value::Number(30.into()));
179    ///
180    /// // match client.user().signup(&custom_signup_data).await {
181    /// //     Ok(response) => {
182    /// //         println!("User with custom data signed up! ObjectId: {}, Session: {}",
183    /// //                  response.object_id, response.session_token);
184    /// //     }
185    /// //     Err(e) => eprintln!("Custom signup failed: {}", e),
186    /// // }
187    /// # Ok(())
188    /// # }
189    /// ```
190    pub async fn signup<T: Serialize + Send + Sync>(
191        &mut self,
192        user_data: &T, // Changed from SignupRequest to generic T for flexibility if ParseObject is used
193    ) -> Result<SignupResponse, ParseError> {
194        // Endpoint for signup is typically "users"
195        match self
196            .client
197            ._request::<_, SignupResponse>(Method::POST, "users", Some(user_data), false, None)
198            .await
199        {
200            Ok(response) => {
201                // Assuming SignupResponse contains a session_token field
202                self.client.session_token = Some(response.session_token.clone());
203                Ok(response)
204            }
205            Err(e) => Err(e),
206        }
207    }
208
209    /// Logs in an existing user with the Parse Server.
210    ///
211    /// This method sends the provided user credentials (typically username and password) to the `/login` endpoint.
212    /// Upon successful login, the Parse Server returns the full user object, including a `sessionToken`.
213    /// This `sessionToken` is automatically stored in the `Parse` instance, making the user
214    /// the current authenticated user for subsequent requests.
215    ///
216    /// # Type Parameters
217    ///
218    /// * `T`: The type of the `user_data` argument. This type must implement `Serialize`, `Send`, and `Sync`.
219    ///   Commonly, this will be [`LoginRequest`](crate::user::LoginRequest) for standard username/password logins.
220    ///   It could also be a `HashMap<String, Value>` if the server supports other login mechanisms via the same endpoint,
221    ///   though this is less common for the standard `/login` route.
222    ///
223    /// # Arguments
224    ///
225    /// * `user_data`: A reference to the credentials for the user to log in. This typically includes `username` and `password`.
226    ///
227    /// # Returns
228    ///
229    /// A `Result` containing the full [`ParseUser`](crate::user::ParseUser) object if the login is successful,
230    /// or a `ParseError` if the login fails (e.g., invalid credentials, user not found, network issue).
231    ///
232    /// # Examples
233    ///
234    /// ```rust,no_run
235    /// use parse_rs::{Parse, ParseError, user::LoginRequest};
236    ///
237    /// # #[tokio::main]
238    /// # async fn main() -> Result<(), ParseError> {
239    /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
240    /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
241    /// # let mut client = Parse::new(&server_url, &app_id, None, None, None)?;
242    ///
243    /// // Assume "test_user" was previously signed up
244    /// let login_details = LoginRequest {
245    ///     username: "test_user",
246    ///     password: "password123",
247    /// };
248    ///
249    /// match client.user().login(&login_details).await {
250    ///     Ok(logged_in_user) => {
251    ///         println!("User '{}' logged in successfully! Session Token: {}",
252    ///                  logged_in_user.username,
253    ///                  logged_in_user.session_token.as_deref().unwrap_or("N/A"));
254    ///         assert_eq!(client.session_token(), logged_in_user.session_token.as_deref());
255    ///         assert_eq!(logged_in_user.username, "test_user");
256    ///     }
257    ///     Err(e) => eprintln!("Login failed for user '{}': {}", login_details.username, e),
258    /// }
259    /// # Ok(())
260    /// # }
261    /// ```
262    pub async fn login<T: Serialize + Send + Sync>(
263        &mut self,
264        user_data: &T, // Changed from LoginRequest to generic T
265    ) -> Result<ParseUser, ParseError> {
266        // Endpoint for login is typically "login"
267        match self
268            .client
269            ._request::<_, ParseUser>(Method::POST, "login", Some(user_data), false, None)
270            .await
271        {
272            Ok(user_response) => {
273                self.client.session_token = user_response.session_token.clone();
274                Ok(user_response)
275            }
276            Err(e) => Err(e),
277        }
278    }
279
280    // GET /users/me - requires session token
281    /// Fetches the details of the currently authenticated user.
282    ///
283    /// This method makes a GET request to the `/users/me` endpoint, which requires a valid
284    /// session token to be present in the `Parse` (set automatically after a successful
285    /// login or signup).
286    ///
287    /// If no session token is available in the client, this method will return a
288    /// `ParseError::SessionTokenMissing` error without making a network request.
289    ///
290    /// # Returns
291    ///
292    /// A `Result` containing the [`ParseUser`](crate::user::ParseUser) object for the currently
293    /// authenticated user if successful, or a `ParseError` if the request fails (e.g., session token
294    /// is invalid or expired, network issue, or no session token is present).
295    ///
296    /// # Examples
297    ///
298    /// ```rust,no_run
299    /// use parse_rs::{Parse, ParseError, user::LoginRequest};
300    ///
301    /// # #[tokio::main]
302    /// # async fn main() -> Result<(), ParseError> {
303    /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
304    /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
305    /// # let mut client = Parse::new(&server_url, &app_id, None, None, None)?;
306    ///
307    /// // First, log in a user (or sign them up)
308    /// // let login_details = LoginRequest { username: "test_user", password: "password123" };
309    /// // client.user().login(&login_details).await?;
310    ///
311    /// if client.is_authenticated() {
312    ///     match client.user().me().await {
313    ///         Ok(current_user) => {
314    ///             println!("Current user: {}, Email: {:?}",
315    ///                      current_user.username, current_user.email.as_deref().unwrap_or("N/A"));
316    ///             // The sessionToken field in the returned ParseUser object from /users/me
317    ///             // might be the same or a new one depending on server configuration.
318    ///             // The client's session token remains the one used for the request.
319    ///         }
320    ///         Err(e) => eprintln!("Failed to fetch current user: {}", e),
321    ///     }
322    /// } else {
323    ///     println!("No user is currently authenticated.");
324    /// }
325    /// # Ok(())
326    /// # }
327    /// ```
328    pub async fn me(&self) -> Result<ParseUser, ParseError> {
329        // Removed &mut self as it only reads session token
330        if self.client.session_token.is_none() {
331            return Err(ParseError::SessionTokenMissing);
332        }
333        // current_user does not take a body
334        self.client
335            ._request(Method::GET, "users/me", None::<&Value>, false, None)
336            .await
337    }
338
339    // POST /logout - requires session token
340    /// Logs out the currently authenticated user.
341    ///
342    /// This method sends a POST request to the `/logout` endpoint using the current session token
343    /// stored in the `Parse`. If successful, the Parse Server invalidates the session token.
344    /// This method also clears the `session_token` from the `Parse` instance, effectively
345    /// ending the current user's session on the client-side as well.
346    ///
347    /// If no session token is available in the client, this method will return a
348    /// `ParseError::SessionTokenMissing` error without making a network request.
349    ///
350    /// # Returns
351    ///
352    /// A `Result` containing `()` (an empty tuple) if the logout is successful, or a `ParseError`
353    /// if the request fails (e.g., session token already invalid, network issue, or no session token
354    /// is present).
355    ///
356    /// # Examples
357    ///
358    /// ```rust,no_run
359    /// use parse_rs::{Parse, ParseError, user::LoginRequest};
360    ///
361    /// # #[tokio::main]
362    /// # async fn main() -> Result<(), ParseError> {
363    /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
364    /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
365    /// # let mut client = Parse::new(&server_url, &app_id, None, None, None)?;
366    ///
367    /// // First, ensure a user is logged in
368    /// // let login_details = LoginRequest { username: "test_user", password: "password123" };
369    /// // client.user().login(&login_details).await?;
370    ///
371    /// if client.is_authenticated() {
372    ///     println!("User is authenticated with token: {:?}", client.session_token());
373    ///     match client.user().logout().await {
374    ///         Ok(_) => {
375    ///             println!("User logged out successfully.");
376    ///             assert!(!client.is_authenticated(), "Client should not be authenticated after logout.");
377    ///             assert!(client.session_token().is_none(), "Session token should be cleared after logout.");
378    ///         }
379    ///         Err(e) => eprintln!("Logout failed: {}", e),
380    ///     }
381    /// } else {
382    ///     println!("No user was logged in to log out.");
383    /// }
384    ///
385    /// // Attempting logout again when not authenticated should fail (or do nothing gracefully)
386    /// // match client.user().logout().await {
387    /// //     Err(ParseError::SessionTokenMissing) => println!("Correctly failed: Session token missing for logout."),
388    /// //     _ => eprintln!("Logout behavior when not authenticated is unexpected."),
389    /// // }
390    /// # Ok(())
391    /// # }
392    /// ```
393    pub async fn logout(&mut self) -> Result<(), ParseError> {
394        if self.client.session_token.is_none() {
395            return Err(ParseError::SessionTokenMissing);
396        }
397        // Logout does not take a body and expects an empty JSON {} or specific success response
398        let result: Result<Value, _> = self
399            .client
400            ._request(Method::POST, "logout", None::<&Value>, false, None)
401            .await;
402        match result {
403            Ok(_value) => {
404                // Parse server returns an empty JSON object {} on successful logout
405                self.client.session_token = None;
406                Ok(())
407            }
408            Err(e) => Err(e),
409        }
410    }
411
412    // POST /requestPasswordReset - public, no session token needed
413    /// Requests a password reset email to be sent to the user associated with the given email address.
414    ///
415    /// This method sends a POST request to the `/requestPasswordReset` endpoint with the user's email.
416    /// The Parse Server then handles sending the password reset email if a user with that email exists.
417    /// This operation does not require a session token and can be called publicly.
418    ///
419    /// # Type Parameters
420    ///
421    /// * `T`: The type of the `email_data` argument. This type must implement `Serialize`, `Send`, and `Sync`.
422    ///   Commonly, this will be [`PasswordResetRequest`](crate::user::PasswordResetRequest).
423    ///
424    /// # Arguments
425    ///
426    /// * `email_data`: A reference to the data containing the email address for the password reset request.
427    ///
428    /// # Returns
429    ///
430    /// A `Result` containing `()` (an empty tuple) if the request is successfully sent to the server,
431    /// or a `ParseError` if the request fails (e.g., invalid email format, network issue).
432    /// Note: A successful response means the request was accepted by the server, not necessarily that
433    /// a user with that email exists or that an email was actually sent (to prevent leaking user information).
434    ///
435    /// # Examples
436    ///
437    /// ```rust,no_run
438    /// use parse_rs::{Parse, ParseError, user::PasswordResetRequest};
439    ///
440    /// # #[tokio::main]
441    /// # async fn main() -> Result<(), ParseError> {
442    /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
443    /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
444    /// # let mut client = Parse::new(&server_url, &app_id, None, None, None)?;
445    ///
446    /// let email_for_reset = "user_to_reset@example.com";
447    /// let reset_request_data = PasswordResetRequest { email: email_for_reset };
448    ///
449    /// match client.user().request_password_reset(&reset_request_data).await {
450    ///     Ok(_) => println!("Password reset request sent for email: {}", email_for_reset),
451    ///     Err(e) => eprintln!("Failed to send password reset request for email '{}': {}", email_for_reset, e),
452    /// }
453    /// # Ok(())
454    /// # }
455    /// ```
456    pub async fn request_password_reset<T: Serialize + Send + Sync>(
457        &self,
458        email_data: &T, // Changed from PasswordResetRequest to generic T
459    ) -> Result<(), ParseError> {
460        // request_password_reset expects an empty JSON {} or specific success response
461        let result: Result<Value, _> = self
462            .client
463            ._request(
464                Method::POST,
465                "requestPasswordReset",
466                Some(email_data),
467                false,
468                None,
469            )
470            .await;
471        match result {
472            Ok(_value) => Ok(()), // Expects empty {} on success
473            Err(e) => Err(e),
474        }
475    }
476
477    // GET /users/me - but with a different session token to become that user
478    // This is a tricky one. The `become` operation itself is a GET to /users/me, but authenticated with the *target* session token.
479    // The client's current session token is replaced upon success.
480    /// Allows the current client to "become" another user by using that user's session token.
481    ///
482    /// This method makes a GET request to `/users/me`, but authenticates it using the
483    /// `session_token_to_become` provided as an argument, instead of the client's current session token.
484    /// If the provided session token is valid, the server responds with the details of the user
485    /// associated with that token. Crucially, upon a successful response, this method **replaces**
486    /// the `Parse`'s current `session_token` with `session_token_to_become`.
487    ///
488    /// This is a powerful operation and should be used with caution, typically in administrative
489    /// contexts or when implementing features like "Log in as user" for support purposes.
490    /// The client performing this operation must have access to the target user's session token.
491    ///
492    /// # Arguments
493    ///
494    /// * `session_token_to_become`: A string slice representing the session token of the user
495    ///   to become.
496    ///
497    /// # Returns
498    ///
499    /// A `Result` containing the [`ParseUser`](crate::user::ParseUser) object for the user
500    /// whose session token was provided, if the operation is successful. Returns a `ParseError`
501    /// if the provided session token is invalid, expired, or if any other error occurs during
502    /// the request.
503    ///
504    /// # Examples
505    ///
506    /// ```rust,no_run
507    /// use parse_rs::{Parse, ParseError};
508    ///
509    /// # #[tokio::main]
510    /// # async fn main() -> Result<(), ParseError> {
511    /// # let server_url = std::env::var("PARSE_SERVER_URL").unwrap_or_else(|_| "http://localhost:1338/parse".to_string());
512    /// # let app_id = std::env::var("PARSE_APP_ID").unwrap_or_else(|_| "myAppId".to_string());
513    /// # let mut client = Parse::new(&server_url, &app_id, None, None, None)?;
514    ///
515    /// // Assume `target_user_session_token` is a valid session token for another user, obtained securely.
516    /// let target_user_session_token = "r:someValidSessionTokenForAnotherUser";
517    ///
518    /// println!("Client's current session token before 'become': {:?}", client.session_token());
519    ///
520    /// match client.user().become_user(target_user_session_token).await {
521    ///     Ok(newly_become_user) => {
522    ///         println!("Successfully became user '{}' (ID: {}).",
523    ///                  newly_become_user.username,
524    ///                  newly_become_user.object_id.as_deref().unwrap_or("N/A"));
525    ///         println!("Client's session token is now: {:?}", client.session_token());
526    ///         assert_eq!(client.session_token(), Some(target_user_session_token));
527    ///     }
528    ///     Err(e) => {
529    ///         eprintln!("Failed to become user with token '{}': {}", target_user_session_token, e);
530    ///         // Client's original session token should be restored if 'become' failed.
531    ///         println!("Client's session token after failed 'become': {:?}", client.session_token());
532    ///     }
533    /// }
534    /// # Ok(())
535    /// # }
536    /// ```
537    pub async fn become_user(
538        &mut self,
539        session_token_to_become: &str,
540    ) -> Result<ParseUser, ParseError> {
541        // Temporarily override client's auth for this specific request
542        // This requires a way to make _request use a specific session token, or a dedicated _request_with_token method.
543        // Current _request uses self.session_token or master_key.
544        // A simple way is to clone the client, set the token, make the request, then update original client.
545        // Or, modify _request to accept an optional override token.
546
547        // For now, let's assume _request needs modification or we use a more direct approach for this one-off auth.
548        // The most straightforward modification to _request would be to allow an optional override token.
549        // Let's simulate this by creating a temporary request builder here, which is not ideal as it bypasses _request's error handling.
550
551        // Ideal approach: Modify _request to handle an override token.
552        // Fallback: Direct reqwest call for this specific case.
553        // Given current _request, this is hard to do cleanly without modifying _request signature or logic.
554
555        // Let's assume for now that we will add a specialized method or enhance _request later.
556        // For this refactor, we'll placeholder it or acknowledge it needs a more specific implementation.
557        // One way to achieve this with current _request: temporarily set self.session_token, then revert.
558        let original_token = self.client.session_token.clone();
559        self.client.session_token = Some(session_token_to_become.to_string());
560
561        match self
562            .client
563            ._request(Method::GET, "users/me", None::<&Value>, false, None)
564            .await
565        {
566            Ok(user_data) => {
567                // self.session_token is already set to session_token_to_become, so this is correct.
568                Ok(user_data)
569            }
570            Err(e) => {
571                self.client.session_token = original_token; // Revert on error
572                Err(e)
573            } // If successful, self.session_token remains as session_token_to_become
574        }
575    }
576}