diff --git a/README.md b/README.md index ccddf91..31d246f 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Web api application prototype written in rust - Crud api endpoints with actix-web and mongodb driver - Integration tests - Workspaces usage +- OpenApi and Swagger-UI - Code coverage report - Static analysis report diff --git a/app/Cargo.toml b/app/Cargo.toml index cdb2a5c..7c6619c 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -7,4 +7,5 @@ edition = "2021" actix-web = "4.4.0" serde = "1.0.188" serde_derive = "1.0.188" -domain = { path = "../domain" } \ No newline at end of file +domain = { path = "../domain" } +utoipa = { version = "4", features = ["actix_extras"] } \ No newline at end of file diff --git a/app/src/endpoints.rs b/app/src/endpoints.rs index b0a929d..800f0b1 100644 --- a/app/src/endpoints.rs +++ b/app/src/endpoints.rs @@ -1,9 +1,21 @@ -use crate::models::{CreateUserRequest, CreatedUserIdResponse, UserResponse}; +use crate::models::{CreateUserRequest, CreatedUserIdResponse, ErrorResponse, UserResponse}; use actix_web::web::{Data, Json, Path}; -use actix_web::HttpResponse; +use actix_web::{get, post, HttpResponse}; use domain::commands::{CreateUserCommand, ICommandHandler}; use domain::queries::{GetUserQuery, IQueryHandler, User}; +/// Get user +/// +/// Get user by id +#[utoipa::path( + get, + path = "/user/{user_id}", + responses( + (status = 200, description = "User created successfully", body = UserResponse), + (status = 400, description = "Something going wrong", body = ErrorResponse) + ) +)] +#[get("/user/{user_id}")] pub async fn get_user( handler: Data>>, path: Path, @@ -20,10 +32,21 @@ pub async fn get_user( age: user.age, id: path.to_string(), }), - None => HttpResponse::NotFound().body(format!("No user found with id {}", path)), + None => HttpResponse::BadRequest().json(ErrorResponse { code: 101 }), } } +/// Create user +/// +/// Create user +#[utoipa::path( + request_body = CreateUserRequest, + responses( + (status = 201, description = "User created successfully", body = CreatedUserIdResponse), + (status = 400, description = "Something going wrong", body = ErrorResponse) + ) +)] +#[post("/user")] pub async fn create_user( handler: Data>>, request: Json, @@ -36,7 +59,7 @@ pub async fn create_user( let option = handler.handle(command).await; match option { - Some(id) => HttpResponse::Ok().json(CreatedUserIdResponse { id }), - None => HttpResponse::BadRequest().body(()), + Some(id) => HttpResponse::Created().json(CreatedUserIdResponse { id }), + None => HttpResponse::BadRequest().json(ErrorResponse { code: 102 }), } } diff --git a/app/src/models.rs b/app/src/models.rs index 4ac084a..69ecc33 100644 --- a/app/src/models.rs +++ b/app/src/models.rs @@ -1,19 +1,25 @@ use serde_derive::{Deserialize, Serialize}; +use utoipa::ToSchema; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, ToSchema)] pub struct CreateUserRequest { pub name: String, pub age: u8, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, ToSchema)] pub struct CreatedUserIdResponse { pub id: String, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, ToSchema)] pub struct UserResponse { pub name: String, pub age: u8, pub id: String, } + +#[derive(Serialize, Deserialize, ToSchema)] +pub struct ErrorResponse { + pub code: i32, +} diff --git a/host/Cargo.toml b/host/Cargo.toml index 8394d85..18a63ae 100644 --- a/host/Cargo.toml +++ b/host/Cargo.toml @@ -10,6 +10,8 @@ mongodb = "2.7.0" serde = "1.0.188" serde_derive = "1.0.188" async-trait = "0.1.73" +utoipa = { version = "4", features = ["actix_extras"] } +utoipa-swagger-ui = { version = "4", features = ["actix-web"] } app = { path = "../app" } domain = { path = "../domain" } diff --git a/host/src/factory.rs b/host/src/factory.rs index 0d7cb14..4a1b0c5 100644 --- a/host/src/factory.rs +++ b/host/src/factory.rs @@ -1,16 +1,29 @@ use actix_web::dev::Server; -use actix_web::web::{get, post, Data}; +use actix_web::web::Data; use actix_web::{App, HttpServer}; use mongodb::{Client, Collection}; use std::net::SocketAddr; use std::sync::Arc; +use utoipa::OpenApi; +use utoipa_swagger_ui::SwaggerUi; use app::endpoints::{create_user, get_user}; +use app::models::*; use domain::commands::{CreateUserCommand, ICommandHandler}; use domain::queries::{GetUserQuery, IQueryHandler, User}; use domain_impl::handlers::{CreateUserCommandHandler, GetUserQueryHandler}; use infra::repositories::UserRepository; +#[derive(OpenApi)] +#[openapi( + paths(app::endpoints::create_user, app::endpoints::get_user), + components( + schemas(CreateUserRequest, CreatedUserIdResponse, ErrorResponse), + schemas(UserResponse, ErrorResponse) + ) +)] +struct ApiDoc; + pub struct ServerInfo { pub server: Server, pub addrs: Vec, @@ -18,7 +31,6 @@ pub struct ServerInfo { pub async fn create_server(port: i32) -> Result { let user_repository_arc = Arc::new(create_user_repository().await); - let command_handler: Arc>> = Arc::new(CreateUserCommandHandler { repo: user_repository_arc.clone(), @@ -28,12 +40,17 @@ pub async fn create_server(port: i32) -> Result { repo: user_repository_arc.clone(), }); + let openapi = ApiDoc::openapi(); + let http_server = HttpServer::new(move || { App::new() + .service( + SwaggerUi::new("/swagger-ui/{_:.*}").url("/api-docs/openapi.json", openapi.clone()), + ) + .service(create_user) + .service(get_user) .app_data(Data::from(command_handler.clone())) .app_data(Data::from(query_handler.clone())) - .route("/user/{user_id}", get().to(get_user)) - .route("/user", post().to(create_user)) }) .bind(format!("127.0.0.1:{}", port)) .expect("Failed to bind to the server."); diff --git a/host/src/main.rs b/host/src/main.rs index cd317a1..5266ad0 100644 --- a/host/src/main.rs +++ b/host/src/main.rs @@ -3,6 +3,7 @@ use host::factory::create_server; #[actix_web::main] async fn main() -> std::io::Result<()> { std::env::set_var("RUST_LOG", "debug"); + env_logger::init(); create_server(8080).await.unwrap().server.await } diff --git a/integrtion-tests/tests/sut.rs b/integrtion-tests/tests/sut.rs index 11c45ca..cc9a1b4 100644 --- a/integrtion-tests/tests/sut.rs +++ b/integrtion-tests/tests/sut.rs @@ -44,7 +44,7 @@ impl Sut { .await .unwrap(); - if response.status() == StatusCode::OK { + if response.status() == StatusCode::CREATED { let user: CreatedUserIdResponse = response.json().await.unwrap(); Ok(user)