aboutsummaryrefslogtreecommitdiff
path: root/src/config.rs
diff options
context:
space:
mode:
authorGus Power <gus@infinitesidequests.com>2025-05-21 16:23:55 +0100
committerGus Power <gus@infinitesidequests.com>2025-05-21 16:23:55 +0100
commitd7ce374a1741fdbb5c3aeef1218058a3d1060e88 (patch)
treeac4297e69ce670155fcd3c37c29b085d3707decc /src/config.rs
parent9ce9c101a10327b1eb6902133173119c3e0f3732 (diff)
config fallback w/ tests. introduced a macro to remove some test boilerplate around Result<T,E> and having return Ok(())
Diffstat (limited to 'src/config.rs')
-rw-r--r--src/config.rs183
1 files changed, 108 insertions, 75 deletions
diff --git a/src/config.rs b/src/config.rs
index 2c5427e..881759c 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -5,8 +5,9 @@ use fqdn::FQDN;
use serde::{Deserialize, Serialize};
use serde_with::DisplayFromStr;
use serde_with::serde_as;
-use std::path::Path;
+use std::path::{Path, PathBuf};
use std::{fs, io};
+use log::warn;
use strum::Display;
#[derive(Debug, Deserialize, Serialize)]
@@ -16,17 +17,38 @@ pub struct Config {
}
impl Config {
- pub fn load<P: AsRef<Path>>(path: P) -> AppResult<Config> {
- let path = path.as_ref();
- let content = fs::read_to_string(path).map_err(|e| match e.kind() {
- io::ErrorKind::NotFound => AppError::FileNotFound(path.to_path_buf()),
- _ => AppError::IoError(e),
- })?;
-
- serde_json::from_str(&content).map_err(|e| AppError::ConfigParseError {
- source: e,
- path: path.to_path_buf(),
- })
+ pub fn load(paths: Vec<PathBuf>) -> AppResult<Config> {
+ let mut index = 1;
+ for path in &paths {
+ let content = fs::read_to_string(&path);
+ match content {
+ Ok(value) => {
+ match serde_json::from_str(&value) {
+ Ok(config) => { return Ok(config) },
+ Err(error) => {
+ if index == paths.len() { return Err(AppError::ConfigParseError {
+ source: error,
+ path: path.to_path_buf(),
+ })}
+ warn!("{}", error);
+ }
+ }
+ }
+ Err(e) => {
+ let error = match e.kind() {
+ io::ErrorKind::NotFound => AppError::ConfigFileNotFound(path.clone()),
+ _ => AppError::IoError(e),
+ };
+ if index == paths.len() {
+ return Err(error);
+ }
+ warn!("{}", error);
+ },
+ }
+ index += 1;
+ }
+
+ Err(AppError::ConfigFileNotProvided)
}
}
@@ -70,91 +92,102 @@ mod tests {
use super::*;
use crate::dyndns_service::DynDnsProvider::Gandi;
use crate::dyndns_service::gandi::GandiConfig;
- use crate::{assert_config_parse_error, assert_error, assert_file_not_found, assert_io_error};
+ use crate::{
+ assert_config_file_not_found, assert_config_parse_error, assert_error, assert_io_error,
+ test,
+ };
use serde_json::json;
- use std::error::Error;
- use std::fs::{File, read_dir};
- use std::io::BufReader;
+ use std::fs::read_dir;
+ use std::path::PathBuf;
use std::str::FromStr;
- #[test]
- fn check_minimal_config() {
- let input = json!({
- "fqdn": "dyn.domain.com",
- "providers": [ {
- "type": "GANDI",
- "api_key": "SOME-API-KEY",
- } ]
- });
-
- let wan_config = serde_json::from_value::<WanConfig>(input).unwrap();
-
- assert_eq!(
- wan_config.dns_record.fqdn,
- FQDN::from_str("dyn.domain.com").unwrap()
- );
- assert_eq!(wan_config.interface, None);
-
- assert_eq!(wan_config.providers.len(), 1);
- let expected = GandiConfig::new("SOME-API-KEY".to_string());
- let actual = wan_config.providers.get(0).unwrap();
- assert_eq!(&Gandi(expected), actual);
+ test! {
+ fn check_minimal_config() {
+ let input = json!({
+ "fqdn": "dyn.domain.com",
+ "providers": [ {
+ "type": "GANDI",
+ "api_key": "SOME-API-KEY",
+ } ]
+ });
+
+ let wan_config = serde_json::from_value::<WanConfig>(input)?;
+
+ assert_eq!(
+ wan_config.dns_record.fqdn,
+ FQDN::from_str("dyn.domain.com")?
+ );
+ assert_eq!(wan_config.interface, None);
+
+ assert_eq!(wan_config.providers.len(), 1);
+ let expected = GandiConfig::new("SOME-API-KEY".to_string());
+ let actual = wan_config.providers.get(0).unwrap();
+ assert_eq!(&Gandi(expected), actual);
+ }
}
- #[test]
- fn check_defaults_on_dns_record_deserialization() {
- let input = json!({
- "fqdn": "dyn.mydomain.com",
- });
+ test! {
+ fn check_defaults_on_dns_record_deserialization() {
+ let input = json!({
+ "fqdn": "dyn.mydomain.com",
+ });
- let dns_record = serde_json::from_value::<DnsRecord>(input).unwrap();
+ let dns_record = serde_json::from_value::<DnsRecord>(input)?;
- assert_eq!(dns_record.record_type, default_record_type());
- assert_eq!(dns_record.ttl, default_ttl());
+ assert_eq!(dns_record.record_type, default_record_type());
+ assert_eq!(dns_record.ttl, default_ttl());
- assert_eq!(dns_record.fqdn, FQDN::from_str("dyn.mydomain.com").unwrap());
+ assert_eq!(dns_record.fqdn, FQDN::from_str("dyn.mydomain.com")?);
+ }
}
- #[test]
- fn check_file_configs() -> Result<(), Box<dyn Error>> {
- let path = Path::new("test");
- for entry in read_dir(path)? {
- let entry = entry?;
- let test_file_path = entry.path();
- if test_file_path.extension().unwrap_or_default() != "json" {
- continue;
+ test! {
+ fn check_test_file_configs() {
+ let path = Path::new("test");
+ for entry in read_dir(path)? {
+ let entry = entry?;
+ let test_file_path = entry.path();
+ if test_file_path.extension().unwrap_or_default() != "json" {
+ continue;
+ }
+
+ Config::load(vec![test_file_path])?;
}
+ }
+ }
- let file = File::open(test_file_path)?;
- let reader = BufReader::new(file);
+ test! {
+ fn check_chooses_fallback_config() {
+ let configs = vec![PathBuf::from_str("test/unknown.json")?,
+ PathBuf::from_str("test/also-unknown.json")?,
+ PathBuf::from_str("test/config.bork")?,
+ PathBuf::from_str("test/noop.json")?];
- let actual: Config = serde_json::from_reader(reader)?;
- assert_eq!(actual.networks.len(), 2);
+ Config::load(configs)?;
}
- Ok(())
}
- #[test]
- fn check_missing_config() -> Result<(), Box<dyn Error>> {
- let path = Path::new("test/unknown.yaml");
+ test! {
+ fn missing_config() {
+ let path = Path::new("test/unknown.yaml");
- assert_file_not_found!(Config::load(path), path);
- Ok(())
+ assert_config_file_not_found!(Config::load(vec![path.to_path_buf()]), path);
+ }
}
- #[test]
- fn check_broken_config() -> Result<(), Box<dyn Error>> {
- let path = Path::new("test/config.bork");
+ test! {
+ fn broken_config() {
+ let path = Path::new("test/config.bork");
- assert_config_parse_error!(Config::load(path), path);
- Ok(())
+ assert_config_parse_error!(Config::load(vec![path.to_path_buf()]), path);
+ }
}
- #[test]
- fn check_inaccessible_config() -> Result<(), Box<dyn Error>> {
- let path = Path::new("/root/secure-config.json");
+ test! {
+ fn inaccessible_config() {
+ let path = Path::new("/root/secure-config.json");
- assert_io_error!(Config::load(path));
- Ok(())
+ assert_io_error!(Config::load(vec![path.to_path_buf()]));
+ }
}
}