自定义响应

994072
2021/11/25 18:29:59

我们的 Todo 服务是对外提供 API 的服务,它的响应格式总是JSON类型。为此,我们可以定义响应类型,以简化 handler 的编写。

本章代码在03/自定义响应分支。

自定义响应

声明response模块,并定义相关的数据结构:

// src/response.rs

#[derive(Serialize)]
pub struct Response<T: Serialize> {
    pub code: i32,
    pub msg: String,
    pub data: Option<T>,
}
  • msg:提示信息,如果没有错误,该值为OK

  • data:响应的数据。如果发生错误,该值为null(Rust 里的None

为了以 JSON 作为响应,这个结构体以及包括data在内的所有字段必须是可序列化的,即实现了Serialize trait。

为了简化操作,定义几个方法:

impl<T> Response<T>
where
    T: Serialize,
{
    pub fn new(code: i32, msg: String, data: Option<T>) -> Self {
        Self { code, msg, data }
    }
    pub fn ok(data: T) -> Self {
        Self::new(0, "OK".to_string(), Some(data))
    }
    pub fn err(code: i32, msg: String) -> Self {
        Self::new(code, msg, None)
    }
}
  • new():创建一个新的响应

  • ok():创建一个没有错误发生响应

  • err():创建一个发生错误的响应

重导出 Response

目前,Response结构体的引用路径是crate::response::Response,这个路径有两个问题:

  • 略显冗长

为了便于其它模块引用这个结构体,我们在src/main.rs中对其进行重导出:

pub use response::Response;

现在它的引用路径变成了:

crate::Response;

在 handler 中使用自定义响应

现在我们把自定义响应和上一章所述的错误处理中实现的自己的Result(注意,别忘了被它隐藏的AppError)在 handler 中使用:

pub async fn usage<'a>() -> Result<Json<Response<Vec<&'a str>>>> {
    let data = r#"
        GET /todo -- 获取所有待办列表
        POST /todo -- 添加待办列表
        GET /todo/:list_id -- 获取待办列表详情
        DELETE /todo/:list_id -- 删除指定的待办列表,包括其所有待办事项
        PUT /todo/:list_id -- 修改待办列表
        GET /todo/:list_id/items -- 获取待办列表的所有待办事项
        GET /todo/:list_id/items/:item_id -- 获取待办事项的详情
        PUT /todo/:list_id/items/:item_id -- 修改待办事项(将其的状态修改为“已完成”)
        DELETE /todo/:list_id/items/:item_id -- 删除待办事项
    "#;
    let data: Vec<&str> = data
        .split('\n')
        .into_iter()
        .map(|line| line.trim())
        .filter(|line| !line.is_empty())
        .collect();
    let data = Response::ok(data);
    Ok(Json(data))
}
  • 这里使用的是 crate::Result

  • 这里使用的是 crate::Response

为了加强印象,请再看一遍crate::Result的定义:

type Result<T> = std::result::Result<T, error::AppError>;

在自定义错误中使用自定义响应

之前我们的自定义错误的 IntoResponse 直接使用的是 Stringinto_response(),现在我们将它改成使用我们自定义响应的 JSON 响应:

impl IntoResponse for AppError {
    type Body = Full<Bytes>;
    type BodyError = Infallible;

    fn into_response(self) -> axum::http::Response<Self::Body> {
        let code = (&self).code();
        let msg = match self.message {
            Some(msg) => msg,
            None => "有错误发生".to_string(),
        };
        let res: Response<()> = Response::err(code, msg);
        Json(res).into_response()
    }
}