use std::fmt;
#[derive(Clone)]
pub struct ApiKey(String);
impl ApiKey {
pub fn new(key: impl Into<String>) -> Self {
Self(key.into())
}
#[cfg(any(feature = "async", feature = "sync"))]
#[allow(dead_code)] pub(crate) fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn expose(self) -> String {
self.0
}
}
impl fmt::Debug for ApiKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ApiKey(<redacted, {} chars>)", self.0.len())
}
}
impl From<String> for ApiKey {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for ApiKey {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
pub type SignerResult<T = ()> =
std::result::Result<T, Box<dyn std::error::Error + Send + Sync + 'static>>;
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub trait RequestSigner: fmt::Debug + Send + Sync + 'static {
fn sign(&self, request: &mut reqwest::Request) -> SignerResult;
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[derive(Debug, Clone)]
pub struct ApiKeySigner {
key: ApiKey,
}
#[cfg(feature = "async")]
impl ApiKeySigner {
#[must_use]
pub fn new(key: ApiKey) -> Self {
Self { key }
}
}
#[cfg(feature = "async")]
impl RequestSigner for ApiKeySigner {
fn sign(&self, request: &mut reqwest::Request) -> SignerResult {
request
.headers_mut()
.insert("x-api-key", self.key.as_str().parse()?);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn debug_redacts_the_secret() {
let k = ApiKey::new("sk-ant-very-secret-value-do-not-leak");
let dbg = format!("{k:?}");
assert!(!dbg.contains("secret"), "{dbg}");
assert!(!dbg.contains("very"), "{dbg}");
assert!(dbg.contains("redacted"), "{dbg}");
assert!(dbg.contains(&k.0.len().to_string()), "{dbg}");
}
#[test]
fn expose_returns_underlying_string() {
let k = ApiKey::new("sk-ant-foo");
assert_eq!(k.expose(), "sk-ant-foo");
}
#[test]
fn from_string_and_str() {
let _: ApiKey = "sk-ant-x".into();
let _: ApiKey = String::from("sk-ant-y").into();
}
}