From a4543c48ec3eaa9c84fb07520b727417a6a76eda Mon Sep 17 00:00:00 2001 From: Edward Shen Date: Sun, 29 Dec 2019 00:48:02 -0500 Subject: [PATCH] move config management to own file --- Cargo.lock | 8 +-- Cargo.toml | 1 + src/config.rs | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 134 +------------------------------------------- 4 files changed, 160 insertions(+), 135 deletions(-) create mode 100644 src/config.rs diff --git a/Cargo.lock b/Cargo.lock index c6efad9..cb10cc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,7 +113,7 @@ dependencies = [ [[package]] name = "actix-server" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -145,7 +145,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "actix-macros 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "actix-rt 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-server 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-server 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "actix-service 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -206,7 +206,7 @@ dependencies = [ "actix-macros 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "actix-router 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "actix-rt 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-server 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-server 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "actix-service 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "actix-testing 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "actix-threadpool 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1738,7 +1738,7 @@ dependencies = [ "checksum actix-macros 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "21705adc76bbe4bc98434890e73a89cd00c6015e5704a60bb6eea6c3b72316b6" "checksum actix-router 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7ad01d9350616bbf91c7a651b40b9205a58076a069c7b8094d15e2fcf17c2edc" "checksum actix-rt 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6a0a55507046441a496b2f0d26a84a65e67c8cafffe279072412f624b5fb6d" -"checksum actix-server 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5d8a9cd23ebd54ebfd8dc4f93313c142f9d2d505b3e40865d6b384fedfc4748" +"checksum actix-server 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "51d3455eaac03ca3e49d7b822eb35c884b861f715627254ccbe4309d08f1841a" "checksum actix-service 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a5ecef49693fcfe2c13a34c7218eb5b7898ff3fbe334db8445759f871fec2df" "checksum actix-testing 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "48494745b72d0ea8ff0cf874aaf9b622a3ee03d7081ee0c04edea4f26d32c911" "checksum actix-threadpool 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf4082192601de5f303013709ff84d81ca6a1bc4af7fb24f367a500a23c6e84e" diff --git a/Cargo.toml b/Cargo.toml index 9517b8d..0fbaf30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ itertools = "0.8" log = "0.4" simple_logger = "1.3" clap = { version = "2.33", features = ["yaml", "wrap_help"] } +# rlua = "0.17" [dev-dependencies] tempfile = "3.1" diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..ee959f5 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,152 @@ +use crate::BunBunError; +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, + pub groups: Vec, +} + +#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] +pub struct RouteGroup { + pub name: String, + pub description: Option, + pub routes: HashMap, +} + +// TODO implement rlua: +// # use rlua::{Lua, Result}; +// # fn main() -> Result<()> { +// let lua = Lua::new(); +// lua.context(|lua_context| { +// lua_context.load(r#" +// print("hello world!") +// "#).exec() +// })?; +// # Ok(()) +// # } + +#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] +struct Route { + dest: Option, + source: Option, + script: Option, +} + +/// 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 { + 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(()) + } +} diff --git a/src/main.rs b/src/main.rs index ac9a4a2..047f3bc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,25 +1,22 @@ #![forbid(unsafe_code)] +use crate::config::{read_config, RouteGroup}; use actix_web::{middleware::Logger, App, HttpServer}; use clap::{crate_authors, crate_version, load_yaml, App as ClapApp}; use error::BunBunError; use handlebars::Handlebars; use hotwatch::{Event, Hotwatch}; -use log::{debug, error, info, trace, warn}; -use serde::{Deserialize, Serialize}; +use log::{debug, info, trace, warn}; use std::cmp::min; use std::collections::HashMap; -use std::fs::{read_to_string, OpenOptions}; -use std::io::Write; use std::sync::{Arc, RwLock}; use std::time::Duration; +mod config; mod error; mod routes; mod template_args; -const DEFAULT_CONFIG: &[u8] = include_bytes!("../bunbun.default.yaml"); - /// Dynamic variables that either need to be present at runtime, or can be /// changed during runtime. pub struct State { @@ -97,57 +94,6 @@ fn init_logger( Ok(()) } -#[derive(Deserialize, Debug, PartialEq)] -struct Config { - bind_address: String, - public_address: String, - default_route: Option, - groups: Vec, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] -struct RouteGroup { - name: String, - description: Option, - routes: HashMap, -} - -/// Attempts to read the config file. If it doesn't exist, generate one a -/// default config file before attempting to parse it. -fn read_config(config_file_path: &str) -> Result { - 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)?) -} - /// Generates a hashmap of routes from the data structure created by the config /// file. This should improve runtime performance and is a better solution than /// just iterating over the config object for every hop resolution. @@ -265,80 +211,6 @@ mod init_logger { } } -#[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(()) - } -} - #[cfg(test)] mod cache_routes { use super::*;