//! Service configuration — reflects `[services.*]` in `el.toml`. use crate::binding::BindingKind; /// Authentication configuration for a service binding. #[derive(Debug, Clone, PartialEq, Eq)] pub enum AuthConfig { /// No authentication. None, /// Bearer token (set in environment variable or injected at runtime). Bearer { token_env: String }, /// Basic auth. Basic { username_env: String, password_env: String }, /// API key in header. ApiKey { header: String, key_env: String }, } impl AuthConfig { /// Produce the HTTP Authorization header value for this auth config. /// Returns `None` if auth is `None` or the env var is not set. pub fn authorization_header(&self) -> Option { match self { Self::None => None, Self::Bearer { token_env } => { let token = std::env::var(token_env).ok()?; Some(format!("Bearer {}", token)) } Self::Basic { username_env, password_env } => { let user = std::env::var(username_env).ok()?; let pass = std::env::var(password_env).ok()?; // Base64 encode user:pass let raw = format!("{}:{}", user, pass); let encoded = base64_encode(raw.as_bytes()); Some(format!("Basic {}", encoded)) } Self::ApiKey { .. } => None, } } /// Produce custom header key/value for API key auth. pub fn api_key_header(&self) -> Option<(String, String)> { match self { Self::ApiKey { header, key_env } => { let key = std::env::var(key_env).ok()?; Some((header.clone(), key)) } _ => None, } } } fn base64_encode(input: &[u8]) -> String { const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; let mut out = String::new(); for chunk in input.chunks(3) { let b0 = chunk[0] as u32; let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 }; let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 }; let n = (b0 << 16) | (b1 << 8) | b2; out.push(CHARS[((n >> 18) & 63) as usize] as char); out.push(CHARS[((n >> 12) & 63) as usize] as char); out.push(if chunk.len() > 1 { CHARS[((n >> 6) & 63) as usize] as char } else { '=' }); out.push(if chunk.len() > 2 { CHARS[(n & 63) as usize] as char } else { '=' }); } out } /// Full configuration for one service, from `[services.ServiceName]`. #[derive(Debug, Clone)] pub struct ServiceConfig { /// Service name (matches the `service Name { ... }` declaration). pub name: String, /// The binding protocol. pub binding: BindingKind, /// Base URL (for REST/WebSocket/gRPC). pub base_url: Option, /// Auth configuration. pub auth: AuthConfig, /// Connection timeout in milliseconds. pub timeout_ms: u64, } impl ServiceConfig { pub fn new(name: impl Into, binding: BindingKind) -> Self { Self { name: name.into(), binding, base_url: None, auth: AuthConfig::None, timeout_ms: 30_000, } } pub fn with_base_url(mut self, url: impl Into) -> Self { self.base_url = Some(url.into()); self } pub fn with_auth(mut self, auth: AuthConfig) -> Self { self.auth = auth; self } pub fn with_timeout(mut self, ms: u64) -> Self { self.timeout_ms = ms; self } }