use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use secrecy::{ExposeSecret, SecretString};
use crate::error::{Error, Result};
pub type ApiKeyProvider = dyn Fn() -> Result<SecretString> + Send + Sync;
pub type AsyncApiKeyProvider =
dyn Fn() -> Pin<Box<dyn Future<Output = Result<SecretString>> + Send>> + Send + Sync;
#[derive(Clone)]
pub enum ApiKeySource {
Static(SecretString),
Dynamic(Arc<ApiKeyProvider>),
AsyncDynamic(Arc<AsyncApiKeyProvider>),
}
impl ApiKeySource {
pub fn from_static<T>(value: T) -> Self
where
T: Into<String>,
{
Self::Static(SecretString::new(value.into().into()))
}
pub fn from_provider<F>(provider: F) -> Self
where
F: Fn() -> Result<SecretString> + Send + Sync + 'static,
{
Self::Dynamic(Arc::new(provider))
}
pub fn from_async_provider<F, Fut>(provider: F) -> Self
where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<SecretString>> + Send + 'static,
{
Self::AsyncDynamic(Arc::new(move || Box::pin(provider())))
}
pub fn resolve(&self) -> Result<SecretString> {
match self {
Self::Static(value) => Ok(value.clone()),
Self::Dynamic(provider) => provider(),
Self::AsyncDynamic(_) => Err(Error::InvalidConfig(
"当前 API Key 来源为异步回调,请使用 resolve_async".into(),
)),
}
}
pub async fn resolve_async(&self) -> Result<SecretString> {
match self {
Self::Static(value) => Ok(value.clone()),
Self::Dynamic(provider) => provider(),
Self::AsyncDynamic(provider) => provider().await,
}
}
pub fn redacted(&self) -> String {
match self {
Self::Static(secret) => redact_secret(secret.expose_secret()),
Self::Dynamic(_) => "<dynamic-api-key-provider>".into(),
Self::AsyncDynamic(_) => "<async-api-key-provider>".into(),
}
}
}
impl fmt::Debug for ApiKeySource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("ApiKeySource")
.field(&self.redacted())
.finish()
}
}
fn redact_secret(secret: &str) -> String {
if secret.is_empty() {
return "<empty-secret>".into();
}
if secret.len() <= 8 {
return "********".into();
}
let prefix = &secret[..4];
let suffix = &secret[secret.len() - 4..];
format!("{prefix}****{suffix}")
}
impl From<SecretString> for ApiKeySource {
fn from(value: SecretString) -> Self {
Self::Static(value)
}
}
impl TryFrom<Option<ApiKeySource>> for ApiKeySource {
type Error = Error;
fn try_from(value: Option<ApiKeySource>) -> Result<Self> {
value.ok_or(Error::MissingCredentials)
}
}