From cb5726e46ec82a26bd7af133300f4ea02f2ff97d Mon Sep 17 00:00:00 2001 From: 0xboji Date: Mon, 7 Oct 2024 23:45:35 +0700 Subject: [PATCH] feat: add swagger ui for both axum and actix (#3) --- Cargo.toml | 7 +- src/day16_to_day18/mod.rs | 6 +- ...evelopment.rs => web_development_actix.rs} | 3 + src/day16_to_day18/web_development_axum.rs | 88 ++++++++++++ src/day19_to_day21/mod.rs | 5 + .../web_development_actix_swaggerui.rs | 134 ++++++++++++++++++ .../web_development_axum_swaggerui.rs | 128 +++++++++++++++++ src/main.rs | 6 +- 8 files changed, 373 insertions(+), 4 deletions(-) rename src/day16_to_day18/{web_development.rs => web_development_actix.rs} (97%) create mode 100644 src/day16_to_day18/web_development_axum.rs create mode 100644 src/day19_to_day21/web_development_actix_swaggerui.rs create mode 100644 src/day19_to_day21/web_development_axum_swaggerui.rs diff --git a/Cargo.toml b/Cargo.toml index 924bfe4..5b4d7bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,5 +8,10 @@ edition = "2021" [dependencies] rayon = "1.5" tokio = { version = "1", features = ["full"] } +serde = { version = "1.0", features = ["derive"] } actix-web = "4" -serde = { version = "1.0", features = ["derive"] } \ No newline at end of file +axum = "0.7.7" +# # utoipa = { version = "4.2.3", features = ["actix_extras"] } +# # utoipa-swagger-ui = { version = "7.1.0", features = ["actix-web"] } +utoipa = { version = "4.2.3", features = ["axum_extras"] } +utoipa-swagger-ui = { version = "7.1.0", features = ["axum"] } \ No newline at end of file diff --git a/src/day16_to_day18/mod.rs b/src/day16_to_day18/mod.rs index 76d8c7e..b6bf66d 100644 --- a/src/day16_to_day18/mod.rs +++ b/src/day16_to_day18/mod.rs @@ -1,6 +1,8 @@ -mod web_development; +mod web_development_actix; +mod web_development_axum; pub fn run() { println!("Days 16-18: Web Development"); - let _ = web_development::main(); + // let _ = web_development_actix::main(); + let _ = web_development_axum::main(); } \ No newline at end of file diff --git a/src/day16_to_day18/web_development.rs b/src/day16_to_day18/web_development_actix.rs similarity index 97% rename from src/day16_to_day18/web_development.rs rename to src/day16_to_day18/web_development_actix.rs index d14db32..dc59eaa 100644 --- a/src/day16_to_day18/web_development.rs +++ b/src/day16_to_day18/web_development_actix.rs @@ -21,6 +21,7 @@ struct UserFilter { /// * `query` - Query parameters for filtering users /// /// Returns a JSON array of filtered users in the system +#[allow(dead_code)] async fn get_users(query: web::Query) -> impl Responder { let users = vec![ User { id: 1, name: "Alice".to_string() }, @@ -45,6 +46,7 @@ async fn get_users(query: web::Query) -> impl Responder { /// * `path` - A Path extractor containing the user ID /// /// Returns a JSON object of the requested user or a 404 if not found +#[allow(dead_code)] async fn get_user(path: web::Path) -> impl Responder { let user_id = path.into_inner(); // In a real application, we would fetch the user from a database @@ -59,6 +61,7 @@ async fn get_user(path: web::Path) -> impl Responder { /// * `user` - JSON payload representing the new user /// /// Returns the created user with a 201 status code +#[allow(dead_code)] async fn create_user(user: web::Json) -> impl Responder { // In a real application, we would save the user to a database HttpResponse::Created().json(user.into_inner()) diff --git a/src/day16_to_day18/web_development_axum.rs b/src/day16_to_day18/web_development_axum.rs new file mode 100644 index 0000000..c187e3b --- /dev/null +++ b/src/day16_to_day18/web_development_axum.rs @@ -0,0 +1,88 @@ +use axum::{ + routing::get, + Router, Json, extract::{Query, Path}, +}; +use serde::{Deserialize, Serialize}; +use std::net::SocketAddr; +use tokio::net::TcpListener; + +/// Represents a user in the system +#[derive(Serialize, Deserialize)] +struct User { + id: u32, + name: String, +} + +/// Represents query parameters for filtering users +#[derive(Deserialize)] +struct UserFilter { + name: Option, +} + +/// Handles GET request to retrieve all users +/// +/// # Arguments +/// +/// * `query` - Query parameters for filtering users +/// +/// Returns a JSON array of filtered users in the system +async fn get_users(Query(query): Query) -> Json> { + let users = vec![ + User { id: 1, name: "Alice".to_string() }, + User { id: 2, name: "Bob".to_string() }, + User { id: 3, name: "Charlie".to_string() }, + User { id: 4, name: "Bobby Pickle".to_string() }, + ]; + + let filtered_users: Vec = users.into_iter() + .filter(|user| { + query.name.as_ref().map_or(true, |name| user.name.to_lowercase().contains(&name.to_lowercase())) + }) + .collect(); + + Json(filtered_users) +} + +/// Handles GET request to retrieve a specific user by ID +/// +/// # Arguments +/// +/// * `path` - A Path extractor containing the user ID +/// +/// Returns a JSON object of the requested user +async fn get_user(Path(user_id): Path) -> Json { + // In a real application, we would fetch the user from a database + let user = User { id: user_id, name: format!("User {}", user_id) }; + Json(user) +} + +/// Handles POST request to create a new user +/// +/// # Arguments +/// +/// * `user` - JSON payload representing the new user +/// +/// Returns the created user +async fn create_user(Json(user): Json) -> Json { + // In a real application, we would save the user to a database + Json(user) +} + +/// Starts the web server and configures the routes +#[tokio::main] +pub async fn main() { + const HOST: &str = "127.0.0.1"; + const PORT: u16 = 8080; + // Run to get all users http://127.0.0.1:8080/api/v1/users + // Run to get user with id 1 http://127.0.0.1:8080/api/v1/users/1 + // Run to get filter user http://127.0.0.1:8080/api/v1/users?name=Alice + println!("Starting web server at http://{}:{}", HOST, PORT); + + let app = Router::new() + .route("/api/v1/users", get(get_users).post(create_user)) + .route("/api/v1/users/:id", get(get_user)); + + let addr = SocketAddr::from(([127, 0, 0, 1], PORT)); + let listener = TcpListener::bind(addr).await.unwrap(); + axum::serve(listener, app).await.unwrap(); +} \ No newline at end of file diff --git a/src/day19_to_day21/mod.rs b/src/day19_to_day21/mod.rs index 4086986..299597c 100644 --- a/src/day19_to_day21/mod.rs +++ b/src/day19_to_day21/mod.rs @@ -1,3 +1,8 @@ +mod web_development_actix_swaggerui; +mod web_development_axum_swaggerui; pub fn run() { + println!("Days 19-21: Web Development with Swagger UI"); + // let _ = web_development_actix_swaggerui::main(); + let _ = web_development_axum_swaggerui::main(); } \ No newline at end of file diff --git a/src/day19_to_day21/web_development_actix_swaggerui.rs b/src/day19_to_day21/web_development_actix_swaggerui.rs new file mode 100644 index 0000000..93a2bfc --- /dev/null +++ b/src/day19_to_day21/web_development_actix_swaggerui.rs @@ -0,0 +1,134 @@ +// use actix_web::{web, App, HttpResponse, HttpServer, Responder}; +// use serde::{Deserialize, Serialize}; +// use utoipa::OpenApi; +// use utoipa_swagger_ui::SwaggerUi; + +// /// Represents a user in the system +// #[derive(Serialize, Deserialize, utoipa::ToSchema)] +// struct User { +// id: u32, +// name: String, +// } + +// /// Represents query parameters for filtering users +// #[derive(Deserialize, utoipa::IntoParams, utoipa::ToSchema)] +// struct UserFilter { +// name: Option, +// } + +// /// Handles GET request to retrieve all users +// /// +// /// # Arguments +// /// +// /// * `query` - Query parameters for filtering users +// /// +// /// Returns a JSON array of filtered users in the system +// #[utoipa::path( +// get, +// path = "/api/v1/users", +// params(UserFilter), +// responses( +// (status = 200, description = "List of users", body = Vec) +// ) +// )] +// #[allow(dead_code)] +// async fn get_users(query: web::Query) -> impl Responder { +// let users = vec![ +// User { id: 1, name: "Alice".to_string() }, +// User { id: 2, name: "Bob".to_string() }, +// User { id: 3, name: "Charlie".to_string() }, +// User { id: 4, name: "Bobby Pickle".to_string() }, +// ]; + +// let filtered_users: Vec = users.into_iter() +// .filter(|user| { +// query.name.as_ref().map_or(true, |name| user.name.to_lowercase().contains(&name.to_lowercase())) +// }) +// .collect(); + +// HttpResponse::Ok().json(filtered_users) +// } + +// /// Handles GET request to retrieve a specific user by ID +// /// +// /// # Arguments +// /// +// /// * `path` - A Path extractor containing the user ID +// /// +// /// Returns a JSON object of the requested user or a 404 if not found +// #[utoipa::path( +// get, +// path = "/api/v1/users/{id}", +// params( +// ("id" = u32, Path, description = "User ID") +// ), +// responses( +// (status = 200, description = "User found", body = User), +// (status = 404, description = "User not found") +// ) +// )] +// #[allow(dead_code)] +// async fn get_user(path: web::Path) -> impl Responder { +// let user_id = path.into_inner(); +// // In a real application, we would fetch the user from a database +// let user = User { id: user_id, name: format!("User {}", user_id) }; +// HttpResponse::Ok().json(user) +// } + +// /// Handles POST request to create a new user +// /// +// /// # Arguments +// /// +// /// * `user` - JSON payload representing the new user +// /// +// /// Returns the created user with a 201 status code +// #[utoipa::path( +// post, +// path = "/api/v1/users", +// request_body = User, +// responses( +// (status = 201, description = "User created", body = User) +// ) +// )] +// #[allow(dead_code)] +// async fn create_user(user: web::Json) -> impl Responder { +// // In a real application, we would save the user to a database +// HttpResponse::Created().json(user.into_inner()) +// } + +// #[derive(OpenApi)] +// #[openapi( +// paths(get_users, get_user, create_user), +// components(schemas(User, UserFilter)) +// )] +// struct ApiDoc; + +// /// Starts the web server and configures the routes +// #[actix_web::main] +// pub async fn main() -> std::io::Result<()> { +// const HOST: &str = "127.0.0.1"; +// const PORT: u16 = 8080; +// // Run to get all users http://127.0.0.1:8080/api/v1/users +// // Run to get user with id 1 http://127.0.0.1:8080/api/v1/users/1 +// // Run to get filter user http://127.0.0.1:8080/api/v1/users +// // Access Swagger UI at http://127.0.0.1:8080/swagger-ui/ +// println!("Starting web server at http://{}:{}", HOST, PORT); +// println!("Access Swagger UI at http://{}:{}/swagger-ui/", HOST, PORT); + +// HttpServer::new(|| { +// App::new() +// .service( +// web::scope("/api/v1") +// .route("/users", web::get().to(get_users)) +// .route("/users/{id}", web::get().to(get_user)) +// .route("/users", web::post().to(create_user)) +// ) +// .service( +// SwaggerUi::new("/swagger-ui/{_:.*}") +// .url("/api-docs/openapi.json", ApiDoc::openapi()) +// ) +// }) +// .bind((HOST, PORT))? +// .run() +// .await +// } \ No newline at end of file diff --git a/src/day19_to_day21/web_development_axum_swaggerui.rs b/src/day19_to_day21/web_development_axum_swaggerui.rs new file mode 100644 index 0000000..83a1d35 --- /dev/null +++ b/src/day19_to_day21/web_development_axum_swaggerui.rs @@ -0,0 +1,128 @@ +use axum::{ + routing::{get}, + Router, Json, extract::{Query, Path}, +}; +use serde::{Deserialize, Serialize}; +use std::net::SocketAddr; +use tokio::net::TcpListener; +use utoipa::OpenApi; +use utoipa_swagger_ui::SwaggerUi; + +/// Represents a user in the system +#[derive(Serialize, Deserialize, utoipa::ToSchema)] +struct User { + id: u32, + name: String, +} + +/// Represents query parameters for filtering users +#[derive(Deserialize, utoipa::IntoParams, utoipa::ToSchema)] +struct UserFilter { + name: Option, +} + +/// Handles GET request to retrieve all users +/// +/// # Arguments +/// +/// * `query` - Query parameters for filtering users +/// +/// Returns a JSON array of filtered users in the system +#[utoipa::path( + get, + path = "/api/v1/users", + params(UserFilter), + responses( + (status = 200, description = "List of users", body = Vec) + ) +)] +async fn get_users(Query(query): Query) -> Json> { + let users = vec![ + User { id: 1, name: "Alice".to_string() }, + User { id: 2, name: "Bob".to_string() }, + User { id: 3, name: "Charlie".to_string() }, + User { id: 4, name: "Bobby Pickle".to_string() }, + ]; + + let filtered_users: Vec = users.into_iter() + .filter(|user| { + query.name.as_ref().map_or(true, |name| user.name.to_lowercase().contains(&name.to_lowercase())) + }) + .collect(); + + Json(filtered_users) +} + +/// Handles GET request to retrieve a specific user by ID +/// +/// # Arguments +/// +/// * `path` - A Path extractor containing the user ID +/// +/// Returns a JSON object of the requested user +#[utoipa::path( + get, + path = "/api/v1/users/{id}", + params( + ("id" = u32, Path, description = "User ID") + ), + responses( + (status = 200, description = "User found", body = User), + (status = 404, description = "User not found") + ) +)] +async fn get_user(Path(user_id): Path) -> Json { + // In a real application, we would fetch the user from a database + let user = User { id: user_id, name: format!("User {}", user_id) }; + Json(user) +} + +/// Handles POST request to create a new user +/// +/// # Arguments +/// +/// * `user` - JSON payload representing the new user +/// +/// Returns the created user +#[utoipa::path( + post, + path = "/api/v1/users", + request_body = User, + responses( + (status = 201, description = "User created", body = User) + ) +)] +async fn create_user(Json(user): Json) -> Json { + // In a real application, we would save the user to a database + Json(user) +} + +#[derive(OpenApi)] +#[openapi( + paths(get_users, get_user, create_user), + components(schemas(User, UserFilter)) +)] +struct ApiDoc; + +/// Starts the web server and configures the routes +#[tokio::main] +pub async fn main() { + const HOST: &str = "127.0.0.1"; + const PORT: u16 = 8080; + // Run to get all users http://127.0.0.1:8080/api/v1/users + // Run to get user with id 1 http://127.0.0.1:8080/api/v1/users/1 + // Run to get filter user http://127.0.0.1:8080/api/v1/users?name=Alice + // Access Swagger UI at http://127.0.0.1:8080/swagger-ui/ + println!("Starting web server at http://{}:{}", HOST, PORT); + println!("Access Swagger UI at http://{}:{}/swagger-ui/", HOST, PORT); + + let app = Router::new() + .route("/api/v1/users", get(get_users).post(create_user)) + .route("/api/v1/users/:id", get(get_user)) + .merge(SwaggerUi::new("/swagger-ui") + .url("/api-docs/openapi.json", ApiDoc::openapi())); + + let addr = SocketAddr::from(([127, 0, 0, 1], PORT)); + let listener = TcpListener::bind(addr).await.unwrap(); + axum::serve(listener, app).await.unwrap(); +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 6d3ca22..5d992e6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,12 +16,16 @@ pub mod day13_to_day15; //Day 16 to day 18 pub mod day16_to_day18; +//Day 19 to day 21 +pub mod day19_to_day21; + fn main() { // day01_to_day03::run(); // day04_to_day06::run(); // day07_to_day09::run(); // day10_to_day12::run(); // day13_to_day15::run(); - day16_to_day18::run(); + // day16_to_day18::run(); + day19_to_day21::run(); }