move config management to own file
This commit is contained in:
parent
cf85a6494a
commit
a4543c48ec
4 changed files with 160 additions and 135 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -113,7 +113,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "actix-server"
|
name = "actix-server"
|
||||||
version = "1.0.0"
|
version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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 = [
|
dependencies = [
|
||||||
"actix-macros 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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-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-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)",
|
"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)",
|
"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-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-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-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-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-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)",
|
"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-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-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-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-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-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"
|
"checksum actix-threadpool 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf4082192601de5f303013709ff84d81ca6a1bc4af7fb24f367a500a23c6e84e"
|
||||||
|
|
|
@ -21,6 +21,7 @@ itertools = "0.8"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
simple_logger = "1.3"
|
simple_logger = "1.3"
|
||||||
clap = { version = "2.33", features = ["yaml", "wrap_help"] }
|
clap = { version = "2.33", features = ["yaml", "wrap_help"] }
|
||||||
|
# rlua = "0.17"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.1"
|
tempfile = "3.1"
|
||||||
|
|
152
src/config.rs
Normal file
152
src/config.rs
Normal file
|
@ -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<String>,
|
||||||
|
pub groups: Vec<RouteGroup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
|
||||||
|
pub struct RouteGroup {
|
||||||
|
pub name: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub routes: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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<String>,
|
||||||
|
source: Option<String>,
|
||||||
|
script: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(())
|
||||||
|
}
|
||||||
|
}
|
134
src/main.rs
134
src/main.rs
|
@ -1,25 +1,22 @@
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
|
use crate::config::{read_config, RouteGroup};
|
||||||
use actix_web::{middleware::Logger, App, HttpServer};
|
use actix_web::{middleware::Logger, App, HttpServer};
|
||||||
use clap::{crate_authors, crate_version, load_yaml, App as ClapApp};
|
use clap::{crate_authors, crate_version, load_yaml, App as ClapApp};
|
||||||
use error::BunBunError;
|
use error::BunBunError;
|
||||||
use handlebars::Handlebars;
|
use handlebars::Handlebars;
|
||||||
use hotwatch::{Event, Hotwatch};
|
use hotwatch::{Event, Hotwatch};
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, info, trace, warn};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs::{read_to_string, OpenOptions};
|
|
||||||
use std::io::Write;
|
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
mod config;
|
||||||
mod error;
|
mod error;
|
||||||
mod routes;
|
mod routes;
|
||||||
mod template_args;
|
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
|
/// Dynamic variables that either need to be present at runtime, or can be
|
||||||
/// changed during runtime.
|
/// changed during runtime.
|
||||||
pub struct State {
|
pub struct State {
|
||||||
|
@ -97,57 +94,6 @@ fn init_logger(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, PartialEq)]
|
|
||||||
struct Config {
|
|
||||||
bind_address: String,
|
|
||||||
public_address: String,
|
|
||||||
default_route: Option<String>,
|
|
||||||
groups: Vec<RouteGroup>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
|
|
||||||
struct RouteGroup {
|
|
||||||
name: String,
|
|
||||||
description: Option<String>,
|
|
||||||
routes: HashMap<String, String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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<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)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generates a hashmap of routes from the data structure created by the config
|
/// 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
|
/// file. This should improve runtime performance and is a better solution than
|
||||||
/// just iterating over the config object for every hop resolution.
|
/// 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)]
|
#[cfg(test)]
|
||||||
mod cache_routes {
|
mod cache_routes {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
Loading…
Reference in a new issue