use crate::error::{GhostError, Result};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ContentApiKey {
key: String,
}
impl ContentApiKey {
pub const MIN_KEY_LENGTH: usize = 26;
pub const MAX_KEY_LENGTH: usize = 26;
pub fn new(key: impl Into<String>) -> Result<Self> {
let key = key.into().trim().to_lowercase();
if key.is_empty() {
return Err(GhostError::auth("Content API key cannot be empty"));
}
if key.len() != Self::MIN_KEY_LENGTH {
return Err(GhostError::auth(format!(
"Content API key must be exactly {} characters, got {}",
Self::MIN_KEY_LENGTH,
key.len()
)));
}
if !key.chars().all(|c| c.is_ascii_hexdigit()) {
return Err(GhostError::auth(
"Content API key must contain only hexadecimal characters (0-9, a-f)",
));
}
Ok(Self { key })
}
pub fn as_str(&self) -> &str {
&self.key
}
pub fn as_query_param(&self) -> String {
format!("key={}", self.key)
}
pub fn is_valid(&self) -> bool {
self.key.len() == Self::MIN_KEY_LENGTH && self.key.chars().all(|c| c.is_ascii_hexdigit())
}
}
impl fmt::Display for ContentApiKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.key.len() >= 12 {
write!(
f,
"ContentApiKey({}...{})",
&self.key[..8],
&self.key[self.key.len() - 4..]
)
} else {
write!(f, "ContentApiKey(***)")
}
}
}
impl AsRef<str> for ContentApiKey {
fn as_ref(&self) -> &str {
&self.key
}
}
#[cfg(test)]
mod tests {
use super::*;
const VALID_KEY: &str = "22444f78447824223cefc48062";
#[test]
fn test_valid_key() {
let key = ContentApiKey::new(VALID_KEY).unwrap();
assert_eq!(key.as_str(), VALID_KEY);
assert!(key.is_valid());
}
#[test]
fn test_key_normalization() {
let key = ContentApiKey::new("22444F78447824223CEFC48062").unwrap();
assert_eq!(key.as_str(), VALID_KEY);
let key = ContentApiKey::new(" 22444f78447824223cefc48062 ").unwrap();
assert_eq!(key.as_str(), VALID_KEY);
}
#[test]
fn test_empty_key() {
let result = ContentApiKey::new("");
assert!(result.is_err());
assert!(result.unwrap_err().is_auth_error());
}
#[test]
fn test_too_short_key() {
let result = ContentApiKey::new("short");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.is_auth_error());
assert!(err.to_string().contains("must be exactly 26 characters"));
}
#[test]
fn test_too_long_key() {
let result = ContentApiKey::new("22444f78447824223cefc480621234");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.is_auth_error());
assert!(err.to_string().contains("must be exactly 26 characters"));
}
#[test]
fn test_invalid_characters() {
let result = ContentApiKey::new("gggggggggggggggggggggggggg");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.is_auth_error());
assert!(err
.to_string()
.contains("must contain only hexadecimal characters"));
}
#[test]
fn test_special_characters() {
let result = ContentApiKey::new("22444f78447824223cefc4806!");
assert!(result.is_err());
}
#[test]
fn test_as_query_param() {
let key = ContentApiKey::new(VALID_KEY).unwrap();
assert_eq!(key.as_query_param(), "key=22444f78447824223cefc48062");
}
#[test]
fn test_display() {
let key = ContentApiKey::new(VALID_KEY).unwrap();
let display = format!("{}", key);
assert_eq!(display, "ContentApiKey(22444f78...8062)");
assert!(!display.contains(VALID_KEY));
}
#[test]
fn test_debug() {
let key = ContentApiKey::new(VALID_KEY).unwrap();
let debug = format!("{:?}", key);
assert!(debug.contains("ContentApiKey"));
}
#[test]
fn test_as_ref() {
let key = ContentApiKey::new(VALID_KEY).unwrap();
let as_ref: &str = key.as_ref();
assert_eq!(as_ref, VALID_KEY);
}
#[test]
fn test_clone() {
let key = ContentApiKey::new(VALID_KEY).unwrap();
let cloned = key.clone();
assert_eq!(key, cloned);
}
#[test]
fn test_eq() {
let key1 = ContentApiKey::new(VALID_KEY).unwrap();
let key2 = ContentApiKey::new(VALID_KEY).unwrap();
assert_eq!(key1, key2);
let key3 = ContentApiKey::new("12444f78447824223cefc48062").unwrap();
assert_ne!(key1, key3);
}
#[test]
fn test_constants() {
assert_eq!(ContentApiKey::MIN_KEY_LENGTH, 26);
assert_eq!(ContentApiKey::MAX_KEY_LENGTH, 26);
}
#[test]
fn test_is_valid() {
let key = ContentApiKey::new(VALID_KEY).unwrap();
assert!(key.is_valid());
}
#[test]
fn test_all_hex_digits() {
let key = ContentApiKey::new("0123456789abcdef0123456789").unwrap();
assert!(key.is_valid());
}
}