在项目的大多数业务处理过程中,除了要处理正常的业务逻辑,大多数情况要面对以下两种情况:
- 不符合业务要求的请求,比如参数校验不通过,需要告知用户如何调整。
- 服务器处理过程中产生错误,需要及时告知用户下一步应该如何处理。
面对以上情况,我们不可能要求参与项目的所有程序员都能灵活地处理,为了让开发人员把重心放在如何处理业务逻辑上,我们在项目搭建的时候,就要统一规范处理异常情况的方式。
示例
在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,有问题也可以给我留言。