use crate::public_route::{PublicPath, PublicPrefix};
use polaris_system::resource::GlobalResource;
use std::net::SocketAddr;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AppConfig {
host: String,
port: u16,
cors_origins: Vec<String>,
allow_any_cors_origin: bool,
public_paths: Vec<PublicPath>,
public_prefixes: Vec<PublicPrefix>,
}
impl GlobalResource for AppConfig {}
impl Default for AppConfig {
fn default() -> Self {
Self {
host: "127.0.0.1".to_string(),
port: 3000,
cors_origins: Vec::new(),
allow_any_cors_origin: false,
public_paths: Vec::new(),
public_prefixes: Vec::new(),
}
}
}
impl AppConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_host(mut self, host: impl Into<String>) -> Self {
self.host = host.into();
self
}
#[must_use]
pub fn with_port(mut self, port: u16) -> Self {
self.port = port;
self
}
#[must_use]
pub fn with_cors_origin(mut self, origin: impl Into<String>) -> Self {
self.cors_origins.push(origin.into());
self
}
#[must_use]
pub fn with_allow_any_cors_origin(mut self) -> Self {
self.allow_any_cors_origin = true;
self
}
#[must_use]
pub fn with_public_path(mut self, path: impl Into<String>) -> Self {
let path = PublicPath::new(path).unwrap_or_else(|err| {
panic!("AppConfig::with_public_path: {err}");
});
self.public_paths.push(path);
self
}
#[must_use]
pub fn with_public_prefix(mut self, prefix: impl Into<String>) -> Self {
let prefix = PublicPrefix::new(prefix).unwrap_or_else(|err| {
panic!("AppConfig::with_public_prefix: {err}");
});
self.public_prefixes.push(prefix);
self
}
#[must_use]
pub fn host(&self) -> &str {
&self.host
}
#[must_use]
pub fn port(&self) -> u16 {
self.port
}
#[must_use]
pub fn cors_origins(&self) -> &[String] {
&self.cors_origins
}
#[must_use]
pub fn allow_any_cors_origin(&self) -> bool {
self.allow_any_cors_origin
}
#[must_use]
pub fn public_paths(&self) -> &[PublicPath] {
&self.public_paths
}
#[must_use]
pub fn public_prefixes(&self) -> &[PublicPrefix] {
&self.public_prefixes
}
#[must_use]
pub fn addr(&self) -> SocketAddr {
let raw = format!("{}:{}", self.host, self.port);
raw.parse()
.expect("AppConfig host must be a valid IP address")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "AppConfig::with_public_prefix: must not be empty")]
fn with_public_prefix_rejects_empty() {
let _ = AppConfig::new().with_public_prefix("");
}
#[test]
#[should_panic(expected = "AppConfig::with_public_prefix: must start with '/'")]
fn with_public_prefix_rejects_missing_leading_slash() {
let _ = AppConfig::new().with_public_prefix("dashboard");
}
#[test]
#[should_panic(expected = "AppConfig::with_public_path: must not be empty")]
fn with_public_path_rejects_empty() {
let _ = AppConfig::new().with_public_path("");
}
#[test]
#[should_panic(expected = "AppConfig::with_public_path: must start with '/'")]
fn with_public_path_rejects_missing_leading_slash() {
let _ = AppConfig::new().with_public_path("healthz");
}
#[test]
#[should_panic(expected = "must end with '/'")]
fn with_public_prefix_rejects_missing_trailing_slash() {
let _ = AppConfig::new().with_public_prefix("/dashboard");
}
#[test]
fn valid_public_path_and_prefix_are_accepted() {
let config = AppConfig::new()
.with_public_path("/healthz")
.with_public_prefix("/dashboard/");
assert_eq!(config.public_paths().len(), 1);
assert_eq!(config.public_paths()[0].as_str(), "/healthz");
assert_eq!(config.public_prefixes().len(), 1);
assert_eq!(config.public_prefixes()[0].as_str(), "/dashboard/");
}
}