1use derive_more::{AsRef, Deref, DerefMut};
4use once_cell::sync::Lazy;
5use regex::Regex;
6
7use serde_with::{DeserializeFromStr, SerializeDisplay};
8use std::{fmt::Display, str::FromStr, string::FromUtf8Error};
9
10#[derive(
21 Debug,
22 SerializeDisplay,
23 DeserializeFromStr,
24 Clone,
25 PartialEq,
26 Eq,
27 PartialOrd,
28 Ord,
29 Deref,
30 DerefMut,
31 AsRef,
32)]
33pub struct ObjectName(String);
34
35impl ObjectName {
36 #[must_use]
50 pub fn to_hex(&self) -> String {
51 hex::encode(&self.0)
52 }
53
54 pub fn try_from_hex(hex: &str) -> Result<Self, ParseObjectNameError> {
61 let bytes = hex::decode(hex)?;
62 let text = String::from_utf8(bytes)?;
63 Ok(Self(text))
64 }
65
66 pub(crate) fn chunk_path(&self, index: u32) -> String {
67 format!("{}/{}", self.to_hex(), index)
68 }
69}
70
71impl Display for ObjectName {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 write!(f, "{}", self.0)
74 }
75}
76
77impl FromStr for ObjectName {
78 type Err = ParseObjectNameError;
79
80 fn from_str(s: &str) -> Result<Self, Self::Err> {
81 if !(1..=1024).contains(&s.len()) {
82 return Err(Self::Err::InvalidLength);
83 }
84
85 for c in s.chars() {
86 if c.is_ascii_control() {
87 return Err(Self::Err::IllegalChar(c));
88 }
89 }
90
91 Ok(Self(s.into()))
92 }
93}
94
95#[derive(Debug, thiserror::Error)]
97pub enum ParseObjectNameError {
98 #[error("invalid hex: {0}")]
100 InvalidHex(#[from] hex::FromHexError),
101
102 #[error("invalid utf-8: {0}")]
104 InvalidUtf8(#[from] FromUtf8Error),
105
106 #[error("invalid character: `{0}`")]
109 IllegalChar(char),
110
111 #[error("invalid name length")]
113 InvalidLength,
114}
115
116#[derive(
130 Debug,
131 SerializeDisplay,
132 DeserializeFromStr,
133 Clone,
134 PartialEq,
135 Eq,
136 PartialOrd,
137 Ord,
138 Deref,
139 DerefMut,
140 AsRef,
141)]
142pub struct BucketName(pub(crate) String);
143
144static BUCKET_RE: Lazy<Regex> =
145 Lazy::new(|| Regex::new(r"^[a-z0-9][a-z0-9\-]{1,61}[a-z0-9]$").unwrap());
146
147impl Display for BucketName {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 write!(f, "{}", self.0)
150 }
151}
152
153impl FromStr for BucketName {
154 type Err = ParseBucketNameError;
155
156 fn from_str(s: &str) -> Result<Self, Self::Err> {
157 if BUCKET_RE.is_match(s) {
158 Ok(Self(s.into()))
159 } else {
160 Err(ParseBucketNameError::InvalidName)
161 }
162 }
163}
164
165#[derive(Debug, thiserror::Error)]
167pub enum ParseBucketNameError {
168 #[error(
170 "bucket names must be between 3 and 63 characters long, \
171 only contain alphanumerics and dashes, and must not begin or end \
172 with a dash (-)"
173 )]
174 InvalidName,
175}