路由设置
修改 src/main.rs,为 Book
添加 CRUD 路由:
添加 CRUD 操作
#[macro_use]
extern crate rocket;
mod model;
use chrono::Utc;
use rocket::http::Status;
use rocket::response::content;
use rocket::response::status::{self, NoContent};
use rocket::serde::json::Json;
use rusqlite::{params, Connection, Result as SqliteResult};
use std::sync::Mutex;
// 初始化数据库实例
lazy_static::lazy_static! {
static ref DB: Mutex<Database> = Mutex::new(Database::new().unwrap());
}
// Define the database schema
pub struct Database {
conn: Connection,
}
impl Database {
pub fn new() -> SqliteResult<Self> {
let conn = Connection::open("test.db")?;
conn.execute(
"CREATE TABLE IF NOT EXISTS books (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
author TEXT NOT NULL,
published_at TEXT NOT NULL,
description TEXT NOT NULL,
isbn TEXT NOT NULL,
total_pages INTEGER NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)",
[],
)?;
Ok(Database { conn })
}
pub fn get_books(&self) -> SqliteResult<Vec<model::Book>> {
let mut stmt = self.conn.prepare("SELECT * FROM books")?;
let rows = stmt.query_map([], |row| {
Ok(model::Book {
id: row.get(0)?,
title: row.get(1)?,
author: row.get(2)?,
published_at: row.get(3)?,
description: row.get(4)?,
isbn: row.get(5)?,
total_pages: row.get(6)?,
created_at: row.get(7)?,
updated_at: row.get(8)?,
})
})?;
let mut books = Vec::new();
for book in rows {
books.push(book?);
}
Ok(books)
}
pub fn get_book(&self, id: u32) -> SqliteResult<Option<model::Book>> {
let mut stmt = self.conn.prepare("SELECT * FROM books WHERE id = ?")?;
let mut rows = stmt.query([id])?;
if let Some(row) = rows.next()? {
Ok(Some(model::Book {
id: row.get(0)?,
title: row.get(1)?,
author: row.get(2)?,
published_at: row.get(3)?,
description: row.get(4)?,
isbn: row.get(5)?,
total_pages: row.get(6)?,
created_at: row.get(7)?,
updated_at: row.get(8)?,
}))
} else {
Ok(None)
}
}
pub fn create_book(&self, book: &model::Book) -> SqliteResult<()> {
self.conn.execute(
"INSERT INTO books (title, author, published_at, description, isbn, total_pages)
VALUES (?, ?, ?, ?, ?, ?)",
params![
book.title,
book.author,
book.published_at,
book.description,
book.isbn,
book.total_pages,
],
)?;
Ok(())
}
pub fn update_book(&self, id: u32, book: &model::Book) -> SqliteResult<()> {
self.conn.execute(
"UPDATE books SET title = ?, author = ?, published_at = ?, description = ?, isbn = ?, total_pages = ?, updated_at = ?
WHERE id = ?",
params![
book.title,
book.author,
book.published_at,
book.description,
book.isbn,
book.total_pages,
Utc::now().to_rfc3339(),
id,
],
)?;
Ok(())
}
pub fn delete_book(&self, id: u32) -> SqliteResult<()> {
self.conn.execute("DELETE FROM books WHERE id = ?", [id])?;
Ok(())
}
}
#[derive(serde::Serialize)]
struct ErrorResponse {
error: String,
}
// Define a health endpoint handler, use `/health` or `/`
#[get("/")]
fn health() -> content::RawJson<&'static str> {
// Return a simple response indicating the server is healthy
content::RawJson("{\"status\":\"ok\"}")
}
#[get("/books")]
fn get_books() -> Result<Json<Vec<model::Book>>, status::Custom<Json<ErrorResponse>>> {
let db = DB.lock().unwrap();
match db.get_books() {
Ok(books) => Ok(Json(books)),
Err(err) => Err(status::Custom(
Status::InternalServerError,
Json(ErrorResponse {
error: err.to_string(),
}),
)),
}
}
#[get("/books/<id>")]
fn get_book(id: u32) -> Result<Json<model::Book>, status::Custom<Json<ErrorResponse>>> {
let db = DB.lock().unwrap();
match db.get_book(id) {
Ok(book) => match book {
Some(b) => Ok(Json(b)),
None => Err(status::Custom(
Status::NotFound,
Json(ErrorResponse {
error: format!("book {id} not found"),
}),
)),
},
Err(err) => Err(status::Custom(
Status::InternalServerError,
Json(ErrorResponse {
error: err.to_string(),
}),
)),
}
}
#[post("/books", format = "json", data = "<book>")]
fn create_book(
book: Json<model::Book>,
) -> Result<Json<model::Book>, status::Custom<Json<ErrorResponse>>> {
let db = DB.lock().unwrap();
match db.create_book(&book) {
Ok(_) => Ok(book),
Err(err) => Err(status::Custom(
Status::InternalServerError,
Json(ErrorResponse {
error: err.to_string(),
}),
)),
}
}
#[put("/books/<id>", format = "json", data = "<book>")]
fn update_book(
id: u32,
book: Json<model::Book>,
) -> Result<Json<model::Book>, status::Custom<Json<ErrorResponse>>> {
let db = DB.lock().unwrap();
match db.update_book(id, &book) {
Ok(_) => Ok(book),
Err(err) => Err(status::Custom(
Status::InternalServerError,
Json(ErrorResponse {
error: err.to_string(),
}),
)),
}
}
#[delete("/books/<id>")]
fn delete_book(id: u32) -> Result<NoContent, status::Custom<Json<ErrorResponse>>> {
let db = DB.lock().unwrap();
match db.delete_book(id) {
Ok(_) => Ok(NoContent),
Err(err) => Err(status::Custom(
Status::InternalServerError,
Json(ErrorResponse {
error: err.to_string(),
}),
)),
}
}
#[launch]
fn rocket() -> _ {
rocket::build().mount(
"/",
routes![
health,
get_books,
get_book,
create_book,
update_book,
delete_book
],
)
}
此刻,暂使用 SQLite
1 数据库作演示使用。
目前的依赖列表, Cargo.toml:
[dependencies]
chrono = { version = "0.4.35", features = ["serde"] }
lazy_static = "1.4.0"
rocket = { version = "0.5.0", features = ["json"] }
rusqlite = "0.31.0"
serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.114"
rusqlite 是一个在 Rust 语言中使用 SQLite 的高效封装库。
curl 测试
创建一本新图书:
curl -X POST \
http://localhost:8000/books \
-H 'Content-Type: application/json' \
-d '{
"id": 0,
"title": "Sample Book",
"author": "John Doe",
"published_at": "2023-01-01",
"description": "A sample book description",
"isbn": "1234567890",
"total_pages": 200,
"created_at": "",
"updated_at": ""
}'
应如下响应:
{"id":0,"title":"Sample Book","author":"John Doe","published_at":"2023-01-01","description":"A sample book description","isbn":"1234567890","total_pages":200,"created_at":"","updated_at":""}
根据 ID 获取一本图书:
curl -X GET http://localhost:8000/books/1
结果:
{
"id": 1,
"title": "Sample Book",
"author": "John Doe",
"published_at": "2023-01-01",
"description": "A sample book description",
"isbn": "1234567890",
"total_pages": 200,
"created_at": "2024-03-14 06:37:27",
"updated_at": "2024-03-14 06:37:27"
}
列出所有图书:
curl -X GET http://localhost:8000/books
结果列表:
[
{
"id": 1,
"title": "Sample Book",
"author": "John Doe",
"published_at": "2023-01-01",
"description": "A sample book description",
"isbn": "1234567890",
"total_pages": 200,
"created_at": "2024-03-14 06:37:27",
"updated_at": "2024-03-14 06:37:27"
}
]
更新一本已有的图书:
curl -X PUT \
http://localhost:8000/books/1 \
-H 'Content-Type: application/json' \
-d '{
"title": "Updated Book Title",
"author": "Jane Smith",
"id": 0,
"published_at": "2023-01-01",
"description": "A sample book description",
"isbn": "1234567890",
"total_pages": 200,
"created_at": "",
"updated_at": ""
}'
结果:
{"id":0,"title":"Updated Book Title","author":"Jane Smith","published_at":"2023-01-01","description":"A sample book description","isbn":"1234567890","total_pages":200,"created_at":"","updated_at":""}
删除一本存在的图书:
curl -X DELETE http://localhost:8000/books/1
服务端返回 code 204 以表示成功删除。
此刻 REST API 服务器已经初具雏形。不错!
Footnotes
-
SQLite: https://www.sqlite.org/index.html ↩