zero-trust-sdk 0.1.0

Rust SDK for Zero Trust Blockchain Database - Secure, programmable access to zero-trust data storage
Documentation
//! Configuration management for the Zero Trust SDK

use crate::error::{Result, ZeroTrustError};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::time::Duration;
use url::Url;

/// Configuration for the Zero Trust SDK
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
    /// API base URL
    pub api_url: String,
    /// Authentication token (JWT)
    pub token: Option<String>,
    /// Request timeout duration
    pub timeout: Duration,
    /// Maximum retry attempts
    pub max_retries: u32,
    /// User agent string
    pub user_agent: String,
    /// Whether to verify SSL certificates
    pub verify_ssl: bool,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            api_url: "http://localhost:3000".to_string(),
            token: None,
            timeout: Duration::from_secs(30),
            max_retries: 3,
            user_agent: format!("zero-trust-sdk/{}", env!("CARGO_PKG_VERSION")),
            verify_ssl: true,
        }
    }
}

impl Config {
    /// Create a new configuration with the given API URL
    ///
    /// # Arguments
    /// 
    /// * `api_url` - The base URL for the Zero Trust API
    ///
    /// # Examples
    ///
    /// ```rust
    /// use zero_trust_sdk::Config;
    ///
    /// let config = Config::new("https://api.zerotrust.com").unwrap();
    /// assert_eq!(config.api_url, "https://api.zerotrust.com");
    /// ```
    pub fn new<S: AsRef<str>>(api_url: S) -> Result<Self> {
        let api_url = api_url.as_ref();
        
        // Validate URL
        Url::parse(api_url)
            .map_err(|e| ZeroTrustError::config(format!("Invalid API URL '{}': {}", api_url, e)))?;
        
        Ok(Self {
            api_url: api_url.to_string(),
            ..Default::default()
        })
    }
    
    /// Set the authentication token
    ///
    /// # Arguments
    /// 
    /// * `token` - JWT authentication token
    ///
    /// # Examples
    ///
    /// ```rust
    /// use zero_trust_sdk::Config;
    ///
    /// let config = Config::new("https://api.zerotrust.com")
    ///     .unwrap()
    ///     .with_token("eyJ0eXAiOiJKV1Q...");
    /// ```
    pub fn with_token<S: Into<String>>(mut self, token: S) -> Self {
        self.token = Some(token.into());
        self
    }
    
    /// Set the request timeout
    ///
    /// # Arguments
    /// 
    /// * `timeout` - Request timeout duration
    ///
    /// # Examples
    ///
    /// ```rust
    /// use zero_trust_sdk::Config;
    /// use std::time::Duration;
    ///
    /// let config = Config::new("https://api.zerotrust.com")
    ///     .unwrap()
    ///     .with_timeout(Duration::from_secs(60));
    /// ```
    pub fn with_timeout(mut self, timeout: Duration) -> Self {
        self.timeout = timeout;
        self
    }
    
    /// Set the maximum retry attempts
    ///
    /// # Arguments
    /// 
    /// * `max_retries` - Maximum number of retry attempts
    ///
    /// # Examples
    ///
    /// ```rust
    /// use zero_trust_sdk::Config;
    ///
    /// let config = Config::new("https://api.zerotrust.com")
    ///     .unwrap()
    ///     .with_max_retries(5);
    /// ```
    pub fn with_max_retries(mut self, max_retries: u32) -> Self {
        self.max_retries = max_retries;
        self
    }
    
    /// Set the user agent string
    ///
    /// # Arguments
    /// 
    /// * `user_agent` - User agent string for HTTP requests
    ///
    /// # Examples
    ///
    /// ```rust
    /// use zero_trust_sdk::Config;
    ///
    /// let config = Config::new("https://api.zerotrust.com")
    ///     .unwrap()
    ///     .with_user_agent("my-app/1.0");
    /// ```
    pub fn with_user_agent<S: Into<String>>(mut self, user_agent: S) -> Self {
        self.user_agent = user_agent.into();
        self
    }
    
    /// Disable SSL verification (for testing only)
    ///
    /// # Examples
    ///
    /// ```rust
    /// use zero_trust_sdk::Config;
    ///
    /// let config = Config::new("https://api.zerotrust.com")
    ///     .unwrap()
    ///     .disable_ssl_verification();
    /// ```
    pub fn disable_ssl_verification(mut self) -> Self {
        self.verify_ssl = false;
        self
    }
    
    /// Load configuration from environment variables
    ///
    /// Environment variables:
    /// - `ZEROTRUST_API_URL` - API base URL
    /// - `ZEROTRUST_TOKEN` - Authentication token
    /// - `ZEROTRUST_TIMEOUT` - Request timeout in seconds
    /// - `ZEROTRUST_MAX_RETRIES` - Maximum retry attempts
    ///
    /// # Examples
    ///
    /// ```rust
    /// use zero_trust_sdk::Config;
    ///
    /// // Set environment variable: ZEROTRUST_API_URL=https://api.zerotrust.com
    /// let config = Config::from_env().unwrap();
    /// ```
    pub fn from_env() -> Result<Self> {
        let api_url = std::env::var("ZEROTRUST_API_URL")
            .unwrap_or_else(|_| "http://localhost:3000".to_string());
        
        let mut config = Self::new(api_url)?;
        
        // Load token from environment
        if let Ok(token) = std::env::var("ZEROTRUST_TOKEN") {
            config.token = Some(token);
        }
        
        // Load timeout from environment
        if let Ok(timeout_str) = std::env::var("ZEROTRUST_TIMEOUT") {
            if let Ok(timeout_secs) = timeout_str.parse::<u64>() {
                config.timeout = Duration::from_secs(timeout_secs);
            }
        }
        
        // Load max retries from environment
        if let Ok(retries_str) = std::env::var("ZEROTRUST_MAX_RETRIES") {
            if let Ok(max_retries) = retries_str.parse::<u32>() {
                config.max_retries = max_retries;
            }
        }
        
        Ok(config)
    }
    
    /// Load configuration from a TOML file
    ///
    /// # Arguments
    /// 
    /// * `path` - Path to the TOML configuration file
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use zero_trust_sdk::Config;
    /// use std::path::Path;
    ///
    /// let config = Config::from_file(Path::new("config.toml")).unwrap();
    /// ```
    pub fn from_file(path: &std::path::Path) -> Result<Self> {
        let content = std::fs::read_to_string(path)?;
        let config: Self = toml::from_str(&content)
            .map_err(|e| ZeroTrustError::config(format!("Invalid config file: {}", e)))?;
        
        // Validate the loaded config
        config.validate()?;
        
        Ok(config)
    }
    
    /// Save configuration to a TOML file
    ///
    /// # Arguments
    /// 
    /// * `path` - Path where to save the configuration file
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use zero_trust_sdk::Config;
    /// use std::path::Path;
    ///
    /// let config = Config::new("https://api.zerotrust.com").unwrap();
    /// config.save_to_file(Path::new("config.toml")).unwrap();
    /// ```
    pub fn save_to_file(&self, path: &std::path::Path) -> Result<()> {
        let content = toml::to_string_pretty(self)
            .map_err(|e| ZeroTrustError::config(format!("Failed to serialize config: {}", e)))?;
        
        // Create parent directories if they don't exist
        if let Some(parent) = path.parent() {
            std::fs::create_dir_all(parent)?;
        }
        
        std::fs::write(path, content)?;
        Ok(())
    }
    
    /// Get the default config file path
    ///
    /// Returns `~/.config/zerotrust/config.toml` on Unix systems
    /// or `%APPDATA%/zerotrust/config.toml` on Windows
    pub fn default_config_path() -> Result<PathBuf> {
        let config_dir = dirs::config_dir()
            .ok_or_else(|| ZeroTrustError::config("Could not find config directory"))?;
        
        Ok(config_dir.join("zerotrust").join("config.toml"))
    }
    
    /// Load configuration from the default config file path
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use zero_trust_sdk::Config;
    ///
    /// let config = Config::load_default().unwrap();
    /// ```
    pub fn load_default() -> Result<Self> {
        let config_path = Self::default_config_path()?;
        
        if config_path.exists() {
            Self::from_file(&config_path)
        } else {
            // Try loading from environment variables if no config file exists
            Self::from_env()
        }
    }
    
    /// Save configuration to the default config file path
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use zero_trust_sdk::Config;
    ///
    /// let config = Config::new("https://api.zerotrust.com").unwrap();
    /// config.save_default().unwrap();
    /// ```
    pub fn save_default(&self) -> Result<()> {
        let config_path = Self::default_config_path()?;
        self.save_to_file(&config_path)
    }
    
    /// Validate the configuration
    pub fn validate(&self) -> Result<()> {
        // Validate API URL
        Url::parse(&self.api_url)
            .map_err(|e| ZeroTrustError::config(format!("Invalid API URL: {}", e)))?;
        
        // Validate timeout
        if self.timeout.as_secs() == 0 {
            return Err(ZeroTrustError::config("Timeout must be greater than 0"));
        }
        
        // Validate user agent
        if self.user_agent.is_empty() {
            return Err(ZeroTrustError::config("User agent cannot be empty"));
        }
        
        Ok(())
    }
    
    /// Get the base URL for API requests
    pub fn base_url(&self) -> &str {
        &self.api_url
    }
    
    /// Check if authentication token is present
    pub fn is_authenticated(&self) -> bool {
        self.token.is_some()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::time::Duration;
    
    #[test]
    fn test_config_creation() {
        let config = Config::new("https://api.zerotrust.com").unwrap();
        assert_eq!(config.api_url, "https://api.zerotrust.com");
        assert!(!config.is_authenticated());
        assert!(config.verify_ssl);
    }
    
    #[test]
    fn test_config_builder() {
        let config = Config::new("https://api.zerotrust.com")
            .unwrap()
            .with_token("test-token")
            .with_timeout(Duration::from_secs(60))
            .with_max_retries(5)
            .with_user_agent("test-app/1.0")
            .disable_ssl_verification();
        
        assert_eq!(config.token, Some("test-token".to_string()));
        assert_eq!(config.timeout, Duration::from_secs(60));
        assert_eq!(config.max_retries, 5);
        assert_eq!(config.user_agent, "test-app/1.0");
        assert!(!config.verify_ssl);
        assert!(config.is_authenticated());
    }
    
    #[test]
    fn test_invalid_url() {
        let result = Config::new("invalid-url");
        assert!(result.is_err());
    }
    
    #[test]
    fn test_validation() {
        let config = Config::new("https://api.zerotrust.com").unwrap();
        assert!(config.validate().is_ok());
        
        let mut invalid_config = config.clone();
        invalid_config.timeout = Duration::from_secs(0);
        assert!(invalid_config.validate().is_err());
    }
}