use std::fmt;
use reqwest::header::HeaderValue;
use secrecy::{ExposeSecret, SecretString};
use crate::KitError;
use crate::error::ValidationViolation;
#[derive(Clone)]
pub struct ApiKey {
secret: SecretString,
}
impl ApiKey {
pub fn new(secret: impl Into<String>) -> Result<Self, KitError> {
let secret = SecretString::new(secret.into().into());
let exposed = secret.expose_secret();
if exposed.trim().is_empty() {
return Err(KitError::config(
ValidationViolation::Empty {
field: "credentials.api_key",
},
"API key must not be empty",
));
}
if exposed != exposed.trim() {
return Err(KitError::config(
ValidationViolation::InvalidFormat {
field: "credentials.api_key",
rule: "must be unpadded",
},
"API key must not contain leading or trailing whitespace",
));
}
if HeaderValue::try_from(exposed).is_err() {
return Err(KitError::config(
ValidationViolation::InvalidFormat {
field: "credentials.api_key",
rule: "valid HTTP header value",
},
"API key must be a valid HTTP header value",
));
}
Ok(Self { secret })
}
pub(crate) fn as_secret(&self) -> &SecretString {
&self.secret
}
}
impl fmt::Debug for ApiKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ApiKey")
.field("secret", &"<redacted>")
.finish()
}
}