Initial commit

Implemented user/users/favicon routes

Implemented catchers 404/403

Implemented Responder traits
This commit is contained in:
texhno 2023-12-11 02:24:54 +01:00
commit 9da559ff4f
4 changed files with 247 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
Cargo.lock

10
Cargo.toml Normal file
View File

@ -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"

2
Rocket.toml Normal file
View File

@ -0,0 +1,2 @@
[default]
port = 3000

233
src/main.rs Normal file
View File

@ -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::<Vec<String>>()
.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/<uuid>", 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<Self, Self::Error> {
const ERORR_MESSAGE: Result<NameGrade, &'static str> = 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::<u8>() {
Ok(n) => Ok(Self {
name: name_grade_vec[0],
grade: n,
}),
Err(_) => ERORR_MESSAGE,
},
_ => ERORR_MESSAGE,
}
}
}
#[route(GET, uri = "/users/<name_grade>?<filters..>")]
fn users(name_grade: NameGrade, filters: Option<Filters>) -> Result<NewUser, Status> {
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<Build> {
rocket::build()
.mount("/", routes![user, users, favicon])
.register("/", catchers![not_found, forbidden])
}