zerodds_webdds_bridge/
metadata.rs1use std::collections::BTreeMap;
15use std::sync::Mutex;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct StoredDocument {
21 pub content_type: String,
23 pub body: Vec<u8>,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum MetadataError {
30 Conflict(String),
32 NotFound(String),
34}
35
36impl std::fmt::Display for MetadataError {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 match self {
39 Self::Conflict(n) => write!(f, "`{n}` already exists"),
40 Self::NotFound(n) => write!(f, "`{n}` not found"),
41 }
42 }
43}
44
45impl std::error::Error for MetadataError {}
46
47#[derive(Default)]
51pub struct NamedStore(Mutex<BTreeMap<String, StoredDocument>>);
52
53impl NamedStore {
54 #[must_use]
56 pub fn new() -> Self {
57 Self::default()
58 }
59
60 pub fn create(&self, name: &str, doc: StoredDocument) -> Result<(), MetadataError> {
63 let mut map = self
64 .0
65 .lock()
66 .unwrap_or_else(std::sync::PoisonError::into_inner);
67 if map.contains_key(name) {
68 return Err(MetadataError::Conflict(name.to_string()));
69 }
70 map.insert(name.to_string(), doc);
71 Ok(())
72 }
73
74 pub fn update(&self, name: &str, doc: StoredDocument) -> Result<(), MetadataError> {
77 let mut map = self
78 .0
79 .lock()
80 .unwrap_or_else(std::sync::PoisonError::into_inner);
81 if !map.contains_key(name) {
82 return Err(MetadataError::NotFound(name.to_string()));
83 }
84 map.insert(name.to_string(), doc);
85 Ok(())
86 }
87
88 pub fn delete(&self, name: &str) -> Result<(), MetadataError> {
91 let mut map = self
92 .0
93 .lock()
94 .unwrap_or_else(std::sync::PoisonError::into_inner);
95 if map.remove(name).is_none() {
96 return Err(MetadataError::NotFound(name.to_string()));
97 }
98 Ok(())
99 }
100
101 pub fn get(&self, name: &str) -> Result<StoredDocument, MetadataError> {
104 let map = self
105 .0
106 .lock()
107 .unwrap_or_else(std::sync::PoisonError::into_inner);
108 map.get(name)
109 .cloned()
110 .ok_or_else(|| MetadataError::NotFound(name.to_string()))
111 }
112
113 #[must_use]
115 pub fn names(&self) -> Vec<String> {
116 let map = self
117 .0
118 .lock()
119 .unwrap_or_else(std::sync::PoisonError::into_inner);
120 map.keys().cloned().collect()
121 }
122}
123
124#[derive(Default)]
127pub struct MetadataStore {
128 pub qos_libraries: NamedStore,
130 pub qos_profiles: NamedStore,
134 pub types: NamedStore,
136}
137
138impl MetadataStore {
139 #[must_use]
141 pub fn new() -> Self {
142 Self::default()
143 }
144
145 #[must_use]
147 pub fn profile_key(library: &str, profile: &str) -> String {
148 format!("{library}/{profile}")
149 }
150}
151
152#[cfg(test)]
153#[allow(clippy::unwrap_used, clippy::expect_used)]
154mod tests {
155 use super::*;
156
157 fn doc(text: &str) -> StoredDocument {
158 StoredDocument {
159 content_type: "application/dds-web+xml".to_string(),
160 body: text.as_bytes().to_vec(),
161 }
162 }
163
164 #[test]
165 fn create_then_get_round_trips() {
166 let s = NamedStore::new();
167 s.create("HighQos", doc("<qos_library/>")).unwrap();
168 assert_eq!(s.get("HighQos").unwrap(), doc("<qos_library/>"));
169 }
170
171 #[test]
172 fn create_twice_is_conflict() {
173 let s = NamedStore::new();
174 s.create("A", doc("x")).unwrap();
175 assert_eq!(
176 s.create("A", doc("y")).unwrap_err(),
177 MetadataError::Conflict("A".to_string())
178 );
179 }
180
181 #[test]
182 fn update_missing_is_not_found() {
183 let s = NamedStore::new();
184 assert_eq!(
185 s.update("A", doc("x")).unwrap_err(),
186 MetadataError::NotFound("A".to_string())
187 );
188 }
189
190 #[test]
191 fn delete_removes_and_get_then_fails() {
192 let s = NamedStore::new();
193 s.create("A", doc("x")).unwrap();
194 s.delete("A").unwrap();
195 assert!(matches!(s.get("A"), Err(MetadataError::NotFound(_))));
196 }
197
198 #[test]
199 fn names_lists_every_entry() {
200 let s = NamedStore::new();
201 s.create("B", doc("x")).unwrap();
202 s.create("A", doc("x")).unwrap();
203 assert_eq!(s.names(), vec!["A".to_string(), "B".to_string()]);
204 }
205
206 #[test]
207 fn profile_key_is_library_slash_profile() {
208 assert_eq!(MetadataStore::profile_key("Lib", "High"), "Lib/High");
209 }
210
211 #[test]
212 fn profiles_of_different_libraries_do_not_collide() {
213 let store = MetadataStore::new();
214 store
215 .qos_profiles
216 .create(&MetadataStore::profile_key("LibA", "P"), doc("a"))
217 .unwrap();
218 store
219 .qos_profiles
220 .create(&MetadataStore::profile_key("LibB", "P"), doc("b"))
221 .unwrap();
222 assert_eq!(
223 store
224 .qos_profiles
225 .get(&MetadataStore::profile_key("LibA", "P"))
226 .unwrap(),
227 doc("a")
228 );
229 }
230}