asimov_module/models/
manifest.rs1use alloc::{string::String, vec::Vec};
4
5#[derive(Clone, Debug, Default)]
6#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
7pub struct ModuleManifest {
8 pub name: String,
9 pub label: String,
10 pub summary: String,
11 pub links: Vec<String>,
12
13 #[cfg_attr(
14 feature = "serde",
15 serde(default, skip_serializing_if = "Provides::is_empty")
16 )]
17 pub provides: Provides,
18
19 #[cfg_attr(
20 feature = "serde",
21 serde(default, skip_serializing_if = "Handles::is_empty")
22 )]
23 pub handles: Handles,
24
25 #[cfg_attr(
26 feature = "serde",
27 serde(alias = "configuration", skip_serializing_if = "Option::is_none")
28 )]
29 pub config: Option<Configuration>,
30}
31
32#[cfg(feature = "std")]
33#[derive(Debug, thiserror::Error)]
34pub enum ReadVarError {
35 #[error("variable named `{0}` not found in module manifest")]
36 UnknownVar(String),
37
38 #[error("a value for variable `{0}` was not configured")]
39 UnconfiguredVar(String),
40
41 #[error("failed to read variable `{name}`: {source}")]
42 Io {
43 name: String,
44 #[source]
45 source: std::io::Error,
46 },
47}
48
49impl ModuleManifest {
50 #[cfg(all(feature = "std", feature = "serde"))]
51 pub fn read_manifest(module_name: &str) -> std::io::Result<Self> {
52 let path = asimov_env::paths::asimov_root()
53 .join("modules")
54 .join(std::format!("{module_name}.yaml"));
55 let content = std::fs::read(&path)?;
56 serde_yml::from_slice(&content).map_err(std::io::Error::other)
57 }
58
59 #[cfg(feature = "std")]
60 pub fn read_variables(
61 &self,
62 profile: Option<&str>,
63 ) -> Result<std::collections::BTreeMap<String, String>, ReadVarError> {
64 self.config
65 .as_ref()
66 .map(|c| c.variables.as_slice())
67 .unwrap_or_default()
68 .iter()
69 .map(|var| Ok((var.name.clone(), self.variable(&var.name, profile)?)))
70 .collect()
71 }
72
73 #[cfg(feature = "std")]
74 pub fn variable(&self, key: &str, profile: Option<&str>) -> Result<String, ReadVarError> {
75 let Some(var) = self
76 .config
77 .as_ref()
78 .and_then(|conf| conf.variables.iter().find(|var| var.name == key))
79 else {
80 return Err(ReadVarError::UnknownVar(key.into()));
81 };
82
83 if let Some(value) = var
84 .environment
85 .as_deref()
86 .and_then(|env_name| std::env::var(env_name).ok())
87 {
88 return Ok(value);
89 }
90
91 let profile = profile.unwrap_or("default");
92 let path = asimov_env::paths::asimov_root()
93 .join("configs")
94 .join(profile)
95 .join(&self.name)
96 .join(key);
97
98 std::fs::read_to_string(&path).or_else(|err| {
99 if err.kind() == std::io::ErrorKind::NotFound {
100 var.default_value
101 .clone()
102 .ok_or_else(|| ReadVarError::UnconfiguredVar(key.into()))
103 } else {
104 Err(ReadVarError::Io {
105 name: key.into(),
106 source: err,
107 })
108 }
109 })
110 }
111}
112
113#[derive(Clone, Debug, Default)]
114#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
115pub struct Provides {
116 pub programs: Vec<String>,
117}
118
119impl Provides {
120 pub fn is_empty(&self) -> bool {
121 self.programs.is_empty()
122 }
123}
124
125#[derive(Clone, Debug, Default)]
126#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
127pub struct Handles {
128 #[cfg_attr(
129 feature = "serde",
130 serde(default, skip_serializing_if = "Vec::is_empty")
131 )]
132 pub url_protocols: Vec<String>,
133
134 #[cfg_attr(
135 feature = "serde",
136 serde(default, skip_serializing_if = "Vec::is_empty")
137 )]
138 pub url_prefixes: Vec<String>,
139
140 #[cfg_attr(
141 feature = "serde",
142 serde(default, skip_serializing_if = "Vec::is_empty")
143 )]
144 pub url_patterns: Vec<String>,
145
146 #[cfg_attr(
147 feature = "serde",
148 serde(default, skip_serializing_if = "Vec::is_empty")
149 )]
150 pub file_extensions: Vec<String>,
151
152 #[cfg_attr(
153 feature = "serde",
154 serde(default, skip_serializing_if = "Vec::is_empty")
155 )]
156 pub content_types: Vec<String>,
157}
158
159impl Handles {
160 pub fn is_empty(&self) -> bool {
161 self.url_protocols.is_empty()
162 && self.url_prefixes.is_empty()
163 && self.url_patterns.is_empty()
164 && self.file_extensions.is_empty()
165 && self.content_types.is_empty()
166 }
167}
168
169#[derive(Clone, Debug, Default)]
170#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
171pub struct Configuration {
172 #[cfg_attr(
173 feature = "serde",
174 serde(default, skip_serializing_if = "Vec::is_empty")
175 )]
176 pub variables: Vec<ConfigurationVariable>,
177}
178
179#[derive(Clone, Debug, Default)]
180#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
181pub struct ConfigurationVariable {
182 pub name: String,
185
186 #[cfg_attr(
188 feature = "serde",
189 serde(default, alias = "desc", skip_serializing_if = "Option::is_none")
190 )]
191 pub description: Option<String>,
192
193 #[cfg_attr(
196 feature = "serde",
197 serde(default, alias = "env", skip_serializing_if = "Option::is_none")
198 )]
199 pub environment: Option<String>,
200
201 #[cfg_attr(
204 feature = "serde",
205 serde(default, alias = "default", skip_serializing_if = "Option::is_none")
206 )]
207 pub default_value: Option<String>,
208}