Skip to main content

aster_forge_utils/
avatar.rs

1//! Avatar presentation helpers.
2//!
3//! This module keeps the product-neutral parts of Gravatar presentation: the
4//! normalized email hash and the conventional URL shape used by Aster services.
5//! Product crates still own avatar source policy, upload handling, route paths,
6//! cache headers, and which sizes they expose.
7
8use md5::{Digest, Md5};
9
10/// Returns the lowercase MD5 hash used by Gravatar for `email`.
11pub fn gravatar_hash(email: &str) -> String {
12    let normalized = email.trim().to_lowercase();
13    let mut hasher = Md5::new();
14    hasher.update(normalized.as_bytes());
15    hex_lower(&hasher.finalize())
16}
17
18/// Builds a Gravatar URL with Aster's default public query parameters.
19pub fn gravatar_url(email: &str, size: u32, base_url: &str) -> String {
20    let hash = gravatar_hash(email);
21    let base = base_url.trim_end_matches('/');
22    format!("{base}/{hash}?d=identicon&s={size}&r=g")
23}
24
25fn hex_lower(bytes: &[u8]) -> String {
26    const HEX: &[u8; 16] = b"0123456789abcdef";
27    let mut output = String::with_capacity(bytes.len() * 2);
28    for byte in bytes {
29        output.push(char::from(HEX[usize::from(byte >> 4)]));
30        output.push(char::from(HEX[usize::from(byte & 0x0f)]));
31    }
32    output
33}
34
35#[cfg(test)]
36mod tests {
37    use super::{gravatar_hash, gravatar_url};
38
39    #[test]
40    fn gravatar_hash_trims_and_lowercases_email() {
41        assert_eq!(
42            gravatar_hash("  MyEmailAddress@example.com "),
43            "0bc83cb571cd1c50ba6f3e8a78ef1346"
44        );
45    }
46
47    #[test]
48    fn gravatar_url_uses_default_query_parameters_and_trims_base_slashes() {
49        assert_eq!(
50            gravatar_url("user@example.com", 512, "https://www.gravatar.com/avatar"),
51            "https://www.gravatar.com/avatar/b58996c504c5638798eb6b511e6f49af?d=identicon&s=512&r=g"
52        );
53        assert_eq!(
54            gravatar_url("user@example.com", 1024, "https://mirror.example/avatar/"),
55            "https://mirror.example/avatar/b58996c504c5638798eb6b511e6f49af?d=identicon&s=1024&r=g"
56        );
57    }
58}