# Best Practices for paft Extensible Enums

This document provides specific best practices for working with paft's extensible enum pattern, focusing on practical implementation strategies for library authors and consumers.

## Table of Contents

1. [Consumer Best Practices](#consumer-best-practices)
2. [Library Author Best Practices](#library-author-best-practices)
3. [Provider Adapter Patterns](#provider-adapter-patterns)
4. [Migration Strategies](#migration-strategies)
5. [Performance Considerations](#performance-considerations)
6. [Testing Strategies](#testing-strategies)

## Consumer Best Practices

### 1. Always Handle Other Variants

**❌ Compilation Error:**
```rust
use paft_money::{Currency, IsoCurrency};

fn process_currency(currency: Currency) -> &'static str {
    match currency {
        Currency::Iso(IsoCurrency::USD) => "US Dollar",
        Currency::Iso(IsoCurrency::EUR) => "Euro",
        // Missing Other variant - compiler error!
    }
}
```

**✅ Correct Approach:**
```rust
use paft_money::{Currency, IsoCurrency};

fn process_currency(currency: Currency) -> String {
    match currency {
        Currency::Iso(IsoCurrency::USD) => "US Dollar".to_string(),
        Currency::Iso(IsoCurrency::EUR) => "Euro".to_string(),
        Currency::Other(code) => format!("Unknown currency: {}", code),
    }
}
```

### 2. Use Helper Methods When Available

Many paft enums provide helper methods to check for canonical variants:

```rust
fn analyze_asset(asset: AssetKind) {
    match asset {
        AssetKind::Equity => println!("Stock analysis"),
        AssetKind::Crypto => println!("Crypto analysis"),
        _ => println!("General analysis"),
    }
}
```

### 3. Create Mapping Functions for Common Cases

```rust
/// Maps provider-specific currency codes to canonical variants.
/// Never produce Other for values we model canonically.
fn normalize_currency(provider_code: &str) -> Currency {
    match provider_code.to_uppercase().as_ref() {
        // Map to canonical variants when possible
        "DOLLAR" | "US_DOLLAR" | "USD" => Currency::Iso(IsoCurrency::USD),
        "EURO" | "EUR" => Currency::Iso(IsoCurrency::EUR),
        "POUND" | "GBP" => Currency::Iso(IsoCurrency::GBP),

        // Map common cryptos to canonical variants when available
        "BITCOIN" | "XBT" | "BTC" => Currency::BTC,
        "ETHEREUM" | "ETH" => Currency::ETH,

        // Preserve other values using the library parser
        _ => Currency::try_from_str(provider_code)
            .unwrap_or_else(|_| Currency::Other(paft_utils::Canonical::try_new(provider_code).unwrap())),
    }
}
```

### 4. Use Default Values Appropriately

```rust
fn get_currency_info(currency: Currency) -> CurrencyInfo {
    match currency {
        Currency::Iso(IsoCurrency::USD) => CurrencyInfo::usd(),
        Currency::Iso(IsoCurrency::EUR) => CurrencyInfo::euro(),
        Currency::Other(code) => {
            // Log unknown currency for monitoring
            log::warn!("Unknown currency encountered: {}", code);
            
            // Return a generic currency info
            CurrencyInfo::generic(code)
        }
    }
}
```

## Library Author Best Practices

### 1. Encourage Ecosystem Convergence

When building libraries on top of paft, map provider-specific strings to canonical variants whenever possible:

```rust
// In your provider adapter
impl From<GenericProviderCurrency> for Currency {
    fn from(gp_currency: GenericProviderCurrency) -> Self {
        match gp_currency.code.as_ref() {
            // Map to canonical variants
            "USD" => Currency::Iso(IsoCurrency::USD),
            "EUR" => Currency::Iso(IsoCurrency::EUR),
            "GBP" => Currency::Iso(IsoCurrency::GBP),
            
            // Normalize crypto currencies
            "BTC" | "BITCOIN" => Currency::BTC,
            "ETH" | "ETHEREUM" => Currency::ETH,
            
            // Preserve other values
            other => Currency::try_from_str(other)
                .unwrap_or_else(|_| Currency::Other(other.trim().to_uppercase())),
        }
    }
}
```

### 2. Provide Conversion Utilities

```rust
pub mod currency_utils {
    use super::Currency;
    use paft_money::IsoCurrency;

    /// Attempts to normalize a currency code to a canonical variant
    pub fn normalize_currency_code(code: &str) -> Currency {
        let trimmed = code.trim();
        if trimmed.is_empty() {
            return Currency::Other(paft_utils::Canonical::try_new("UNKNOWN").unwrap());
        }
        match trimmed.to_uppercase().as_ref() {
            "DOLLAR" | "US_DOLLAR" | "USD" => Currency::Iso(IsoCurrency::USD),
            "EURO" | "EUR" => Currency::Iso(IsoCurrency::EUR),
            "POUND" | "GBP" => Currency::Iso(IsoCurrency::GBP),
            "BITCOIN" | "XBT" | "BTC" => Currency::BTC,
            "ETHEREUM" | "ETH" => Currency::ETH,
            other => Currency::Other(paft_utils::Canonical::try_new(other).unwrap()),
        }
    }

    /// Returns true if the currency is commonly used
    pub fn is_common_currency(currency: &Currency) -> bool {
        matches!(
            currency,
            Currency::Iso(IsoCurrency::USD)
                | Currency::Iso(IsoCurrency::EUR)
                | Currency::Iso(IsoCurrency::GBP)
                | Currency::Iso(IsoCurrency::JPY)
                | Currency::BTC
                | Currency::ETH
        )
    }
}
```

### 3. Document Your Mappings

```rust
/// Currency mapping for Alpha Vantage provider
/// 
/// Known mappings:
/// - "US_DOLLAR" -> Currency::Iso(IsoCurrency::USD) (canonical)
/// - "EURO" -> Currency::Iso(IsoCurrency::EUR) (canonical)
/// - "BITCOIN" -> BTC (canonical)
/// 
/// Unmapped values are preserved as Other(Canonical) in uppercase
impl From<AlphaVantageCurrency> for Currency {
    fn from(av_currency: AlphaVantageCurrency) -> Self {
        match av_currency.code.to_uppercase().as_ref() {
            "US_DOLLAR" => Currency::Iso(IsoCurrency::USD),
            "EURO" => Currency::Iso(IsoCurrency::EUR),
            "BITCOIN" => Currency::BTC,
            _ => Currency::Other(paft_utils::Canonical::try_new(&av_currency.code).unwrap()),
        }
    }
}
```

### 4. Add Validation and Logging

```rust
pub struct CurrencyProcessor {
    unknown_currencies: std::collections::HashSet<String>,
}

impl CurrencyProcessor {
    pub fn process_currency(&mut self, currency: Currency) -> Result<CurrencyInfo, Error> {
        match currency {
            Currency::Iso(IsoCurrency::USD) => Ok(CurrencyInfo::usd()),
            Currency::Iso(IsoCurrency::EUR) => Ok(CurrencyInfo::euro()),
            Currency::Other(code) => {
                // Track unknown currencies for analysis
                self.unknown_currencies.insert(code.as_ref().to_string());
                
                // Log for monitoring
                log::warn!("Unknown currency encountered: {}", code);
                
                // Return error or generic info based on your needs
                Err(Error::UnsupportedCurrency(code.as_ref().to_string()))
            }
        }
    }
    
    /// Get statistics about unknown currencies encountered
    pub fn unknown_currency_stats(&self) -> &std::collections::HashSet<String> {
        &self.unknown_currencies
    }
}
```

## Provider Adapter Patterns

### 1. Comprehensive Mapping Strategy

```rust
pub struct ProviderAdapter {
    currency_mappings: std::collections::HashMap<String, Currency>,
    exchange_mappings: std::collections::HashMap<String, Exchange>,
}

impl ProviderAdapter {
    pub fn new() -> Self {
        let mut currency_mappings = std::collections::HashMap::new();
        let mut exchange_mappings = std::collections::HashMap::new();
        
        // Populate mappings
        currency_mappings.insert("DOLLAR".to_string(), Currency::Iso(IsoCurrency::USD));
        currency_mappings.insert("EURO".to_string(), Currency::Iso(IsoCurrency::EUR));
        currency_mappings.insert("BITCOIN".to_string(), Currency::BTC);
        
        exchange_mappings.insert("NASDAQ-GS".to_string(), Exchange::NASDAQ);
        exchange_mappings.insert("NYSE-ARCA".to_string(), Exchange::NYSE);
        
        Self {
            currency_mappings,
            exchange_mappings,
        }
    }
    
    pub fn normalize_currency(&self, provider_code: &str) -> Currency {
        self.currency_mappings
            .get(&provider_code.to_uppercase())
            .cloned()
            .unwrap_or_else(|| Currency::Other(paft_utils::Canonical::try_new(provider_code).unwrap()))
    }
    
    pub fn normalize_exchange(&self, provider_code: &str) -> Exchange {
        self.exchange_mappings
            .get(&provider_code.to_uppercase())
            .cloned()
            .unwrap_or_else(|| Exchange::Other(paft_utils::Canonical::try_new(provider_code).unwrap()))
    }
}
```

### 2. Lazy Loading Pattern

```rust
pub struct LazyProviderAdapter {
    mappings: std::sync::OnceLock<std::collections::HashMap<String, Currency>>,
}

impl LazyProviderAdapter {
    fn get_mappings(&self) -> &std::collections::HashMap<String, Currency> {
        self.mappings.get_or_init(|| {
            let mut mappings = std::collections::HashMap::new();
            mappings.insert("DOLLAR".to_string(), Currency::Iso(IsoCurrency::USD));
            mappings.insert("EURO".to_string(), Currency::Iso(IsoCurrency::EUR));
            mappings.insert("BITCOIN".to_string(), Currency::BTC);
            mappings
        })
    }
    
    pub fn normalize_currency(&self, provider_code: &str) -> Currency {
        self.get_mappings()
            .get(&provider_code.to_uppercase())
            .cloned()
            .unwrap_or_else(|| Currency::Other(paft_utils::Canonical::try_new(provider_code).unwrap()))
    }
}
```

## Migration Strategies

### 1. Gradual Migration from String-based Code

**Before (string-based):**
```rust
fn process_currency(currency_code: &str) -> String {
    match currency_code {
        "USD" => "US Dollar".to_string(),
        "EUR" => "Euro".to_string(),
        _ => format!("Unknown: {}", currency_code),
    }
}
```

**After (paft-based):**
```rust
fn process_currency(currency: Currency) -> String {
    match currency {
        Currency::Iso(IsoCurrency::USD) => "US Dollar".to_string(),
        Currency::Iso(IsoCurrency::EUR) => "Euro".to_string(),
        Currency::Other(code) => format!("Unknown: {}", code),
    }
}

// Migration helper
fn migrate_currency_code(code: &str) -> Currency {
    match code {
        "USD" => Currency::Iso(IsoCurrency::USD),
        "EUR" => Currency::Iso(IsoCurrency::EUR),
        _ => Currency::Other(paft_utils::Canonical::try_new(code).unwrap()),
    }
}
```

### 2. Backward Compatibility Layer

```rust
pub struct CurrencyCompat {
    currency: Currency,
}

impl CurrencyCompat {
    pub fn from_string(code: &str) -> Self {
        Self {
            currency: Currency::try_from_str(code)
                .unwrap_or_else(|_| Currency::Other(paft_utils::Canonical::try_new(code).unwrap())),
        }
    }
    
    pub fn to_string(&self) -> String {
        self.currency.to_string()
    }
    
    pub fn is_known(&self) -> bool {
        !matches!(self.currency, Currency::Other(_))
    }
}
```

## Performance Considerations

### 1. Avoid Repeated String Allocations

**❌ Inefficient:**
```rust
fn process_currencies(currencies: Vec<Currency>) {
    for currency in currencies {
        match currency {
            Currency::Other(code) => {
                // Creates new string allocation
                let upper_code = code.to_uppercase();
                println!("Processing: {}", upper_code);
            }
            _ => {}
        }
    }
}
```

**✅ Efficient:**
```rust
fn process_currencies(currencies: Vec<Currency>) {
    for currency in currencies {
        match currency {
            Currency::Other(code) => {
                // Use the string directly
                println!("Processing: {}", code);
            }
            _ => {}
        }
    }
}
```

### 2. Use String Interning for Common Other Values

```rust
use std::sync::OnceLock;

pub struct CurrencyCache {
    btc: OnceLock<Currency>,
    eth: OnceLock<Currency>,
}

impl CurrencyCache {
    pub fn btc() -> Currency {
        static CACHE: OnceLock<Currency> = OnceLock::new();
        *CACHE.get_or_init(|| Currency::Other("BTC".to_string()))
    }
    
    pub fn eth() -> Currency {
        static CACHE: OnceLock<Currency> = OnceLock::new();
        *CACHE.get_or_init(|| Currency::Other("ETH".to_string()))
    }
}
```

## Testing Strategies

### 1. Test Both Canonical and Other Variants

```rust
#[cfg(test)]
mod tests {
    use super::*;
    use paft_money::IsoCurrency;
    
    #[test]
    fn test_currency_processing() {
        // Test canonical variants
        assert_eq!(process_currency(Currency::Iso(IsoCurrency::USD)), "US Dollar");
        assert_eq!(process_currency(Currency::Iso(IsoCurrency::EUR)), "Euro");
        
        // Test Other variants
        assert_eq!(
            process_currency(Currency::Other("BTC".to_string())),
            "Unknown currency: BTC"
        );
        assert_eq!(
            process_currency(Currency::Other("UNKNOWN".to_string())),
            "Unknown currency: UNKNOWN"
        );
    }
    
    #[test]
    fn test_currency_normalization() {
        assert_eq!(normalize_currency("DOLLAR"), Currency::Iso(IsoCurrency::USD));
        assert_eq!(normalize_currency("BITCOIN"), Currency::BTC);
        assert_eq!(normalize_currency("UNKNOWN"), Currency::Other("UNKNOWN".to_string()));
    }
}
```

### 2. Property-based Testing

```rust
use proptest::prelude::*;

proptest! {
    #[test]
    fn test_currency_roundtrip(currency in any::<Currency>()) {
        let string_repr = currency.to_string();
        let parsed = Currency::try_from_str(&string_repr).unwrap();
        assert_eq!(currency, parsed);
    }
    
    #[test]
    fn test_currency_normalization_preserves_other(
        code in "[A-Z]{1,10}"
    ) {
        let currency = Currency::Other(code.clone());
        assert_eq!(currency.to_string(), code);
    }
}
```

### 3. Integration Testing with Real Provider Data

```rust
#[test]
fn test_generic_provider_currency_mapping() {
    let test_cases = vec![
        ("USD", Currency::Iso(IsoCurrency::USD)),
        ("EUR", Currency::Iso(IsoCurrency::EUR)),
        ("BTC", Currency::BTC),
        ("UNKNOWN", Currency::Other("UNKNOWN".to_string())),
    ];
    
    for (provider_code, expected) in test_cases {
        let result = normalize_generic_provider_currency(provider_code);
        assert_eq!(result, expected, "Failed for provider code: {}", provider_code);
    }
}
```

## Conclusion

The extensible enum pattern in paft provides a robust foundation for handling the fragmented nature of financial data providers. By following these best practices:

1. **Always handle Other variants** in your match statements
2. **Map provider-specific strings** to canonical variants when possible
3. **Use helper methods** like `is_canonical()` for cleaner code
4. **Document your mappings** for future maintainers
5. **Test both canonical and Other variants** thoroughly
6. **Consider performance implications** of string operations

You can build reliable, maintainable financial applications that work seamlessly across different data providers while encouraging ecosystem convergence over time.
