commit 9da559ff4f8304516a559095b31dac5d4a43d0af Author: texhno Date: Mon Dec 11 02:24:54 2023 +0100 Initial commit Implemented user/users/favicon routes Implemented catchers 404/403 Implemented Responder traits diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..97ae356 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "routes_rocket" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +lazy_static = "1.4.0" +rocket = "0.5.0" diff --git a/Rocket.toml b/Rocket.toml new file mode 100644 index 0000000..468701a --- /dev/null +++ b/Rocket.toml @@ -0,0 +1,2 @@ +[default] +port = 3000 diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..c5a701e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,233 @@ +#[macro_use] extern crate rocket; + +use rocket::Request; +use rocket::fs::{NamedFile, relative}; +use rocket::http::{ContentType, Status}; +use rocket::{response::{self, Responder, Response}, Build, Rocket, request::FromParam}; +use lazy_static::lazy_static; +use std::collections::HashMap; +use std::io::Cursor; +use std::path::Path; + +fn default_response<'r>() -> response::Response<'r> { + Response::build() + .header(ContentType::Plain) + .raw_header("X-CUSTOM-ID", "CUSTOM") + .finalize() +} + +#[derive(FromForm)] +struct Filters { + age: u8, + active: bool, +} + +#[derive(Debug)] +struct User { + uuid: String, + name: String, + age: u8, + grade: u8, + active: bool, +} + +impl<'r> Responder<'r, 'r> for &'r User { + fn respond_to(self, _: &'r rocket::Request<'_>) -> response::Result<'r> { + let base_response = default_response(); + let user = format!("Found user: {:?}", self); + Response::build() + .sized_body(user.len(), Cursor::new(user)) + .raw_header("X-USER-ID", self.uuid.to_string()) + .merge(base_response) + // .header(ContentType::Plain) + .ok() + } +} + +/* + * Defining a new type where neither the type nor impl are in our app or crate. + */ + +struct NewUser<'a>(Vec<&'a User>); + +impl<'r> Responder<'r, 'r> for NewUser<'r> { + fn respond_to(self, _: &'r rocket::Request<'_>) -> response::Result<'r> { + let base_response = default_response(); + let user = self + .0 + .iter() + .map(|u| format!("{:?}", u)) + .collect::>() + .join(","); + Response::build() + .sized_body(user.len(), Cursor::new(user)) + .raw_header("X-CUSTOM-ID", "USERS") + .join(base_response) + .ok() + } +} + +lazy_static! { + static ref USERS: HashMap<&'static str, User> = { + let mut map = HashMap::new(); + map.insert( + "111-111-111-222-111", + User { + uuid: String::from("111-111-111-222-111"), + name: String::from("JohnDoe"), + age: 18, + grade: 1, + active: true, + } + ); + map.insert( + "111-111-111-222-112", + User { + uuid: String::from("111-111-111-222-112"), + name: String::from("JohnDoe2"), + age: 20, + grade: 2, + active: true, + } + ); + map.insert( + "111-111-111-222-113", + User { + uuid: String::from("111-111-111-222-113"), + name: String::from("JohnDoe3"), + age: 12, + grade: 1, + active: true, + } + ); + map.insert( + "111-111-111-222-114", + User { + uuid: String::from("111-111-111-222-114"), + name: String::from("JohnDoe4"), + age: 20, + grade: 1, + active: true, + } + ); + map.insert( + "111-111-111-222-115", + User { + uuid: String::from("111-111-111-222-115"), + name: String::from("JohnDoe5"), + age: 18, + grade: 1, + active: true, + } + ); + map.insert( + "111-111-111-222-116", + User { + uuid: String::from("111-111-111-222-116"), + name: String::from("JohnDoe6"), + age: 19, + grade: 3, + active: true, + } + ); + map.insert( + "111-111-111-222-117", + User { + uuid: String::from("111-111-111-222-117"), + name: String::from("JohnDoe7"), + age: 19, + grade: 2, + active: true, + } + ); + map.insert( + "111-111-111-222-118", + User { + uuid: String::from("111-111-111-222-118"), + name: String::from("JohnDoe8"), + age: 21, + grade: 2, + active: true, + } + ); + map.insert( + "111-111-111-222-119", + User { + uuid: String::from("111-111-111-222-119"), + name: String::from("JohnDoe9"), + age: 21, + grade: 2, + active: true, + } + ); + map + }; +} + +#[route(GET,uri = "/user/", rank = 1, format = "text/plain")] +fn user(uuid: &str) -> Result<&User, Status> { + USERS.get(uuid).ok_or(Status::NotFound) +} + +struct NameGrade<'r> { + name: &'r str, + grade: u8, +} + +impl<'r> FromParam<'r> for NameGrade<'r> { + type Error = &'static str; + fn from_param(param: &'r str) -> Result { + const ERORR_MESSAGE: Result = Err("Error parting user parameter"); + let name_grade_vec: Vec<&'r str> = param.split("_").collect(); + match name_grade_vec.len() { + 2 => match name_grade_vec[1].parse::() { + Ok(n) => Ok(Self { + name: name_grade_vec[0], + grade: n, + }), + Err(_) => ERORR_MESSAGE, + }, + _ => ERORR_MESSAGE, + } + } +} + +#[route(GET, uri = "/users/?")] +fn users(name_grade: NameGrade, filters: Option) -> Result { + let users: Vec<&User> = USERS + .values() + .filter(|user| user.name.contains(&name_grade.name) && user.grade == name_grade.grade) + .filter(|user| { + if let Some(fts) = &filters { + user.age == fts.age && user.active == fts.active + } else { true } + }) + .collect(); + if users.len() > 0 { + Ok(NewUser(users)) + } else { + Err(Status::Forbidden) + } +} + +#[get("/favicon.png")] +async fn favicon() -> NamedFile { + NamedFile::open(Path::new(relative!("static")).join("favicon.png")).await.unwrap() +} + +#[catch(403)] +fn forbidden(req: &Request) -> String { + format!("Access forbidden {}.", req.uri()) +} + +#[catch(404)] +fn not_found(req: &Request) -> String { + format!("We cannot find this page {}.", req.uri()) +} + +#[launch] +fn rocket() -> Rocket { + rocket::build() + .mount("/", routes![user, users, favicon]) + .register("/", catchers![not_found, forbidden]) +}