Smol changes
This commit is contained in:
27
Cargo.lock
generated
27
Cargo.lock
generated
@@ -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",
|
||||||
|
|||||||
@@ -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"] }
|
||||||
|
|||||||
@@ -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
28
src/config.rs
Normal 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
38
src/error.rs
Normal 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>;
|
||||||
@@ -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,
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(),
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
16
src/main.rs
16
src/main.rs
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user