1use std::path::Path;
2
3use serde::de::DeserializeOwned;
4
5use crate::error::LaunchError;
6use crate::fs::{DirEntry, FileSystem};
7use crate::types::{DirContext, Monorepo, Service};
8
9#[derive(Default)]
10pub struct SignalOutput {
11 pub services: Vec<Service>,
12 pub context: Vec<DirContext>,
13 pub monorepo: Option<Monorepo>,
14}
15
16pub fn read_config<T: DeserializeOwned>(
20 fs: &dyn FileSystem,
21 path: &Path,
22 signal_name: &str,
23) -> Option<T> {
24 let bytes = match fs.read_file(path) {
25 Ok(b) => b,
26 Err(e) => {
27 eprintln!("{signal_name}: failed to read {}: {e}", path.display());
28 return None;
29 }
30 };
31
32 let text = match std::str::from_utf8(&bytes) {
33 Ok(s) => s,
34 Err(e) => {
35 eprintln!("{signal_name}: invalid utf8 in {}: {e}", path.display());
36 return None;
37 }
38 };
39
40 let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
41
42 let result: Result<T, String> = match ext {
43 "json" => serde_json::from_str(text).map_err(|e| e.to_string()),
44 "yaml" | "yml" => serde_yml::from_str(text).map_err(|e| e.to_string()),
45 _ => toml::from_str(text).map_err(|e| e.to_string()),
46 };
47
48 match result {
49 Ok(config) => Some(config),
50 Err(e) => {
51 eprintln!("{signal_name}: failed to parse {}: {e}", path.display());
52 None
53 }
54 }
55}
56
57pub trait Signal: Send {
58 fn name(&self) -> &'static str;
59 fn observe(&mut self, dir: &Path, entry: &DirEntry);
60 fn generate(&mut self, fs: &dyn FileSystem) -> Result<SignalOutput, LaunchError>;
61}