acton-reactive 8.2.0

Acton Reactive is the main crate of the Acton framework, designed for building reactive, event-driven, and asynchronous systems. It provides intuitive abstractions to make working with distributed actors seamless and efficient.
Documentation
/*
 * Copyright (c) 2024. Govcraft
 *
 * Licensed under either of
 *   * Apache License, Version 2.0 (the "License");
 *     you may not use this file except in compliance with the License.
 *     you may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 *   * MIT license: http://opensource.org/licenses/MIT
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the applicable License for the specific language governing permissions and
 * limitations under that License.
 */

use serde::{Deserialize, Serialize};
use std::sync::LazyLock;
use std::time::Duration;

/// Configuration for the Acton Reactive framework
///
/// This struct contains all configurable values for the Acton framework,
/// loaded from TOML files in XDG-compliant directories.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
#[derive(Default)]
pub struct ActonConfig {
    /// Timeout configuration
    pub timeouts: TimeoutConfig,
    /// Limits and capacity configuration
    pub limits: LimitsConfig,
    /// Default values configuration
    pub defaults: DefaultsConfig,
    /// Tracing and logging configuration
    pub tracing: TracingConfig,
    /// Path configuration for various directories
    pub paths: PathsConfig,
    /// Behavioral configuration switches
    pub behavior: BehaviorConfig,
}

/// Timeout-related configuration values (all values in milliseconds)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeoutConfig {
    /// Default actor shutdown timeout in milliseconds
    pub actor_shutdown: u64,
    /// Default system-wide shutdown timeout in milliseconds
    pub system_shutdown: u64,
    /// Maximum wait time before flushing read-only handler futures (in milliseconds)
    pub read_only_handler_flush: u64,
}

/// Limits and capacity configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LimitsConfig {
    /// Maximum concurrent read-only handlers before forced flush
    pub concurrent_handlers_high_water_mark: usize,
    /// Default MPSC channel size for actor message inbox
    pub actor_inbox_capacity: usize,
    /// Dummy channel size for closed/default channels
    pub dummy_channel_size: usize,
}

/// Default configuration values
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DefaultsConfig {
    /// Default actor name when none provided
    pub actor_name: String,
    /// Default root Ern identifier
    pub root_ern: String,
}

/// Tracing and logging configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TracingConfig {
    /// Debug verbosity setting
    pub debug: String,
    /// Trace verbosity setting
    pub trace: String,
    /// Info verbosity setting
    pub info: String,
}

/// Path configuration for various directories
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PathsConfig {
    /// Path to log files directory
    pub logs: String,
    /// Path to cache files directory
    pub cache: String,
    /// Path to data files directory
    pub data: String,
    /// Path to configuration files directory
    pub config: String,
}

/// Behavioral configuration switches
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BehaviorConfig {
    /// Enable tracing
    pub enable_tracing: bool,
    /// Enable metrics collection
    pub enable_metrics: bool,
}

impl Default for TimeoutConfig {
    fn default() -> Self {
        Self {
            actor_shutdown: 10_000,
            system_shutdown: 30_000,
            read_only_handler_flush: 10,
        }
    }
}

impl Default for LimitsConfig {
    fn default() -> Self {
        Self {
            concurrent_handlers_high_water_mark: 100,
            actor_inbox_capacity: 512,
            dummy_channel_size: 1,
        }
    }
}

impl Default for DefaultsConfig {
    fn default() -> Self {
        Self {
            actor_name: "actor".to_string(),
            root_ern: "default".to_string(),
        }
    }
}

impl Default for TracingConfig {
    fn default() -> Self {
        Self {
            debug: "debug".to_string(),
            trace: "trace".to_string(),
            info: "info".to_string(),
        }
    }
}

impl Default for PathsConfig {
    fn default() -> Self {
        Self {
            logs: "~/.local/share/acton/logs".to_string(),
            cache: "~/.cache/acton".to_string(),
            data: "~/.local/share/acton".to_string(),
            config: "~/.config/acton".to_string(),
        }
    }
}

impl Default for BehaviorConfig {
    fn default() -> Self {
        Self {
            enable_tracing: true,
            enable_metrics: false,
        }
    }
}

impl ActonConfig {
    /// Convert system shutdown timeout to Duration
    pub const fn system_shutdown_timeout(&self) -> Duration {
        Duration::from_millis(self.timeouts.system_shutdown)
    }

    /// Load configuration from XDG-compliant locations
    ///
    /// This function attempts to load configuration from the following locations
    /// in order of preference:
    /// 1. `$XDG_CONFIG_HOME/acton/config.toml` (Linux/macOS)
    /// 2. `~/.config/acton/config.toml` (Linux fallback)
    /// 3. `~/Library/Application Support/acton/config.toml` (macOS fallback)
    /// 4. `%APPDATA%/acton/config.toml` (Windows)
    ///
    /// If no configuration file is found, returns the default configuration.
    /// If a configuration file exists but is malformed, logs an error and uses defaults.
    pub fn load() -> Self {
        use tracing::{error, info};

        // Get the XDG base directories
        let xdg_dirs = match xdg::BaseDirectories::with_prefix("acton") {
            Ok(dirs) => dirs,
            Err(e) => {
                error!("Failed to initialize XDG directories: {}", e);
                return Self::default();
            }
        };

        // Try to find the configuration file
        xdg_dirs.find_config_file("config.toml").map_or_else(
            || {
                info!("No configuration file found, using defaults");
                Self::default()
            },
            |path| {
                info!("Loading configuration from: {}", path.display());
                match std::fs::read_to_string(&path) {
                    Ok(config_str) => match toml::from_str::<Self>(&config_str) {
                        Ok(config) => {
                            info!("Successfully loaded configuration");
                            config
                        }
                        Err(e) => {
                            error!(
                                "Failed to parse configuration file {}: {}",
                                path.display(),
                                e
                            );
                            Self::default()
                        }
                    },
                    Err(e) => {
                        error!(
                            "Failed to read configuration file {}: {}",
                            path.display(),
                            e
                        );
                        Self::default()
                    }
                }
            },
        )
    }
}

/// Global configuration instance loaded from XDG-compliant locations
pub static CONFIG: LazyLock<ActonConfig> = LazyLock::new(ActonConfig::load);