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"
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",

View File

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

View File

@@ -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;

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 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<SharedState>,
req_id: Option<Extension<RequestId>>,
Json(payload): Json<MessageRequest>,
) -> Json<MessageResponse> {
) -> AppResult<Json<MessageResponse>> {
let id = req_id
.map(|rid| rid.0.0)
.unwrap_or_else(generate_request_id);
Json(MessageResponse {
Ok(Json(MessageResponse {
echoed: payload.message,
id,
})
}))
}

View File

@@ -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<SharedState>) -> Json<HealthResponse> {
Json(HealthResponse {
pub async fn health(State(state): State<SharedState>) -> AppResult<Json<HealthResponse>> {
Ok(Json(HealthResponse {
status: "ok".to_string(),
service: state.app_name.clone(),
})
}))
}

View File

@@ -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,

View File

@@ -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;

View File

@@ -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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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)