pzzld-server 0.0.2

A production ready server optimized for WASM applications
Documentation
/*
    Appellation: settings <module>
    Contrib: FL03 <jo3mccain@icloud.com>
*/
use super::{kinds::*, types::*};
use crate::workers::WorkerConfig;

type ConfigBuilder<S = config::builder::DefaultState> = config::builder::ConfigBuilder<S>;

type ConfigResult<T> = core::result::Result<T, config::ConfigError>;

fn fmt_src(dir: impl core::fmt::Display, fname: impl core::fmt::Display) -> String {
    format!("{dir}/{fname}")
}

fn set_default(builder: ConfigBuilder) -> ConfigResult<ConfigBuilder> {
    let builder = builder
        .set_default("mode", "debug")?
        .set_default("name", crate::APP_NAME)?
        .set_default("version", env!("CARGO_PKG_VERSION"))?
        .set_default("scope.context", ".")?
        .set_default("scope.workdir", crate::DEFAULT_WORKDIR)?
        .set_default("network.address.host", crate::DEFAULT_HOST)?
        .set_default("network.address.port", crate::DEFAULT_PORT)?
        .set_default("services.tracing.level", "info")?;
    Ok(builder)
}

fn add_sources(builder: ConfigBuilder) -> ConfigBuilder {
    let workdir =
        std::env::var("PZZLD_CONFIG").unwrap_or_else(|_| crate::DEFAULT_CONFIG_DIR.to_string());
    let fname = std::env::var("PZZLD_SETTINGS").unwrap_or_else(|_| "Puzzled.toml".to_string());

    builder
        .add_source(config::Environment::with_prefix("PZZLD").separator("_"))
        .add_source(config::File::with_name(&fmt_src(&workdir, "default.config")).required(false))
        .add_source(config::File::with_name(&fmt_src(&workdir, "server.config")).required(false))
        .add_source(config::File::with_name(&fmt_src(&workdir, "docker.config")).required(false))
        .add_source(config::File::with_name(&fmt_src(&workdir, &fname)).required(false))
        .add_source(config::File::with_name(&fname).required(false))
        .add_source(config::File::with_name(&fmt_src(&workdir, "prod.config")).required(false))
}

fn set_overrides(builder: ConfigBuilder) -> ConfigResult<ConfigBuilder> {
    Ok(builder
        .set_override_option("mode", std::env::var("RUNMODE").ok())?
        .set_override_option("name", std::env::var("APP_NAME").ok())?
        .set_override_option("network.address.host", std::env::var("HOSTNAME").ok())?
        .set_override_option("network.address.port", std::env::var("HOSTPORT").ok())?
        .set_override_option("scope.context", std::env::var("PZZLD_CONTEXT").ok())?
        .set_override_option("scope.workdir", std::env::var("PZZLD_WORKDIR").ok())?)
}

#[derive(
    Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize,
)]
#[serde(default)]
pub struct Settings {
    pub mode: Mode,
    pub name: String,
    pub network: NetworkConfig,
    pub scope: Scope,
    pub services: ServicesConfig,
    pub version: String,
    pub worker: Vec<WorkerConfig>,
}

impl Settings {
    pub fn new(mode: Mode) -> Self {
        Self {
            mode,
            name: crate::APP_NAME.to_string(),
            network: NetworkConfig::default(),
            scope: Scope::default(),
            services: ServicesConfig::default(),
            version: env!("CARGO_PKG_VERSION").to_string(),
            worker: Vec::new(),
        }
    }

    pub fn build() -> Result<Self, config::ConfigError> {
        Self::builder_base()?.build()?.try_deserialize()
    }

    pub async fn bind(&self) -> std::io::Result<tokio::net::TcpListener> {
        self.network().bind().await
    }
    /// Initialize tracing modules
    pub fn init_tracing(&self) {
        self.services().tracing().init_tracing(crate::APP_NAME);
    }
    /// Returns a new [Settings] instance with the [Mode] set to [Mode::Debug].
    pub fn debug(self) -> Self {
        Self {
            mode: Mode::debug(),
            ..self
        }
    }
    /// Returns a new [Settings] instance with the [Mode] set to [Mode::Release].
    pub fn release(self) -> Self {
        Self {
            mode: Mode::release(),
            ..self
        }
    }

    getter! {
        mode: Mode,
        name: String,
        network: NetworkConfig,
        scope: Scope,
        services: ServicesConfig,
        version: String,
    }

    setwith! {
        mode: Mode,
        name: String,
        scope: Scope,
        network: NetworkConfig,
        services: ServicesConfig,
        version: String,
    }
    /// Returns an immutable reference to the worker configuration.
    pub fn workers(&self) -> &[WorkerConfig] {
        &self.worker
    }
    /// Returns a mutable reference to the worker configuration.
    pub fn workers_mut(&mut self) -> &mut Vec<WorkerConfig> {
        &mut self.worker
    }
    /// Push a new worker configuration to the worker configuration list.
    pub fn push_worker(&mut self, worker: WorkerConfig) {
        self.worker.push(worker)
    }
    /// set the working directory of the scope
    pub fn set_workdir(&mut self, workdir: impl ToString) {
        self.scope_mut().set_workdir(workdir);
    }
    /// if the workdir is set, set it to the given workdir
    pub fn set_some_workdir(&mut self, workdir: Option<impl ToString>) {
        self.scope_mut().set_some_workdir(workdir);
    }
    /// Set the context of the scope
    pub fn set_scope_context(&mut self, context: impl ToString) {
        self.scope_mut().set_context(context);
    }

    pub fn set_port(&mut self, port: u16) {
        self.network_mut().set_port(port);
    }

    pub fn set_log_level(&mut self, level: LogLevel) {
        self.services_mut().tracing_mut().set_level(level);
    }

    fn builder_base() -> ConfigResult<ConfigBuilder> {
        // initialize the builder
        let mut builder = config::Config::builder();
        // set defaults
        builder = set_default(builder)?;
        // add sources
        builder = add_sources(builder);
        // set overrides
        builder = set_overrides(builder)?;
        // return the builder
        Ok(builder)
    }
}

impl Default for Settings {
    fn default() -> Self {
        Self::new(Mode::Debug)
    }
}