diff --git a/Cargo.lock b/Cargo.lock index db12e10..4168970 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -362,6 +362,15 @@ dependencies = [ "apistos-plugins", ] +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "arrayvec" version = "0.7.6" @@ -871,6 +880,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "derive_more" version = "2.1.1" @@ -989,6 +1009,7 @@ checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" dependencies = [ "crc32fast", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -1928,6 +1949,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -2323,6 +2350,40 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rust-embed" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.111", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rust_decimal" version = "1.40.0" @@ -2360,6 +2421,15 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -2646,6 +2716,9 @@ dependencies = [ "sea-orm", "serde", "tokio", + "utoipa", + "utoipa-axum", + "utoipa-swagger-ui", ] [[package]] @@ -3312,6 +3385,61 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utoipa" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993" +dependencies = [ + "indexmap", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-axum" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c25bae5bccc842449ec0c5ddc5cbb6a3a1eaeac4503895dc105a1138f8234a0" +dependencies = [ + "axum", + "paste", + "tower-layer", + "tower-service", + "utoipa", +] + +[[package]] +name = "utoipa-gen" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d79d08d92ab8af4c5e8a6da20c47ae3f61a0f1dabc1997cdf2d082b757ca08b" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.111", +] + +[[package]] +name = "utoipa-swagger-ui" +version = "9.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d047458f1b5b65237c2f6dc6db136945667f40a7668627b3490b9513a3d43a55" +dependencies = [ + "axum", + "base64", + "mime_guess", + "regex", + "rust-embed", + "serde", + "serde_json", + "url", + "utoipa", + "zip", +] + [[package]] name = "uuid" version = "1.20.0" @@ -3335,6 +3463,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -3411,6 +3549,15 @@ dependencies = [ "wasite", ] +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "windows-core" version = "0.62.2" @@ -3840,12 +3987,44 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "zip" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12598812502ed0105f607f941c386f43d441e00148fce9dec3ca5ffb0bde9308" +dependencies = [ + "arbitrary", + "crc32fast", + "flate2", + "indexmap", + "memchr", + "zopfli", +] + +[[package]] +name = "zlib-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" + [[package]] name = "zmij" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9747e91771f56fd7893e1164abd78febd14a670ceec257caad15e051de35f06" +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + [[package]] name = "zstd" version = "0.13.3" diff --git a/Cargo.toml b/Cargo.toml index 05fe7bb..157b511 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,4 +14,11 @@ actix-web = "4.12.1" paperclip = { version = "0.9.5", features = ["actix4"] } apistos = { version = "0.6.0", features = ["swagger-ui"] } schemars = { package = "apistos-schemars", version = "0.8" } -sea-orm = { version = "1.1.19", features = ["macros", "runtime-tokio", "sqlx-sqlite"] } +sea-orm = { version = "1.1.19", features = [ + "macros", + "runtime-tokio", + "sqlx-sqlite", +] } +utoipa = { version = "5.4.0", features = ["axum_extras", "chrono"] } +utoipa-axum = "0.2.0" +utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] } diff --git a/src/main.rs b/src/main.rs index 9e2e9bb..ce72e65 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,14 @@ use std::sync::{Arc, Mutex}; -use actix_web::{App, HttpServer, web}; +use actix_web::{App, HttpServer, web::{self, resource}}; use apistos::{SwaggerUIConfig, app::{BuildConfig, OpenApiWrapper}, info::Info, spec::Spec}; use cron_tab::Cron; use gpio_cdev::Chip; use sea_orm::Database; +use tokio::net::TcpListener; +use utoipa::OpenApi; +use utoipa_axum::router::OpenApiRouter; +use utoipa_swagger_ui::SwaggerUi; use crate::{ringer::BeepRinger, scheduler::Scheduler}; @@ -36,34 +40,29 @@ async fn app_state() -> AppState { #[tokio::main] async fn main() -> Result<(), Box> { - let app_state = app_state().await; + // let app_state = app_state().await; - app_state.scheduler.start(); + // app_state.scheduler.start(); - let _ = start_actix_server(app_state).await; + start_axum_server().await; Ok(()) } +#[derive(OpenApi)] +struct ApiDocs; -async fn start_actix_server(app_state: AppState) -> std::io::Result<()> { - let _ = HttpServer::new(move || { - let spec = Spec { - info: Info { - title: "Snooze Pal".to_string(), - version: "0.0.1".to_string(), - ..Default::default() - }, - ..Default::default() - }; +async fn start_axum_server() { + let docs = ApiDocs::openapi(); - App::new() - .document(spec) - .service(resources::v1()) - .app_data(web::Data::new(app_state.clone())) - .build_with("/openapi.json", BuildConfig::default().with(SwaggerUIConfig::new(&"/swagger"))) + let (router, spec) = OpenApiRouter::with_openapi(docs) + .nest("/v1", resources::router()) + .split_for_parts(); - }).bind("0.0.0.0:8080")?.run().await; + let router = router.merge(SwaggerUi::new("/swagger-ui").url("/openapi.json", spec)); + let listener = TcpListener::bind("0.0.0.0:8080") + .await.expect("Failed to bind to port 8080. It may be taken by another process."); - Ok(()) + axum::serve(listener, router) + .await.expect("Failed to serve"); } diff --git a/src/resources/alarm/mod.rs b/src/resources/alarm/mod.rs index 1f15c9d..baaecc7 100644 --- a/src/resources/alarm/mod.rs +++ b/src/resources/alarm/mod.rs @@ -1,7 +1,7 @@ mod post; -use apistos::web; +use utoipa_axum::{router::OpenApiRouter, routes}; -pub fn resource() -> web::Resource { - web::resource("/alarm") - .route(web::post().to(post::post)) +pub fn router() -> OpenApiRouter { + OpenApiRouter::new() + .routes(routes!(post::post_handler)) } diff --git a/src/resources/alarm/post.rs b/src/resources/alarm/post.rs index 2b746c7..092eede 100644 --- a/src/resources/alarm/post.rs +++ b/src/resources/alarm/post.rs @@ -1,56 +1,40 @@ -use std::{fmt::Display}; - -use actix_web::{ResponseError, web::{Data, Json}}; -use apistos::{ApiComponent, ApiErrorComponent, actix::CreatedJson}; +use axum::Json; +use axum::http::StatusCode; +use axum::debug_handler; +use axum::response::IntoResponse; use chrono::{DateTime, Local}; use schemars::JsonSchema; -use serde::{Serialize, Deserialize}; +use serde::Deserialize; +use utoipa::{IntoResponses, ToSchema}; -use crate::AppState; - -#[derive(Debug, Deserialize, JsonSchema, ApiComponent)] +#[derive(Debug, Deserialize, JsonSchema, ToSchema)] pub struct RequestBody { time: DateTime, } -#[derive(Debug, Serialize, JsonSchema, ApiComponent)] -pub struct SuccessResponse { - pub time: DateTime, - pub enabled: bool +#[derive(IntoResponses)] +pub enum Responses { + #[response(status = 200)] + Ok } -#[derive(Debug, Deserialize, Serialize, ApiErrorComponent)] -#[openapi_error(status(code = 500, description = "The alarm could not be created"))] -pub enum ErrorResponse { - InternalError(String) -} - -impl Display for ErrorResponse { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl IntoResponse for Responses { + fn into_response(self) -> axum::response::Response { match self { - ErrorResponse::InternalError(e) => write!(f, "Internal error: {}", e), + Responses::Ok => (StatusCode::OK).into_response(), } } + } -impl ResponseError for ErrorResponse { - fn status_code(&self) -> actix_web::http::StatusCode { - match self { - ErrorResponse::InternalError(_) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR, - } - } -} - -#[apistos::api_operation( - summary = "Add new alarm", - description = r###"Creates new Alarm"###, - error_code= 500, +#[utoipa::path( + post, + path = "", + responses( + Responses + ) )] -pub async fn post(data: Data, body: Json) -> Result, ErrorResponse> { - let result = data.scheduler.add_alarm(body.time); - - match result { - Ok(alarm) => Ok(CreatedJson(SuccessResponse { time: alarm.time, enabled: alarm.enabled })), - Err(e) => Err(ErrorResponse::InternalError(e)), - } +#[debug_handler] +pub async fn post_handler(Json(body): Json) -> Responses { + todo!() } diff --git a/src/resources/mod.rs b/src/resources/mod.rs index 82862ad..5730bd4 100644 --- a/src/resources/mod.rs +++ b/src/resources/mod.rs @@ -1,5 +1,8 @@ +use utoipa_axum::router::OpenApiRouter; + mod alarm; -pub fn v1() -> apistos::web::Scope { - apistos::web::scope("/v1").service(alarm::resource()) +pub fn router() -> OpenApiRouter { + OpenApiRouter::new() + .nest("/alarm", alarm::router()) }