salvo-data-extraction
1
总安装量
1
周安装量
#43134
全站排名
安装命令
npx skills add https://github.com/salvo-rs/salvo-skills --skill salvo-data-extraction
Agent 安装分布
amp
1
opencode
1
cursor
1
kimi-cli
1
codex
1
github-copilot
1
Skill 文档
Salvo Data Extraction
This skill helps extract and validate data from HTTP requests in Salvo applications.
Manual Extraction (Simplest)
For simple cases, extract directly from Request:
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")
}
Using JsonBody Extractor
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
The Extractible derive macro enables automatic data extraction from requests.
Basic Usage
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
#[derive(Extractible, Deserialize)]
#[salvo(extract(default_source(from = "body")))]
struct UserData {
name: String,
email: String,
}
Query Parameters
#[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
#[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
#[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:
#[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
}
Depot Extraction
Extract data from Depot that was injected by middleware. This is useful for accessing authenticated user information or other request-scoped data.
Basic Depot Extraction
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));
Supported Depot Types
Depot extraction supports the following types:
Stringand&'static str- Signed integers:
i8,i16,i32,i64,i128,isize - Unsigned integers:
u8,u16,u32,u64,u128,usize - Floating point:
f32,f64 bool
Mixed Sources with Depot
Combine depot with other data sources:
#[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
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)
}
Custom Validation Rules
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
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
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
#[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))
}
Complete Example
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
JsonBody<T>for simple JSON extraction - Use
Extractiblefor complex multi-source extraction - 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
into_inner()to unwrap extracted data - Add
#[serde(default)]for optional fields