1use anyhow::Context as _;
2use sha2::{Digest, Sha256};
3
4pub fn api_key_fingerprint(api_key: &str) -> String {
5 let digest = Sha256::digest(api_key.as_bytes());
6 let mut fingerprint = String::with_capacity(16);
7 for byte in &digest[..8] {
8 use std::fmt::Write as _;
9 let _ = write!(fingerprint, "{byte:02x}");
10 }
11 fingerprint
12}
13
14pub fn short_id(id: &str) -> String {
15 id.chars().take(8).collect()
16}
17
18pub(crate) fn i64_to_usize(value: i64, column: &str) -> anyhow::Result<usize> {
19 value
20 .try_into()
21 .with_context(|| format!("column `{column}` contains negative or too-large value {value}"))
22}
23
24#[cfg(test)]
25mod tests {
26 use super::*;
27
28 #[test]
29 fn short_id_truncates_long_ids() {
30 assert_eq!(short_id("1234567890"), "12345678");
31 }
32
33 #[test]
34 fn short_id_returns_input_for_short_strings() {
35 assert_eq!(short_id("abc"), "abc");
36 }
37
38 #[test]
39 fn short_id_returns_input_for_exact_length() {
40 assert_eq!(short_id("12345678"), "12345678");
41 }
42
43 #[test]
44 fn short_id_handles_unicode() {
45 let value = "\u{00e9}".repeat(9);
46 let expected = "\u{00e9}".repeat(8);
47 assert_eq!(short_id(&value), expected);
48 }
49
50 #[test]
51 fn api_key_fingerprint_uses_stable_short_sha256() {
52 assert_eq!(api_key_fingerprint("secret-key"), "85dbe15d75ef9308");
53 }
54}