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 }
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 pub fn lambda_by_name(&self, name: &str) -> Option<Arc<LambdaSpec>> {
84 self.lambdas.get(name).cloned()
85 }
86
87 pub fn discrete_lambdas(&self) -> Vec<Arc<LambdaSpec>> {
89 self.lambdas.values().cloned().collect()
90 }
91
92 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 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}