1use std::collections::HashMap;
2
3use crate::cbor;
4
5#[derive(Debug, Clone)]
12pub enum LocalizedString {
13 Plain(String),
15 Localized(HashMap<String, String>),
17}
18
19impl Default for LocalizedString {
20 fn default() -> Self {
21 Self::Plain(String::new())
22 }
23}
24
25impl LocalizedString {
26 pub fn plain(text: impl Into<String>) -> Self {
28 Self::Plain(text.into())
29 }
30
31 pub fn new(lang: impl Into<String>, text: impl Into<String>) -> Self {
33 let mut map = HashMap::new();
34 map.insert(lang.into(), text.into());
35 Self::Localized(map)
36 }
37
38 pub fn get(&self, lang: &str) -> Option<&str> {
43 match self {
44 Self::Plain(text) => Some(text.as_str()),
45 Self::Localized(map) => map.get(lang).map(|s| s.as_str()),
46 }
47 }
48
49 pub fn resolve(&self, lang: &str) -> &str {
54 match self {
55 Self::Plain(text) => text.as_str(),
56 Self::Localized(map) => {
57 if let Some(text) = map.get(lang) {
59 return text.as_str();
60 }
61 if let Some(text) = map
63 .iter()
64 .find(|(tag, _)| tag.starts_with(lang) || lang.starts_with(tag.as_str()))
65 .map(|(_, text)| text.as_str())
66 {
67 return text;
68 }
69 map.values().next().map(|s| s.as_str()).unwrap_or("")
71 }
72 }
73 }
74
75 pub fn any_text(&self) -> &str {
78 match self {
79 Self::Plain(text) => text.as_str(),
80 Self::Localized(map) => map.values().next().map(|s| s.as_str()).unwrap_or(""),
81 }
82 }
83}
84
85impl From<String> for LocalizedString {
86 fn from(s: String) -> Self {
87 Self::Plain(s)
88 }
89}
90
91impl From<&str> for LocalizedString {
92 fn from(s: &str) -> Self {
93 Self::Plain(s.to_string())
94 }
95}
96
97impl From<Vec<(String, String)>> for LocalizedString {
98 fn from(v: Vec<(String, String)>) -> Self {
99 Self::Localized(v.into_iter().collect())
100 }
101}
102
103impl From<HashMap<String, String>> for LocalizedString {
104 fn from(map: HashMap<String, String>) -> Self {
105 Self::Localized(map)
106 }
107}
108
109#[derive(Debug, Clone, Default)]
115pub struct Metadata(HashMap<String, serde_json::Value>);
116
117impl Metadata {
118 pub fn new() -> Self {
119 Self(HashMap::new())
120 }
121
122 pub fn insert(&mut self, key: impl Into<String>, value: impl Into<serde_json::Value>) {
124 self.0.insert(key.into(), value.into());
125 }
126
127 pub fn get(&self, key: &str) -> Option<&serde_json::Value> {
129 self.0.get(key)
130 }
131
132 pub fn get_as<T: serde::de::DeserializeOwned>(&self, key: &str) -> Option<T> {
134 self.0
135 .get(key)
136 .and_then(|v| serde_json::from_value(v.clone()).ok())
137 }
138
139 pub fn contains_key(&self, key: &str) -> bool {
141 self.0.contains_key(key)
142 }
143
144 pub fn is_empty(&self) -> bool {
146 self.0.is_empty()
147 }
148
149 pub fn iter(&self) -> impl Iterator<Item = (&String, &serde_json::Value)> {
151 self.0.iter()
152 }
153
154 pub fn to_json(&self) -> Option<serde_json::Value> {
156 if self.0.is_empty() {
157 return None;
158 }
159 let map: serde_json::Map<String, serde_json::Value> =
160 self.0.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
161 Some(serde_json::Value::Object(map))
162 }
163}
164
165impl From<Vec<(String, Vec<u8>)>> for Metadata {
167 fn from(v: Vec<(String, Vec<u8>)>) -> Self {
168 Self(
169 v.into_iter()
170 .filter_map(|(k, cbor_bytes)| {
171 let val = cbor::cbor_to_json(&cbor_bytes).ok()?;
172 Some((k, val))
173 })
174 .collect(),
175 )
176 }
177}
178
179impl From<Metadata> for Vec<(String, Vec<u8>)> {
181 fn from(m: Metadata) -> Self {
182 m.0.into_iter()
183 .map(|(k, v)| (k, cbor::to_cbor(&v)))
184 .collect()
185 }
186}
187
188macro_rules! cbor_wrapper {
192 ($(#[$meta:meta])* $name:ident) => {
193 $(#[$meta])*
194 #[derive(Debug, Clone)]
195 pub struct $name(Vec<u8>);
196
197 impl $name {
198 pub fn from_json(value: &serde_json::Value) -> Result<Self, cbor::CborError> {
200 cbor::json_to_cbor(value).map(Self)
201 }
202
203 pub fn from_json_opt(value: &Option<serde_json::Value>) -> Result<Option<Self>, cbor::CborError> {
205 match value {
206 Some(val) => Self::from_json(val).map(Some),
207 None => Ok(None),
208 }
209 }
210
211 pub fn as_bytes(&self) -> &[u8] {
213 &self.0
214 }
215
216 pub fn to_json(&self) -> Result<serde_json::Value, cbor::CborError> {
218 cbor::cbor_to_json(&self.0)
219 }
220
221 pub fn deserialize<T: serde::de::DeserializeOwned>(&self) -> Result<T, cbor::CborError> {
223 cbor::from_cbor(&self.0)
224 }
225 }
226
227 impl From<Vec<u8>> for $name {
228 fn from(v: Vec<u8>) -> Self {
229 Self(v)
230 }
231 }
232
233 impl From<$name> for Vec<u8> {
234 fn from(w: $name) -> Self {
235 w.0
236 }
237 }
238 };
239}
240
241cbor_wrapper!(
242 Args
244);
245
246cbor_wrapper!(
247 Config
249);
250
251use crate::constants::*;
252
253#[derive(Debug, Clone)]
257pub struct ActError {
258 pub kind: String,
259 pub message: String,
260}
261
262impl ActError {
263 pub fn new(kind: impl Into<String>, message: impl Into<String>) -> Self {
264 Self {
265 kind: kind.into(),
266 message: message.into(),
267 }
268 }
269
270 pub fn not_found(message: impl Into<String>) -> Self {
271 Self::new(ERR_NOT_FOUND, message)
272 }
273
274 pub fn invalid_args(message: impl Into<String>) -> Self {
275 Self::new(ERR_INVALID_ARGS, message)
276 }
277
278 pub fn internal(message: impl Into<String>) -> Self {
279 Self::new(ERR_INTERNAL, message)
280 }
281
282 pub fn timeout(message: impl Into<String>) -> Self {
283 Self::new(ERR_TIMEOUT, message)
284 }
285
286 pub fn capability_denied(message: impl Into<String>) -> Self {
287 Self::new(ERR_CAPABILITY_DENIED, message)
288 }
289}
290
291impl std::fmt::Display for ActError {
292 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
293 write!(f, "{}: {}", self.kind, self.message)
294 }
295}
296
297impl std::error::Error for ActError {}
298
299pub type ActResult<T> = Result<T, ActError>;
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305 use serde_json::json;
306
307 #[test]
308 fn localized_string_plain() {
309 let ls = LocalizedString::plain("hello");
310 assert_eq!(ls.resolve("en"), "hello");
311 assert_eq!(ls.any_text(), "hello");
312 }
313
314 #[test]
315 fn localized_string_from_str() {
316 let ls = LocalizedString::from("hello");
317 assert_eq!(ls.any_text(), "hello");
318 }
319
320 #[test]
321 fn localized_string_default() {
322 let ls = LocalizedString::default();
323 assert_eq!(ls.any_text(), "");
324 }
325
326 #[test]
327 fn localized_string_resolve_by_lang() {
328 let mut map = std::collections::HashMap::new();
329 map.insert("en".to_string(), "hello".to_string());
330 map.insert("ru".to_string(), "привет".to_string());
331 let ls = LocalizedString::Localized(map);
332 assert_eq!(ls.resolve("ru"), "привет");
333 assert_eq!(ls.resolve("en"), "hello");
334 assert!(!ls.resolve("fr").is_empty());
336 }
337
338 #[test]
339 fn localized_string_resolve_prefix() {
340 let mut map = HashMap::new();
341 map.insert("zh-Hans".to_string(), "你好".to_string());
342 map.insert("en".to_string(), "hello".to_string());
343 let ls = LocalizedString::Localized(map);
344 assert_eq!(ls.resolve("zh"), "你好");
345 }
346
347 #[test]
348 fn localized_string_get() {
349 let ls = LocalizedString::new("en", "hello");
350 assert_eq!(ls.get("en"), Some("hello"));
351 assert_eq!(ls.get("ru"), None);
352 }
353
354 #[test]
355 fn localized_string_from_vec() {
356 let v = vec![("en".to_string(), "hi".to_string())];
357 let ls = LocalizedString::from(v);
358 assert_eq!(ls.resolve("en"), "hi");
359 }
360
361 #[test]
362 fn metadata_insert_and_get() {
363 let mut m = Metadata::new();
364 m.insert("std:read-only", true);
365 assert_eq!(m.get("std:read-only"), Some(&json!(true)));
366 assert_eq!(m.get_as::<bool>("std:read-only"), Some(true));
367 }
368
369 #[test]
370 fn metadata_to_json_empty() {
371 assert!(Metadata::new().to_json().is_none());
372 }
373
374 #[test]
375 fn metadata_to_json_with_values() {
376 let mut m = Metadata::new();
377 m.insert("std:read-only", true);
378 let json = m.to_json().unwrap();
379 assert_eq!(json["std:read-only"], json!(true));
380 }
381
382 #[test]
383 fn metadata_from_vec() {
384 let v = vec![("key".to_string(), cbor::to_cbor(&42u32))];
385 let m = Metadata::from(v);
386 assert_eq!(m.get("key"), Some(&json!(42)));
387 assert_eq!(m.get_as::<u32>("key"), Some(42));
388 }
389
390 #[test]
391 fn args_from_json_roundtrip() {
392 let val = json!({"code": "2+2"});
393 let args = Args::from_json(&val).unwrap();
394 let decoded = args.to_json().unwrap();
395 assert_eq!(val, decoded);
396 }
397
398 #[test]
399 fn args_deserialize_typed() {
400 #[derive(serde::Deserialize, PartialEq, Debug)]
401 struct Params {
402 code: String,
403 }
404 let val = json!({"code": "hello"});
405 let args = Args::from_json(&val).unwrap();
406 let params: Params = args.deserialize().unwrap();
407 assert_eq!(params.code, "hello");
408 }
409
410 #[test]
411 fn config_from_json_opt_none() {
412 assert!(Config::from_json_opt(&None).unwrap().is_none());
413 }
414
415 #[test]
416 fn config_from_json_opt_some() {
417 let val = json!({"key": "value"});
418 let config = Config::from_json_opt(&Some(val.clone())).unwrap().unwrap();
419 let decoded = config.to_json().unwrap();
420 assert_eq!(decoded, val);
421 }
422}