salvo-data-extraction
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSalvo Data Extraction
Salvo 数据提取
This skill helps extract and validate data from HTTP requests in Salvo applications.
本技能可帮助在Salvo应用中从HTTP请求提取并验证数据。
Manual Extraction (Simplest)
手动提取(最简方式)
For simple cases, extract directly from Request:
rust
use salvo::prelude::*;
#[handler]
async fn handler(req: &mut Request) -> String {
// Query parameter
let name = req.query::<String>("name").unwrap_or_default();
// Path parameter (requires route like /users/{id})
let id = req.param::<i64>("id").unwrap();
// Header
let token = req.header::<String>("Authorization");
// Parse JSON body
let body: UserData = req.parse_json().await.unwrap();
// Parse form data
let form: LoginForm = req.parse_form().await.unwrap();
// Parse query parameters as struct
let pagination: Pagination = req.parse_queries().unwrap();
format!("Processed request")
}对于简单场景,可直接从Request中提取数据:
rust
use salvo::prelude::*;
#[handler]
async fn handler(req: &mut Request) -> String {
// 查询参数
let name = req.query::<String>("name").unwrap_or_default();
// 路径参数(需要类似 /users/{id} 的路由)
let id = req.param::<i64>("id").unwrap();
// 请求头
let token = req.header::<String>("Authorization");
// 解析JSON请求体
let body: UserData = req.parse_json().await.unwrap();
// 解析表单数据
let form: LoginForm = req.parse_form().await.unwrap();
// 将查询参数解析为结构体
let pagination: Pagination = req.parse_queries().unwrap();
format!("Processed request")
}Using JsonBody Extractor
使用JsonBody提取器
rust
use salvo::prelude::*;
use serde::Deserialize;
#[derive(Deserialize)]
struct CreateUser {
name: String,
email: String,
}
#[handler]
async fn create_user(body: JsonBody<CreateUser>) -> StatusCode {
let user = body.into_inner();
println!("Name: {}, Email: {}", user.name, user.email);
StatusCode::CREATED
}rust
use salvo::prelude::*;
use serde::Deserialize;
#[derive(Deserialize)]
struct CreateUser {
name: String,
email: String,
}
#[handler]
async fn create_user(body: JsonBody<CreateUser>) -> StatusCode {
let user = body.into_inner();
println!("Name: {}, Email: {}", user.name, user.email);
StatusCode::CREATED
}Extractible Trait
Extractible 特性
The derive macro enables automatic data extraction from requests.
ExtractibleExtractibleBasic Usage
基础用法
rust
use salvo::prelude::*;
use serde::Deserialize;
#[derive(Extractible, Deserialize, Debug)]
#[salvo(extract(default_source(from = "body")))]
struct CreateUser {
name: String,
email: String,
}
#[handler]
async fn create_user(user: CreateUser) -> String {
format!("Created user: {:?}", user)
}rust
use salvo::prelude::*;
use serde::Deserialize;
#[derive(Extractible, Deserialize, Debug)]
#[salvo(extract(default_source(from = "body")))]
struct CreateUser {
name: String,
email: String,
}
#[handler]
async fn create_user(user: CreateUser) -> String {
format!("Created user: {:?}", user)
}Data Sources
数据源
JSON Body
JSON 请求体
rust
#[derive(Extractible, Deserialize)]
#[salvo(extract(default_source(from = "body")))]
struct UserData {
name: String,
email: String,
}rust
#[derive(Extractible, Deserialize)]
#[salvo(extract(default_source(from = "body")))]
struct UserData {
name: String,
email: String,
}Query Parameters
查询参数
rust
#[derive(Extractible, Deserialize)]
#[salvo(extract(default_source(from = "query")))]
struct Pagination {
page: Option<u32>,
per_page: Option<u32>,
}
#[handler]
async fn list_items(query: Pagination) -> String {
let page = query.page.unwrap_or(1);
let per_page = query.per_page.unwrap_or(20);
format!("Page {} with {} items", page, per_page)
}rust
#[derive(Extractible, Deserialize)]
#[salvo(extract(default_source(from = "query")))]
struct Pagination {
page: Option<u32>,
per_page: Option<u32>,
}
#[handler]
async fn list_items(query: Pagination) -> String {
let page = query.page.unwrap_or(1);
let per_page = query.per_page.unwrap_or(20);
format!("Page {} with {} items", page, per_page)
}Path Parameters
路径参数
rust
#[derive(Extractible, Deserialize)]
#[salvo(extract(default_source(from = "param")))]
struct UserId {
id: i64,
}
#[handler]
async fn show_user(params: UserId) -> String {
format!("User ID: {}", params.id)
}rust
#[derive(Extractible, Deserialize)]
#[salvo(extract(default_source(from = "param")))]
struct UserId {
id: i64,
}
#[handler]
async fn show_user(params: UserId) -> String {
format!("User ID: {}", params.id)
}Form Data
表单数据
rust
#[derive(Extractible, Deserialize)]
#[salvo(extract(default_source(from = "body"), default_format = "form"))]
struct LoginForm {
username: String,
password: String,
}
#[handler]
async fn login(form: LoginForm) -> Result<String, StatusError> {
Ok(format!("Login: {}", form.username))
}rust
#[derive(Extractible, Deserialize)]
#[salvo(extract(default_source(from = "body"), default_format = "form"))]
struct LoginForm {
username: String,
password: String,
}
#[handler]
async fn login(form: LoginForm) -> Result<String, StatusError> {
Ok(format!("Login: {}", form.username))
}Mixed Sources
混合数据源
Extract from multiple sources simultaneously:
rust
#[derive(Extractible, Deserialize)]
struct UpdateUser {
#[salvo(extract(source(from = "param")))]
id: i64,
#[salvo(extract(source(from = "body")))]
name: String,
#[salvo(extract(source(from = "body")))]
email: String,
}
#[handler]
async fn update_user(data: UpdateUser) -> StatusCode {
// data.id from path, name and email from body
println!("Update user {}: {} {}", data.id, data.name, data.email);
StatusCode::OK
}同时从多个数据源提取数据:
rust
#[derive(Extractible, Deserialize)]
struct UpdateUser {
#[salvo(extract(source(from = "param")))]
id: i64,
#[salvo(extract(source(from = "body")))]
name: String,
#[salvo(extract(source(from = "body")))]
email: String,
}
#[handler]
async fn update_user(data: UpdateUser) -> StatusCode {
// data.id 来自路径,name和email来自请求体
println!("Update user {}: {} {}", data.id, data.name, data.email);
StatusCode::OK
}Depot Extraction
从Depot提取数据
Extract data from that was injected by middleware. This is useful for accessing authenticated user information or other request-scoped data.
Depot从中间件注入的中提取数据。这对于访问已认证用户信息或其他请求作用域数据非常有用。
DepotBasic Depot Extraction
基础Depot提取
rust
use salvo::prelude::*;
use serde::{Deserialize, Serialize};
/// Middleware that injects user data into depot
#[handler]
async fn inject_user(depot: &mut Depot) {
depot.insert("user_id", 123i64);
depot.insert("username", "alice".to_string());
depot.insert("is_admin", true);
}
/// Extract user context from depot
#[derive(Serialize, Deserialize, Extractible, Debug)]
#[salvo(extract(default_source(from = "depot")))]
struct UserContext {
user_id: i64,
username: String,
is_admin: bool,
}
#[handler]
async fn protected_handler(user: UserContext) -> String {
format!("Hello {}, your ID is {}", user.username, user.user_id)
}
// Router setup with middleware
let router = Router::new()
.hoop(inject_user)
.push(Router::with_path("protected").get(protected_handler));rust
use salvo::prelude::*;
use serde::{Deserialize, Serialize};
/// 将用户数据注入Depot的中间件
#[handler]
async fn inject_user(depot: &mut Depot) {
depot.insert("user_id", 123i64);
depot.insert("username", "alice".to_string());
depot.insert("is_admin", true);
}
/// 从depot提取用户上下文
#[derive(Serialize, Deserialize, Extractible, Debug)]
#[salvo(extract(default_source(from = "depot")))]
struct UserContext {
user_id: i64,
username: String,
is_admin: bool,
}
#[handler]
async fn protected_handler(user: UserContext) -> String {
format!("Hello {}, your ID is {}", user.username, user.user_id)
}
// 带中间件的路由设置
let router = Router::new()
.hoop(inject_user)
.push(Router::with_path("protected").get(protected_handler));Supported Depot Types
支持的Depot类型
Depot extraction supports the following types:
- and
String&'static str - Signed integers: ,
i8,i16,i32,i64,i128isize - Unsigned integers: ,
u8,u16,u32,u64,u128usize - Floating point: ,
f32f64 bool
Depot提取支持以下类型:
- 和
String&'static str - 有符号整数:,
i8,i16,i32,i64,i128isize - 无符号整数:,
u8,u16,u32,u64,u128usize - 浮点数:,
f32f64 bool
Mixed Sources with Depot
结合Depot与其他数据源
Combine depot with other data sources:
rust
#[derive(Serialize, Deserialize, Extractible, Debug)]
struct RequestData {
#[salvo(extract(source(from = "depot")))]
user_id: i64,
#[salvo(extract(source(from = "query")))]
page: i64,
#[salvo(extract(source(from = "body")))]
content: String,
}rust
#[derive(Serialize, Deserialize, Extractible, Debug)]
struct RequestData {
#[salvo(extract(source(from = "depot")))]
user_id: i64,
#[salvo(extract(source(from = "query")))]
page: i64,
#[salvo(extract(source(from = "body")))]
content: String,
}Validation with validator Crate
使用validator库进行验证
rust
use salvo::prelude::*;
use serde::Deserialize;
use validator::Validate;
#[derive(Extractible, Deserialize, Validate)]
#[salvo(extract(default_source(from = "body")))]
struct CreateUser {
#[validate(length(min = 1, max = 100))]
name: String,
#[validate(email)]
email: String,
#[validate(range(min = 18, max = 120))]
age: u8,
}
#[handler]
async fn create_user(user: CreateUser) -> Result<StatusCode, StatusError> {
// Validate input
if let Err(errors) = user.validate() {
return Err(StatusError::bad_request().brief(errors.to_string()));
}
Ok(StatusCode::CREATED)
}rust
use salvo::prelude::*;
use serde::Deserialize;
use validator::Validate;
#[derive(Extractible, Deserialize, Validate)]
#[salvo(extract(default_source(from = "body")))]
struct CreateUser {
#[validate(length(min = 1, max = 100))]
name: String,
#[validate(email)]
email: String,
#[validate(range(min = 18, max = 120))]
age: u8,
}
#[handler]
async fn create_user(user: CreateUser) -> Result<StatusCode, StatusError> {
// 验证输入
if let Err(errors) = user.validate() {
return Err(StatusError::bad_request().brief(errors.to_string()));
}
Ok(StatusCode::CREATED)
}Custom Validation Rules
自定义验证规则
rust
use validator::{Validate, ValidationError};
fn validate_username(username: &str) -> Result<(), ValidationError> {
if username.contains("admin") {
return Err(ValidationError::new("forbidden_username"));
}
Ok(())
}
#[derive(Deserialize, Validate)]
struct User {
#[validate(custom(function = "validate_username"))]
username: String,
}rust
use validator::{Validate, ValidationError};
fn validate_username(username: &str) -> Result<(), ValidationError> {
if username.contains("admin") {
return Err(ValidationError::new("forbidden_username"));
}
Ok(())
}
#[derive(Deserialize, Validate)]
struct User {
#[validate(custom(function = "validate_username"))]
username: String,
}Nested Structures
嵌套结构体
rust
use salvo::prelude::*;
use serde::Deserialize;
#[derive(Deserialize)]
struct Address {
street: String,
city: String,
country: String,
}
#[derive(Extractible, Deserialize)]
#[salvo(extract(default_source(from = "body")))]
struct CreateUserWithAddress {
name: String,
email: String,
address: Address,
}
#[handler]
async fn create_user(data: CreateUserWithAddress) -> Result<String, StatusError> {
Ok(format!("User {} from {}", data.name, data.address.city))
}rust
use salvo::prelude::*;
use serde::Deserialize;
#[derive(Deserialize)]
struct Address {
street: String,
city: String,
country: String,
}
#[derive(Extractible, Deserialize)]
#[salvo(extract(default_source(from = "body")))]
struct CreateUserWithAddress {
name: String,
email: String,
address: Address,
}
#[handler]
async fn create_user(data: CreateUserWithAddress) -> Result<String, StatusError> {
Ok(format!("User {} from {}", data.name, data.address.city))
}Error Handling
错误处理
rust
use salvo::prelude::*;
use serde::Deserialize;
#[derive(Deserialize)]
struct CreateUser {
name: String,
email: String,
}
#[handler]
async fn create_user(req: &mut Request, res: &mut Response) {
match req.parse_json::<CreateUser>().await {
Ok(user) => {
res.render(Json(serde_json::json!({
"success": true,
"user": {"name": user.name, "email": user.email}
})));
}
Err(e) => {
res.status_code(StatusCode::BAD_REQUEST);
res.render(Json(serde_json::json!({
"error": format!("Invalid JSON: {}", e)
})));
}
}
}rust
use salvo::prelude::*;
use serde::Deserialize;
#[derive(Deserialize)]
struct CreateUser {
name: String,
email: String,
}
#[handler]
async fn create_user(req: &mut Request, res: &mut Response) {
match req.parse_json::<CreateUser>().await {
Ok(user) => {
res.render(Json(serde_json::json!({
"success": true,
"user": {"name": user.name, "email": user.email}
})));
}
Err(e) => {
res.status_code(StatusCode::BAD_REQUEST);
res.render(Json(serde_json::json!({
"error": format!("Invalid JSON: {}", e)
})));
}
}
}Headers Extraction
请求头提取
rust
#[handler]
async fn handler(req: &mut Request) -> Result<String, StatusError> {
// Get specific header
let auth = req.header::<String>("Authorization")
.ok_or_else(|| StatusError::unauthorized())?;
// Get content type
let content_type = req.header::<String>("Content-Type");
Ok(format!("Auth: {}", auth))
}rust
#[handler]
async fn handler(req: &mut Request) -> Result<String, StatusError> {
// 获取指定请求头
let auth = req.header::<String>("Authorization")
.ok_or_else(|| StatusError::unauthorized())?;
// 获取Content-Type
let content_type = req.header::<String>("Content-Type");
Ok(format!("Auth: {}", auth))
}Complete Example
完整示例
rust
use salvo::prelude::*;
use serde::{Deserialize, Serialize};
use validator::Validate;
#[derive(Deserialize, Validate)]
struct CreateUser {
#[validate(length(min = 1, max = 100))]
name: String,
#[validate(email)]
email: String,
}
#[derive(Deserialize)]
struct Pagination {
page: Option<u32>,
per_page: Option<u32>,
}
#[derive(Serialize)]
struct User {
id: i64,
name: String,
email: String,
}
#[handler]
async fn list_users(req: &mut Request) -> Json<Vec<User>> {
let pagination: Pagination = req.parse_queries().unwrap_or(Pagination {
page: Some(1),
per_page: Some(20),
});
Json(vec![User {
id: 1,
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
}])
}
#[handler]
async fn create_user(body: JsonBody<CreateUser>) -> Result<StatusCode, StatusError> {
let user = body.into_inner();
if let Err(e) = user.validate() {
return Err(StatusError::bad_request().brief(e.to_string()));
}
Ok(StatusCode::CREATED)
}
#[handler]
async fn get_user(req: &mut Request) -> Result<Json<User>, StatusError> {
let id = req.param::<i64>("id")
.ok_or_else(|| StatusError::bad_request())?;
Ok(Json(User {
id,
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
}))
}
#[tokio::main]
async fn main() {
let router = Router::new()
.push(
Router::with_path("users")
.get(list_users)
.post(create_user)
.push(Router::with_path("{id}").get(get_user))
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}rust
use salvo::prelude::*;
use serde::{Deserialize, Serialize};
use validator::Validate;
#[derive(Deserialize, Validate)]
struct CreateUser {
#[validate(length(min = 1, max = 100))]
name: String,
#[validate(email)]
email: String,
}
#[derive(Deserialize)]
struct Pagination {
page: Option<u32>,
per_page: Option<u32>,
}
#[derive(Serialize)]
struct User {
id: i64,
name: String,
email: String,
}
#[handler]
async fn list_users(req: &mut Request) -> Json<Vec<User>> {
let pagination: Pagination = req.parse_queries().unwrap_or(Pagination {
page: Some(1),
per_page: Some(20),
});
Json(vec![User {
id: 1,
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
}])
}
#[handler]
async fn create_user(body: JsonBody<CreateUser>) -> Result<StatusCode, StatusError> {
let user = body.into_inner();
if let Err(e) = user.validate() {
return Err(StatusError::bad_request().brief(e.to_string()));
}
Ok(StatusCode::CREATED)
}
#[handler]
async fn get_user(req: &mut Request) -> Result<Json<User>, StatusError> {
let id = req.param::<i64>("id")
.ok_or_else(|| StatusError::bad_request())?;
Ok(Json(User {
id,
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
}))
}
#[tokio::main]
async fn main() {
let router = Router::new()
.push(
Router::with_path("users")
.get(list_users)
.post(create_user)
.push(Router::with_path("{id}").get(get_user))
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}Best Practices
最佳实践
- Use for simple JSON extraction
JsonBody<T> - Use for complex multi-source extraction
Extractible - Specify data sources explicitly for clarity
- Validate input data at API boundaries
- Use typed path parameters ()
req.param::<i64> - Handle extraction errors with proper error responses
- Use to unwrap extracted data
into_inner() - Add for optional fields
#[serde(default)]
- 简单JSON提取使用
JsonBody<T> - 复杂多源提取使用
Extractible - 明确指定数据源以提升代码清晰度
- 在API边界验证输入数据
- 使用类型化路径参数()
req.param::<i64> - 处理提取错误并返回合适的错误响应
- 使用解包提取的数据
into_inner() - 为可选字段添加
#[serde(default)]