diff --git a/src/api/auth/register.rs b/src/api/auth/register.rs index cea10d2..191480f 100644 --- a/src/api/auth/register.rs +++ b/src/api/auth/register.rs @@ -1,18 +1,126 @@ pub mod register { - use custom_error::custom_error; use reqwest::Client; - use serde_json::json; + use serde_json::{from_str, json, Value}; - use crate::{api::schemas::schemas::RegisterSchema, instance::Instance}; + use crate::{ + api::{ + limits::LimitType, + schemas::schemas::{ErrorResponse, RegisterSchema}, + }, + errors::InstanceServerError, + instance::{Instance, Token}, + }; impl Instance { - pub fn register(&mut self, register_schema: &RegisterSchema) { + /** + Registers a new user on the Spacebar server. + # Arguments + * `register_schema` - The [`RegisterSchema`] that contains all the information that is needed to register a new user. + # Errors + * [`InstanceServerError`] - If the server does not respond. + */ + pub async fn register( + &mut self, + register_schema: &RegisterSchema, + ) -> Result { let json_schema = json!(register_schema); - let limited_requester = &self.requester; + let limited_requester = &mut self.requester; let client = Client::new(); let endpoint_url = self.urls.get_api().to_string() + "/auth/register"; let request_builder = client.post(endpoint_url).body(json_schema.to_string()); - // TODO + let response = limited_requester + .send_request(request_builder, LimitType::AuthRegister) + .await; + if response.is_none() { + return Err(InstanceServerError::NoResponse); + } + + let response_unwrap = response.unwrap(); + let status = response_unwrap.status(); + let response_text_string = response_unwrap.text().await.unwrap(); + if status.is_client_error() { + let json: ErrorResponse = serde_json::from_str(&response_text_string).unwrap(); + let error_type = json.errors.errors.iter().next().unwrap().0.to_owned(); + let mut error = "".to_string(); + for (_, value) in json.errors.errors.iter() { + for error_item in value._errors.iter() { + error += &(error_item.message.to_string() + " (" + &error_item.code + ")"); + } + } + return Err(InstanceServerError::InvalidFormBodyError { error_type, error }); + } + return Ok(Token { + token: response_text_string, + }); } } } + +#[cfg(test)] +mod test { + use crate::api::schemas::schemas::RegisterSchema; + use crate::errors::InstanceServerError; + use crate::instance::Instance; + use crate::limit::LimitedRequester; + use crate::URLBundle; + #[tokio::test] + async fn test_incomplete_registration() { + let urls = URLBundle::new( + "http://localhost:3001/api".to_string(), + "http://localhost:3001".to_string(), + "http://localhost:3001".to_string(), + ); + let limited_requester = LimitedRequester::new(urls.get_api().to_string()).await; + let mut test_instance = Instance::new(urls.clone(), limited_requester) + .await + .unwrap(); + let reg = RegisterSchema::new( + "aaa".to_string(), + None, + true, + Some("me@mail.xy".to_string()), + None, + None, + None, + None, + None, + None, + ) + .unwrap(); + assert_eq!( + InstanceServerError::InvalidFormBodyError { + error_type: "date_of_birth".to_string(), + error: "This field is required (BASE_TYPE_REQUIRED)".to_string() + }, + test_instance.register(®).await.err().unwrap() + ); + } + + #[tokio::test] + async fn test_registration() { + let urls = URLBundle::new( + "http://localhost:3001/api".to_string(), + "http://localhost:3001".to_string(), + "http://localhost:3001".to_string(), + ); + let limited_requester = LimitedRequester::new(urls.get_api().to_string()).await; + let mut test_instance = Instance::new(urls.clone(), limited_requester) + .await + .unwrap(); + let reg = RegisterSchema::new( + "Hiiii".to_string(), + Some("mysupersecurepass123!".to_string()), + true, + Some("flori@mail.xyz".to_string()), + None, + None, + Some("2000-01-01".to_string()), + None, + None, + None, + ) + .unwrap(); + let token = test_instance.register(®).await.unwrap().token; + println!("{}", token); + } +} diff --git a/src/api/policies/instance/instance.rs b/src/api/policies/instance/instance.rs index aa5f8ae..2eaac5b 100644 --- a/src/api/policies/instance/instance.rs +++ b/src/api/policies/instance/instance.rs @@ -1,32 +1,25 @@ pub mod instance { - use custom_error::custom_error; use reqwest::Client; use serde_json::from_str; + use crate::errors::InstanceServerError; use crate::{api::schemas::schemas::InstancePoliciesSchema, instance::Instance}; - custom_error! { - #[derive(PartialEq, Eq)] - pub InstancePoliciesError - RequestErrorError{url:String, error:String} = "An error occured while trying to GET from {url}: {error}", - ReceivedErrorCodeError{error_code:String} = "Received the following error code while requesting from the route: {error_code}" - } - impl Instance { /** Gets the instance policies schema. # Errors - [`InstancePoliciesError`] - If the request fails. + [`InstanceServerError`] - If the request fails. */ pub async fn instance_policies_schema( &self, - ) -> Result { + ) -> Result { let client = Client::new(); let endpoint_url = self.urls.get_api().to_string() + "/policies/instance/"; let request = match client.get(&endpoint_url).send().await { Ok(result) => result, Err(e) => { - return Err(InstancePoliciesError::RequestErrorError { + return Err(InstanceServerError::RequestErrorError { url: endpoint_url, error: e.to_string(), }); @@ -34,7 +27,7 @@ pub mod instance { }; if request.status().as_str().chars().next().unwrap() != '2' { - return Err(InstancePoliciesError::ReceivedErrorCodeError { + return Err(InstanceServerError::ReceivedErrorCodeError { error_code: request.status().to_string(), }); } diff --git a/src/api/schemas.rs b/src/api/schemas.rs index 73cd464..e0a770d 100644 --- a/src/api/schemas.rs +++ b/src/api/schemas.rs @@ -1,9 +1,9 @@ pub mod schemas { - use std::fmt; - - use custom_error::custom_error; use regex::Regex; use serde::{Deserialize, Serialize}; + use std::fmt; + + use crate::errors::FieldFormatError; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] @@ -20,23 +20,14 @@ pub mod schemas { promotional_email_opt_in: Option, } - custom_error! { - #[derive(PartialEq, Eq)] - pub RegisterSchemaError - PasswordError = "Password must be between 1 and 72 characters.", - UsernameError = "Username must be between 2 and 32 characters.", - ConsentError = "Consent must be 'true' to register.", - EmailError = "The provided email address is in an invalid format." - } - impl RegisterSchema { /** - Returns a new [`Result`]. + Returns a new [`Result`]. ## Arguments All but "String::username" and "bool::consent" are optional. ## Errors - You will receive a [`RegisterSchemaError`], if: + You will receive a [`FieldFormatError`], if: - The username is less than 2 or more than 32 characters in length - You supply a `password` which is less than 1 or more than 72 characters in length. @@ -53,22 +44,22 @@ pub mod schemas { gift_code_sku_id: Option, captcha_key: Option, promotional_email_opt_in: Option, - ) -> Result { + ) -> Result { if username.len() < 2 || username.len() > 32 { - return Err(RegisterSchemaError::UsernameError); + return Err(FieldFormatError::UsernameError); } if password.is_some() && (password.as_ref().unwrap().len() < 1 || password.as_ref().unwrap().len() > 72) { - return Err(RegisterSchemaError::PasswordError); + return Err(FieldFormatError::PasswordError); } if !consent { - return Err(RegisterSchemaError::ConsentError); + return Err(FieldFormatError::ConsentError); } let regex = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap(); if email.clone().is_some() && !regex.is_match(email.clone().unwrap().as_str()) { - return Err(RegisterSchemaError::EmailError); + return Err(FieldFormatError::EmailError); } return Ok(RegisterSchema { @@ -159,6 +150,31 @@ pub mod schemas { ) } } + + #[derive(Serialize, Deserialize, Debug)] + pub struct ErrorResponse { + pub code: i32, + pub message: String, + pub errors: IntermittentError, + } + + #[derive(Serialize, Deserialize, Debug)] + pub struct IntermittentError { + #[serde(flatten)] + pub errors: std::collections::HashMap, + } + + #[derive(Serialize, Deserialize, Debug, Default)] + pub struct ErrorField { + #[serde(default)] + pub _errors: Vec, + } + + #[derive(Serialize, Deserialize, Debug)] + pub struct Error { + pub message: String, + pub code: String, + } } // I know that some of these tests are... really really basic and unneccessary, but sometimes, I @@ -166,6 +182,7 @@ pub mod schemas { #[cfg(test)] mod schemas_tests { use super::schemas::*; + use crate::errors::FieldFormatError; #[test] fn password_too_short() { @@ -182,7 +199,7 @@ mod schemas_tests { None, None, ), - Err(RegisterSchemaError::PasswordError) + Err(FieldFormatError::PasswordError) ); } @@ -205,7 +222,7 @@ mod schemas_tests { None, None, ), - Err(RegisterSchemaError::PasswordError) + Err(FieldFormatError::PasswordError) ); } @@ -224,7 +241,7 @@ mod schemas_tests { None, None, ), - Err(RegisterSchemaError::UsernameError) + Err(FieldFormatError::UsernameError) ); } @@ -236,7 +253,7 @@ mod schemas_tests { } assert_eq!( RegisterSchema::new(long_un, None, true, None, None, None, None, None, None, None,), - Err(RegisterSchemaError::UsernameError) + Err(FieldFormatError::UsernameError) ); } @@ -255,7 +272,7 @@ mod schemas_tests { None, None, ), - Err(RegisterSchemaError::ConsentError) + Err(FieldFormatError::ConsentError) ); } @@ -274,7 +291,7 @@ mod schemas_tests { None, None, ), - Err(RegisterSchemaError::EmailError) + Err(FieldFormatError::EmailError) ) } @@ -292,6 +309,6 @@ mod schemas_tests { None, None, ); - assert_ne!(reg, Err(RegisterSchemaError::EmailError)); + assert_ne!(reg, Err(FieldFormatError::EmailError)); } } diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..e57d5c8 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,20 @@ +use custom_error::custom_error; + +custom_error! { + #[derive(PartialEq, Eq)] + pub FieldFormatError + PasswordError = "Password must be between 1 and 72 characters.", + UsernameError = "Username must be between 2 and 32 characters.", + ConsentError = "Consent must be 'true' to register.", + EmailError = "The provided email address is in an invalid format.", +} + +custom_error! { + #[derive(PartialEq, Eq)] + pub InstanceServerError + NoResponse = "Did not receive a response from the Server.", + RequestErrorError{url:String, error:String} = "An error occured while trying to GET from {url}: {error}", + ReceivedErrorCodeError{error_code:String} = "Received the following error code while requesting from the route: {error_code}", + CantGetInfoError{error:String} = "Something seems to be wrong with the instance. Cannot get information about the instance: {error}", + InvalidFormBodyError{error_type: String, error:String} = "The server responded with: {error_type}: {error}", +} diff --git a/src/instance.rs b/src/instance.rs index db1502d..a655735 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1,8 +1,5 @@ -use regex::internal::Inst; - -use crate::api::instance; use crate::api::schemas::schemas::InstancePoliciesSchema; -use crate::gateway::Gateway; +use crate::errors::{FieldFormatError, InstanceServerError}; use crate::limit::LimitedRequester; use crate::URLBundle; @@ -31,7 +28,7 @@ impl Instance { pub async fn new( urls: URLBundle, requester: LimitedRequester, - ) -> Result { + ) -> Result { let users: HashMap = HashMap::new(); let mut instance = Instance { urls, @@ -51,36 +48,27 @@ impl Instance { }; instance.instance_info = match instance.instance_policies_schema().await { Ok(schema) => schema, - Err(e) => return Err(InstanceError{message: format!("Something seems to be wrong with the instance. Cannot get information about the instance: {}", e)}), + Err(e) => { + return Err(InstanceServerError::CantGetInfoError { + error: e.to_string(), + }) + } }; Ok(instance) } } -#[derive(Debug, PartialEq, Eq)] -pub struct InstanceError { - pub message: String, -} - -impl InstanceError { - fn new(message: String) -> Self { - InstanceError { message } - } -} - -impl fmt::Display for InstanceError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.message) - } -} - -impl std::error::Error for InstanceError {} - #[derive(Debug, PartialEq, Eq)] pub struct Token { pub token: String, } +impl fmt::Display for Token { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.token) + } +} + #[derive(Debug, PartialEq, Eq)] pub struct Username { pub username: String, @@ -92,31 +80,10 @@ impl Username { /// * `username` - The username that will be used to create the [`Username`]. /// # Errors /// * [`UsernameFormatError`] - If the username is not between 2 and 32 characters. - pub fn new(username: String) -> Result { + pub fn new(username: String) -> Result { if username.len() < 2 || username.len() > 32 { - return Err(UsernameFormatError::new( - "Username must be between 2 and 32 characters".to_string(), - )); + return Err(FieldFormatError::UsernameError); } return Ok(Username { username }); } } - -#[derive(Debug, PartialEq, Eq)] -pub struct UsernameFormatError { - pub message: String, -} - -impl UsernameFormatError { - fn new(message: String) -> Self { - UsernameFormatError { message } - } -} - -impl fmt::Display for UsernameFormatError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.message) - } -} - -impl std::error::Error for UsernameFormatError {} diff --git a/src/lib.rs b/src/lib.rs index 838f31b..ab97b74 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ mod api; +mod errors; mod gateway; mod instance; mod limit;