1use crate::extension::Extension;
2use crate::host::Host;
3use crate::port::PortsKind;
4use kvarn::prelude::{CompactString, ToCompactString};
5use log::{info, warn};
6use serde::de::DeserializeOwned;
7use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, HashSet, VecDeque};
9use std::path::{Path, PathBuf};
10use std::sync::Arc;
11
12pub(crate) type Result<T> = std::result::Result<T, String>;
13
14#[derive(Debug, Serialize, Deserialize)]
15#[serde(deny_unknown_fields)]
16pub(crate) struct KvarnConfig {
17 extensions: HashMap<String, Vec<Extension>>,
18 hosts: Vec<Host>,
19 host_collections: Option<HashMap<String, Vec<String>>>,
20 ports: Option<PortsKind>,
21 import: Option<Vec<String>>,
22}
23
24pub struct CliOptions<'a> {
25 pub high_ports: bool,
26 pub cache: bool,
27 pub dev: bool,
28 pub default_host: Option<&'a str>,
29}
30
31#[allow(clippy::or_fun_call)] async fn read_config(file: impl AsRef<Path>) -> Result<KvarnConfig> {
34 let s = file.as_ref();
35 let config_file_name = Path::new(s.file_name().unwrap_or(s.as_ref()));
36 info!("Read config {}", config_file_name.display());
37 let file = tokio::fs::read_to_string(s)
38 .await
39 .map_err(|err| format!("Failed to read config file {}: {err}", s.display()))?;
40 ron::Options::default()
41 .with_default_extension(ron::extensions::Extensions::UNWRAP_NEWTYPES)
42 .with_default_extension(ron::extensions::Extensions::IMPLICIT_SOME)
43 .with_default_extension(ron::extensions::Extensions::UNWRAP_VARIANT_NEWTYPES)
44 .from_str(&file)
45 .map_err(|err| {
46 format!(
47 "Parsing config {} failed at {} with message \"{}\"",
48 config_file_name.display(),
49 err.position,
50 err.code
51 )
52 })
53}
54pub async fn read_and_resolve(
58 file: impl AsRef<str>,
59 custom_extensions: &CustomExtensions,
60 opts: &CliOptions<'_>,
61) -> Result<kvarn::RunConfig> {
62 #[derive(Debug)]
63 enum Imported {
64 File(PathBuf),
65 Deserialized(KvarnConfig, PathBuf),
66 }
67
68 let file = file.as_ref();
69 let root_config_dir = Path::new(file)
70 .parent()
71 .expect("config file is in no directory");
72
73 let mut hosts = HashMap::new();
74 let mut ports = None;
75 let mut collections: HashMap<CompactString, Vec<CompactString>> = HashMap::new();
76 let mut extensions = HashMap::new();
77
78 let mut imports: VecDeque<Imported> = VecDeque::new();
79 imports.push_back(Imported::File(file.to_owned().into()));
80 let mut imported = HashSet::new();
81
82 while let Some(import) = imports.pop_back() {
83 let (mut cfg, import) = match import {
84 Imported::File(import) => {
85 if imports.is_empty() {
86 (read_config(&import).await?, import)
87 } else {
88 match read_config(&import).await {
89 Ok(c) => (c, import),
90 Err(s) => {
91 if s.contains("No such file or directory") {
92 warn!("Skipping config {}: {s}", import.display());
93 continue;
94 } else {
95 return Err(s);
96 }
97 }
98 }
99 }
100 }
101 Imported::Deserialized(cfg, file) => (cfg, file),
102 };
103 let imports_count = cfg.import.as_ref().map_or(0, Vec::len);
104 let config_dir = Path::new(&import)
105 .parent()
106 .expect("config file is in no directory");
107 let descendant_imports = cfg
108 .import
109 .take()
110 .into_iter()
111 .flatten()
112 .map(|file| config_dir.join(file))
113 .filter(|file| imported.insert(file.clone()))
114 .map(Imported::File);
115 if imports_count > 0 {
117 imports.push_front(Imported::Deserialized(cfg, import.clone()));
118 imports.extend(descendant_imports);
119 continue;
120 } else {
121 imports.extend(descendant_imports);
122 }
123
124 if let Some(ports_kind) = cfg.ports.take() {
125 if let Some((_, first)) = &ports {
126 return Err(format!(
127 "Two config files contain a ports parameter. \
128 You must specify exactly 1 per import tree. \
129 First ports parameter in {first:?}, \
130 second ports in {import:?}."
131 ));
132 }
133 ports = Some((ports_kind, import.clone()))
134 }
135 for (name, ext) in cfg.extensions {
136 if let Some((_, file)) = extensions.get(name.as_str()) {
137 return Err(format!(
138 "Duplicate extension with name {name}. Second occurrence in file {import:?}. \
139 First occurrence in {file:?}.",
140 ));
141 }
142 extensions.insert(name.to_compact_string(), (ext, import.clone()));
143 }
144 for host in cfg.hosts {
145 let host = host
146 .resolve(
147 &extensions,
148 custom_extensions,
149 config_dir,
150 root_config_dir,
151 opts.dev,
152 )
153 .await?;
154
155 info!(
156 "Loaded host {} from {} with extensions {:?}.",
157 host.host.name,
158 import.display(),
159 host.exts
160 );
161 if let Some((_, file)) = hosts.get(&host.host.name) {
162 return Err(format!(
163 "Duplicate host with name {}. Second occurrence in file {import:?}. \
164 First occurrence in {file:?}.",
165 host.host.name
166 ));
167 }
168 hosts.insert(host.host.name.clone(), (host, import.clone()));
169 }
170
171 if let Some(collection) = cfg.host_collections {
172 for (name, mut host_names) in collection {
173 let entry = collections.entry(name.to_compact_string());
174 let entry = entry.or_default();
175 host_names.extend(entry.iter().map(|v| v.to_string()));
176 *entry = host_names.into_iter().map(CompactString::from).collect();
177 }
178 }
179 }
180
181 if let Some(default_host) = opts.default_host {
182 if !hosts.contains_key(default_host) {
183 return Err(format!(
184 "Your choosen default host {default_host} wasn't found. Available: {:?}",
185 hosts.keys().collect::<Vec<_>>()
186 ));
187 }
188 }
189
190 let mut built_collections = HashMap::new();
191 for (name, host_names) in collections {
192 info!("Create host collection \"{name}\" with hosts {host_names:?}");
193 let collection = construct_collection(
194 &host_names,
195 &hosts,
196 &extensions,
197 custom_extensions,
198 opts,
199 false,
200 )
201 .await?;
202 built_collections.insert(name, (host_names, collection));
203 }
204 let mut rc = kvarn::RunConfig::new();
205 for descriptor in ports
206 .ok_or("Your config must contain a `ports` paramter.")?
207 .0
208 .resolve(
209 &built_collections,
210 &hosts,
211 &extensions,
212 custom_extensions,
213 opts,
214 )
215 .await?
216 {
217 rc = rc.bind(descriptor);
218 }
219
220 Ok(rc)
221}
222
223type CustomExtensionFn = Box<
224 dyn for<'a> Fn(
225 &'a mut kvarn::Extensions,
226 ron::Value,
227 PathBuf,
228 ) -> kvarn::extensions::RetSyncFut<'a, Result<()>>,
229>;
230type CustomExtensionsInner = HashMap<String, CustomExtensionFn>;
231pub struct CustomExtensions(pub(crate) CustomExtensionsInner);
232impl CustomExtensions {
233 pub fn empty() -> Self {
234 Self(HashMap::new())
235 }
236 pub fn insert_without_data_or_config_dir(
239 &mut self,
240 name: impl Into<String>,
241 extension: impl Fn(&mut kvarn::Extensions) -> kvarn::extensions::RetSyncFut<Result<()>>
242 + Send
243 + Sync
244 + 'static,
245 ) {
246 self.insert_without_data(name, move |ext, _| extension(ext))
247 }
248 pub fn insert_without_data(
251 &mut self,
252 name: impl Into<String>,
253 extension: impl Fn(&mut kvarn::Extensions, PathBuf) -> kvarn::extensions::RetSyncFut<Result<()>>
254 + Send
255 + Sync
256 + 'static,
257 ) {
258 self.insert::<()>(name, move |ext, (), config_dir| extension(ext, config_dir));
259 }
260 pub fn insert<T: DeserializeOwned + Sync + Send + 'static>(
261 &mut self,
262 name: impl Into<String>,
263 extension: impl Fn(&mut kvarn::Extensions, T, PathBuf) -> kvarn::extensions::RetSyncFut<Result<()>>
264 + Send
265 + Sync
266 + 'static,
267 ) {
268 let extension = Arc::new(extension);
269 let f: CustomExtensionFn = Box::new(move |exts, value: ron::Value, config_dir: PathBuf| {
270 let extension = Arc::clone(&extension);
271 Box::pin(async move {
272 let config = value
273 .into_rust()
274 .map_err(|err| format!("Custom extension data has invalid format: {err}"))?;
275 extension(exts, config, config_dir).await?;
276 Ok::<(), String>(())
277 })
278 });
279 self.0.insert(name.into(), f);
280 }
281}
282impl Default for CustomExtensions {
283 fn default() -> Self {
284 Self::empty()
285 }
286}
287
288pub async fn construct_collection(
289 host_names: impl AsRef<[CompactString]>,
290 hosts: &Hosts,
291 exts: &ExtensionBundles,
292 custom_exts: &CustomExtensions,
293 opts: &CliOptions<'_>,
294 execute_extensions_addons: bool,
295) -> Result<Arc<kvarn::host::Collection>> {
296 let mut b = kvarn::host::Collection::builder();
297 let mut se = vec![];
298 let mut cert_collection_senders = Vec::new();
299 for host in host_names.as_ref() {
300 let mut host = hosts
301 .get(host)
302 .ok_or_else(|| format!("Didn't find a host with name {host}."))?
303 .0
304 .clone_with_extensions(exts, custom_exts, execute_extensions_addons, opts.dev)
305 .await?;
306 for se_handle in host.search_engine_handles {
307 se.push((host.host.name.clone(), se_handle))
308 }
309 cert_collection_senders.extend(host.cert_collection_senders);
310 if !opts.cache {
311 host.host.disable_client_cache().disable_server_cache();
312 }
313 if opts
314 .default_host
315 .map_or(false, |default| default == host.host.name)
316 {
317 b = b.default(host.host);
318 } else {
319 b = b.insert(host.host);
320 }
321 }
322 let collection = b.build();
323
324 for cert_collection_sender in cert_collection_senders {
325 cert_collection_sender.send(collection.clone()).unwrap();
326 }
327 for (host, se) in se {
328 core::mem::forget(
330 se.watch(host, collection.clone())
331 .await
332 .map_err(|err| format!("Failed to start search engine watch: {err:?}"))?,
333 );
334 }
335 Ok(collection)
336}
337
338pub type HostCollections =
339 HashMap<CompactString, (Vec<CompactString>, Arc<kvarn::host::Collection>)>;
340pub type Hosts = HashMap<CompactString, (crate::host::CloneableHost, PathBuf)>;
341pub type ExtensionBundles = HashMap<CompactString, (Vec<crate::extension::Extension>, PathBuf)>;