Skip to content

Commit 556d1d2

Browse files
Integrate warp API, implement CRUD for book
1 parent 9767a03 commit 556d1d2

File tree

16 files changed

+495
-39
lines changed

16 files changed

+495
-39
lines changed

.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
DATABASE_URL=sqlite://book_lending.db?mode=rwc
2-
RUST_LOG=warp=debug,hyper=debug
2+
RUST_LOG=debug,warp=debug,hyper=debug
33
PORT=8005
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
###
2+
# Create a new book
3+
POST http://localhost:8005/books
4+
Content-Type: application/json
5+
6+
{
7+
"title": "The Rust Programming Language",
8+
"author": "Steve Klabnik, Carol Nichols",
9+
"published_year": 2019,
10+
"isbn": "9781593278281",
11+
"total_copies": 87,
12+
"available_copies": 87
13+
}
14+
15+
###
16+
# Update a book
17+
PUT http://localhost:8005/books/1
18+
Content-Type: application/json
19+
20+
{
21+
"title": "The Rust Programming Language (2nd Edition)",
22+
"author": "Steve Klabnik, Carol Nichols",
23+
"published_year": 2024,
24+
"isbn": "9781593278281",
25+
"available_copies": 10
26+
}
27+
28+
###
29+
# Delete a book
30+
DELETE http://localhost:8005/books/1
31+
32+
###
33+
# Get all books (supports pagination)
34+
GET http://localhost:8005/books?page=1&page_size=10
35+
36+
###
37+
# Get book by id
38+
GET http://localhost:8005/books/2
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use common::server_error::ServerError;
2+
use std::sync::Arc;
3+
use warp::Filter;
4+
5+
use application::books::{
6+
BookService,
7+
command_handlers::{CreateBookCommandHandler, CreateBookCommandHandlerTrait},
8+
};
9+
use domain::commands::CreateBookCommand;
10+
11+
pub fn route<T>(
12+
handler: Arc<CreateBookCommandHandler<T>>,
13+
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone
14+
where
15+
T: BookService + Send + Sync + 'static,
16+
{
17+
// POST /books
18+
let endpoint = warp::path!("books")
19+
.and(warp::post())
20+
.and(with_handler(handler))
21+
.and(warp::body::json())
22+
.and_then(handle_create_book);
23+
24+
endpoint
25+
}
26+
27+
fn with_handler<T>(
28+
handler: Arc<CreateBookCommandHandler<T>>,
29+
) -> impl Filter<
30+
Extract = (Arc<CreateBookCommandHandler<T>>,),
31+
Error = std::convert::Infallible,
32+
> + Clone
33+
where
34+
T: BookService + Send + Sync + 'static,
35+
{
36+
warp::any().map(move || handler.clone())
37+
}
38+
39+
async fn handle_create_book<T>(
40+
handler: Arc<CreateBookCommandHandler<T>>,
41+
cmd: CreateBookCommand,
42+
) -> Result<impl warp::Reply, warp::Rejection>
43+
where
44+
T: BookService + Send + Sync + 'static,
45+
{
46+
match handler.handle(cmd).await {
47+
Ok(book) => Ok(warp::reply::json(&book)),
48+
Err(_) => Err(warp::reject::custom(ServerError)),
49+
}
50+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use common::server_error::ServerError;
2+
use std::sync::Arc;
3+
use warp::Filter;
4+
5+
use application::books::{
6+
BookService,
7+
command_handlers::{DeleteBookCommandHandler, DeleteBookCommandHandlerTrait},
8+
};
9+
use domain::commands::DeleteBookCommand;
10+
11+
pub fn route<T>(
12+
handler: Arc<DeleteBookCommandHandler<T>>,
13+
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone
14+
where
15+
T: BookService + Send + Sync + 'static,
16+
{
17+
// DELETE /books
18+
let endpoint = warp::path!("books" / i32)
19+
.and(warp::delete())
20+
.and(with_handler(handler))
21+
.and_then(handle_create_book);
22+
23+
endpoint
24+
}
25+
26+
fn with_handler<T>(
27+
handler: Arc<DeleteBookCommandHandler<T>>,
28+
) -> impl Filter<
29+
Extract = (Arc<DeleteBookCommandHandler<T>>,),
30+
Error = std::convert::Infallible,
31+
> + Clone
32+
where
33+
T: BookService + Send + Sync + 'static,
34+
{
35+
warp::any().map(move || handler.clone())
36+
}
37+
38+
async fn handle_create_book<T>(
39+
id: i32,
40+
handler: Arc<DeleteBookCommandHandler<T>>,
41+
) -> Result<impl warp::Reply, warp::Rejection>
42+
where
43+
T: BookService + Send + Sync + 'static,
44+
{
45+
match handler.handle(DeleteBookCommand { id }).await {
46+
Ok(book) => match book {
47+
Some(book) => Ok(warp::reply::json(&book)),
48+
None => {
49+
// return 404
50+
Err(warp::reject::not_found())
51+
}
52+
},
53+
Err(_) => Err(warp::reject::custom(ServerError)),
54+
}
55+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use common::server_error::ServerError;
2+
use std::sync::Arc;
3+
use warp::Filter;
4+
5+
use application::books::{
6+
query_handlers::get_all_handler::{
7+
GetAllBooksQueryHandler, GetAllBooksQueryHandlerTrait,
8+
},
9+
service::BookService,
10+
};
11+
use domain::queries::GetAllBooksQuery;
12+
13+
pub fn route<T>(
14+
handler: Arc<GetAllBooksQueryHandler<T>>,
15+
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone
16+
where
17+
T: BookService + Send + Sync + 'static,
18+
{
19+
// GET /books
20+
warp::path!("books")
21+
.and(warp::get())
22+
.and(with_handler(handler))
23+
.and_then(handle_get_all_books)
24+
}
25+
26+
fn with_handler<T>(
27+
handler: Arc<GetAllBooksQueryHandler<T>>,
28+
) -> impl Filter<
29+
Extract = (Arc<GetAllBooksQueryHandler<T>>,),
30+
Error = std::convert::Infallible,
31+
> + Clone
32+
where
33+
T: BookService + Send + Sync + 'static,
34+
{
35+
warp::any().map(move || handler.clone())
36+
}
37+
38+
async fn handle_get_all_books<T>(
39+
handler: Arc<GetAllBooksQueryHandler<T>>,
40+
) -> Result<impl warp::Reply, warp::Rejection>
41+
where
42+
T: BookService + Send + Sync + 'static,
43+
{
44+
let query = GetAllBooksQuery;
45+
match handler.handle(query).await {
46+
Ok(books) => Ok(warp::reply::json(&books)),
47+
Err(err) => Err(warp::reject::custom(ServerError)),
48+
}
49+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use common::server_error::ServerError;
2+
use std::sync::Arc;
3+
use warp::Filter;
4+
5+
use application::books::{
6+
query_handlers::get_by_id_handler::{
7+
GetBookByIdQueryHandler, GetBookByIdQueryHandlerTrait,
8+
},
9+
service::BookService,
10+
};
11+
use domain::queries::GetBookByIdQuery;
12+
13+
pub fn route<T>(
14+
handler: Arc<GetBookByIdQueryHandler<T>>,
15+
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone
16+
where
17+
T: BookService + Send + Sync + 'static,
18+
{
19+
// GET /books/<i32>
20+
warp::path!("books" / i32)
21+
.and(warp::get())
22+
.and(with_handler(handler))
23+
.and_then(handle_get_by_id_book)
24+
}
25+
26+
fn with_handler<T>(
27+
handler: Arc<GetBookByIdQueryHandler<T>>,
28+
) -> impl Filter<
29+
Extract = (Arc<GetBookByIdQueryHandler<T>>,),
30+
Error = std::convert::Infallible,
31+
> + Clone
32+
where
33+
T: BookService + Send + Sync + 'static,
34+
{
35+
warp::any().map(move || handler.clone())
36+
}
37+
38+
async fn handle_get_by_id_book<T>(
39+
id: i32,
40+
handler: Arc<GetBookByIdQueryHandler<T>>,
41+
) -> Result<impl warp::Reply, warp::Rejection>
42+
where
43+
T: BookService + Send + Sync + 'static,
44+
{
45+
match handler.handle(GetBookByIdQuery { id }).await {
46+
Ok(book) => match book {
47+
Some(book) => Ok(warp::reply::json(&book)),
48+
None => {
49+
// return 404
50+
Err(warp::reject::not_found())
51+
}
52+
},
53+
Err(_) => Err(warp::reject::custom(ServerError)),
54+
}
55+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
mod create;
2+
mod delete;
3+
mod get_all;
4+
mod get_by_id;
5+
mod update;
6+
7+
use std::sync::Arc;
8+
use warp::Filter;
9+
10+
use application::books::{
11+
command_handlers::CreateBookCommandHandler,
12+
command_handlers::DeleteBookCommandHandler,
13+
command_handlers::UpdateBookCommandHandler,
14+
query_handlers::GetAllBooksQueryHandler,
15+
query_handlers::GetBookByIdQueryHandler,
16+
};
17+
use infrastructure::{
18+
repositories::book_repository::BookRepository,
19+
services::book_service::BookServiceImpl,
20+
};
21+
22+
use crate::startup::AppState;
23+
24+
pub fn register(
25+
state: Arc<AppState>,
26+
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
27+
let repo = BookRepository::new(state.db.connection.clone());
28+
let service = Arc::new(BookServiceImpl::new(repo));
29+
let create_handler = Arc::new(CreateBookCommandHandler::new(service.clone()));
30+
let update_handler = Arc::new(UpdateBookCommandHandler::new(service.clone()));
31+
let delete_handler = Arc::new(DeleteBookCommandHandler::new(service.clone()));
32+
let get_all_handler = Arc::new(GetAllBooksQueryHandler::new(service.clone()));
33+
let get_by_id_handler =
34+
Arc::new(GetBookByIdQueryHandler::new(service.clone()));
35+
36+
// Each route is self-contained under /books
37+
let create = create::route(create_handler);
38+
let update = update::route(update_handler);
39+
let delete = delete::route(delete_handler);
40+
let get_all = get_all::route(get_all_handler);
41+
let get_by_id = get_by_id::route(get_by_id_handler);
42+
43+
let routes = create.or(update).or(delete).or(get_all).or(get_by_id);
44+
45+
routes
46+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use common::server_error::ServerError;
2+
use std::sync::Arc;
3+
use warp::Filter;
4+
5+
use application::books::{
6+
BookService, UpdateBookCommandHandlerTrait, command_handlers::UpdateBookCommandHandler
7+
};
8+
use domain::commands::UpdateBookCommand;
9+
10+
pub fn route<T>(
11+
handler: Arc<UpdateBookCommandHandler<T>>,
12+
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone
13+
where
14+
T: BookService + Send + Sync + 'static,
15+
{
16+
// PUT /books/{book_id}
17+
let endpoint = warp::path!("books" / i32)
18+
.and(warp::put())
19+
.and(with_handler(handler))
20+
.and(warp::body::json())
21+
.and_then(handle_update_book);
22+
23+
endpoint
24+
}
25+
26+
fn with_handler<T>(
27+
handler: Arc<UpdateBookCommandHandler<T>>,
28+
) -> impl Filter<
29+
Extract = (Arc<UpdateBookCommandHandler<T>>,),
30+
Error = std::convert::Infallible,
31+
> + Clone
32+
where
33+
T: BookService + Send + Sync + 'static,
34+
{
35+
warp::any().map(move || handler.clone())
36+
}
37+
38+
async fn handle_update_book<T>(
39+
book_id: i32,
40+
handler: Arc<UpdateBookCommandHandler<T>>,
41+
mut cmd: UpdateBookCommand,
42+
) -> Result<impl warp::Reply, warp::Rejection>
43+
where
44+
T: BookService + Send + Sync + 'static,
45+
{
46+
cmd.id = book_id;
47+
48+
match handler.handle(cmd).await {
49+
Ok(book) => Ok(warp::reply::json(&book)),
50+
Err(_) => Err(warp::reject::custom(ServerError)),
51+
}
52+
}

packages/api/src/endpoints/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
pub mod books;
2+
3+
use crate::startup::AppState;
4+
use std::sync::Arc;
5+
use warp::Filter;
6+
7+
pub fn register(
8+
state: Arc<AppState>,
9+
) -> impl Filter<Extract = impl warp::Reply> + Clone {
10+
books::register(state)
11+
}

0 commit comments

Comments
 (0)