use std::{
fs::File,
io::Read,
path::{Path, PathBuf},
};
use miette::{IntoDiagnostic, Result, WrapErr};
use tracing::{debug, instrument};
#[instrument(level = "debug")]
pub fn load_config(root: &Path, package: Option<&str>) -> Result<super::structure::TamanuConfig> {
serde_json::from_value(load_config_as_object(root, package)?).into_diagnostic()
}
#[instrument(level = "debug")]
pub fn load_config_as_object(root: &Path, package: Option<&str>) -> Result<serde_json::Value> {
let mut config = package_config(root, package, "default.json5")
.transpose()?
.unwrap_or_else(|| serde_json::Value::Object(Default::default()));
if let Ok(env_name) = std::env::var("NODE_ENV") {
if let Some(env_config) =
package_config(root, package, &format!("{env_name}.json5")).transpose()?
{
config = merge_json(config, env_config);
}
} else if let Some(env_config) = package_config(root, package, "production.json5").transpose()? {
config = merge_json(config, env_config);
}
if let Some(local_config) = package_config(root, package, "local.json5").transpose()? {
config = merge_json(config, local_config);
}
Ok(config)
}
#[instrument(level = "debug")]
pub fn find_config_dir(root: &Path, package: Option<&str>, file: &str) -> Option<PathBuf> {
if let Some(package) = package {
let path = root
.join("packages")
.join(package)
.join("config")
.join(file);
if path.exists() {
return Some(path);
}
} else {
for package in ["central-server", "facility-server"] {
let path = root
.join("packages")
.join(package)
.join("config")
.join(file);
if path.exists() {
return Some(path);
}
}
}
let path = root.join(file);
if path.exists() {
return Some(path);
}
None
}
#[instrument(level = "debug")]
pub fn package_config(
root: &Path,
package: Option<&str>,
file: &str,
) -> Option<Result<serde_json::Value>> {
fn inner(path: &Path) -> Result<serde_json::Value> {
debug!(?path, "opening config file");
let mut file = File::open(path).into_diagnostic()?;
let mut contents = String::new();
let bytes = file.read_to_string(&mut contents).into_diagnostic()?;
debug!(%bytes, "read config file");
let config: serde_json::Value = json5::from_str(&contents).into_diagnostic()?;
Ok(config)
}
find_config_dir(root, package, file)
.map(|path| inner(&path).wrap_err(path.to_string_lossy().into_owned()))
}
#[instrument(level = "trace")]
pub fn merge_json(
mut base: serde_json::Value,
mut overlay: serde_json::Value,
) -> serde_json::Value {
if let (Some(base), Some(overlay)) = (base.as_object_mut(), overlay.as_object_mut()) {
for (key, value) in overlay {
if let Some(base_value) = base.get_mut(key) {
*base_value = merge_json(base_value.clone(), value.clone());
} else {
base.insert(key.clone(), value.clone());
}
}
} else {
base = overlay
}
base
}