JSON响应 #
JSON是现代Web API的标准数据格式。本节将深入介绍如何设计优雅的JSON API响应。
标准响应格式 #
统一响应结构 #
rust
use rocket::serde::{Serialize, Deserialize};
use rocket::serde::json::Json;
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
struct ApiResponse<T> {
success: bool,
data: Option<T>,
error: Option<ApiError>,
message: Option<String>,
}
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
struct ApiError {
code: String,
message: String,
}
impl<T: Serialize> ApiResponse<T> {
fn success(data: T) -> Self {
ApiResponse {
success: true,
data: Some(data),
error: None,
message: None,
}
}
fn error(code: &str, message: &str) -> Self {
ApiResponse {
success: false,
data: None,
error: Some(ApiError {
code: code.to_string(),
message: message.to_string(),
}),
message: None,
}
}
}
使用示例 #
rust
#[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
struct User {
id: u32,
name: String,
email: String,
}
#[get("/users/<id>")]
fn get_user(id: u32) -> Json<ApiResponse<User>> {
if id == 0 {
Json(ApiResponse::error("USER_NOT_FOUND", "User does not exist"))
} else {
Json(ApiResponse::success(User {
id,
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
}))
}
}
分页响应 #
分页结构 #
rust
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
struct PaginatedResponse<T> {
data: Vec<T>,
pagination: Pagination,
}
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
struct Pagination {
page: u32,
per_page: u32,
total: u64,
total_pages: u32,
has_next: bool,
has_prev: bool,
}
impl<T: Serialize> PaginatedResponse<T> {
fn new(data: Vec<T>, page: u32, per_page: u32, total: u64) -> Self {
let total_pages = ((total as f64) / (per_page as f64)).ceil() as u32;
PaginatedResponse {
data,
pagination: Pagination {
page,
per_page,
total,
total_pages,
has_next: page < total_pages,
has_prev: page > 1,
},
}
}
}
分页路由 #
rust
use rocket::request::Query;
use rocket::FromForm;
#[derive(FromForm)]
struct PaginationParams {
#[field(default = 1)]
page: u32,
#[field(default = 10)]
per_page: u32,
}
#[get("/users?<params..>")]
fn list_users(params: Query<PaginationParams>) -> Json<PaginatedResponse<User>> {
let users = vec![
User { id: 1, name: "Alice".to_string(), email: "alice@example.com".to_string() },
User { id: 2, name: "Bob".to_string(), email: "bob@example.com".to_string() },
];
Json(PaginatedResponse::new(users, params.page, params.per_page, 100))
}
错误响应 #
错误码设计 #
rust
#[derive(Debug, Serialize)]
#[serde(crate = "rocket::serde")]
struct ErrorResponse {
success: bool,
error: ErrorDetail,
}
#[derive(Debug, Serialize)]
#[serde(crate = "rocket::serde")]
struct ErrorDetail {
code: String,
message: String,
#[serde(skip_serializing_if = "Option::is_none")]
details: Option<Vec<FieldError>>,
}
#[derive(Debug, Serialize)]
#[serde(crate = "rocket::serde")]
struct FieldError {
field: String,
message: String,
}
impl ErrorResponse {
fn new(code: &str, message: &str) -> Self {
ErrorResponse {
success: false,
error: ErrorDetail {
code: code.to_string(),
message: message.to_string(),
details: None,
},
}
}
fn with_details(code: &str, message: &str, details: Vec<FieldError>) -> Self {
ErrorResponse {
success: false,
error: ErrorDetail {
code: code.to_string(),
message: message.to_string(),
details: Some(details),
},
}
}
}
错误码常量 #
rust
mod error_codes {
pub const VALIDATION_ERROR: &str = "VALIDATION_ERROR";
pub const NOT_FOUND: &str = "NOT_FOUND";
pub const UNAUTHORIZED: &str = "UNAUTHORIZED";
pub const FORBIDDEN: &str = "FORBIDDEN";
pub const INTERNAL_ERROR: &str = "INTERNAL_ERROR";
}
RESTful API设计 #
资源命名 #
rust
#[get("/users")]
fn list_users() -> Json<Vec<User>> {
Json(vec![])
}
#[get("/users/<id>")]
fn get_user(id: u32) -> Result<Json<User>, Status> {
Ok(Json(User { id, name: "User".to_string(), email: "user@example.com".to_string() }))
}
#[post("/users", format = "json", data = "<user>")]
fn create_user(user: Json<CreateUser>) -> status::Created<Json<User>> {
status::Created(Json(User {
id: 1,
name: user.name.clone(),
email: user.email.clone(),
}))
}
#[put("/users/<id>", format = "json", data = "<user>")]
fn update_user(id: u32, user: Json<UpdateUser>) -> Json<User> {
Json(User {
id,
name: user.name.clone().unwrap_or_default(),
email: user.email.clone().unwrap_or_default(),
})
}
#[delete("/users/<id>")]
fn delete_user(id: u32) -> status::NoContent {
status::NoContent
}
嵌套资源 #
rust
#[get("/users/<user_id>/posts")]
fn list_user_posts(user_id: u32) -> Json<Vec<Post>> {
Json(vec![])
}
#[get("/users/<user_id>/posts/<post_id>")]
fn get_user_post(user_id: u32, post_id: u32) -> Json<Post> {
Json(Post { id: post_id, user_id, title: "Post".to_string() })
}
#[post("/users/<user_id>/posts", format = "json", data = "<post>")]
fn create_user_post(user_id: u32, post: Json<CreatePost>) -> status::Created<Json<Post>> {
status::Created(Json(Post {
id: 1,
user_id,
title: post.title.clone(),
}))
}
响应头设置 #
自定义响应头 #
rust
use rocket::http::Header;
use rocket::response::content::RawJson;
#[get("/with-headers")]
fn with_headers() -> (RawJson<&'static str>, Header<'static>) {
(
RawJson(r#"{"message":"ok"}"#),
Header::new("X-Request-Id", "abc123"),
)
}
CORS头 #
rust
use rocket::http::{Header, Status};
use rocket::response::{Responder, Result};
use rocket::request::Request;
struct Cors<T>(T);
impl<'r, T: Responder<'r, 'r>> Responder<'r, 'r> for Cors<T> {
fn respond_to(self, request: &'r Request<'_>) -> Result<'r> {
let mut response = self.0.respond_to(request)?;
response.set_header(Header::new("Access-Control-Allow-Origin", "*"));
response.set_header(Header::new("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE"));
response.set_header(Header::new("Access-Control-Allow-Headers", "Content-Type, Authorization"));
Ok(response)
}
}
#[get("/api/data")]
fn api_data() -> Cors<Json<Data>> {
Cors(Json(Data { value: "Hello".to_string() }))
}
完整示例 #
rust
#[macro_use] extern crate rocket;
use rocket::serde::{Serialize, Deserialize};
use rocket::serde::json::Json;
use rocket::http::Status;
use rocket::response::status;
use rocket::request::Query;
use rocket::FromForm;
use std::collections::HashMap;
use std::sync::Mutex;
use rocket::State;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
struct User {
id: u32,
name: String,
email: String,
}
#[derive(Debug, Deserialize)]
#[serde(crate = "rocket::serde")]
struct CreateUser {
name: String,
email: String,
}
#[derive(Debug, Deserialize)]
#[serde(crate = "rocket::serde")]
struct UpdateUser {
name: Option<String>,
email: Option<String>,
}
#[derive(Debug, Serialize)]
#[serde(crate = "rocket::serde")]
struct ApiResponse<T> {
success: bool,
data: Option<T>,
error: Option<ErrorDetail>,
}
#[derive(Debug, Serialize)]
#[serde(crate = "rocket::serde")]
struct ErrorDetail {
code: String,
message: String,
}
#[derive(Debug, Serialize)]
#[serde(crate = "rocket::serde")]
struct PaginatedResponse<T> {
data: Vec<T>,
page: u32,
per_page: u32,
total: u64,
}
type Db = Mutex<HashMap<u32, User>>;
impl<T: Serialize> ApiResponse<T> {
fn success(data: T) -> Self {
ApiResponse {
success: true,
data: Some(data),
error: None,
}
}
fn error(code: &str, message: &str) -> Self {
ApiResponse {
success: false,
data: None,
error: Some(ErrorDetail {
code: code.to_string(),
message: message.to_string(),
}),
}
}
}
#[derive(FromForm)]
struct Pagination {
#[field(default = 1)]
page: u32,
#[field(default = 10)]
per_page: u32,
}
#[get("/users")]
fn list_users(db: &State<Db>, pagination: Query<Pagination>) -> Json<PaginatedResponse<User>> {
let users = db.lock().unwrap();
let all_users: Vec<User> = users.values().cloned().collect();
let total = all_users.len() as u64;
let start = ((pagination.page - 1) * pagination.per_page) as usize;
let end = std::cmp::min(start + pagination.per_page as usize, all_users.len());
let page_users: Vec<User> = all_users.into_iter().skip(start).take(end - start).collect();
Json(PaginatedResponse {
data: page_users,
page: pagination.page,
per_page: pagination.per_page,
total,
})
}
#[get("/users/<id>")]
fn get_user(id: u32, db: &State<Db>) -> Json<ApiResponse<User>> {
let users = db.lock().unwrap();
match users.get(&id) {
Some(user) => Json(ApiResponse::success(user.clone())),
None => Json(ApiResponse::error("NOT_FOUND", &format!("User {} not found", id))),
}
}
#[post("/users", format = "json", data = "<data>")]
fn create_user(data: Json<CreateUser>, db: &State<Db>) -> status::Created<Json<ApiResponse<User>>> {
let mut users = db.lock().unwrap();
let id = (users.len() + 1) as u32;
let user = User {
id,
name: data.name.clone(),
email: data.email.clone(),
};
users.insert(id, user.clone());
status::Created(Json(ApiResponse::success(user)))
}
#[put("/users/<id>", format = "json", data = "<data>")]
fn update_user(id: u32, data: Json<UpdateUser>, db: &State<Db>) -> Json<ApiResponse<User>> {
let mut users = db.lock().unwrap();
match users.get_mut(&id) {
Some(user) => {
if let Some(name) = &data.name {
user.name = name.clone();
}
if let Some(email) = &data.email {
user.email = email.clone();
}
Json(ApiResponse::success(user.clone()))
}
None => Json(ApiResponse::error("NOT_FOUND", &format!("User {} not found", id))),
}
}
#[delete("/users/<id>")]
fn delete_user(id: u32, db: &State<Db>) -> Result<status::NoContent, Json<ApiResponse<()>>> {
let mut users = db.lock().unwrap();
if users.remove(&id).is_some() {
Ok(status::NoContent)
} else {
Err(Json(ApiResponse::error("NOT_FOUND", &format!("User {} not found", id))))
}
}
#[launch]
fn rocket() -> _ {
rocket::build()
.manage(Mutex::new(HashMap::<u32, User>::new()))
.mount("/api", routes![
list_users,
get_user,
create_user,
update_user,
delete_user
])
}
下一步 #
掌握了JSON响应后,让我们继续学习 模板渲染,了解如何渲染动态HTML页面。
最后更新:2026-03-28