Smol changes

This commit is contained in:
2025-12-12 23:57:25 -07:00
parent 1441df435f
commit 8fc7b91a77
10 changed files with 115 additions and 19 deletions

27
Cargo.lock generated
View File

@@ -757,6 +757,26 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 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]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.16" version = "0.2.16"
@@ -1047,13 +1067,14 @@ dependencies = [
[[package]] [[package]]
name = "rustapi" name = "rustapi"
version = "0.1.0" version = "1.0.0"
dependencies = [ dependencies = [
"axum", "axum",
"http 0.2.12", "http 0.2.12",
"http-body-util", "http-body-util",
"serde", "serde",
"serde_json", "serde_json",
"thiserror 2.0.17",
"tokio", "tokio",
"tower 0.4.13", "tower 0.4.13",
"tower-http 0.5.2", "tower-http 0.5.2",
@@ -1454,6 +1475,10 @@ version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
dependencies = [ dependencies = [
"futures-core",
"futures-util",
"pin-project",
"pin-project-lite",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
"tracing", "tracing",

View File

@@ -10,11 +10,12 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
tracing = "0.1" tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] } 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"] } tower-http = { version = "0.5", features = ["trace", "normalize-path"] }
http = "0.2" http = "0.2"
utoipa = "4" utoipa = "4"
utoipa-swagger-ui = { version = "7", features = ["axum"] } utoipa-swagger-ui = { version = "7", features = ["axum"] }
thiserror = "2.0.17"
[dev-dependencies] [dev-dependencies]
axum = { version = "0.7", features = ["macros"] } axum = { version = "0.7", features = ["macros"] }

View File

@@ -28,7 +28,7 @@ mod tests {
use super::*; use super::*;
use axum::{body::Body, http::{Request, StatusCode}}; use axum::{body::Body, http::{Request, StatusCode}};
use http_body_util::BodyExt; use http_body_util::BodyExt;
use tower::ServiceExt; use tower::util::ServiceExt;
use crate::handlers::types::{HealthResponse, MessageResponse}; use crate::handlers::types::{HealthResponse, MessageResponse};
use crate::state::new_state; use crate::state::new_state;

28
src/config.rs Normal file
View File

@@ -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")
}
}

38
src/error.rs Normal file
View File

@@ -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<T> = Result<T, AppError>;

View File

@@ -1,5 +1,6 @@
use axum::{Json, extract::{Extension, State}}; use axum::{Json, extract::{Extension, State}};
use crate::error::AppResult;
use crate::handlers::types::{MessageRequest, MessageResponse}; use crate::handlers::types::{MessageRequest, MessageResponse};
use crate::middleware::request_id::{RequestId, generate_request_id}; use crate::middleware::request_id::{RequestId, generate_request_id};
use crate::state::SharedState; use crate::state::SharedState;
@@ -16,13 +17,13 @@ pub async fn echo(
State(_state): State<SharedState>, State(_state): State<SharedState>,
req_id: Option<Extension<RequestId>>, req_id: Option<Extension<RequestId>>,
Json(payload): Json<MessageRequest>, Json(payload): Json<MessageRequest>,
) -> Json<MessageResponse> { ) -> AppResult<Json<MessageResponse>> {
let id = req_id let id = req_id
.map(|rid| rid.0.0) .map(|rid| rid.0.0)
.unwrap_or_else(generate_request_id); .unwrap_or_else(generate_request_id);
Json(MessageResponse { Ok(Json(MessageResponse {
echoed: payload.message, echoed: payload.message,
id, id,
}) }))
} }

View File

@@ -1,5 +1,6 @@
use axum::{Json, extract::State}; use axum::{Json, extract::State};
use crate::error::AppResult;
use crate::handlers::types::HealthResponse; use crate::handlers::types::HealthResponse;
use crate::state::SharedState; use crate::state::SharedState;
@@ -10,9 +11,9 @@ use crate::state::SharedState;
(status = 200, description = "Health check", body = HealthResponse) (status = 200, description = "Health check", body = HealthResponse)
) )
)] )]
pub async fn health(State(state): State<SharedState>) -> Json<HealthResponse> { pub async fn health(State(state): State<SharedState>) -> AppResult<Json<HealthResponse>> {
Json(HealthResponse { Ok(Json(HealthResponse {
status: "ok".to_string(), status: "ok".to_string(),
service: state.app_name.clone(), service: state.app_name.clone(),
}) }))
} }

View File

@@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use utoipa::ToSchema; use utoipa::ToSchema;
#[derive(Debug, Serialize, ToSchema)] #[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct HealthResponse { pub struct HealthResponse {
pub status: String, pub status: String,
pub service: String, pub service: String,
@@ -12,7 +12,7 @@ pub struct MessageRequest {
pub message: String, pub message: String,
} }
#[derive(Debug, Serialize, ToSchema)] #[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct MessageResponse { pub struct MessageResponse {
pub echoed: String, pub echoed: String,
pub id: String, pub id: String,

View File

@@ -1,5 +1,7 @@
pub mod app; pub mod app;
pub mod config;
pub mod docs; pub mod docs;
pub mod error;
pub mod handlers; pub mod handlers;
pub mod middleware; pub mod middleware;
pub mod state; pub mod state;

View File

@@ -1,18 +1,19 @@
use std::net::SocketAddr;
use axum::body::Body; use axum::body::Body;
use axum::http::Request; use axum::http::Request;
use axum::ServiceExt; use axum::ServiceExt;
use rustapi::config::Config;
use tower::Layer; use tower::Layer;
use tower_http::normalize_path::NormalizePathLayer; use tower_http::normalize_path::NormalizePathLayer;
use tracing::info; use tracing::info;
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
init_tracing(); let config = Config::from_env();
init_tracing(&config);
let state = rustapi::state::new_state("rustapi"); 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?; let listener = tokio::net::TcpListener::bind(addr).await?;
info!("listening on http://{}", addr); info!("listening on http://{}", addr);
@@ -28,12 +29,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(()) Ok(())
} }
fn init_tracing() { fn init_tracing(config: &Config) {
use tracing_subscriber::{fmt, EnvFilter}; use tracing_subscriber::{fmt, EnvFilter};
let filter = EnvFilter::try_from_default_env() let filter = EnvFilter::try_new(&config.log_level)
.or_else(|_| EnvFilter::try_new("info")) .unwrap_or_else(|_| EnvFilter::new("info"));
.unwrap();
fmt() fmt()
.with_env_filter(filter) .with_env_filter(filter)