1use std::sync::{Arc, RwLock};
35
36#[derive(Debug, Clone)]
41pub struct BufferKind {
42 slug: Arc<str>,
43}
44
45impl BufferKind {
46 pub fn new(slug: impl Into<String>) -> Self {
50 let slug = slug.into();
51 let arc = BufferKindRegistry::global().intern(&slug);
52 BufferKind { slug: arc }
53 }
54
55 pub fn slug(&self) -> &str {
57 &self.slug
58 }
59
60 pub fn raw() -> Self {
63 Self::new("raw")
64 }
65 pub fn pcm16() -> Self {
66 Self::new("pcm16")
67 }
68 pub fn mulaw8() -> Self {
69 Self::new("mulaw8")
70 }
71 pub fn wav() -> Self {
72 Self::new("wav")
73 }
74 pub fn mp3() -> Self {
75 Self::new("mp3")
76 }
77 pub fn opus() -> Self {
78 Self::new("opus")
79 }
80 pub fn jpeg() -> Self {
81 Self::new("jpeg")
82 }
83 pub fn png() -> Self {
84 Self::new("png")
85 }
86 pub fn webp() -> Self {
87 Self::new("webp")
88 }
89 pub fn mp4() -> Self {
90 Self::new("mp4")
91 }
92 pub fn webm() -> Self {
93 Self::new("webm")
94 }
95 pub fn pdf() -> Self {
96 Self::new("pdf")
97 }
98 pub fn json() -> Self {
99 Self::new("json")
100 }
101 pub fn csv() -> Self {
102 Self::new("csv")
103 }
104}
105
106impl PartialEq for BufferKind {
107 fn eq(&self, other: &Self) -> bool {
108 Arc::ptr_eq(&self.slug, &other.slug) || self.slug == other.slug
112 }
113}
114
115impl Eq for BufferKind {}
116
117impl PartialOrd for BufferKind {
118 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
119 Some(self.cmp(other))
120 }
121}
122
123impl Ord for BufferKind {
124 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
125 self.slug.as_ref().cmp(other.slug.as_ref())
126 }
127}
128
129impl std::hash::Hash for BufferKind {
130 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
131 self.slug.hash(state);
132 }
133}
134
135impl std::fmt::Display for BufferKind {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 f.write_str(&self.slug)
138 }
139}
140
141pub struct BufferKindRegistry {
147 inner: RwLock<BufferKindRegistryInner>,
148}
149
150struct BufferKindRegistryInner {
151 slugs: std::collections::HashMap<String, Arc<str>>,
152}
153
154impl BufferKindRegistry {
155 pub fn new() -> Self {
156 let mut inner = BufferKindRegistryInner {
157 slugs: std::collections::HashMap::new(),
158 };
159 for seeded in SEEDED_KINDS {
160 let arc: Arc<str> = Arc::from(*seeded);
161 inner.slugs.insert((*seeded).to_string(), arc);
162 }
163 BufferKindRegistry {
164 inner: RwLock::new(inner),
165 }
166 }
167
168 pub fn global() -> &'static BufferKindRegistry {
171 use std::sync::OnceLock;
172 static GLOBAL: OnceLock<BufferKindRegistry> = OnceLock::new();
173 GLOBAL.get_or_init(BufferKindRegistry::new)
174 }
175
176 pub fn intern(&self, slug: &str) -> Arc<str> {
178 {
180 let guard = self.inner.read().expect("registry poisoned");
181 if let Some(existing) = guard.slugs.get(slug) {
182 return Arc::clone(existing);
183 }
184 }
185 let mut guard = self.inner.write().expect("registry poisoned");
187 Arc::clone(
188 guard
189 .slugs
190 .entry(slug.to_string())
191 .or_insert_with(|| Arc::from(slug)),
192 )
193 }
194
195 pub fn known_slugs(&self) -> Vec<String> {
198 let guard = self.inner.read().expect("registry poisoned");
199 let mut v: Vec<String> = guard.slugs.keys().cloned().collect();
200 v.sort();
201 v
202 }
203}
204
205impl Default for BufferKindRegistry {
206 fn default() -> Self {
207 Self::new()
208 }
209}
210
211const SEEDED_KINDS: &[&str] = &[
212 "raw", "pcm16", "mulaw8", "wav", "mp3", "opus", "jpeg", "png", "webp",
213 "mp4", "webm", "pdf", "json", "csv",
214];
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn seeded_kinds_are_equal_across_constructors() {
222 assert_eq!(BufferKind::raw(), BufferKind::new("raw"));
223 assert_eq!(BufferKind::pcm16(), BufferKind::new("pcm16"));
224 }
225
226 #[test]
227 fn intern_returns_same_arc_for_same_slug() {
228 let a = BufferKind::new("custom_kind_a");
229 let b = BufferKind::new("custom_kind_a");
230 assert_eq!(a, b);
231 assert!(Arc::ptr_eq(&a.slug, &b.slug));
233 }
234
235 #[test]
236 fn different_slugs_are_not_equal() {
237 assert_ne!(BufferKind::new("a"), BufferKind::new("b"));
238 }
239
240 #[test]
241 fn known_slugs_includes_seeded() {
242 let slugs = BufferKindRegistry::global().known_slugs();
243 for seeded in SEEDED_KINDS {
244 assert!(
245 slugs.contains(&seeded.to_string()),
246 "seeded kind {seeded} missing from registry"
247 );
248 }
249 }
250
251 #[test]
252 fn display_format_matches_slug() {
253 let k = BufferKind::new("opus");
254 assert_eq!(format!("{k}"), "opus");
255 }
256}