携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情
在项目的大多数业务处理过程中,除了要处理正常的业务逻辑,大多数情况要面对以下两种情况:
- 不符合业务要求的请求,比如参数校验不通过,需要告知用户如何调整。
- 服务器处理过程中产生错误,需要及时告知用户下一步应该如何处理。
面对以上情况,我们不可能要求参与项目的所有程序员都能灵活地处理,为了让开发人员把重心放在如何处理业务逻辑上,我们在项目搭建的时候,就要统一规范处理异常情况的方式。
示例
在salvo的使用过程中,提供了自定义异常的处理方式:
use salvo::prelude::*;
struct CustomError;
#[async_trait]
impl Writer for CustomError {
async fn write(mut self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) {
res.set_status_code(StatusCode::INTERNAL_SERVER_ERROR);
res.render("custom error");
}
}
#[handler]
async fn handle_anyhow() -> Result<(), anyhow::Error> {
Err(anyhow::anyhow!("anyhow error"))
}
#[handler]
async fn handle_custom() -> Result<(), CustomError> {
Err(CustomError)
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::new()
.push(Router::with_path("anyhow").get(handle_anyhow))
.push(Router::with_path("custom").get(handle_custom));
tracing::info!("Listening on http://127.0.0.1:7878");
Server::new(TcpListener::bind("127.0.0.1:7878")).serve(router).await;
}
自定义error结构体
根据以上示例,我们可以来创建自己的自定义error:
首先,我们需要先创建一个结构体来承接至少3个字段:
- code:便于前端判断响应结果是否正确。
- msg:用于提示用户下一步应该如何处理
- error:用于存放真实错误原因,主要是给开发人员看
use serde::Serialize;
// 先创建一个结构体来承接以下至少3个字段:
// code:便于前端判断响应结果是否正确
// msg:用于提示用户下一步应该如何处理
// error:用于存放真实错误原因,主要是给开发人员看
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
pub struct GlobalError {
code: u16,
// 提示信息
msg: String,
// 错误信息
error: String,
}
实例化error方法
其次,我们需要给出一些可以创建GlobalError实例的方法。
- new函数是少不了的,在此基础上,我们可以补充一些更有针对性的方法,比如参数错误的时候,我们通过bad_request方法指定的error,就能够提高咱们的开发体验度。
- 我们提供了将code自动转换为http status code的方式,让响应码优先使用http协议的响应码标准。
use salvo::{Depot, Request, Response, Writer, async_trait};
use salvo::http::StatusCode;
use salvo::prelude::Json;
impl GlobalError {
pub fn new(code: u16, msg: &str, error: &str) -> GlobalError {
GlobalError {
code,
msg: String::from(msg),
error: String::from(error),
}
}
pub fn bad_request(msg: &str, error: &str) -> GlobalError {
GlobalError::new(StatusCode::BAD_REQUEST.as_u16(), msg, error)
}
// 将自定义error协议响应数据中
pub fn write(self, res: &mut Response) {
// 先把自定义code转换成http响应码
let statusCode = StatusCode::from_u16(self.code);
match statusCode {
Err(_) => {
// 转换失败的情况,说明这是自定义的code,那么http响应码就应该为200.
res.set_status_code(StatusCode::OK);
}
Ok(code) => {
res.set_status_code(code);
}
}
// 写入响应数据
res.render(Json(self));
}
}
实现Writer trait
最后,在我们大概实现了一些自定义实例化的方法后,我们接下来需要实现salvo的自定义error标准了,我们现在给自定义error实现salvo的Writer trait:
#[async_trait]
impl Writer for GlobalError {
async fn write(self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) {
self.write(res);
}
}
真正的实现只需要调用我们前面写好的write方法。
如何使用
为了演示如何使用咱们的自定义error,我们编写了以下两个代码示例:
-
示例1
#[handler] async fn upload(req: &mut Request, res: &mut Response) { let file = req.file("file").await; // 如果从请求中拿到了文件 if let Some(file) = file { match std::fs::copy(&file.path(), Path::new(&dest_file)) { // 如果文件存储成功,那么返回成功响应 Ok(_) => { res.render(format!("id:{} \nfile_name:{} \n文件上传成功:{}", id, file_name.name, &dest_file)); } // 如果文件存储失败,那么告知用户,并且把真正的错误信息也要给出来,方便开发人员排查错误。 // ps:真正的错误信息并不是要展示给用户看,前端可以选择不展示,或者把这个错误信息给到日志管理服务 Err(e) => { GlobalError::new(500, "file store failure", e.to_string().as_str()).write(res); } } } else { // 如果请求中没有文件内容,直接创建error,并写入响应数据。 GlobalError::bad_request("没有找到您要上传的文件,请重新上传!", "file not found in request").write(res); } }
-
示例2
#[handler] async fn create_account(req: &mut Request) -> Result<String, GlobalError> { let user_info: UserInfo = req.extract_body().await.unwrap(); let account = &user_info.account; let password = &user_info.password; match AccountService::add_account(account, password) { // 如果账号添加成功,那么返回Ok Ok(x) => { info!("受影响的行数:{}", x); Ok(String::from("成功!")) } // 如果添加失败,直接返回自定义error Err(e) => Err(GlobalError::new(200, "创建账号失败", e.to_string().as_str())) } }通过以上示例,相信大部分小伙伴应该已经学会如何来使用自定义error了。
对于rust语言感兴趣的小伙伴欢迎一起来探讨学习,这个项目我已经把它上传到github上了,大家可以去上面下载hello_salvo,有问题也可以给我留言。