zakat 0.5.0

A type-safe, comprehensive, and Fiqh-compliant Zakat calculation library. Supports Gold, Silver, Business, Agriculture, Livestock, Professional Income, and Modern Assets (Crypto/Stocks).
Documentation
███████╗ █████╗ ██╗  ██╗ █████╗ ████████╗
╚══███╔╝██╔══██╗██║ ██╔╝██╔══██╗╚══██╔══╝
  ███╔╝ ███████║█████╔╝ ███████║   ██║   
 ███╔╝  ██╔══██║██╔═██╗ ██╔══██║   ██║   
███████╗██║  ██║██║  ██╗██║  ██║   ██║   
╚══════╝╚═╝  ╚═╝╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝   

Zakat

Crates.io Docs.rs License: MIT

Rust library for Islamic Zakat calculation. Uses rust_decimal for precision.

Features

  • Gold, Silver, Business, Agriculture, Livestock, Mining & Rikaz
  • Stocks, Mutual Funds, Crypto (as liquid assets)
  • Professional Income (Gross/Net)
  • Zakat Fitrah
  • Configurable Nisab thresholds
  • Portfolio aggregation (Dam' al-Amwal)
  • Asset Labeling (e.g., "Main Store", "Crypto Wallet")
  • Input Sanitization & Validation (Rejects negative values, ensures safe configuration)
  • Flexible Configuration (Env Vars, JSON, Fluent API)
  • Fiqh Compliance (Jewelry exemptions, Madhab-specific rules, Hawl requirements)
  • Async Support (Optional integration with tokio and async-trait)
  • Live Pricing Interface (e.g. for API integration)
  • Detailed Reporting (Livestock in-kind details, calculation traces, metadata support)
  • [NEW] explain() Debugging (Get human-readable trace of calculations)

Install

With Async Support (Default):

[dependencies]

zakat = "0.5"

rust_decimal = "1.39"

tokio = { version = "1", features = ["full"] } # Required if using async features

Synchronous Only (Lighter weight):

[dependencies]

zakat = { version = "0.5", default-features = false }

rust_decimal = "1.39"

Usage

Business Zakat

Note: v0.5 uses a new Fluent API. No more Builders! You can pass standard Rust types (i32, f64, &str) directly.

use zakat::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = ZakatConfig::new()
        .with_gold_price(65)  // $65/g
        .with_silver_price(1); // $1/g

    // Fluent API: Infallible construction, chained setters
    let store = BusinessZakat::new()
        .cash(10_000)
        .inventory(50_000)
        .label("Main Store")
        .hawl(true);

    // Validation & Calculation happens here
    let result = store.calculate_zakat(&config)?;

    if result.is_payable {
        println!("Zakat for {}: ${}", result.label.unwrap_or_default(), result.zakat_due);
    }
    
    // NEW: Get a human-readable trace of the calculation
    println!("{}", result.explain());
    
    Ok(())
}

Advanced Usage (Complex Scenarios)

For complex scenarios involving debts and receivables:

let assets = BusinessZakat::new()
    .cash(50000)
    .inventory(20000)
    .receivables(5000)
    .liabilities(1000)
    .debt(500) // Deductible immediate debt
    .label("Tech Startup")
    .hawl(true);

Portfolio Management

Handles multiple assets with "Dam' al-Amwal" (Wealth Aggregation) logic.

use zakat::prelude::*;
use zakat::portfolio::PortfolioStatus;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = ZakatConfig::new()
        .with_gold_price(65)
        .with_silver_price(1);

    let portfolio = ZakatPortfolio::new()
        .add(IncomeZakatCalculator::new()
            .income(5000)
            .method(IncomeCalculationMethod::Gross)
            .label("Monthly Salary"))
        .add(PreciousMetals::new()
            .weight(100)
            .metal_type(WealthType::Gold)
            .label("Wife's Gold"))
        .add(InvestmentAssets::new()
            .value(20000)
            .kind(InvestmentType::Crypto)
            .debt(2000)
            .label("Binance Portfolio"));

    let result = portfolio.calculate_total(&config);
    println!("Total Zakat Due: ${}", result.total_zakat_due);
    
    // Robust error handling for partial failures
    match result.status {
        PortfolioStatus::Complete => println!("All assets calculated successfully."),
        PortfolioStatus::Partial => {
            println!("Warning: Some assets failed calculation.");
            for failure in result.failures() {
                 println!("Failed item: {:?}", failure);
            }
        }
        PortfolioStatus::Failed => println!("Critical: All asset calculations failed."),
    }

    // Iterate successful details
    for detail in result.successes() {
        if let Some(label) = &detail.label {
            println!(" - {}: ${}", label, detail.zakat_due);
        }
    }
    Ok(())
}

Async & Live Pricing (Optional)

Enable the async feature to use these capabilities.

use zakat::prelude::*;
use zakat::pricing::{PriceProvider, Prices};

struct MockApi;

#[cfg(feature = "async")]
#[async_trait::async_trait]
impl PriceProvider for MockApi {
    async fn get_prices(&self) -> Result<Prices, ZakatError> {
        // Simulate API call
        Ok(Prices::new(90.0, 1.2)?)
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    #[cfg(feature = "async")]
    {
        let api = MockApi;
        // Initialize config from provider
        let config = ZakatConfig::from_provider(&api).await?;
        
        let portfolio = AsyncZakatPortfolio::new()
            .add(BusinessZakat::new()
                .cash(10_000));
                
        let result = portfolio.calculate_total_async(&config).await;
        println!("Total Due: {}", result.total_zakat_due);
    }
    Ok(())
}

Configuration

Flexible and safe configuration options.

use zakat::prelude::*;

// Load from Environment Variables (ZAKAT_GOLD_PRICE, etc.)
let config = ZakatConfig::from_env()?;

// Or load from JSON
let config = ZakatConfig::try_from_json("config.json")?;

// Or using Fluent API
let config = ZakatConfig::new()
    .with_gold_price(100)
    .with_silver_price(1)
    .with_madhab(Madhab::Hanafi);

Advanced Assets (Jewelry & Livestock)

use zakat::prelude::*;

// Personal Jewelry (Exempt in Shafi/Maliki, Payable in Hanafi)
let necklace = PreciousMetals::new()
    .weight(100)
    .metal_type(WealthType::Gold)
    .usage(JewelryUsage::PersonalUse)
    .label("Wife's Wedding Necklace");

// Livestock Reporting
let prices = LivestockPrices::new()
    .sheep_price(200)
    .cow_price(1500)
    .camel_price(3000);
    
let camels = LivestockAssets::new()
    .count(30)
    .animal_type(LivestockType::Camel)
    .prices(prices);

let result = camels.calculate_zakat(&config)?;

if result.is_payable {
    // Access detailed "in-kind" payment info
    if let crate::types::PaymentPayload::Livestock { description, .. } = result.payload {
        println!("Pay Due: {}", description);
        // Output: "Pay Due: 1 Bint Makhad"
    }
}

Modules

Module Nisab
maal::precious_metals 85g Gold / 595g Silver
maal::business 85g Gold
maal::income 85g Gold
maal::investments 85g Gold
maal::agriculture 653 kg
maal::livestock Count-based
maal::mining Rikaz: None / Mines: 85g Gold
fitrah N/A

Migrating from v0.4 (Builder Pattern)

v0.5 removes all *Builder structs. Migration is straightforward:

- let assets = BusinessZakatBuilder::default()
-     .cash_on_hand(10_000)
-     .build()?;
+ let assets = BusinessZakat::new()
+     .cash(10_000);

Key changes:

  • ::builder()::new() (now infallible, returns Self)
  • .build()? → removed, object is ready immediately
  • Validation errors now occur at .calculate_zakat() instead of .build()
  • Errors include Asset Labels for better debugging

Contributing

  1. Add tests
  2. Use rust_decimal
  3. If adding async features, ensure they are gated behind #[cfg(feature = "async")]
  4. Run cargo test and cargo check --no-default-features

Support

GitHub Sponsors Ko-fi Patreon PayPal Saweria

"Those who spend their wealth in the cause of Allah..."Al-Baqarah 2:262

License

MIT