Useage
use askama::Template;
use athene::prelude::*;
use athene::{html, json, status};
use headers::authorization::Bearer;
use headers::Authorization;
use serde::{Deserialize, Serialize};
use tracing::info;
use validator::{Validate, ValidationError};
#[derive(Serialize, Deserialize, Clone, Validate)]
pub struct User {
#[validate(email)]
pub username: String,
#[validate(range(min = 18, max = 20))]
pub age: u16,
#[validate(custom = "validate_user_role")]
pub role: Option<String>,
}
fn validate_user_role(role: &str) -> Result<(), ValidationError> {
if role.ne("student") {
return Err(ValidationError::new("invalid role"));
}
Ok(())
}
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct AtheneController {
pub label: String,
pub keyword: String,
}
impl Controller for AtheneController {
const BASE_PATH: &'static str = "/api/v1/athene";
fn method(&self) -> Vec<ControllerMethod<Self>>
where
Self: Sized,
{
let r = ControllerBuilder::new();
r.get("/index", |_, _req: Request| async {
"athene is a simple and lightweight rust web framework based on Hyper"
})
.post("/add", Self::add)
.delete("/{label}/{keyword}", Self::delete)
.put("/update", Self::update)
.get("/get", Self::get)
.build()
}
}
impl AtheneController {
pub async fn add(&self, mut req: Request) -> impl Responder {
let obj = req.parse::<Self>().await?;
Ok::<_, Error>((200, Json(obj)))
}
pub async fn delete(&self, mut req: Request) -> impl Responder {
let lable = req.param("label");
let keyword = req.param("keyword");
info!("{}, .... {}", lable, keyword);
(200, "delete success")
}
async fn update(&self, mut req: Request) -> impl Responder {
if let Ok(obj) = req.body_mut().parse::<Form<Self>>().await {
info!("{:#?}", obj);
(200, String::from("update success"))
} else {
(400, String::from("update failed"))
}
}
async fn get(&self, req: Request) -> impl Responder {
#[derive(Deserialize, Serialize)]
struct QueryParam<'a> {
label: &'a str,
keyword: &'a str,
}
let arg = req.query::<QueryParam>()?;
let res = json!(&arg);
Ok::<_, Error>(res)
}
}
pub struct UserController {}
#[controller(prefix = "api", version = 1, name = "user")]
impl UserController {
#[get("/*/**")]
pub async fn match_any_route(&self, req: Request) -> impl Responder {
let uri_path = req.uri().path().to_string();
let method = req.method().to_string();
(200, format!("uri : {}, method: {}", uri_path, method))
}
#[delete("/{username}/{age}")]
pub async fn delete_by_param(&self, username: String, age: Option<u16>) -> impl Responder {
(
200,
format!("username is : {}, and age is : {:?}", username, age),
)
}
#[get("/get_query_1")]
pub async fn get_query_1(&self, username: String, age: u16) -> impl Responder {
(
200,
Json(User {
username,
age,
role: None,
}),
)
}
#[get("/get_query_2")]
pub async fn get_query_2(&self, request: Request) -> impl Responder {
let user = request.query::<User>()?;
Ok::<_, Error>((200, Json(user)))
}
#[post("/post_json")]
#[validator] pub async fn post_json(&self, user: Json<User>) -> impl Responder {
Ok::<_,Error>((200, user))
}
#[get("/user_form")]
#[post("/user_form")]
#[validator(exclude("user"))] async fn user_form(&self, user: Form<User>) -> impl Responder {
(200, user)
}
#[post("/parse_body")]
#[validator] async fn parse_body(&self, mut req: Request) ->impl Responder{
let user = req.parse::<User>().await?;
Ok::<_,Error>((
200,
format!("username = {} and age = {}", user.username, user.age),
))
}
#[post("/files")]
async fn files(&self, mut req: Request) ->impl Responder {
let files = req.files("files").await?;
let fist_file_name = files[0].name().unwrap();
let second_file_name = files[1].name().unwrap();
let third_file_name = files[2].name().unwrap();
Ok::<_,Error>((
200,
format!(
"fist {}, second {}, third {}",
fist_file_name, second_file_name, third_file_name
),
))
}
}
struct ApiKeyMiddleware {
api_key: String,
}
#[middleware]
impl ApiKeyMiddleware {
async fn next(&self, ctx: Context, chain: &dyn Next) -> Result<Context, Error> {
if let Some(bearer) = ctx
.state
.request_unchecked()
.header::<Authorization<Bearer>>()
{
let token = bearer.0.token();
if token == self.api_key {
info!(
"Handler {} will be used",
ctx.metadata.name.unwrap_or("unknown")
);
chain.next(ctx).await
} else {
info!("Invalid token");
Ok(ctx)
}
} else {
info!("Not Authenticated, ");
Ok(ctx)
}
}
}
async fn log_middleware(ctx: Context, next: &'static dyn Next) -> Result<Context, Error> {
info!(
"new request on path: {}",
ctx.state.request_unchecked().uri().path()
);
let ctx = next.next(ctx).await?;
info!(
"new response with status: {}",
ctx.state.response_unchecked().status()
);
Ok(ctx)
}
pub async fn hello(_req: Request) -> impl Responder {
(200, "Hello, World!")
}
pub async fn user_params(mut req: Request) -> impl Responder {
let username = req.param("username");
let age = req.param("age");
let age = age.parse::<u16>().unwrap();
(
200,
Json(User {
username,
age,
role: None,
}),
)
}
pub async fn login(mut req: Request) -> impl Responder {
if let Ok(user) = req.body_mut().parse::<Json<User>>().await {
(
200,
format!("username: {} , age: {} ", user.username, user.age),
)
} else {
(400, String::from("Bad user format"))
}
}
pub async fn sign_up(mut req: Request) -> impl Responder {
let user = req.body_mut().parse::<Form<User>>().await?;
Ok::<_,Error>((200, Json(user)))
}
pub async fn read_body(mut req: Request) -> impl Responder {
let body = req.body_mut().parse::<String>().await?;
Ok::<_,Error>((200, body))
}
pub fn user_router(r: Router) -> Router {
r.get("/hello", hello)
.get("/{username}/{age}", user_params)
.post("/login", login)
.post("/sign_up", sign_up)
.put("/read_body", read_body)
}
#[tokio::main]
pub async fn main() -> Result<(), Error> {
tracing_subscriber::fmt().compact().init();
let app = athene::new()
.router(|r| {
r.get("/", |_req: Request| async {
"Welcome to the athene web framework "
})
.get("/permanent", |_req: Request| async {
let res = Builder::new();
res.redirect(StatusCode::PERMANENT_REDIRECT, "/")
})
.get("/temporary", |req: Request| async move {
let uri = req.uri().path();
info!("uri = {}", uri);
let res = Builder::new();
res.redirect(StatusCode::TEMPORARY_REDIRECT, "/download")
})
.get("/upload", |_req: Request| async {
html!(INDEX_HTML)
})
.post("/upload", |mut req: Request| async move {
req.upload("file", "temp").await
})
.get("/download", |_req: Request| async {
let mut res = status!(hyper::StatusCode::OK);
res.write_file("templates/author.txt", DispositionType::Attachment)?;
Ok::<_, Error>(res)
})
.delete("/delete", |req: Request| async move {
let path = req.uri().path().to_string();
(200, path)
})
.patch("/patch", |_req: Request| async {
let user = User {
username: "Rustacean".to_string(),
age: 18,
role: None,
};
json!(&user)
})
.get("/html", |_req: Request| async {
#[derive(Serialize, Template)]
#[template(path = "favorite.html")]
pub struct Favorite<'a> {
title: &'a str,
time: i32,
}
let love = Favorite {
title: "rust_lang",
time: 1314,
};
html!(love.render().unwrap())
})
})
.router(user_router)
.router(|r| {
r.controller(UserController {})
.controller(AtheneController::default())
})
.middleware(|m| {
m.apply(
ApiKeyMiddleware {
api_key: "athene".to_string(),
},
vec!["/api/v1/user"],
vec!["/api/v1/athene"],
)
.apply(log_middleware, vec!["/"], None)
})
.build();
app.listen("127.0.0.1:7878").await
}
static INDEX_HTML: &str = r#"<!DOCTYPE html>
<html>
<head>
<title>Upload Test</title>
</head>
<body>
<h1>Upload Test</h1>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" value="upload" />
</form>
</body>
</html>
"#;