use crate::error::{CarbemError, Result};
use crate::models::{CarbonEmission, EmissionQuery};
use crate::providers::CarbonProvider;
use crate::providers::azure::AzureConfig;
use crate::providers::ibm::IbmConfig;
use crate::providers::registry::ProviderRegistry;
use serde_json::json;
use std::marker::PhantomData;
pub struct CarbemClientBuilder<State> {
registry: ProviderRegistry,
providers: Vec<Box<dyn CarbonProvider + Send + Sync>>,
_state: PhantomData<State>,
}
pub struct Empty;
pub struct Configured;
impl CarbemClientBuilder<Empty> {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self {
registry: ProviderRegistry::new(),
providers: Vec::new(),
_state: PhantomData,
}
}
pub fn with_azure(mut self, config: AzureConfig) -> Result<CarbemClientBuilder<Configured>> {
let provider = self.registry.create_provider("azure", json!(config))?;
self.providers.push(provider);
Ok(CarbemClientBuilder {
registry: self.registry,
providers: self.providers,
_state: PhantomData,
})
}
pub fn with_azure_from_env(self) -> Result<CarbemClientBuilder<Configured>> {
let access_token = std::env::var("AZURE_TOKEN")
.or_else(|_| std::env::var("CARBEM_AZURE_ACCESS_TOKEN"))
.map_err(|_| {
CarbemError::Config(
"AZURE_TOKEN or CARBEM_AZURE_ACCESS_TOKEN environment variable not set"
.to_string(),
)
})?;
let config = AzureConfig { access_token };
self.with_azure(config)
}
pub fn with_ibm(mut self, config: IbmConfig) -> Result<CarbemClientBuilder<Configured>> {
let provider = self.registry.create_provider("ibm", json!(config))?;
self.providers.push(provider);
Ok(CarbemClientBuilder {
registry: self.registry,
providers: self.providers,
_state: PhantomData,
})
}
pub fn with_ibm_from_env(self) -> Result<CarbemClientBuilder<Configured>> {
let api_key = std::env::var("IBM_API_KEY")
.or_else(|_| std::env::var("CARBEM_IBM_API_KEY"))
.map_err(|_| {
CarbemError::Config(
"IBM_API_KEY or CARBEM_IBM_API_KEY environment variable not set".to_string(),
)
})?;
let config = IbmConfig { api_key };
self.with_ibm(config)
}
}
impl CarbemClientBuilder<Configured> {
pub fn with_azure(mut self, config: AzureConfig) -> Result<Self> {
let provider = self.registry.create_provider("azure", json!(config))?;
self.providers.push(provider);
Ok(self)
}
pub fn with_ibm(mut self, config: IbmConfig) -> Result<Self> {
let provider = self.registry.create_provider("ibm", json!(config))?;
self.providers.push(provider);
Ok(self)
}
pub fn with_provider_from_json(
mut self,
provider_name: &str,
config_json: &str,
) -> Result<Self> {
let config: serde_json::Value = serde_json::from_str(config_json)
.map_err(|e| CarbemError::Config(format!("Invalid JSON config: {}", e)))?;
let provider = self.registry.create_provider(provider_name, config)?;
self.providers.push(provider);
Ok(self)
}
pub fn build(self) -> CarbemClient {
CarbemClient {
providers: self.providers,
}
}
}
pub struct CarbemClient {
providers: Vec<Box<dyn CarbonProvider + Send + Sync>>,
}
impl Clone for CarbemClient {
fn clone(&self) -> Self {
Self {
providers: self.providers.iter().map(|p| p.clone_provider()).collect(),
}
}
}
impl CarbemClient {
pub fn builder() -> CarbemClientBuilder<Empty> {
CarbemClientBuilder::new()
}
pub async fn query_emissions(&self, query: &EmissionQuery) -> Result<Vec<CarbonEmission>> {
for provider in &self.providers {
if provider.name() == query.provider {
return provider.get_emissions(query).await;
}
}
Err(CarbemError::UnsupportedProvider(query.provider.clone()))
}
pub fn available_providers(&self) -> Vec<&str> {
self.providers.iter().map(|p| p.name()).collect()
}
pub fn has_provider(&self, name: &str) -> bool {
self.providers.iter().any(|p| p.name() == name)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_type_safe_builder() {
let config = AzureConfig {
access_token: "test".to_string(),
};
let client = CarbemClient::builder().with_azure(config).unwrap().build();
assert!(client.has_provider("azure"));
}
#[test]
fn test_multiple_providers() {
let config1 = AzureConfig {
access_token: "test1".to_string(),
};
let config2 = AzureConfig {
access_token: "test2".to_string(),
};
let client = CarbemClient::builder()
.with_azure(config1)
.unwrap()
.with_azure(config2)
.unwrap()
.build();
assert_eq!(client.available_providers().len(), 2);
}
}