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 len(&self) -> usize {
156 self.0.len()
157 }
158}
159
160impl From<serde_json::Value> for Metadata {
162 fn from(value: serde_json::Value) -> Self {
163 match value {
164 serde_json::Value::Object(map) => Self(map.into_iter().collect()),
165 _ => Self::new(),
166 }
167 }
168}
169
170impl From<Metadata> for serde_json::Value {
172 fn from(m: Metadata) -> Self {
173 serde_json::Value::Object(m.0.into_iter().collect())
174 }
175}
176
177impl From<Vec<(String, Vec<u8>)>> for Metadata {
179 fn from(v: Vec<(String, Vec<u8>)>) -> Self {
180 Self(
181 v.into_iter()
182 .filter_map(|(k, cbor_bytes)| {
183 let val = cbor::cbor_to_json(&cbor_bytes).ok()?;
184 Some((k, val))
185 })
186 .collect(),
187 )
188 }
189}
190
191impl From<Metadata> for Vec<(String, Vec<u8>)> {
193 fn from(m: Metadata) -> Self {
194 m.0.into_iter()
195 .map(|(k, v)| (k, cbor::to_cbor(&v)))
196 .collect()
197 }
198}
199
200use crate::constants::*;
201
202#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
206pub struct ComponentCapability {
207 pub id: String,
208 pub required: bool,
209 #[serde(default, skip_serializing_if = "Option::is_none")]
210 pub description: Option<String>,
211}
212
213#[non_exhaustive]
221#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
222pub struct ComponentInfo {
223 #[serde(rename = "std:name", default)]
224 pub name: String,
225 #[serde(rename = "std:version", default)]
226 pub version: String,
227 #[serde(rename = "std:description", default)]
228 pub description: String,
229 #[serde(
230 rename = "std:default-language",
231 default,
232 skip_serializing_if = "Option::is_none"
233 )]
234 pub default_language: Option<String>,
235 #[serde(
236 rename = "std:capabilities",
237 default,
238 skip_serializing_if = "Vec::is_empty"
239 )]
240 pub capabilities: Vec<ComponentCapability>,
241 #[serde(flatten, default, skip_serializing_if = "HashMap::is_empty")]
243 pub metadata: HashMap<String, serde_json::Value>,
244}
245
246impl ComponentInfo {
247 pub fn new(
248 name: impl Into<String>,
249 version: impl Into<String>,
250 description: impl Into<String>,
251 ) -> Self {
252 Self {
253 name: name.into(),
254 version: version.into(),
255 description: description.into(),
256 ..Default::default()
257 }
258 }
259}
260
261#[derive(Debug, Clone)]
265pub struct ActError {
266 pub kind: String,
267 pub message: String,
268}
269
270impl ActError {
271 pub fn new(kind: impl Into<String>, message: impl Into<String>) -> Self {
272 Self {
273 kind: kind.into(),
274 message: message.into(),
275 }
276 }
277
278 pub fn not_found(message: impl Into<String>) -> Self {
279 Self::new(ERR_NOT_FOUND, message)
280 }
281
282 pub fn invalid_args(message: impl Into<String>) -> Self {
283 Self::new(ERR_INVALID_ARGS, message)
284 }
285
286 pub fn internal(message: impl Into<String>) -> Self {
287 Self::new(ERR_INTERNAL, message)
288 }
289
290 pub fn timeout(message: impl Into<String>) -> Self {
291 Self::new(ERR_TIMEOUT, message)
292 }
293
294 pub fn capability_denied(message: impl Into<String>) -> Self {
295 Self::new(ERR_CAPABILITY_DENIED, message)
296 }
297}
298
299impl std::fmt::Display for ActError {
300 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
301 write!(f, "{}: {}", self.kind, self.message)
302 }
303}
304
305impl std::error::Error for ActError {}
306
307pub type ActResult<T> = Result<T, ActError>;
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313 use serde_json::json;
314
315 #[test]
316 fn localized_string_plain() {
317 let ls = LocalizedString::plain("hello");
318 assert_eq!(ls.resolve("en"), "hello");
319 assert_eq!(ls.any_text(), "hello");
320 }
321
322 #[test]
323 fn localized_string_from_str() {
324 let ls = LocalizedString::from("hello");
325 assert_eq!(ls.any_text(), "hello");
326 }
327
328 #[test]
329 fn localized_string_default() {
330 let ls = LocalizedString::default();
331 assert_eq!(ls.any_text(), "");
332 }
333
334 #[test]
335 fn localized_string_resolve_by_lang() {
336 let mut map = std::collections::HashMap::new();
337 map.insert("en".to_string(), "hello".to_string());
338 map.insert("ru".to_string(), "привет".to_string());
339 let ls = LocalizedString::Localized(map);
340 assert_eq!(ls.resolve("ru"), "привет");
341 assert_eq!(ls.resolve("en"), "hello");
342 assert!(!ls.resolve("fr").is_empty());
344 }
345
346 #[test]
347 fn localized_string_resolve_prefix() {
348 let mut map = HashMap::new();
349 map.insert("zh-Hans".to_string(), "你好".to_string());
350 map.insert("en".to_string(), "hello".to_string());
351 let ls = LocalizedString::Localized(map);
352 assert_eq!(ls.resolve("zh"), "你好");
353 }
354
355 #[test]
356 fn localized_string_get() {
357 let ls = LocalizedString::new("en", "hello");
358 assert_eq!(ls.get("en"), Some("hello"));
359 assert_eq!(ls.get("ru"), None);
360 }
361
362 #[test]
363 fn localized_string_from_vec() {
364 let v = vec![("en".to_string(), "hi".to_string())];
365 let ls = LocalizedString::from(v);
366 assert_eq!(ls.resolve("en"), "hi");
367 }
368
369 #[test]
370 fn metadata_insert_and_get() {
371 let mut m = Metadata::new();
372 m.insert("std:read-only", true);
373 assert_eq!(m.get("std:read-only"), Some(&json!(true)));
374 assert_eq!(m.get_as::<bool>("std:read-only"), Some(true));
375 }
376
377 #[test]
378 fn metadata_to_json_empty() {
379 let json: serde_json::Value = Metadata::new().into();
380 assert_eq!(json, json!({}));
381 }
382
383 #[test]
384 fn metadata_to_json_with_values() {
385 let mut m = Metadata::new();
386 m.insert("std:read-only", true);
387 let json: serde_json::Value = m.into();
388 assert_eq!(json["std:read-only"], json!(true));
389 }
390
391 #[test]
392 fn metadata_from_vec() {
393 let v = vec![("key".to_string(), cbor::to_cbor(&42u32))];
394 let m = Metadata::from(v);
395 assert_eq!(m.get("key"), Some(&json!(42)));
396 assert_eq!(m.get_as::<u32>("key"), Some(42));
397 }
398}