bunbun/src/config.rs

134 lines
3.5 KiB
Rust
Raw Normal View History

2019-12-31 22:36:21 +00:00
use crate::{routes::Route, BunBunError};
2019-12-29 05:48:02 +00:00
use log::{debug, error, info, trace};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs::{read_to_string, OpenOptions};
use std::io::Write;
const DEFAULT_CONFIG: &[u8] = include_bytes!("../bunbun.default.yaml");
#[derive(Deserialize, Debug, PartialEq)]
pub struct Config {
pub bind_address: String,
pub public_address: String,
pub default_route: Option<String>,
pub groups: Vec<RouteGroup>,
}
#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
pub struct RouteGroup {
pub name: String,
pub description: Option<String>,
2019-12-31 22:36:21 +00:00
pub routes: HashMap<String, Route>,
2019-12-29 05:48:02 +00:00
}
/// Attempts to read the config file. If it doesn't exist, generate one a
/// default config file before attempting to parse it.
pub fn read_config(config_file_path: &str) -> Result<Config, BunBunError> {
trace!("Loading config file...");
let config_str = match read_to_string(config_file_path) {
Ok(conf_str) => {
debug!("Successfully loaded config file into memory.");
conf_str
}
Err(_) => {
info!(
"Unable to find a {} file. Creating default!",
config_file_path
);
let fd = OpenOptions::new()
.write(true)
.create_new(true)
.open(config_file_path);
match fd {
Ok(mut fd) => fd.write_all(DEFAULT_CONFIG)?,
Err(e) => {
error!("Failed to write to {}: {}. Default config will be loaded but not saved.", config_file_path, e);
}
};
String::from_utf8_lossy(DEFAULT_CONFIG).into_owned()
}
};
// Reading from memory is faster than reading directly from a reader for some
// reason; see https://github.com/serde-rs/json/issues/160
Ok(serde_yaml::from_str(&config_str)?)
}
#[cfg(test)]
mod read_config {
use super::*;
use tempfile::NamedTempFile;
#[test]
fn returns_default_config_if_path_does_not_exist() {
assert_eq!(
read_config("/this_is_a_non_existent_file").unwrap(),
serde_yaml::from_slice(DEFAULT_CONFIG).unwrap()
);
}
#[test]
fn returns_error_if_given_empty_config() {
assert_eq!(
read_config("/dev/null").unwrap_err().to_string(),
"EOF while parsing a value"
);
}
#[test]
fn returns_error_if_given_invalid_config() -> Result<(), std::io::Error> {
let mut tmp_file = NamedTempFile::new()?;
tmp_file.write_all(b"g")?;
assert_eq!(
read_config(tmp_file.path().to_str().unwrap())
.unwrap_err()
.to_string(),
r#"invalid type: string "g", expected struct Config at line 1 column 1"#
);
Ok(())
}
#[test]
fn returns_error_if_config_missing_field() -> Result<(), std::io::Error> {
let mut tmp_file = NamedTempFile::new()?;
tmp_file.write_all(
br#"
bind_address: "localhost"
public_address: "localhost"
"#,
)?;
assert_eq!(
read_config(tmp_file.path().to_str().unwrap())
.unwrap_err()
.to_string(),
"missing field `groups` at line 2 column 19"
);
Ok(())
}
#[test]
fn returns_ok_if_valid_config() -> Result<(), std::io::Error> {
let mut tmp_file = NamedTempFile::new()?;
tmp_file.write_all(
br#"
bind_address: "a"
public_address: "b"
groups: []"#,
)?;
assert_eq!(
read_config(tmp_file.path().to_str().unwrap()).unwrap(),
Config {
bind_address: String::from("a"),
public_address: String::from("b"),
groups: vec![],
default_route: None,
}
);
Ok(())
}
}