use std::net::{Ipv4Addr, SocketAddr};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ConfigError {
#[error(transparent)]
AddrParse(#[from] std::net::AddrParseError),
#[error(transparent)]
ParseInt(#[from] std::num::ParseIntError),
#[error("required environment variable {0} is not set")]
MissingEnv(&'static str),
#[error("invalid boolean for {key}: {value}")]
InvalidBool {
key: &'static str,
value: String,
},
#[error("forwarding secret must not be empty")]
MissingForwardingSecret,
#[error("invalid compression level {0}: must be 0-12")]
InvalidCompressionLevel(i32),
}
#[derive(Debug, Clone)]
pub struct Config {
pub listen_addr: SocketAddr,
pub grpc_addr: SocketAddr,
pub online_mode: bool,
pub forwarding_secret: Vec<u8>,
pub compression_threshold: i32,
pub compression_level: i32,
pub motd: String,
pub max_players: i32,
pub read_timeout_ms: u64,
pub try_servers: Vec<String>,
pub log_level: String,
pub log_json: bool,
}
impl Config {
pub fn from_env() -> Result<Self, ConfigError> {
let listen_addr = env_or("DEEPSLATE_ADDR", "0.0.0.0:25565").parse()?;
let grpc_addr = env_or("DEEPSLATE_GRPC_ADDR", "0.0.0.0:25577").parse()?;
let online_mode = env_bool("DEEPSLATE_ONLINE_MODE", true)?;
let forwarding_secret = env_required("DEEPSLATE_FORWARDING_SECRET")?.into_bytes();
let compression_threshold = env_or("DEEPSLATE_COMPRESSION_THRESHOLD", "256").parse()?;
let compression_level: i32 = env_or("DEEPSLATE_COMPRESSION_LEVEL", "1").parse()?;
let motd = env_or("DEEPSLATE_MOTD", "A Deepslate Proxy");
let max_players = env_or("DEEPSLATE_MAX_PLAYERS", "500").parse()?;
let read_timeout_ms = env_or("DEEPSLATE_READ_TIMEOUT", "30000").parse()?;
let try_servers = env_or("DEEPSLATE_TRY_SERVERS", "")
.split(',')
.map(str::trim)
.filter(|s| !s.is_empty())
.map(String::from)
.collect();
let log_level = env_or("DEEPSLATE_LOG_LEVEL", "info");
let log_json = env_bool("DEEPSLATE_LOG_JSON", false)?;
Self {
listen_addr,
grpc_addr,
online_mode,
forwarding_secret,
compression_threshold,
compression_level,
motd,
max_players,
read_timeout_ms,
try_servers,
log_level,
log_json,
}
.validate()
}
pub fn validate(self) -> Result<Self, ConfigError> {
if self.forwarding_secret.is_empty() {
return Err(ConfigError::MissingForwardingSecret);
}
if !(0..=12).contains(&self.compression_level) {
return Err(ConfigError::InvalidCompressionLevel(self.compression_level));
}
Ok(self)
}
}
impl Default for Config {
fn default() -> Self {
Self {
listen_addr: SocketAddr::from((Ipv4Addr::UNSPECIFIED, 25_565)),
grpc_addr: SocketAddr::from((Ipv4Addr::UNSPECIFIED, 25_577)),
online_mode: true,
forwarding_secret: Vec::new(),
compression_threshold: 256,
compression_level: 1,
motd: "A Deepslate Proxy".to_string(),
max_players: 500,
read_timeout_ms: 30_000,
try_servers: vec![],
log_level: "info".to_string(),
log_json: false,
}
}
}
fn env_or(key: &str, default: &str) -> String {
std::env::var(key).unwrap_or_else(|_| default.to_string())
}
fn env_required(key: &'static str) -> Result<String, ConfigError> {
std::env::var(key).map_err(|_| ConfigError::MissingEnv(key))
}
fn env_bool(key: &'static str, default: bool) -> Result<bool, ConfigError> {
std::env::var(key).map_or_else(
|_| Ok(default),
|val| match val.to_lowercase().as_str() {
"true" | "1" => Ok(true),
"false" | "0" => Ok(false),
_ => Err(ConfigError::InvalidBool { key, value: val }),
},
)
}