use std::collections::hash_map::HashMap;
use std::fs::File;
use std::io;
use std::path::Path;
use serde::Deserialize;
use serde_json::Value;
use thiserror::Error;
use crate::handler::Handler;
#[derive(Deserialize, Debug)]
pub struct Config {
#[serde(default)]
pub(crate) post_paths: HashMap<String, Handler>,
secrets: Option<String>,
}
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("deserialization error: {}", source)]
Deserialize {
#[source]
source: serde_json::Error,
},
#[error("failed to read file: {}", source)]
Read {
#[source]
source: io::Error,
},
}
impl Config {
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, ConfigError> {
let fin = File::open(path.as_ref()).map_err(|source| {
ConfigError::Read {
source,
}
})?;
serde_json::from_reader(fin).map_err(|source| {
ConfigError::Deserialize {
source,
}
})
}
pub(crate) fn secrets(&self) -> Result<Value, ConfigError> {
if let Some(ref path) = self.secrets {
let fin = File::open(path).map_err(|source| {
ConfigError::Read {
source,
}
})?;
serde_json::from_reader(fin).map_err(|source| {
ConfigError::Deserialize {
source,
}
})
} else {
Ok(Value::Null)
}
}
#[cfg(test)]
pub(crate) fn secrets_path(&self) -> Option<&String> {
self.secrets.as_ref()
}
}
#[cfg(test)]
mod test {
use std::fs::File;
use std::io::Write;
use serde_json::{json, Value};
use crate::config::{Config, ConfigError};
use crate::test_utils;
#[test]
fn test_config_empty_paths() {
let tempdir = test_utils::create_tempdir();
let path = test_utils::write_config(tempdir.path(), json!({}));
let config = Config::from_path(path).unwrap();
assert!(config.post_paths.is_empty());
assert_eq!(config.secrets, None);
assert_eq!(config.secrets().unwrap(), Value::Null);
}
#[test]
fn test_config_unreadable() {
let path = {
let tempdir = test_utils::create_tempdir();
test_utils::write_config(tempdir.path(), json!({}))
};
let err = Config::from_path(path).unwrap_err();
if let ConfigError::Read {
..
} = err
{
} else {
panic!("unexpected error: {:?}", err);
}
}
#[test]
fn test_config_unparseable() {
let tempdir = test_utils::create_tempdir();
let path = tempdir.path().join("config.json");
{
let mut fout = File::create(&path).unwrap();
fout.write_all(b"not json\n").unwrap();
}
let err = Config::from_path(path).unwrap_err();
if let ConfigError::Deserialize {
..
} = err
{
} else {
panic!("unexpected error: {:?}", err);
}
}
#[test]
fn test_secrets_unreadable() {
let config = {
let tempdir = test_utils::create_tempdir();
let (path, _) = test_utils::write_config_secrets(tempdir.path(), json!({}), json!({}));
Config::from_path(path).unwrap()
};
let err = config.secrets().unwrap_err();
if let ConfigError::Read {
..
} = err
{
} else {
panic!("unexpected error: {:?}", err);
}
}
#[test]
fn test_secrets_unparseable() {
let tempdir = test_utils::create_tempdir();
let secrets_path = tempdir.path().join("secrets.json");
let path = test_utils::write_config(
tempdir.path(),
json!({
"secrets": secrets_path.to_str().unwrap(),
}),
);
{
let mut fout = File::create(&secrets_path).unwrap();
fout.write_all(b"not json\n").unwrap();
}
let config = Config::from_path(path).unwrap();
let err = config.secrets().unwrap_err();
if let ConfigError::Deserialize {
..
} = err
{
} else {
panic!("unexpected error: {:?}", err);
}
}
#[test]
fn test_config_parse() {
let tempdir = test_utils::create_tempdir();
let path = test_utils::write_config(
tempdir.path(),
json!({
"post_paths": {
"hostname.example": {
"path": "path",
"filters": [
{
"kind": "blah",
},
],
"header_name": "X-WebHook-Type",
},
},
}),
);
let config = Config::from_path(path).unwrap();
assert_eq!(config.post_paths.len(), 1);
}
}