1use std::fmt;
2use std::ops::Deref;
3
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(try_from = "String", into = "String")]
11pub struct KeyId(String);
12
13impl KeyId {
14 const MAX_LENGTH: usize = 128;
15
16 pub fn decode(s: &str) -> Result<Self, KeyIdError> {
18 if s.is_empty() {
19 return Err(KeyIdError::Empty);
20 }
21 if s.len() > Self::MAX_LENGTH {
22 return Err(KeyIdError::TooLong);
23 }
24 if !s.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_') {
25 return Err(KeyIdError::Invalid);
26 }
27 Ok(Self(s.to_owned()))
28 }
29
30 pub fn encode(&self) -> &str {
32 &self.0
33 }
34
35 pub fn random() -> Self {
37 let mut bytes = [0u8; 8];
38 aws_lc_rs::rand::fill(&mut bytes).expect("failed to generate random bytes");
39 let hex: String = bytes.iter().map(|b| format!("{b:02x}")).collect();
40 Self(hex)
41 }
42}
43
44impl fmt::Display for KeyId {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 f.write_str(&self.0)
47 }
48}
49
50impl AsRef<str> for KeyId {
51 fn as_ref(&self) -> &str {
52 &self.0
53 }
54}
55
56impl Deref for KeyId {
57 type Target = str;
58
59 fn deref(&self) -> &str {
60 &self.0
61 }
62}
63
64impl From<KeyId> for String {
65 fn from(kid: KeyId) -> Self {
66 kid.0
67 }
68}
69
70impl TryFrom<String> for KeyId {
71 type Error = KeyIdError;
72
73 fn try_from(s: String) -> Result<Self, Self::Error> {
74 KeyId::decode(&s)
75 }
76}
77
78#[derive(Debug, Clone, thiserror::Error)]
79#[non_exhaustive]
80pub enum KeyIdError {
81 #[error("key ID must not be empty")]
82 Empty,
83
84 #[error("key ID exceeds maximum length of 128 characters")]
85 TooLong,
86
87 #[error("key ID contains invalid characters (only alphanumeric, hyphens, underscores allowed)")]
88 Invalid,
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn valid_key_ids() {
97 assert!(KeyId::decode("abc-123_DEF").is_ok());
98 assert!(KeyId::decode("simple").is_ok());
99 assert!(KeyId::decode("key-1").is_ok());
100 assert!(KeyId::decode("a").is_ok());
101 }
102
103 #[test]
104 fn invalid_key_ids() {
105 assert!(KeyId::decode("").is_err());
106 assert!(KeyId::decode("../etc/passwd").is_err());
107 assert!(KeyId::decode("key with spaces").is_err());
108 assert!(KeyId::decode("key/slash").is_err());
109 assert!(KeyId::decode("key.dot").is_err());
110 }
111
112 #[test]
113 fn random_key_id_is_valid() {
114 let kid = KeyId::random();
115 assert!(KeyId::decode(kid.encode()).is_ok());
116 assert!(!kid.encode().is_empty());
117 }
118}