l3_fn_config/
lib.rs

1mod configs;
2mod from_toml;
3mod model;
4
5#[cfg(test)]
6mod lib_test;
7
8use std::{
9    collections::{HashMap, HashSet},
10    fs,
11    path::PathBuf,
12    sync::Arc,
13};
14
15use l3_fn_env::EnvVarsParseError;
16
17use configs::ProjectConfig;
18
19pub use model::*;
20
21#[derive(thiserror::Error, Debug, PartialEq)]
22pub enum ConfigParseError {
23    #[error("{0}")]
24    EnvVarsParseError(#[from] EnvVarsParseError),
25    #[error("io error reading {path} env file: {kind}")]
26    IoError {
27        path: PathBuf,
28        kind: std::io::ErrorKind,
29    },
30    #[error("{0}")]
31    TomlParseError(#[from] toml::de::Error),
32    #[error("{field} is {actual} and must be {expected}")]
33    Misconfigured {
34        field: String,
35        expected: String,
36        actual: String,
37    },
38    #[error("{0}")]
39    UnresolvedLanguage(#[from] UnresolvedLanguage),
40    // #[error("error parsing {file_name} env vars: {cause}")]
41    // SyntaxError {
42    //     cause: String,
43    //     content: String,
44    //     file_name: String,
45    //     line: usize,
46    // },
47}
48
49#[derive(Default)]
50pub struct ConfigUpdate {
51    pub config_errs: Vec<ConfigParseError>,
52    pub mutations: HashSet<UpdateMutation>,
53}
54
55impl ConfigUpdate {
56    fn extend(&mut self, other: ConfigUpdate) {
57        self.config_errs.extend(other.config_errs);
58        self.mutations.extend(other.mutations);
59    }
60}
61
62#[derive(Debug, Eq, Hash, PartialEq)]
63pub enum UpdateMutation {
64    ProjectEnv,
65}
66
67pub struct LLLConfigs {
68    lambdas: HashMap<String, Arc<LambdaSpec>>,
69    project: Option<ProjectConfig>,
70    project_dir: Arc<PathBuf>,
71}
72
73impl LLLConfigs {
74    pub fn new(project_dir: Arc<PathBuf>) -> Self {
75        Self {
76            project_dir,
77            lambdas: HashMap::new(),
78            project: None,
79        }
80    }
81
82    // lookup any discrete or routing configured lambda by name
83    pub fn lambda_by_name(&self, name: &str) -> Option<Arc<LambdaSpec>> {
84        self.lambdas.get(name).cloned()
85    }
86
87    // lambdas configured via l3.toml [[lambdas]]
88    pub fn discrete_lambdas(&self) -> Vec<Arc<LambdaSpec>> {
89        self.lambdas.values().cloned().collect()
90    }
91
92    // env file paths configured via l3.toml [env_files]
93    // fn project_env_files(&self) -> Option<&Vec<Arc<PathBuf>>> {
94    //     self.project
95    //         .as_ref()
96    //         .and_then(|p| p.env.as_ref())
97    //         .and_then(|e| e.env_files.as_ref())
98    // }
99
100    // env vars configured via l3.toml [env]
101    // fn project_env_vars(&self) -> Option<&HashMap<String, String>> {
102    //     self.project
103    //         .as_ref()
104    //         .and_then(|p| p.env.as_ref())
105    //         .and_then(|e| e.env_vars.as_ref())
106    // }
107
108    pub fn update_all_configs(&mut self) -> ConfigUpdate {
109        let paths = ["l3.toml"].iter().map(PathBuf::from).collect();
110        self.update_configs(&paths)
111    }
112
113    pub fn update_configs(&mut self, paths: &Vec<PathBuf>) -> ConfigUpdate {
114        let mut result = ConfigUpdate::default();
115        for path in paths {
116            match path.to_str() {
117                Some("l3.toml") => {
118                    result.extend(self.update_project_config());
119                }
120                _ => panic!(),
121            }
122        }
123        result
124    }
125
126    fn update_project_config(&mut self) -> ConfigUpdate {
127        let mut result = ConfigUpdate::default();
128        let pc = match self.read_from_project_dir(&"l3.toml".into()) {
129            Ok(pc) => ProjectConfig::try_from(&pc.parse::<toml::Table>().unwrap()).unwrap(),
130            Err(err) => {
131                result.config_errs.push(err);
132                return result;
133            }
134        };
135
136        for lambda in &pc.lambdas {
137            if let (Some(language), Some(name), Some(handler), Some(source)) = (
138                &lambda.language,
139                lambda.name.clone(),
140                lambda.handler.clone(),
141                lambda.source.clone(),
142            ) {
143                let runtime = match language {
144                    Language::JavaScript | Language::TypeScript => LambdaRuntimeSpec::Node,
145                    Language::Python => LambdaRuntimeSpec::Python,
146                };
147                self.lambdas.insert(
148                    lambda.name.clone().unwrap(),
149                    Arc::new(LambdaSpec {
150                        name,
151                        handler,
152                        source,
153                        runtime,
154                    }),
155                );
156            } else {
157                panic!();
158            }
159        }
160
161        self.project = Some(pc);
162        result
163    }
164
165    // read path relative to project dir
166    fn read_from_project_dir(&self, p: &PathBuf) -> Result<String, ConfigParseError> {
167        debug_assert!(p.is_relative());
168        fs::read_to_string(self.project_dir.join(p)).map_err(|err| ConfigParseError::IoError {
169            path: p.clone(),
170            kind: err.kind(),
171        })
172    }
173}
174
175pub trait OptionallyParsable<T>: Sized {
176    fn has_value(value: &T) -> bool;
177
178    fn optionally_parse(value: T) -> Result<Option<Self>, ConfigParseError> {
179        if Self::has_value(&value) {
180            Self::parse(value).map(Some)
181        } else {
182            Ok(None)
183        }
184    }
185
186    fn parse(value: T) -> Result<Self, ConfigParseError>;
187}