mod configs;
mod from_toml;
mod model;
#[cfg(test)]
mod lib_test;
use std::{
collections::{HashMap, HashSet},
fs,
path::PathBuf,
sync::Arc,
};
use l3_fn_env::EnvVarsParseError;
use configs::ProjectConfig;
pub use model::*;
#[derive(thiserror::Error, Debug, PartialEq)]
pub enum ConfigParseError {
#[error("{0}")]
EnvVarsParseError(#[from] EnvVarsParseError),
#[error("io error reading {path} env file: {kind}")]
IoError {
path: PathBuf,
kind: std::io::ErrorKind,
},
#[error("{0}")]
TomlParseError(#[from] toml::de::Error),
#[error("{field} is {actual} and must be {expected}")]
Misconfigured {
field: String,
expected: String,
actual: String,
},
#[error("{0}")]
UnresolvedLanguage(#[from] UnresolvedLanguage),
}
#[derive(Default)]
pub struct ConfigUpdate {
pub config_errs: Vec<ConfigParseError>,
pub mutations: HashSet<UpdateMutation>,
}
impl ConfigUpdate {
fn extend(&mut self, other: ConfigUpdate) {
self.config_errs.extend(other.config_errs);
self.mutations.extend(other.mutations);
}
}
#[derive(Debug, Eq, Hash, PartialEq)]
pub enum UpdateMutation {
ProjectEnv,
}
pub struct LLLConfigs {
lambdas: HashMap<String, Arc<LambdaSpec>>,
project: Option<ProjectConfig>,
project_dir: Arc<PathBuf>,
}
impl LLLConfigs {
pub fn new(project_dir: Arc<PathBuf>) -> Self {
Self {
project_dir,
lambdas: HashMap::new(),
project: None,
}
}
pub fn lambda_by_name(&self, name: &str) -> Option<Arc<LambdaSpec>> {
self.lambdas.get(name).cloned()
}
pub fn discrete_lambdas(&self) -> Vec<Arc<LambdaSpec>> {
self.lambdas.values().cloned().collect()
}
pub fn update_all_configs(&mut self) -> ConfigUpdate {
let paths = ["l3.toml"].iter().map(PathBuf::from).collect();
self.update_configs(&paths)
}
pub fn update_configs(&mut self, paths: &Vec<PathBuf>) -> ConfigUpdate {
let mut result = ConfigUpdate::default();
for path in paths {
match path.to_str() {
Some("l3.toml") => {
result.extend(self.update_project_config());
}
_ => panic!(),
}
}
result
}
fn update_project_config(&mut self) -> ConfigUpdate {
let mut result = ConfigUpdate::default();
let pc = match self.read_from_project_dir(&"l3.toml".into()) {
Ok(pc) => ProjectConfig::try_from(&pc.parse::<toml::Table>().unwrap()).unwrap(),
Err(err) => {
result.config_errs.push(err);
return result;
}
};
for lambda in &pc.lambdas {
if let (Some(language), Some(name), Some(handler), Some(source)) = (
&lambda.language,
lambda.name.clone(),
lambda.handler.clone(),
lambda.source.clone(),
) {
let runtime = match language {
Language::JavaScript | Language::TypeScript => LambdaRuntimeSpec::Node,
Language::Python => LambdaRuntimeSpec::Python,
};
self.lambdas.insert(
lambda.name.clone().unwrap(),
Arc::new(LambdaSpec {
name,
handler,
source,
runtime,
}),
);
} else {
panic!();
}
}
self.project = Some(pc);
result
}
fn read_from_project_dir(&self, p: &PathBuf) -> Result<String, ConfigParseError> {
debug_assert!(p.is_relative());
fs::read_to_string(self.project_dir.join(p)).map_err(|err| ConfigParseError::IoError {
path: p.clone(),
kind: err.kind(),
})
}
}
pub trait OptionallyParsable<T>: Sized {
fn has_value(value: &T) -> bool;
fn optionally_parse(value: T) -> Result<Option<Self>, ConfigParseError> {
if Self::has_value(&value) {
Self::parse(value).map(Some)
} else {
Ok(None)
}
}
fn parse(value: T) -> Result<Self, ConfigParseError>;
}