1use std::fmt;
2
3#[doc(hidden)]
4pub mod __private_generation {
6 pub use covey_manifest_macros::include_manifest;
7 pub use serde;
8 pub use serde_json;
9}
10
11#[macro_export]
12macro_rules! include_manifest {
13 ($path:literal) => {
14 $crate::manifest::__private_generation::include_manifest!(
15 file = $path,
16 serde_path = covey_plugin::manifest::__private_generation::serde,
18 covey_plugin_path = $crate,
19 );
20
21 impl $crate::manifest::ManifestDeserialization for self::Config {
22 fn try_from_input(s: &str) -> Result<Self, $crate::manifest::DeserializationError> {
23 $crate::manifest::__private_generation::serde_json::from_str(s)
24 .map_err(|e| $crate::manifest::DeserializationError(e.to_string()))
25 }
26 }
27 };
28 () => {
29 $crate::include_manifest!("./manifest.toml");
30 };
31}
32
33pub trait ManifestDeserialization: Sized {
37 fn try_from_input(s: &str) -> Result<Self, DeserializationError>;
42}
43
44impl ManifestDeserialization for () {
45 fn try_from_input(_: &str) -> Result<Self, DeserializationError> {
47 Ok(())
48 }
49}
50
51#[derive(Debug, Clone)]
53pub struct DeserializationError(pub String);
54
55impl fmt::Display for DeserializationError {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 f.write_str("failed to deserialize plugin configuration: ")?;
58 f.write_str(&self.0)?;
59 Ok(())
60 }
61}
62
63impl std::error::Error for DeserializationError {}
64
65#[cfg(test)]
66mod tests {
67 use std::collections::HashMap;
68
69 mod config {
70 use crate::manifest::__private_generation;
71
72 __private_generation::include_manifest!(
73 serde_path = crate::manifest::__private_generation::serde,
74 covey_plugin_path = crate,
75 inline = r#"
76 name = "Open"
77 description = "Open URLs with a query"
78 repository = "https://github.com/blorbb/covey-plugins"
79 authors = ["blorbb"]
80
81 [[schema]]
82 id = "urls"
83 title = "List of URLs to show"
84 type.map.value-type.struct.fields = { name = "text", url = "text", extra-field = "int" }
85
86 [[schema]]
87 id = "thing-with-dash"
88 title = "Some selection"
89 type.selection.allowed-values = ["a", "ab", "ab-cde"]
90
91 [[schema]]
92 id = "with-default"
93 title = "Yet another selection"
94 type.selection.allowed-values = ["oaiwrha", "iosdg"]
95 type.selection.default = "iosdg"
96 "#
97 );
98 }
99
100 #[test]
101 fn expanded_types_exist() {
102 config::Config {
103 urls: HashMap::from([(
104 "key".to_string(),
105 config::urls::UrlsValue {
106 name: "name".to_string(),
107 url: "urls".to_string(),
108 extra_field: 10,
109 },
110 )]),
111 thing_with_dash: config::thing_with_dash::ThingWithDashSelection::AbCde,
112 with_default: config::with_default::WithDefaultSelection::default(),
113 };
114
115 use config::CommandExt;
116 crate::ListItem::new("ajwroiajw").on_activate(async |menu| {
117 menu.close();
118 Ok(())
119 });
120 }
121
122 #[test]
123 fn deserialize_impls() {
124 let input = serde_json::json!({
125 "urls": {
126 "crates": {
127 "name": "crates docs",
128 "url": "docs.rs",
129 "extra-field": 500
130 }
131 },
132 "thing-with-dash": "ab-cde",
133 });
134
135 let deserialized: config::Config = serde_json::from_str(&input.to_string()).unwrap();
136 assert_eq!(
137 deserialized,
138 config::Config {
139 urls: HashMap::from([(
140 "crates".to_string(),
141 config::urls::UrlsValue {
142 name: "crates docs".to_string(),
143 url: "docs.rs".to_string(),
144 extra_field: 500,
145 }
146 )]),
147 thing_with_dash: config::thing_with_dash::ThingWithDashSelection::AbCde,
148 with_default: config::with_default::WithDefaultSelection::Iosdg,
149 }
150 )
151 }
152}