1use base64::Engine;
2use base64::engine::general_purpose::URL_SAFE_NO_PAD;
3
4const KEY_SIZE: usize = 16;
5pub(crate) const BASE64_LEN: usize = 22;
6const BASE64_DECODE_BUFFER_LEN: usize = (BASE64_LEN * 2).div_ceil(4) * 3;
7
8#[derive(Debug, thiserror::Error)]
10pub enum ParseError {
11 #[error("invalid base64 length \"{length}\", expected length of \"{BASE64_LEN}\"")]
13 InvalidBase64Length { length: usize },
14
15 #[error("base64 decode error")]
17 Base64Decode(#[from] base64::DecodeSliceError),
18
19 #[error("invalid key length \"{length}\", expected length of \"{KEY_SIZE}\"")]
21 InvalidLength { length: usize },
22}
23
24#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
28#[serde(into = "String", try_from = "String")]
29pub struct FolderKey(pub u128);
30
31impl std::str::FromStr for FolderKey {
32 type Err = ParseError;
33
34 fn from_str(input: &str) -> Result<Self, Self::Err> {
35 let length = input.len();
36 if length < BASE64_LEN {
37 return Err(ParseError::InvalidBase64Length { length });
38 }
39
40 let mut base64_decode_buffer = [0; BASE64_DECODE_BUFFER_LEN];
41 let decoded_len = URL_SAFE_NO_PAD.decode_slice(input, &mut base64_decode_buffer)?;
42 let input = &base64_decode_buffer[..decoded_len];
43
44 let length = input.len();
45 if length != KEY_SIZE {
46 return Err(ParseError::InvalidLength { length });
47 }
48
49 let key = u128::from_be_bytes(input.try_into().unwrap());
51
52 Ok(Self(key))
53 }
54}
55
56impl std::fmt::Display for FolderKey {
57 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
58 let mut buffer = [0; BASE64_LEN];
59 let encoded_len = URL_SAFE_NO_PAD
60 .encode_slice(self.0.to_be_bytes(), &mut buffer)
61 .expect("output buffer should never be too small");
62 let value = std::str::from_utf8(&buffer[..encoded_len]).expect("output should be utf8");
63
64 f.write_str(value)
65 }
66}
67
68impl From<FolderKey> for String {
69 fn from(key: FolderKey) -> Self {
70 key.to_string()
71 }
72}
73
74impl TryFrom<String> for FolderKey {
75 type Error = <Self as std::str::FromStr>::Err;
76
77 fn try_from(value: String) -> Result<Self, Self::Error> {
78 value.parse()
79 }
80}
81
82#[cfg(test)]
83mod test {
84 use super::*;
85 use crate::test::*;
86
87 #[test]
88 fn round() {
89 let folder_key = FolderKey(TEST_FOLDER_KEY_DECODED);
90 let folder_key_string = folder_key.to_string();
91 let new_folder_key: FolderKey = folder_key_string.parse().expect("failed to parse");
92 assert!(folder_key.0 == new_folder_key.0);
93 }
94}