use std::sync::Arc;
use async_trait::async_trait;
#[async_trait]
pub trait GatewayKeyResolver: Send + Sync {
async fn resolve(&self, org_id: &str) -> Option<String>;
}
#[derive(Debug, Clone, Default)]
pub struct EnvGatewayKeyResolver {
env_key: Option<String>,
}
impl EnvGatewayKeyResolver {
#[must_use]
pub fn new(env_key: Option<String>) -> Self {
Self { env_key }
}
}
#[async_trait]
impl GatewayKeyResolver for EnvGatewayKeyResolver {
async fn resolve(&self, _org_id: &str) -> Option<String> {
self.env_key.clone()
}
}
pub async fn resolve_gateway_key(
resolver: &Arc<dyn GatewayKeyResolver>,
org_id: &str,
env_key: Option<&str>,
) -> Option<String> {
match resolver.resolve(org_id).await {
Some(key) => Some(key),
None => env_key.map(str::to_string),
}
}
#[cfg(test)]
mod tests {
use super::*;
struct OneOrgResolver {
org: String,
key: String,
}
#[async_trait]
impl GatewayKeyResolver for OneOrgResolver {
async fn resolve(&self, org_id: &str) -> Option<String> {
if org_id == self.org {
Some(self.key.clone())
} else {
None
}
}
}
#[tokio::test]
async fn env_resolver_returns_env_key_for_every_org() {
let resolver = EnvGatewayKeyResolver::new(Some("env-key".to_string()));
assert_eq!(resolver.resolve("org-a").await, Some("env-key".to_string()));
assert_eq!(resolver.resolve("org-b").await, Some("env-key".to_string()));
}
#[tokio::test]
async fn env_resolver_returns_none_when_env_absent() {
let resolver = EnvGatewayKeyResolver::new(None);
assert_eq!(resolver.resolve("org-a").await, None);
}
#[tokio::test]
async fn injected_resolver_overrides_per_org() {
let resolver: Arc<dyn GatewayKeyResolver> = Arc::new(OneOrgResolver {
org: "org-a".to_string(),
key: "org-a-key".to_string(),
});
assert_eq!(
resolve_gateway_key(&resolver, "org-a", Some("env-key")).await,
Some("org-a-key".to_string())
);
}
#[tokio::test]
async fn falls_back_to_env_when_resolver_returns_none() {
let resolver: Arc<dyn GatewayKeyResolver> = Arc::new(OneOrgResolver {
org: "org-a".to_string(),
key: "org-a-key".to_string(),
});
assert_eq!(
resolve_gateway_key(&resolver, "org-b", Some("env-key")).await,
Some("env-key".to_string())
);
}
#[tokio::test]
async fn resolves_to_none_when_neither_resolver_nor_env_supply_a_key() {
let resolver: Arc<dyn GatewayKeyResolver> = Arc::new(EnvGatewayKeyResolver::new(None));
assert_eq!(resolve_gateway_key(&resolver, "org-a", None).await, None);
}
}