use axum::Router;
use dev_utils::{debug, info};
use std::{
net::{IpAddr, SocketAddr},
path::PathBuf,
str::FromStr,
sync::{Arc, Mutex},
time::SystemTime,
};
use tokio::net::TcpListener;
use crate::api::health::{AppState, SharedAppState};
use super::create_health_routes;
pub struct PrismConfig<S = String, P = PathBuf>
where
S: Into<String> + Clone,
P: Into<PathBuf> + Clone,
{
pub project_name: S,
pub version: S,
pub description: Option<S>,
pub static_assets_path: Option<P>,
pub host: S,
pub port: u16,
}
impl<S, P> PrismConfig<S, P>
where
S: Into<String> + Clone,
P: Into<PathBuf> + Clone,
{
pub fn new(
project_name: S,
version: S,
description: Option<S>,
static_assets_path: Option<P>,
host: S,
port: u16,
) -> Self {
Self {
project_name,
version,
description,
static_assets_path,
host,
port,
}
}
pub fn into_concrete(self) -> PrismConfig<String, PathBuf> {
PrismConfig {
project_name: self.project_name.into(),
version: self.version.into(),
description: self.description.map(Into::into),
static_assets_path: self.static_assets_path.map(Into::into),
host: self.host.into(),
port: self.port,
}
}
}
impl Default for PrismConfig<String, PathBuf> {
fn default() -> Self {
Self {
project_name: "Prism API".into(),
version: env!("CARGO_PKG_VERSION").into(),
description: None,
static_assets_path: None,
host: "localhost".into(),
port: 8080,
}
}
}
pub struct PrismApi {
pub config: PrismConfig<String, PathBuf>,
pub state: SharedAppState,
}
impl Default for PrismApi {
fn default() -> Self {
Self {
config: PrismConfig::default(),
state: Arc::new(Mutex::new(AppState {
start_time: SystemTime::now(),
database_connected: true,
})),
}
}
}
impl PrismApi {
pub fn with_config<T, P>(
config: PrismConfig<T, P>,
) -> Self
where
T: Into<String> + Clone,
P: Into<PathBuf> + Clone,
{
let state = Arc::new(Mutex::new(AppState {
start_time: SystemTime::now(),
database_connected: true, }));
Self {
config: config.into_concrete(),
state,
}
}
pub fn print_welcome(&self, host: &str, port: u16) {
info!("===========================================");
info!("🚀 {} v{}", self.config.project_name, self.config.version);
if let Some(desc) = &self.config.description {
debug!("{}", desc);
}
info!("===========================================");
let address = format!("http://{}:{}", host, port);
info!("📚 API documentation: {address}/docs");
info!("📡 Server running at: {address}");
info!("🏥 Health status: {address}/health");
info!("===========================================");
}
pub fn get_state(&self) -> SharedAppState {
self.state.clone()
}
pub fn build_router(&self) -> Router {
let router = Router::new()
.nest("/health", create_health_routes());
router.with_state(self.state.clone())
}
pub async fn serve(&self) -> Result<(), Box<dyn std::error::Error>> {
let app = self.build_router();
self.print_welcome(&self.config.host, self.config.port);
let socket_addr = format!("{}:{}", self.config.host, self.config.port)
.parse::<SocketAddr>()
.unwrap_or_else(|_| {
println!(
"Warning: Could not parse host '{}', falling back to localhost",
self.config.host
);
SocketAddr::from(([127, 0, 0, 1], self.config.port))
});
println!("Binding to {}", socket_addr);
let listener = TcpListener::bind(socket_addr).await?;
axum::serve(listener, app).await?;
Ok(())
}
}