use serde::{Deserialize, Serialize};
use std::fmt;
use std::str::FromStr;
const SCHEME: &str = "dw-img://";
#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ImageToken(pub [u8; 32]);
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum TokenParseError {
#[error("token hex must be exactly 64 characters (got {0})")]
WrongLength(usize),
#[error("token hex contains non-hex characters")]
InvalidHex,
}
impl ImageToken {
pub fn to_dw_img_uri(self) -> String {
format!("{SCHEME}{}", hex::encode(self.0))
}
pub fn to_hex(self) -> String {
hex::encode(self.0)
}
pub fn looks_like_token(s: &str) -> bool {
s.starts_with(SCHEME)
}
}
impl fmt::Debug for ImageToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ImageToken({})", self.to_dw_img_uri())
}
}
impl fmt::Display for ImageToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.to_dw_img_uri())
}
}
impl FromStr for ImageToken {
type Err = TokenParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let hex_str = s.strip_prefix(SCHEME).unwrap_or(s);
if hex_str.len() != 64 {
return Err(TokenParseError::WrongLength(hex_str.len()));
}
let mut out = [0u8; 32];
hex::decode_to_slice(hex_str, &mut out).map_err(|_| TokenParseError::InvalidHex)?;
Ok(ImageToken(out))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample() -> ImageToken {
let mut bytes = [0u8; 32];
for (i, b) in bytes.iter_mut().enumerate() {
*b = i as u8;
}
ImageToken(bytes)
}
#[test]
fn round_trip_via_dw_img_uri() {
let t = sample();
let s = t.to_dw_img_uri();
assert!(s.starts_with("dw-img://"));
let parsed: ImageToken = s.parse().unwrap();
assert_eq!(parsed, t);
}
#[test]
fn round_trip_via_bare_hex() {
let t = sample();
let parsed: ImageToken = t.to_hex().parse().unwrap();
assert_eq!(parsed, t);
}
#[test]
fn rejects_wrong_length() {
let err: TokenParseError = "dw-img://abcd".parse::<ImageToken>().unwrap_err();
assert!(matches!(err, TokenParseError::WrongLength(4)));
}
#[test]
fn rejects_non_hex_chars() {
let bad = format!("dw-img://{}", "z".repeat(64));
let err = bad.parse::<ImageToken>().unwrap_err();
assert_eq!(err, TokenParseError::InvalidHex);
}
#[test]
fn looks_like_token_only_matches_scheme() {
assert!(ImageToken::looks_like_token("dw-img://anything"));
assert!(!ImageToken::looks_like_token("https://example.com/foo"));
assert!(!ImageToken::looks_like_token("data:image/png;base64,iVB="));
assert!(!ImageToken::looks_like_token(""));
}
}