koios-sdk 0.1.1

A Rust SDK for the Koios Cardano API
Documentation
// src/client/config.rs

use crate::network::Network;
use governor::Quota;
use std::time::Duration;

/// Authentication configuration for the client
#[derive(Debug, Clone)]
pub struct AuthConfig {
    /// JWT Bearer token
    pub token: String,
    /// Optional expiry time for the token
    pub expiry: Option<chrono::DateTime<chrono::Utc>>,
}

impl AuthConfig {
    /// Create a new auth configuration with just a token
    pub fn new(token: String) -> Self {
        Self {
            token,
            expiry: None,
        }
    }

    /// Create a new auth configuration with token and expiry
    pub fn with_expiry(token: String, expiry: chrono::DateTime<chrono::Utc>) -> Self {
        Self {
            token,
            expiry: Some(expiry),
        }
    }

    /// Check if the token is still valid
    pub fn is_valid(&self) -> bool {
        match self.expiry {
            Some(expiry) => chrono::Utc::now() < expiry,
            None => true,
        }
    }
}

/// Configuration options for the Koios client
#[derive(Debug, Clone)]
pub struct Config {
    /// Network to connect to
    pub network: Network,
    /// Optional custom base URL (overrides network URL)
    pub custom_url: Option<String>,
    /// Authentication configuration (if required)
    pub auth: Option<AuthConfig>,
    /// Request timeout
    pub timeout: Duration,
    /// Rate limiting quota
    pub rate_limit: Quota,
}

impl Config {
    /// Create a new configuration with custom settings
    pub fn new(
        network: Network,
        custom_url: Option<String>,
        auth: Option<AuthConfig>,
        timeout: Duration,
        rate_limit: Quota,
    ) -> Self {
        Self {
            network,
            custom_url,
            auth,
            timeout,
            rate_limit,
        }
    }

    /// Get the base URL to use
    pub fn base_url(&self) -> String {
        self.custom_url
            .clone()
            .unwrap_or_else(|| self.network.base_url().to_string())
    }

    /// Add authentication to the configuration
    pub fn with_auth(mut self, token: String) -> Self {
        self.auth = Some(AuthConfig::new(token));
        self
    }

    /// Add authentication with expiry to the configuration
    pub fn with_auth_expiry(
        mut self,
        token: String,
        expiry: chrono::DateTime<chrono::Utc>,
    ) -> Self {
        self.auth = Some(AuthConfig::with_expiry(token, expiry));
        self
    }

    /// Set request timeout
    pub fn with_timeout(mut self, timeout: Duration) -> Self {
        self.timeout = timeout;
        self
    }

    /// Set rate limit quota
    pub fn with_rate_limit(mut self, rate_limit: Quota) -> Self {
        self.rate_limit = rate_limit;
        self
    }
}

impl Default for Config {
    fn default() -> Self {
        Self {
            network: Network::default(),
            custom_url: None,
            auth: None,
            timeout: Duration::from_secs(30),
            rate_limit: Quota::per_second(100u32.try_into().unwrap()),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::Utc;
    use std::time::Duration;

    #[test]
    fn test_default_config() {
        let config = Config::default();
        assert_eq!(config.network, Network::Mainnet);
        assert!(config.custom_url.is_none());
        assert!(config.auth.is_none());
        assert_eq!(config.timeout, Duration::from_secs(30));
    }

    #[test]
    fn test_config_builder_pattern() {
        let config = Config::default()
            .with_auth("test-token".to_string())
            .with_timeout(Duration::from_secs(60))
            .with_rate_limit(Quota::per_second(50u32.try_into().unwrap()));

        assert!(config.auth.is_some());
        assert_eq!(config.timeout, Duration::from_secs(60));
    }

    #[test]
    fn test_auth_config_validation() {
        // Test without expiry
        let auth = AuthConfig::new("test-token".to_string());
        assert!(auth.is_valid());

        // Test with future expiry
        let future = Utc::now() + chrono::Duration::hours(1);
        let auth = AuthConfig::with_expiry("test-token".to_string(), future);
        assert!(auth.is_valid());

        // Test with past expiry
        let past = Utc::now() - chrono::Duration::hours(1);
        let auth = AuthConfig::with_expiry("test-token".to_string(), past);
        assert!(!auth.is_valid());
    }

    #[test]
    fn test_config_base_url() {
        // Test default network URL
        let config = Config::default();
        assert_eq!(config.base_url(), Network::Mainnet.base_url());

        // Test custom URL
        let custom_url = "https://custom.api.com".to_string();
        let config = Config::new(
            Network::Mainnet,
            Some(custom_url.clone()),
            None,
            Duration::from_secs(30),
            Quota::per_second(100u32.try_into().unwrap()),
        );
        assert_eq!(config.base_url(), custom_url);

        // Test network URL
        let config = Config::new(
            Network::Preprod,
            None,
            None,
            Duration::from_secs(30),
            Quota::per_second(100u32.try_into().unwrap()),
        );
        assert_eq!(config.base_url(), Network::Preprod.base_url());
    }

    #[test]
    fn test_config_with_auth_expiry() {
        let expiry = Utc::now() + chrono::Duration::hours(1);
        let config = Config::default().with_auth_expiry("test-token".to_string(), expiry);

        assert!(config.auth.is_some());
        let auth = config.auth.unwrap();
        assert_eq!(auth.token, "test-token");
        assert!(auth.expiry.is_some());
        assert!(auth.is_valid());
    }
}