blog
blog copied to clipboard
用 Rust Actix-web 写一个 Todo 应用(三)── migrations 和错误处理
使用 diesel 管理数据库变化
diesel 是一个用 rust 写的 ORM 库,支持多种数据库,同时 diesel 提供了对数据库结构的管理功能。我们将使用 diesel 对我们的数据库结构变化进行管理。
首先,安装命令行工具 diesel_cli
,并初始化数据库设置
# 安装 diesel_cli,支持 postgres
cargo install diesel_cli --no-default-features --features postgres
# 设置数据库连接
echo DATABASE_URL=postgres://actix:actix@localhost:5432/actix >> .env
# 生成 diesel.toml 文件指向 schema 所在
diesel setup
# 创建数据库 migration
diesel migration generate create_db
在生成的 migrations
目录中,填入数据库变化的 sql 语句,up.sql
用于修改,down.sql
用于撤销修改。
up.sql
:
create table todo_list (
id serial primary key,
title varchar(150) not null
);
create table todo_item (
id serial primary key,
title varchar(150) not null,
checked boolean not null default false,
list_id integer not null,
foreign key (list_id) references todo_list(id)
);
down.sql
:
drop table if exists todo_item;
drop table if exists todo_list;
由于之前已经有对应的数据表结构,需要将原来的表结构删除,再运行数据库变更:
# 删除原有的数据表之后
diesel migrations run
其中,对应生成的 schema 为:
table! {
todo_item (id) {
id -> Int4,
title -> Varchar,
checked -> Bool,
list_id -> Int4,
}
}
table! {
todo_list (id) {
id -> Int4,
title -> Varchar,
}
}
joinable!(todo_item -> todo_list (list_id));
allow_tables_to_appear_in_same_query!(
todo_item,
todo_list,
);
此时,数据库中的表机构就和我们之前是一样的,同时增加了一个用于记录已经做过的 migrations 的数据库。
ORM
鉴于 diesel 没有 async 版本,以及 quaint 不是 type-safe,不做 ORM 的支持。
错误处理
自定义错误,并将常见的错误统一处理。
新增 errors.rs
:
use actix_web::http::StatusCode;
use actix_web::{HttpResponse, ResponseError};
use deadpool_postgres::PoolError;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug)]
#[allow(dead_code)]
pub enum Error {
InternalServerError(String),
NotFound(String),
PoolError(String),
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ErrorResponse {
errors: Vec<String>,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self)
}
}
impl ResponseError for Error {
fn error_response(&self) -> HttpResponse {
match self {
Error::NotFound(message) => {
HttpResponse::NotFound().json::<ErrorResponse>(message.into())
}
_ => HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR),
}
}
}
// 支持 字符串 into
impl From<&String> for ErrorResponse {
fn from(error: &String) -> Self {
ErrorResponse {
errors: vec![error.into()],
}
}
}
// 处理 PoolError
impl From<PoolError> for Error {
fn from(error: PoolError) -> Self {
Error::PoolError(error.to_string())
}
}
修改 db.rs
:
+use crate::errors::Error;
// ...
- None => Err(Error::new(ErrorKind::NotFound, "Not found")),
+ None => Err(Error::NotFound("Not found".into())),
修改 handlers.rs
,其中一个请求处理
+use crate::errors::Error;
// error_response:items from traits can only be used if the trait is in scope
+use actix_web::ResponseError;
// ...
-pub async fn todos(state: web::Data<AppState>) -> impl Responder {
+pub async fn todos(state: web::Data<AppState>) -> Result<HttpResponse, Error> {
let client: Client = state
.pool
.get()
@@ -33,12 +34,15 @@ pub async fn todos(state: web::Data<AppState>) -> impl Responder {
.expect("Error connecting to the database");
let result = db::get_todos(&client).await;
match result {
- Ok(todos) => HttpResponse::Ok().json(todos),
- Err(_) => HttpResponse::InternalServerError().into(),
+ Ok(todos) => Ok(HttpResponse::Ok().json(todos)),
+ Err(e) => Ok(e.error_response()),
}
}
小结
- 管理数据库结构变更;
- 自定义错误处理
参考文档和项目
- Creating a simple TODO service with Actix
- actix-web 官方文档
- 官方 actix-web 示例
GitHub repo: qiwihui/blog
Follow me: @qiwihui
Site: QIWIHUI