Skip to main content

launch/
signal.rs

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
16/// Read a config file, detect format by extension, and deserialize.
17/// Returns `None` on any error (read, utf8, parse), logging to stderr.
18/// Supports `.toml`, `.json`, `.yaml`, and `.yml`.
19pub 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}