From 8fc7b91a7791218223edf40bdc4568b7992669d3 Mon Sep 17 00:00:00 2001 From: Atridad Lahiji Date: Fri, 12 Dec 2025 23:57:25 -0700 Subject: [PATCH] Smol changes --- Cargo.lock | 27 ++++++++++++++++++++++++++- Cargo.toml | 3 ++- src/app.rs | 2 +- src/config.rs | 28 ++++++++++++++++++++++++++++ src/error.rs | 38 ++++++++++++++++++++++++++++++++++++++ src/handlers/echo.rs | 7 ++++--- src/handlers/health.rs | 7 ++++--- src/handlers/types.rs | 4 ++-- src/lib.rs | 2 ++ src/main.rs | 16 ++++++++-------- 10 files changed, 115 insertions(+), 19 deletions(-) create mode 100644 src/config.rs create mode 100644 src/error.rs diff --git a/Cargo.lock b/Cargo.lock index 6f18848..d7e492d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -757,6 +757,26 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1047,13 +1067,14 @@ dependencies = [ [[package]] name = "rustapi" -version = "0.1.0" +version = "1.0.0" dependencies = [ "axum", "http 0.2.12", "http-body-util", "serde", "serde_json", + "thiserror 2.0.17", "tokio", "tower 0.4.13", "tower-http 0.5.2", @@ -1454,6 +1475,10 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", "tower-layer", "tower-service", "tracing", diff --git a/Cargo.toml b/Cargo.toml index ecb9291..1e095c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,11 +10,12 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] } -tower = "0.4" +tower = { version = "0.4", features = ["util"] } tower-http = { version = "0.5", features = ["trace", "normalize-path"] } http = "0.2" utoipa = "4" utoipa-swagger-ui = { version = "7", features = ["axum"] } +thiserror = "2.0.17" [dev-dependencies] axum = { version = "0.7", features = ["macros"] } diff --git a/src/app.rs b/src/app.rs index 4c6c23d..587e289 100644 --- a/src/app.rs +++ b/src/app.rs @@ -28,7 +28,7 @@ mod tests { use super::*; use axum::{body::Body, http::{Request, StatusCode}}; use http_body_util::BodyExt; - use tower::ServiceExt; + use tower::util::ServiceExt; use crate::handlers::types::{HealthResponse, MessageResponse}; use crate::state::new_state; diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..102dca5 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,28 @@ +use std::env; +use std::net::SocketAddr; + +#[derive(Debug, Clone)] +pub struct Config { + pub host: String, + pub port: u16, + pub log_level: String, +} + +impl Config { + pub fn from_env() -> Self { + Self { + host: env::var("HOST").unwrap_or_else(|_| "0.0.0.0".to_string()), + port: env::var("PORT") + .ok() + .and_then(|p| p.parse().ok()) + .unwrap_or(8080), + log_level: env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()), + } + } + + pub fn socket_addr(&self) -> SocketAddr { + format!("{}:{}", self.host, self.port) + .parse() + .expect("Invalid host/port configuration") + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..17c711a --- /dev/null +++ b/src/error.rs @@ -0,0 +1,38 @@ +use axum::{ + http::StatusCode, + response::{IntoResponse, Response}, + Json, +}; +use serde_json::json; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum AppError { + #[error("Internal Server Error")] + InternalServerError, + #[error("Bad Request: {0}")] + BadRequest(String), + #[error("Not Found")] + NotFound, + // Add more errors here as needed, e.g.: + // #[error("Database error: {0}")] + // DatabaseError(#[from] sqlx::Error), +} + +impl IntoResponse for AppError { + fn into_response(self) -> Response { + let (status, message) = match self { + AppError::InternalServerError => (StatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error".to_string()), + AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg), + AppError::NotFound => (StatusCode::NOT_FOUND, "Not Found".to_string()), + }; + + let body = Json(json!({ + "error": message, + })); + + (status, body).into_response() + } +} + +pub type AppResult = Result; diff --git a/src/handlers/echo.rs b/src/handlers/echo.rs index 3950408..278ab78 100644 --- a/src/handlers/echo.rs +++ b/src/handlers/echo.rs @@ -1,5 +1,6 @@ use axum::{Json, extract::{Extension, State}}; +use crate::error::AppResult; use crate::handlers::types::{MessageRequest, MessageResponse}; use crate::middleware::request_id::{RequestId, generate_request_id}; use crate::state::SharedState; @@ -16,13 +17,13 @@ pub async fn echo( State(_state): State, req_id: Option>, Json(payload): Json, -) -> Json { +) -> AppResult> { let id = req_id .map(|rid| rid.0.0) .unwrap_or_else(generate_request_id); - Json(MessageResponse { + Ok(Json(MessageResponse { echoed: payload.message, id, - }) + })) } diff --git a/src/handlers/health.rs b/src/handlers/health.rs index 0e19c6a..b5a4201 100644 --- a/src/handlers/health.rs +++ b/src/handlers/health.rs @@ -1,5 +1,6 @@ use axum::{Json, extract::State}; +use crate::error::AppResult; use crate::handlers::types::HealthResponse; use crate::state::SharedState; @@ -10,9 +11,9 @@ use crate::state::SharedState; (status = 200, description = "Health check", body = HealthResponse) ) )] -pub async fn health(State(state): State) -> Json { - Json(HealthResponse { +pub async fn health(State(state): State) -> AppResult> { + Ok(Json(HealthResponse { status: "ok".to_string(), service: state.app_name.clone(), - }) + })) } diff --git a/src/handlers/types.rs b/src/handlers/types.rs index 48af93f..2f5bf0d 100644 --- a/src/handlers/types.rs +++ b/src/handlers/types.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use utoipa::ToSchema; -#[derive(Debug, Serialize, ToSchema)] +#[derive(Debug, Serialize, Deserialize, ToSchema)] pub struct HealthResponse { pub status: String, pub service: String, @@ -12,7 +12,7 @@ pub struct MessageRequest { pub message: String, } -#[derive(Debug, Serialize, ToSchema)] +#[derive(Debug, Serialize, Deserialize, ToSchema)] pub struct MessageResponse { pub echoed: String, pub id: String, diff --git a/src/lib.rs b/src/lib.rs index ad2e30d..c90b563 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ pub mod app; +pub mod config; pub mod docs; +pub mod error; pub mod handlers; pub mod middleware; pub mod state; diff --git a/src/main.rs b/src/main.rs index 084c3bc..13a94e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,19 @@ -use std::net::SocketAddr; - use axum::body::Body; use axum::http::Request; use axum::ServiceExt; +use rustapi::config::Config; use tower::Layer; use tower_http::normalize_path::NormalizePathLayer; use tracing::info; + #[tokio::main] async fn main() -> Result<(), Box> { - init_tracing(); + let config = Config::from_env(); + init_tracing(&config); let state = rustapi::state::new_state("rustapi"); - let addr: SocketAddr = ([0, 0, 0, 0], 8080).into(); + let addr = config.socket_addr(); let listener = tokio::net::TcpListener::bind(addr).await?; info!("listening on http://{}", addr); @@ -28,12 +29,11 @@ async fn main() -> Result<(), Box> { Ok(()) } -fn init_tracing() { +fn init_tracing(config: &Config) { use tracing_subscriber::{fmt, EnvFilter}; - let filter = EnvFilter::try_from_default_env() - .or_else(|_| EnvFilter::try_new("info")) - .unwrap(); + let filter = EnvFilter::try_new(&config.log_level) + .unwrap_or_else(|_| EnvFilter::new("info")); fmt() .with_env_filter(filter)