HTTP Request URL Parameters Syntax
| Pattern |
Kind |
Description |
:name |
Normal |
Matches a path piece, excludes / |
:name? |
Optional |
Matches an optional path piece, excludes / |
/:name?/ /:name? |
OptionalSegment |
Matches an optional path segment, excludes /, prefix or suffix should be / |
+ :name+ |
OneOrMore |
Matches a path piece, includes / |
* :name* |
ZeroOrMore |
Matches an optional path piece, includes / |
/*/ /* /:name*/ /:name* |
ZeroOrMoreSegment |
Matches zero or more path segments, prefix or suffix should be / |
Supports
| Case |
Parameters |
:a:b |
a b |
:a:b? |
a b |
:a-:b :a.:b :a~:b |
a b |
:a_a-:b_b |
a_a b_b |
:a\\: :a\\_ |
a |
:a\\::b :a\\_:b |
a b |
:a* |
a |
* |
*1 |
*.* |
*1 *2 |
:a+ |
a |
+ |
+1 |
+.+ |
+1 +2 |
/*/abc/+/def/g |
*1 +2 |
⚡️ Quick Start
use hypers's full feature
use hypers::prelude::*;
use std::time::Instant;
use utoipa::{
openapi::security::{ApiKey, ApiKeyValue, SecurityScheme},
Modify,
};
use utoipa::{IntoParams, OpenApi, ToSchema};
struct SecurityAddon;
impl Modify for SecurityAddon {
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
let components = openapi.components.as_mut().unwrap(); components.add_security_scheme(
"api_key",
SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::new("access_token"))),
)
}
}
mod extract {
use super::*;
pub struct Extract;
#[derive(Serialize, Deserialize, ToSchema, Extract)]
#[extract(source("header"))]
pub struct HeaderParam {
pub host: Option<String>,
#[serde(rename = "user-agent")]
pub user_agent: Option<String>,
pub accept: Option<String>,
}
#[derive(Serialize, Deserialize, Extract)]
#[extract(source("cookie"))]
pub struct CookieParams {
hypers: String,
rust: String,
}
#[derive(Serialize, Deserialize, IntoParams, ToSchema, Extract, Debug)]
#[into_params(parameter_in = Query)]
pub struct Data {
pub id: Option<u16>,
pub name: Option<String>,
pub age: Option<u16>,
pub first_name: Option<String>,
pub last_name: Option<String>,
}
#[derive(Serialize, Deserialize, IntoParams, ToSchema, Extract, Debug)]
pub struct Params {
pub id: Option<u16>,
pub name: Option<String>,
pub age: Option<u16>,
}
#[derive(Serialize, Deserialize, ToSchema)]
pub enum DataError {
Config(String),
NotFound(String),
Normal(Params),
}
impl Responder for DataError {
fn response(self, builder: Response) -> Response {
match self {
Self::Config(e) => builder.status(409).text(e.to_string()),
Self::NotFound(e) => builder.status(404).text(e.to_string()),
Self::Normal(p) => builder.status(200).json(&p),
}
}
}
#[openapi(prefix = "api", version = 1, name = "extract")]
impl Extract {
#[get("/header")]
pub fn header(input: HeaderParam) -> impl Responder {
(200, input)
}
#[get("/set_cookies")]
pub async fn set_cookies() -> impl Responder {
let mut cookie1 = Cookie::new("hypers", "hypers2023");
cookie1.set_path("/extract");
let mut cookie2 = Cookie::new("rust", "rust2023");
cookie2.set_path("/extract");
let mut cookie_jar = CookieJar::new();
cookie_jar.add(cookie1);
cookie_jar.add(cookie2);
(200, format!("cookie_jar is :{:?}", cookie_jar))
}
#[get("/cookies")]
pub async fn cookies(input: CookieParams) -> impl Responder {
(200, input)
}
#[get("param/:id/:name/:age")]
pub async fn param(data: Params) -> impl Responder {
if data.age.ne(&Some(18)) {
(
404,
DataError::NotFound(String::from("age must be equal to 18")),
)
} else {
(200, DataError::Normal(data))
}
}
#[post("post_query")]
#[get("query")]
pub async fn query(data: Data) -> impl Responder {
(200, data)
}
#[get("get_form")]
#[patch("form")]
pub async fn form(user: Data) -> impl Responder {
Ok::<_, Error>((200, user))
}
#[post("multipart_form")]
pub async fn multipart_form(form_data: Data) -> impl Responder {
Ok::<_, Error>((200, form_data))
}
#[post("json")]
pub async fn json(user: Data) -> impl Responder {
Ok::<_, Error>((200, user))
}
}
}
mod parse {
use super::*;
#[derive(Serialize, Deserialize, ToSchema, Debug)]
pub struct Data {
pub id: Option<u16>,
pub name: Option<String>,
pub age: Option<u16>,
pub first_name: Option<String>,
pub last_name: Option<String>,
}
#[utoipa::path(
get,
path = "/parse/header",
tag = "parse request headers",
responses(
(status = 200, description = "parse request header successfully"),
(status = 400, description = "parse request header failed")
))]
#[handler]
pub fn header(host: Header<String, true>, accept: Header<String, true>) -> impl Responder {
(
200,
format!(
"host is :{}\n,accept is :{}\n",
host.inner(),
accept.inner(),
),
)
}
#[utoipa::path(
get,
path = "/parse/cookies",
tag = "parse cookies",
responses(
(status = 200, description = "Parse cookies from request successfully",body = String),
(status = 400, description = "Parse cookies from request failed")
))]
#[handler]
pub fn cookies(
hypers: CookieParam<String, true>,
rust: CookieParam<String, true>,
) -> impl Responder {
Ok::<_, Error>((
200,
format!("first cookie is {}, second cookie is {}", hypers, rust),
))
}
#[utoipa::path(
delete,
path = "/parse/param/{id}/{name}/{age}",
tag = "parse request url path params",
params(
("id" = u16, Path, description = "Id of readme to delete"),
("age" = u16, Path, description = "Age of readme to delete"),
("name" = String, Path, description = "Name of readme to delete"),
),
responses(
(status = 200, description = "Parse Url Path Params successfully",body = Data),
(status = 400, description = "Parse Url Path Params failed")
),
security(
("api_key" = [])
))]
#[handler]
pub fn param(
req: &mut Request,
id: Path<u16>,
name: Path<String>,
age: Path<u16>,
) -> impl Responder {
let app_state = req.get::<&str>("key");
println!("app_state = {:?}", app_state);
let api_key = req
.headers()
.get("readme_apikey")
.map(|v| v.to_str().unwrap_or_default().to_string())
.unwrap_or_default();
println!("api_key = {:?}", api_key);
(
200,
Json(Data {
id: Some(id.0),
name: Some(name.0),
age: Some(age.0),
first_name: None,
last_name: None,
}),
)
}
#[utoipa::path(
get,
path = "/parse/query",
tag = "parse request url query params",
params(
("id" = u16, Query, description = "Id of readme to get"),
("age" = u16, Query, description = "Age of readme to get"),
("name" = String, Query, description = "Name of readme to get"),
),
responses(
(status = 200, description = "Parse request url query params successfully", body = Data),
(status = 400, description = "Parse request url query params failed")
))]
#[handler]
pub fn query(
id: Query<u16, true>,
name: Query<String, true>,
age: Query<u16, true>,
) -> impl Responder {
Ok::<_, Error>((
200,
Json(Data {
id: Some(id.inner()),
name: Some(name.inner()),
age: Some(age.inner()),
first_name: None,
last_name: None,
}),
))
}
#[utoipa::path(
get,
path = "/parse/query_vec",
tag = "parse request url query params",
params(
("name" = Vec<String>, Query, description = "Url Query Params name"),
("age" = u16, Query, description = "Url Query Params age"),
),
responses(
(status = 200, description = "successfully, response = String"),
(status = 400, description = "failed")
))]
#[handler]
pub fn query_vec(req: &mut Request) -> impl Responder {
let name = req.query::<Vec<String>>("name")?;
let age = req.query::<u16>("age")?;
Some((200, format!("name = {:?} , age = {}", name, age)))
}
#[utoipa::path(
patch,
path = "/parse/form",
tag = "parse request body",
request_body(
content = Data,
content_type = "application/x-www-form-urlencoded",
),
responses(
(status = 200, description = "FormParams created successfully", body = Data),
(status = 409, description = "FormParams already exists")
))]
#[handler]
pub fn form(user: Form<Data>) -> impl Responder {
Ok::<_, Error>((200, Json(user.0)))
}
#[utoipa::path(
post,
path = "/parse/multipart_form",
tag = "parse request body",
request_body(
content = Data,
content_type = "multipart/form-data",
),
responses(
(status = 200, description = "FormParams created successfully", body = Data),
(status = 409, description = "FormParams already exists")
))]
#[handler]
pub fn multipart_form(form_data: Form<Data>) -> impl Responder {
Ok::<_, Error>((200, Json(form_data.0)))
}
#[utoipa::path(
post,
path = "/parse/multipart_file",
tag = "upload files",
responses(
(status = 200, description = "upload files successfully", body = String),
(status = 400, description = "upload files failed")
))]
#[handler]
pub async fn multipart_file(req: &mut Request) -> impl Responder {
let file = req.file("file").await?;
let file_name = file.name()?;
let file_name = file_name.to_string();
let img = req.files("imgs").await?;
let imgs_name = img
.iter()
.map(|m| m.name().unwrap().to_string())
.collect::<Vec<String>>()
.join(",");
Some((
200,
format!("file_name = {}, imgs_name = {}", file_name, imgs_name),
))
}
#[utoipa::path(
post,
path = "/parse/json",
tag = "parse request body",
request_body(
content = Data,
content_type = "application/json",
),
responses(
(status = 200, description = "Data item created successfully", body = Data),
(status = 409, description = "Data already exists")
))]
#[handler]
pub fn json(user: Json<Data>) -> impl Responder {
Ok::<_, Error>((200, user))
}
#[derive(OpenApi)]
#[openapi(
info(title = "Parse Api", description = "Parse Api description"),
paths(header,cookies,param,query,query_vec,form,multipart_form,multipart_file,json),
components(schemas(Data)),
modifiers(&SecurityAddon),
tags(
(name = "parse", description = "Parse items management API")
))]
struct ParseApiDoc;
pub fn parse_router() -> Router {
let mut parse = Router::new("parse");
parse.get("header", header);
parse.get("cookies", cookies);
parse.delete("param/:id/:name/:age", param);
parse.get("query", query);
parse.get("query_vec", query_vec);
parse.patch("form", form);
parse.post("multipart_form", multipart_form);
parse.post("multipart_file", multipart_file);
parse.post("json", json);
parse.openapi(ParseApiDoc::openapi());
parse
}
}
mod middleware {
use super::*;
#[handler]
pub async fn stat_time(req: &mut Request, next: &Next<'_>) -> Result {
let start_time = Instant::now();
let res = next.next(req).await?;
let elapsed_time = start_time.elapsed();
println!(
"The current request processing function takes time :{:?}",
elapsed_time
);
Ok(res)
}
#[handler]
pub async fn app_state(req: &mut Request, next: &Next<'_>) -> Result {
req.set("key", "Hello World");
next.next(req).await
}
use jsonwebtoken::{
decode, encode, errors::ErrorKind, DecodingKey, EncodingKey, Header, Validation,
};
pub const SECRET_KEY: &str = "afafava-rust-lang";
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct JWTClaims {
pub username: String,
pub exp: i64,
}
#[handler]
impl JWTClaims {
pub fn generate_token(&self, secret: &str) -> Result<String> {
return match encode(
&Header::default(),
&self,
&EncodingKey::from_secret(secret.as_ref()),
) {
Ok(t) => Ok(t),
Err(_) => Err(Error::Response(401, json!("JWTToken encode fail!"))),
};
}
pub fn verify(secret: &str, token: &str) -> Result<Self, Error> {
let validation = Validation::default();
return match decode::<JWTClaims>(
token,
&DecodingKey::from_secret(secret.as_ref()),
&validation,
) {
Ok(c) => Ok(c.claims),
Err(err) => match *err.kind() {
ErrorKind::InvalidToken => {
return Err(Error::Response(401, json!("InvalidToken")))
}
ErrorKind::InvalidIssuer => {
return Err(Error::Response(401, json!("InvalidIssuer")))
}
_ => return Err(Error::Response(401, json!("InvalidToken other errors"))),
},
};
}
pub fn checked_token(token: &str) -> Result<JWTClaims> {
let claims = JWTClaims::verify(SECRET_KEY, token);
match claims {
Ok(token) => Ok(token),
Err(e) => Err(Error::Other(e.to_string())),
}
}
async fn handle(&self, req: &mut Request, next: &Next<'_>) -> Result<Response> {
let token = req
.headers()
.get("access_token")
.map(|v| v.to_str().unwrap_or_default().to_string())
.unwrap_or_default();
match Self::checked_token(&token) {
Ok(_) => next.next(req).await,
Err(e) => Err(Error::Other(e.to_string())),
}
}
}
}
mod root {
use super::*;
use middleware::{JWTClaims, SECRET_KEY};
use time::{Duration, OffsetDateTime};
#[utoipa::path(
get,
path = "/html",
tag = "html",
responses(
(status = 200, description = "html successfully", body = String, content_type = "text/html" ),
(status = 400, description = "html failed")
))]
#[handler]
pub async fn html() -> impl Responder {
Text::Html("<html><body>hello</body></html>")
}
#[handler]
pub async fn websocket(req: &mut Request) -> impl Responder {
let name = req.param::<String>("name").unwrap_or_default();
WebSocketUpgrade::new()
.upgrade(req, |mut ws| async move {
while let Ok(msg) = ws.receive().await {
if let Some(msg) = msg {
match msg {
Message::Text(text) => {
let text = format!("{},{}", name, text);
ws.send(Message::Text(text)).await.unwrap();
}
Message::Close(_) => break,
_ => {}
}
}
}
})
.await
}
#[derive(Serialize, Deserialize, Default, ToSchema, Debug)]
pub struct User {
pub name: String,
pub age: u16,
}
#[derive(Serialize, Deserialize, Default)]
pub struct ApiResponse<T> {
code: u32,
msg: String,
data: T,
}
#[utoipa::path(
post,
path = "/register",
tag = "register user",
request_body(
content = User,
content_type = "application/json",
),
responses(
// (status = 200, description = "register user successfully", body = ApiResponse),
(status = 200, description = "register user successfully"),
(status = 400, description = "register user failed")
))]
#[handler]
async fn register(user: Json<User>) -> impl Responder {
let exp = OffsetDateTime::now_utc() + Duration::days(14);
let exp = exp.unix_timestamp();
let jwt = JWTClaims {
username: user.0.name,
exp,
};
let token = jwt.generate_token(SECRET_KEY)?;
Ok::<_, Error>((
200,
Json(ApiResponse {
code: 200,
msg: String::from("successful"),
data: token,
}),
))
}
#[derive(OpenApi)]
#[openapi(
info(title = "Root Api", description = "Root Api description"),
paths(html,register),
components(schemas(User)),
tags(
(name = "root", description = "Root items management API")
))]
struct RootApiDoc;
pub fn root_router() -> Router {
let mut root = Router::new("");
root.get("/*", StaticDir::new("src").listing(true));
root.get("html", html);
root.get(":name/ws", websocket);
root.post("register", register);
root.hook(middleware::stat_time, vec!["/"], None);
root.hook(
middleware::JWTClaims::default(),
vec!["api/v1/extract"],
vec!["/register"],
);
root.hook(
middleware::app_state,
vec!["/parse/param/:id/:name/:age"],
None,
);
root.controller(extract::Extract);
root.push(parse::parse_router());
root.openapi(RootApiDoc::openapi());
root
}
}
fn main() -> Result<()> {
let mut root = crate::root::root_router();
root.swagger("swagger-ui");
println!("root router = {:#?}", root);
hypers::run(root, "127.0.0.1:7878")
}