Rust Web 全栈开发之增加字段和重构
Rust Web 全栈开发之增加字段和重构
增加字段和重构
现状
重构项目
目录
ws on main [✘!?] via 🦀 1.67.1 via 🅒 base
➜ tree -a -I "target|.git"
.
├── .env
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── README.md
└── webservice
├── Cargo.toml
└── src
├── bin
│ └── teacher-service.rs
├── dbaccess
│ ├── course.rs
│ └── mod.rs
├── errors.rs
├── handlers
│ ├── course.rs
│ ├── general.rs
│ └── mod.rs
├── main.rs
├── models
│ ├── course.rs
│ └── mod.rs
├── routers.rs
└── state.rs
7 directories, 18 files
ws on main [✘!?] via 🦀 1.67.1 via 🅒 base
➜
webservice/Cargo.toml
[package]
name = "webservice"
version = "0.1.0"
edition = "2021"
default-run = "teacher-service"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "4.3.1"
actix-rt = "2.8.0"
dotenv = "0.15.0"
serde = { version = "1.0.163", features = ["derive"] }
chrono = { version = "0.4.24", features = ["serde"] }
openssl = { version = "0.10.52", features = ["vendored"] }
sqlx = { version = "0.6.3", default_features = false, features = [
"postgres",
"runtime-tokio-rustls",
"macros",
"chrono",
] }
[[bin]]
name = "teacher-service"
webservice/src/bin/teacher-service.rs
use actix_web::{web, App, HttpServer};
use dotenv::dotenv;
use sqlx::postgres::PgPoolOptions;
use std::env;
use std::io;
use std::sync::Mutex;
#[path = "../dbaccess/mod.rs"]
mod dbaccess;
#[path = "../errors.rs"]
mod errors;
#[path = "../handlers/mod.rs"]
mod handlers;
#[path = "../models/mod.rs"]
mod models;
#[path = "../routers.rs"]
mod routers;
#[path = "../state.rs"]
mod state;
use routers::*;
use state::AppState;
#[actix_rt::main]
async fn main() -> io::Result<()> {
dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set.");
let db_pool = PgPoolOptions::new().connect(&database_url).await.unwrap();
let shared_data = web::Data::new(AppState {
health_check_response: "I'm Ok.".to_string(),
visit_count: Mutex::new(0),
// courses: Mutex::new(vec![]),
db: db_pool,
});
let app = move || {
App::new()
.app_data(shared_data.clone())
.configure(general_routes)
.configure(course_routes) // 路由注册
};
HttpServer::new(app).bind("127.0.0.1:3000")?.run().await
}
webservice/src/dbaccess/course.rs
use crate::errors::MyError;
use crate::models::course::Course;
use chrono::NaiveDateTime;
use sqlx::postgres::PgPool;
pub async fn get_courses_for_teacher_db(
pool: &PgPool,
teacher_id: i32,
) -> Result<Vec<Course>, MyError> {
let rows = sqlx::query!(
r#"SELECT id, teacher_id, name, time FROM course WHERE teacher_id = $1"#,
teacher_id
)
.fetch_all(pool)
.await?;
let courses: Vec<Course> = rows
.iter()
.map(|row| Course {
id: Some(row.id),
teacher_id: row.teacher_id,
name: row.name.clone(),
time: Some(NaiveDateTime::from(row.time.unwrap())),
})
.collect();
match courses.len() {
0 => Err(MyError::NotFound("Courses not found teacher".into())),
_ => Ok(courses),
}
}
pub async fn get_courses_detail_db(
pool: &PgPool,
teacher_id: i32,
course_id: i32,
) -> Result<Course, MyError> {
let row = sqlx::query!(
r#"SELECT id, teacher_id, name, time FROM course WHERE teacher_id = $1 and id = $2"#,
teacher_id,
course_id
)
.fetch_one(pool)
.await;
if let Ok(row) = row {
Ok(Course {
id: Some(row.id),
teacher_id: row.teacher_id,
name: row.name.clone(),
time: Some(NaiveDateTime::from(row.time.unwrap())),
})
} else {
Err(MyError::NotFound("Course Id not found".into()))
}
}
pub async fn post_new_course_db(pool: &PgPool, new_course: Course) -> Result<Course, MyError> {
let row = sqlx::query!(
r#"INSERT INTO course (id, teacher_id, name) VALUES ($1, $2, $3)
RETURNING id, teacher_id, name, time"#,
new_course.id,
new_course.teacher_id,
new_course.name
)
.fetch_one(pool)
.await?;
Ok(Course {
id: Some(row.id),
teacher_id: row.teacher_id,
name: row.name.clone(),
time: Some(NaiveDateTime::from(row.time.unwrap())),
})
}
webservice/src/dbaccess/mod.rs
pub mod course;
webservice/src/handlers/course.rs
use crate::dbaccess::course::*;
use crate::errors::MyError;
use crate::state::AppState;
use actix_web::{web, HttpResponse};
use crate::models::course::Course;
pub async fn new_course(
new_course: web::Json<Course>,
app_state: web::Data<AppState>,
) -> Result<HttpResponse, MyError> {
post_new_course_db(&app_state.db, new_course.into())
.await
.map(|course| HttpResponse::Ok().json(course))
}
pub async fn get_courses_for_tescher(
app_state: web::Data<AppState>,
params: web::Path<usize>,
) -> Result<HttpResponse, MyError> {
let teacher_id = i32::try_from(params.into_inner()).unwrap();
get_courses_for_teacher_db(&app_state.db, teacher_id)
.await
.map(|courses| HttpResponse::Ok().json(courses))
}
pub async fn get_courses_detail(
app_state: web::Data<AppState>,
params: web::Path<(usize, usize)>,
) -> Result<HttpResponse, MyError> {
let teacher_id = i32::try_from(params.0).unwrap();
let course_id = i32::try_from(params.1).unwrap();
get_courses_detail_db(&app_state.db, teacher_id, course_id)
.await
.map(|course| HttpResponse::Ok().json(course))
}
#[cfg(test)]
mod tests {
use super::*;
use actix_web::http::StatusCode;
use dotenv::dotenv;
use sqlx::postgres::PgPoolOptions;
use std::env;
use std::sync::Mutex;
#[ignore]
#[actix_rt::test] // 异步测试
async fn post_course_test() {
dotenv().ok();
let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set");
let db_pool = PgPoolOptions::new().connect(&db_url).await.unwrap();
let app_state: web::Data<AppState> = web::Data::new(AppState {
health_check_response: "".to_string(),
visit_count: Mutex::new(0),
db: db_pool,
});
let course = web::Json(Course {
teacher_id: 1,
name: "Test course".into(),
id: Some(5), // serial
time: None,
});
let resp = new_course(course, app_state).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[actix_rt::test]
async fn get_all_courses_success() {
dotenv().ok();
let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set");
let db_pool = PgPoolOptions::new().connect(&db_url).await.unwrap();
let app_state: web::Data<AppState> = web::Data::new(AppState {
health_check_response: "".to_string(),
visit_count: Mutex::new(0),
db: db_pool,
});
let teacher_id: web::Path<usize> = web::Path::from(1);
let resp = get_courses_for_tescher(app_state, teacher_id)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[actix_rt::test]
async fn get_one_course_success() {
dotenv().ok();
let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set");
let db_pool = PgPoolOptions::new().connect(&db_url).await.unwrap();
let app_state: web::Data<AppState> = web::Data::new(AppState {
health_check_response: "".to_string(),
visit_count: Mutex::new(0),
db: db_pool,
});
let params: web::Path<(usize, usize)> = web::Path::from((1, 1));
let resp = get_courses_detail(app_state, params).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
}
webservice/src/handlers/general.rs
use crate::state::AppState;
use actix_web::{web, HttpResponse};
pub async fn health_check_handler(app_state: web::Data<AppState>) -> HttpResponse {
let health_check_response = &app_state.health_check_response;
let mut visit_count = app_state.visit_count.lock().unwrap();
let response = format!("{} {} times", health_check_response, visit_count);
*visit_count += 1;
HttpResponse::Ok().json(&response)
}
webservice/src/handlers/mod.rs
pub mod course;
pub mod general;
webservice/src/models/course.rs
use actix_web::web;
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
// use create::models::course::Course;
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Course {
pub teacher_id: i32,
pub id: Option<i32>,
pub name: String,
pub time: Option<NaiveDateTime>,
}
impl From<web::Json<Course>> for Course {
fn from(course: web::Json<Course>) -> Self {
Course {
teacher_id: course.teacher_id,
id: course.id,
name: course.name.clone(),
time: course.time,
}
}
}
webservice/src/models/mod.rs
pub mod course;
webservice/src/routers.rs
use crate::handlers::{course::*, general::*};
use actix_web::web;
pub fn general_routes(cfg: &mut web::ServiceConfig) {
cfg.route("/health", web::get().to(health_check_handler));
}
pub fn course_routes(cfg: &mut web::ServiceConfig) {
// courses 是一套资源的根路径
cfg.service(
web::scope("/courses")
.route("/", web::post().to(new_course))
.route("/{teacher_id}", web::get().to(get_courses_for_tescher))
.route("/{teacher_id}/{course_id}", web::get().to(get_courses_detail)),
);
}
增加字段
webservice/src/models/course.rs
use crate::errors::MyError;
use actix_web::web;
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
// use create::models::course::Course;
#[derive(Serialize, Debug, Clone, sqlx::FromRow)]
pub struct Course {
pub teacher_id: i32,
pub id: i32,
pub name: String,
pub time: Option<NaiveDateTime>,
pub description: Option<String>,
pub format: Option<String>,
pub structure: Option<String>,
pub duration: Option<String>,
pub price: Option<i32>,
pub language: Option<String>,
pub level: Option<String>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct CreateCourse {
pub teacher_id: i32,
pub name: String,
pub description: Option<String>,
pub format: Option<String>,
pub structure: Option<String>,
pub duration: Option<String>,
pub price: Option<i32>,
pub language: Option<String>,
pub level: Option<String>,
}
// impl From<web::Json<CreateCourse>> for CreateCourse {
// fn from(course: web::Json<CreateCourse>) -> Self {
// CreateCourse {
// teacher_id: course.teacher_id,
// name: course.name.clone(),
// description: course.description.clone(),
// format: course.format.clone(),
// structure: course.structure.clone(),
// duration: course.duration.clone(),
// price: course.price,
// language: course.language.clone(),
// level: course.level.clone(),
// }
// }
// }
impl TryFrom<web::Json<CreateCourse>> for CreateCourse {
type Error = MyError;
fn try_from(course: web::Json<CreateCourse>) -> Result<Self, Self::Error> {
Ok(CreateCourse {
teacher_id: course.teacher_id,
name: course.name.clone(),
description: course.description.clone(),
format: course.format.clone(),
structure: course.structure.clone(),
duration: course.duration.clone(),
price: course.price,
language: course.language.clone(),
level: course.level.clone(),
})
}
}
#[derive(Deserialize, Debug, Clone)]
pub struct UpdateCourse {
pub name: Option<String>,
pub description: Option<String>,
pub format: Option<String>,
pub structure: Option<String>,
pub duration: Option<String>,
pub price: Option<i32>,
pub language: Option<String>,
pub level: Option<String>,
}
impl From<web::Json<UpdateCourse>> for UpdateCourse {
fn from(course: web::Json<UpdateCourse>) -> Self {
UpdateCourse {
name: course.name.clone(),
description: course.description.clone(),
format: course.format.clone(),
structure: course.structure.clone(),
duration: course.duration.clone(),
price: course.price,
language: course.language.clone(),
level: course.level.clone(),
}
}
}
webservice/src/dbaccess/course.rs
use crate::errors::MyError;
use crate::models::course::*;
use sqlx::postgres::PgPool;
pub async fn get_courses_for_teacher_db(
pool: &PgPool,
teacher_id: i32,
) -> Result<Vec<Course>, MyError> {
let rows: Vec<Course> = sqlx::query_as!(
Course,
r#"SELECT * FROM course WHERE teacher_id = $1"#,
teacher_id
)
.fetch_all(pool)
.await?;
Ok(rows)
}
pub async fn get_courses_detail_db(
pool: &PgPool,
teacher_id: i32,
course_id: i32,
) -> Result<Course, MyError> {
let row = sqlx::query_as!(
Course,
r#"SELECT * FROM course WHERE teacher_id = $1 and id = $2"#,
teacher_id,
course_id
)
.fetch_optional(pool)
.await?;
if let Some(course) = row {
Ok(course)
} else {
Err(MyError::NotFound("Course Id not found".into()))
}
}
pub async fn post_new_course_db(
pool: &PgPool,
new_course: CreateCourse,
) -> Result<Course, MyError> {
let row = sqlx::query_as!(
Course,
r#"INSERT INTO course (teacher_id, name, description, format, structure, duration, price, language, level)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING id, teacher_id, name, time, description, format, structure, duration, price, language, level"#,
new_course.teacher_id, new_course.name, new_course.description,
new_course.format, new_course.structure, new_course.duration,
new_course.price, new_course.language, new_course.level,
)
.fetch_one(pool)
.await?;
Ok(row)
}
pub async fn delete_course_db(pool: &PgPool, teacher_id: i32, id: i32) -> Result<String, MyError> {
let course_row = sqlx::query!(
"DELETE FROM course WHERE teacher_id = $1 AND id = $2",
teacher_id,
id,
)
.execute(pool)
.await?;
Ok(format!("Deleted {:?} record", course_row))
}
pub async fn update_course_details_db(
pool: &PgPool,
teacher_id: i32,
id: i32,
update_course: UpdateCourse,
) -> Result<Course, MyError> {
let current_course_row = sqlx::query_as!(
Course,
"SELECT * FROM course WHERE teacher_id = $1 AND id = $2",
teacher_id,
id
)
.fetch_one(pool)
.await
.map_err(|_err| MyError::NotFound("Course Id not found".into()))?;
let name: String = if let Some(name) = update_course.name {
name
} else {
current_course_row.name
};
let description: String = if let Some(desc) = update_course.description {
desc
} else {
current_course_row.description.unwrap_or_default()
};
let format: String = if let Some(format) = update_course.format {
format
} else {
current_course_row.format.unwrap_or_default()
};
let structure: String = if let Some(structure) = update_course.structure {
structure
} else {
current_course_row.structure.unwrap_or_default()
};
let duration: String = if let Some(duration) = update_course.duration {
duration
} else {
current_course_row.duration.unwrap_or_default()
};
let level: String = if let Some(level) = update_course.level {
level
} else {
current_course_row.level.unwrap_or_default()
};
let language: String = if let Some(language) = update_course.language {
language
} else {
current_course_row.language.unwrap_or_default()
};
let price: i32 = if let Some(price) = update_course.price {
price
} else {
current_course_row.price.unwrap_or_default()
};
let course_row = sqlx::query_as!(
Course,
"UPDATE course SET name = $1, description = $2, format = $3,
structure = $4, duration = $5, price = $6, language = $7,
level = $8 where teacher_id = $9 and id = $10
RETURNING id, teacher_id, name, time, description, format,
structure, duration, price, language, level",
name,
description,
format,
structure,
duration,
price,
language,
level,
teacher_id,
id
)
.fetch_one(pool)
.await;
if let Ok(course) = course_row {
Ok(course)
} else {
Err(MyError::NotFound("Course id not found".into()))
}
}
webservice/src/routers.rs
use crate::handlers::{course::*, general::*};
use actix_web::web;
pub fn general_routes(cfg: &mut web::ServiceConfig) {
cfg.route("/health", web::get().to(health_check_handler));
}
pub fn course_routes(cfg: &mut web::ServiceConfig) {
// courses 是一套资源的根路径
cfg.service(
web::scope("/courses")
.route("/", web::post().to(post_new_course))
.route("/{teacher_id}", web::get().to(get_courses_for_tescher))
.route(
"/{teacher_id}/{course_id}",
web::get().to(get_courses_detail),
)
.route("/{teacher_id}/{course_id}", web::delete().to(delete_course))
.route(
"/{teacher_id}/{course_id}",
web::put().to(update_course_details),
),
);
}
webservice/src/handlers/course.rs
use crate::dbaccess::course::*;
use crate::errors::MyError;
use crate::state::AppState;
use actix_web::{web, HttpResponse};
use crate::models::course::{CreateCourse, UpdateCourse};
pub async fn post_new_course(
new_course: web::Json<CreateCourse>,
app_state: web::Data<AppState>,
) -> Result<HttpResponse, MyError> {
post_new_course_db(&app_state.db, new_course.try_into()?)
.await
.map(|course| HttpResponse::Ok().json(course))
}
pub async fn get_courses_for_tescher(
app_state: web::Data<AppState>,
params: web::Path<i32>,
) -> Result<HttpResponse, MyError> {
// let teacher_id = i32::try_from(params.into_inner()).unwrap();
let teacher_id = params.into_inner();
get_courses_for_teacher_db(&app_state.db, teacher_id)
.await
.map(|courses| HttpResponse::Ok().json(courses))
}
// 获取课程的明细
pub async fn get_courses_detail(
app_state: web::Data<AppState>,
params: web::Path<(i32, i32)>,
) -> Result<HttpResponse, MyError> {
// let teacher_id = i32::try_from(params.0).unwrap();
// let course_id = i32::try_from(params.1).unwrap();
let (teacher_id, course_id) = params.into_inner();
get_courses_detail_db(&app_state.db, teacher_id, course_id)
.await
.map(|course| HttpResponse::Ok().json(course))
}
// 删除课程
pub async fn delete_course(
app_state: web::Data<AppState>,
params: web::Path<(i32, i32)>,
) -> Result<HttpResponse, MyError> {
let (teacher_id, course_id) = params.into_inner();
delete_course_db(&app_state.db, teacher_id, course_id)
.await
.map(|resp| HttpResponse::Ok().json(resp))
}
// 更新课程明细
pub async fn update_course_details(
app_state: web::Data<AppState>,
update_course: web::Json<UpdateCourse>,
params: web::Path<(i32, i32)>,
) -> Result<HttpResponse, MyError> {
let (teacher_id, course_id) = params.into_inner();
update_course_details_db(&app_state.db, teacher_id, course_id, update_course.into())
.await
.map(|course| HttpResponse::Ok().json(course))
}
#[cfg(test)]
mod tests {
use super::*;
use actix_web::http::StatusCode;
use actix_web::ResponseError;
use dotenv::dotenv;
use sqlx::postgres::PgPoolOptions;
use std::env;
use std::sync::Mutex;
// #[ignore]
#[actix_rt::test] // 异步测试
async fn post_course_test() {
dotenv().ok();
let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set");
let db_pool = PgPoolOptions::new().connect(&db_url).await.unwrap();
let app_state: web::Data<AppState> = web::Data::new(AppState {
health_check_response: "".to_string(),
visit_count: Mutex::new(0),
db: db_pool,
});
let course = web::Json(CreateCourse {
teacher_id: 1,
name: "Test course".into(),
description: Some("This is a test course".into()),
format: None,
structure: None,
duration: None,
price: None,
language: Some("English".into()),
level: Some("Beginner".into()),
});
let resp = post_new_course(course, app_state).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[actix_rt::test]
async fn get_all_courses_success() {
dotenv().ok();
let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set");
let db_pool = PgPoolOptions::new().connect(&db_url).await.unwrap();
let app_state: web::Data<AppState> = web::Data::new(AppState {
health_check_response: "".to_string(),
visit_count: Mutex::new(0),
db: db_pool,
});
let teacher_id: web::Path<i32> = web::Path::from(1);
let resp = get_courses_for_tescher(app_state, teacher_id)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[actix_rt::test]
async fn get_one_course_success() {
dotenv().ok();
let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set");
let db_pool = PgPoolOptions::new().connect(&db_url).await.unwrap();
let app_state: web::Data<AppState> = web::Data::new(AppState {
health_check_response: "".to_string(),
visit_count: Mutex::new(0),
db: db_pool,
});
let params: web::Path<(i32, i32)> = web::Path::from((1, 1));
let resp = get_courses_detail(app_state, params).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[actix_rt::test]
async fn get_one_course_failure() {
dotenv().ok();
let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set");
let db_pool = PgPoolOptions::new().connect(&db_url).await.unwrap();
let app_state: web::Data<AppState> = web::Data::new(AppState {
health_check_response: "".to_string(),
visit_count: Mutex::new(0),
db: db_pool,
});
let params: web::Path<(i32, i32)> = web::Path::from((1, 100));
let resp = get_courses_detail(app_state, params).await;
match resp {
Ok(_) => println!("Something wrong ..."),
Err(err) => assert_eq!(err.status_code(), StatusCode::NOT_FOUND),
}
}
#[actix_rt::test]
async fn update_course_success() {
dotenv().ok();
let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set");
let db_pool = PgPoolOptions::new().connect(&db_url).await.unwrap();
let app_state: web::Data<AppState> = web::Data::new(AppState {
health_check_response: "".to_string(),
visit_count: Mutex::new(0),
db: db_pool,
});
let update_course = UpdateCourse {
name: Some("Course name changed".into()),
description: Some("This is another test course".into()),
format: None,
level: Some("Intermediate".into()),
price: None,
duration: None,
language: Some("Chinese".into()),
structure: None,
};
let params: web::Path<(i32, i32)> = web::Path::from((1, 2));
let update_param = web::Json(update_course);
let resp = update_course_details(app_state, update_param, params)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
// #[ignore]
#[actix_rt::test]
async fn delete_course_success() {
dotenv().ok();
let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set");
let db_pool = PgPoolOptions::new().connect(&db_url).await.unwrap();
let app_state: web::Data<AppState> = web::Data::new(AppState {
health_check_response: "".to_string(),
visit_count: Mutex::new(0),
db: db_pool,
});
let params: web::Path<(i32, i32)> = web::Path::from((1, 3));
let resp = delete_course(app_state, params).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[actix_rt::test]
async fn delete_course_failure() {
dotenv().ok();
let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set");
let db_pool = PgPoolOptions::new().connect(&db_url).await.unwrap();
let app_state: web::Data<AppState> = web::Data::new(AppState {
health_check_response: "".to_string(),
visit_count: Mutex::new(0),
db: db_pool,
});
let params: web::Path<(i32, i32)> = web::Path::from((1, 101));
let resp = delete_course(app_state, params).await;
match resp {
Ok(_) => println!("Something wrong"),
Err(err) => assert_eq!(err.status_code(), StatusCode::NOT_FOUND),
}
}
}
webservice/src/errors.rs
use actix_web::{error, http::StatusCode, HttpResponse, Result};
use serde::Serialize;
use sqlx::error::Error as SQLxError;
use std::fmt;
#[derive(Debug, Serialize)]
pub enum MyError {
DBError(String),
ActixError(String),
NotFound(String),
}
#[derive(Debug, Serialize)]
pub struct MyErrorResponse {
error_message: String,
}
impl MyError {
fn error_response(&self) -> String {
match self {
MyError::DBError(msg) => {
println!("Database error occurred: {:?}", msg);
"Database error".into()
}
MyError::ActixError(msg) => {
println!("Server error occurred: {:?}", msg);
"Internal server error".into()
}
MyError::NotFound(msg) => {
println!("Not found error occurred: {:?}", msg);
msg.into()
}
}
}
}
impl error::ResponseError for MyError {
fn status_code(&self) -> StatusCode {
match self {
MyError::DBError(_msg) | MyError::ActixError(_msg) => StatusCode::INTERNAL_SERVER_ERROR,
MyError::NotFound(_msg) => StatusCode::NOT_FOUND,
}
}
fn error_response(&self) -> HttpResponse {
HttpResponse::build(self.status_code()).json(MyErrorResponse {
error_message: self.error_response(),
})
}
}
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "{}", self)
}
}
impl From<actix_web::error::Error> for MyError {
fn from(err: actix_web::error::Error) -> Self {
MyError::ActixError(err.to_string())
}
}
impl From<SQLxError> for MyError {
fn from(err: SQLxError) -> Self {
MyError::DBError(err.to_string())
}
}
检查
ws on main [✘!?] via 🦀 1.67.1 via 🅒 base
➜ cargo check
Finished dev [unoptimized + debuginfo] target(s) in 0.35s
ws on main [✘!?] via 🦀 1.67.1 via 🅒 base
➜
测试
ws on main [✘!?] via 🦀 1.67.1 via 🅒 base
➜ cargo test
Compiling webservice v0.1.0 (/Users/qiaopengjun/rust/ws/webservice)
Finished test [unoptimized + debuginfo] target(s) in 1.66s
Running unittests src/bin/teacher-service.rs (target/debug/deps/teacher_service-32d6a48d6ee3c4b4)
running 7 tests
test handlers::course::tests::get_one_course_success ... ok
test handlers::course::tests::delete_course_failure ... ok
test handlers::course::tests::post_course_test ... ok
test handlers::course::tests::get_one_course_failure ... ok
test handlers::course::tests::get_all_courses_success ... ok
test handlers::course::tests::delete_course_success ... ok
test handlers::course::tests::update_course_success ... ok
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s
Running unittests src/main.rs (target/debug/deps/webservice-77b07bbb613fc996)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
ws on main [✘!?] via 🦀 1.67.1 via 🅒 base took 2.7s
运行
ws on main [✘!?] via 🦀 1.67.1 via 🅒 base took 2.7s
➜ cargo run
Compiling webservice v0.1.0 (/Users/qiaopengjun/rust/ws/webservice)
Finished dev [unoptimized + debuginfo] target(s) in 2.97s
Running `target/debug/teacher-service`
![image-20230601190512390](../../../Library/Application Support/typora-user-images/image-20230601190512390.png)
![image-20230601190633303](../../../Library/Application Support/typora-user-images/image-20230601190633303.png)
本文来自博客园,作者:寻月隐君,转载请注明原文链接:https://www.cnblogs.com/QiaoPengjun/p/17449975.html