openstranded_common_wasmcontract/
registry.rs1use std::collections::HashMap;
2
3use crate::ServiceError;
4
5#[cfg(feature = "parse")]
6use crate::Value;
7#[cfg(feature = "parse")]
8use ron::value::Value as RonValue;
9
10#[derive(Clone, Debug)]
16pub struct RegistryEntry {
17 pub filename: String,
19
20 pub data: Vec<u8>,
22}
23
24#[derive(Clone, Debug, Default)]
40pub struct Registry {
41 pub domains: HashMap<String, HashMap<String, Vec<u8>>>,
43}
44
45impl Registry {
46 #[must_use]
48 pub fn new() -> Self {
49 Self {
50 domains: HashMap::new(),
51 }
52 }
53
54 pub fn file(&self, domain: &str, filename: &str) -> Result<&[u8], String> {
58 self.domains
59 .get(domain)
60 .and_then(|files| files.get(filename))
61 .map(std::vec::Vec::as_slice)
62 .ok_or_else(|| format!("file '{domain}/{filename}' not found in registry"))
63 }
64
65 #[must_use]
67 pub fn has_domain(&self, name: &str) -> bool {
68 self.domains.contains_key(name)
69 }
70
71 #[must_use]
73 pub fn is_empty(&self) -> bool {
74 self.domains.is_empty()
75 }
76
77 pub fn domain(&self, name: &str) -> Result<&HashMap<String, Vec<u8>>, String> {
81 self.domains
82 .get(name)
83 .ok_or_else(|| format!("registry domain '{name}' not found"))
84 }
85
86 pub fn add_file(&mut self, domain: &str, filename: &str, data: Vec<u8>) {
88 self.domains
89 .entry(domain.to_owned())
90 .or_default()
91 .insert(filename.to_owned(), data);
92 }
93
94 pub fn domain_names(&self) -> impl Iterator<Item = &str> {
96 self.domains.keys().map(std::string::String::as_str)
97 }
98
99 #[must_use]
101 pub fn len(&self) -> usize {
102 self.domains.len()
103 }
104}
105
106#[cfg(feature = "parse")]
125pub fn parse_registry_data(bytes: &[u8]) -> Result<Value, ServiceError> {
126 let s = std::str::from_utf8(bytes).map_err(|e| {
127 ServiceError::ParseError(format!("registry data is not valid UTF-8: {e}"))
128 })?;
129 let ron_value: RonValue = ron::from_str(s).map_err(|e| {
130 ServiceError::ParseError(format!("failed to parse registry data: {e}"))
131 })?;
132 Ok(convert_ron_value(ron_value))
133}
134
135#[cfg(feature = "parse")]
149pub fn parse_registry_list(bytes: &[u8]) -> Result<Vec<Value>, ServiceError> {
150 let root = parse_registry_data(bytes)?;
151 match root {
152 Value::Array(arr) => Ok(arr),
153 other => Err(ServiceError::TypeMismatch {
154 expected: "array".into(),
155 found: format!("{other:?}"),
156 }),
157 }
158}
159
160#[cfg(feature = "parse")]
164fn convert_ron_value(v: RonValue) -> Value {
165 match v {
166 RonValue::Unit => Value::Null,
167 RonValue::Bool(b) => Value::Bool(b),
168 RonValue::Number(n) => {
169 match n {
170 ron::value::Number::I32(i) => Value::I32(i),
171 ron::value::Number::I64(i) => {
172 if let Ok(n) = i32::try_from(i) {
173 Value::I32(n)
174 } else {
175 Value::F64(i as f64)
176 }
177 }
178 ron::value::Number::F32(f) => Value::F64(f64::from(f.0)),
179 ron::value::Number::F64(f) => Value::F64(f.0),
180 ron::value::Number::U32(u) => Value::U32(u),
181 ron::value::Number::U64(u) => {
182 if let Ok(n) = u32::try_from(u) {
183 Value::U32(n)
184 } else {
185 Value::F64(u as f64)
186 }
187 }
188 ron::value::Number::I8(i) => Value::I32(i32::from(i)),
190 ron::value::Number::I16(i) => Value::I32(i32::from(i)),
191 ron::value::Number::U8(u) => Value::U32(u32::from(u)),
192 ron::value::Number::U16(u) => Value::U32(u32::from(u)),
193 #[allow(unreachable_patterns)]
194 _ => Value::Null,
195 }
196 }
197 RonValue::Char(c) => Value::String(c.to_string()),
198 RonValue::String(s) => Value::String(s),
199 RonValue::Bytes(b) => Value::Bytes(b),
200 RonValue::Seq(arr) => {
201 Value::Array(arr.into_iter().map(convert_ron_value).collect())
202 }
203 RonValue::Map(map) => {
204 Value::Map(
205 map.into_iter()
206 .map(|(k, v)| (convert_ron_key(k), convert_ron_value(v)))
207 .collect(),
208 )
209 }
210 RonValue::Option(Some(v)) => convert_ron_value(*v),
211 RonValue::Option(None) => Value::Null,
212 }
213}
214
215#[cfg(feature = "parse")]
217fn convert_ron_key(k: RonValue) -> String {
218 match k {
219 RonValue::String(s) => s,
220 RonValue::Char(c) => c.to_string(),
221 other => format!("{other:?}"),
222 }
223}
224
225#[cfg(test)]
228#[cfg(feature = "parse")]
229mod tests {
230 use super::*;
231
232 #[test]
233 fn test_parse_registry_data_object() {
234 let ron = br#"ItemDef(id: 1, name: "Wood")"#;
235 let value = parse_registry_data(ron).unwrap();
236 assert_eq!(value.get("id").unwrap().as_u32().unwrap(), 1);
237 assert_eq!(value.get("name").unwrap().as_str().unwrap(), "Wood");
238 }
239
240 #[test]
241 fn test_parse_registry_data_array() {
242 let ron = br#"[1, 2, 3]"#;
243 let value = parse_registry_data(ron).unwrap();
244 assert_eq!(value.as_array().unwrap().len(), 3);
245 }
246
247 #[test]
248 fn test_parse_registry_list_ok() {
249 let ron = br#"[ItemDef(id: 1), ItemDef(id: 2)]"#;
250 let items = parse_registry_list(ron).unwrap();
251 assert_eq!(items.len(), 2);
252 assert_eq!(items[0].get("id").unwrap().as_u32().unwrap(), 1);
253 }
254
255 #[test]
256 fn test_parse_registry_list_not_array() {
257 let ron = br#"ItemDef(id: 1)"#;
258 let err = parse_registry_list(ron).unwrap_err();
259 assert!(matches!(err, ServiceError::TypeMismatch { .. }));
260 }
261
262 #[test]
263 fn test_parse_invalid_ron() {
264 let ron = b"not valid ron {{{";
265 assert!(parse_registry_data(ron).is_err());
266 }
267
268 #[test]
269 fn test_registry_add_file() {
270 let mut reg = Registry::new();
271 reg.add_file("items", "wood.ron", b"hello".to_vec());
272 assert!(reg.has_domain("items"));
273 assert!(!reg.is_empty());
274 assert_eq!(reg.len(), 1);
275 }
276
277 #[test]
278 fn test_registry_file_ok() {
279 let mut reg = Registry::new();
280 reg.add_file("items", "wood.ron", b"hello".to_vec());
281 let data = reg.file("items", "wood.ron").unwrap();
282 assert_eq!(data, b"hello");
283 }
284
285 #[test]
286 fn test_registry_file_not_found() {
287 let reg = Registry::new();
288 assert!(reg.file("nope", "x.ron").is_err());
289 }
290
291 #[test]
292 fn test_registry_domain() {
293 let mut reg = Registry::new();
294 reg.add_file("items", "a.ron", vec![]);
295 let files = reg.domain("items").unwrap();
296 assert_eq!(files.len(), 1);
297 assert!(reg.domain("nope").is_err());
298 }
299
300 #[test]
301 fn test_registry_domain_names() {
302 let mut reg = Registry::new();
303 reg.add_file("items", "a.ron", vec![]);
304 reg.add_file("recipes", "b.ron", vec![]);
305 let names: Vec<&str> = reg.domain_names().collect();
306 assert_eq!(names.len(), 2);
307 assert!(names.contains(&"items"));
308 assert!(names.contains(&"recipes"));
309 }
310
311 #[test]
312 fn test_registry_default_empty() {
313 let reg = Registry::default();
314 assert!(reg.is_empty());
315 }
316
317 #[test]
318 fn test_registry_clone() {
319 let mut reg = Registry::new();
320 reg.add_file("test", "f.ron", vec![1, 2, 3]);
321 let cloned = reg.clone();
322 assert_eq!(cloned.file("test", "f.ron").unwrap(), &[1, 2, 3]);
323 }
324}