You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
genmarkov/src/config.rs

283 lines
6.8 KiB

//! Server config
use super::*;
use std::{
net::SocketAddr,
path::Path,
io,
borrow::Cow,
num::NonZeroU64,
error,
fmt,
};
use tokio::{
fs::OpenOptions,
prelude::*,
time::Duration,
io::BufReader,
};
use ipfilt::IpFilter;
pub const DEFAULT_FILE_LOCATION: &'static str = "markov.toml";
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash, Serialize, Deserialize)]
pub struct Config
{
pub bindpoint: String,
pub file: String,
pub max_content_length: u64,
pub max_gen_size: usize,
pub save_interval_secs: Option<NonZeroU64>,
pub trust_x_forwarded_for: bool,
#[serde(default)]
pub feed_bounds: String,
#[serde(default)]
pub filter: FilterConfig,
#[serde(default)]
pub writer: WriterConfig,
#[serde(default)]
pub mask: IpFilter,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Hash, Serialize, Deserialize)]
pub struct FilterConfig
{
#[serde(default)]
inbound: String,
#[serde(default)]
outbound: String,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Hash, Serialize, Deserialize)]
pub struct WriterConfig
{
pub backlog: usize,
pub internal_backlog: usize,
pub capacity: usize,
pub timeout_ms: Option<u64>,
pub throttle_ms: Option<u64>,
}
impl Default for WriterConfig
{
#[inline]
fn default() -> Self
{
Self {
backlog: 32,
internal_backlog: 8,
capacity: 4,
timeout_ms: None,
throttle_ms: None,
}
}
}
impl WriterConfig
{
fn create_settings(self, bounds: range::DynRange<usize>) -> handle::Settings
{
handle::Settings{
backlog: self.backlog,
internal_backlog: self.internal_backlog,
capacity: self.capacity,
timeout: self.timeout_ms.map(tokio::time::Duration::from_millis).unwrap_or(handle::DEFAULT_TIMEOUT),
throttle: self.throttle_ms.map(tokio::time::Duration::from_millis),
bounds,
}
}
}
impl FilterConfig
{
fn get_inbound_filter(&self) -> sanitise::filter::Filter
{
let filt: sanitise::filter::Filter = self.inbound.parse().unwrap();
if !filt.is_empty()
{
info!("Loaded inbound filter: {:?}", filt.iter().collect::<String>());
}
filt
}
fn get_outbound_filter(&self) -> sanitise::filter::Filter
{
let filt: sanitise::filter::Filter = self.outbound.parse().unwrap();
if !filt.is_empty()
{
info!("Loaded outbound filter: {:?}", filt.iter().collect::<String>());
}
filt
}
}
impl Default for Config
{
#[inline]
fn default() -> Self
{
Self {
bindpoint: SocketAddr::from(([127,0,0,1], 8001)).to_string(),
file: "chain.dat".to_owned(),
max_content_length: 1024 * 1024 * 4,
max_gen_size: 256,
save_interval_secs: Some(unsafe{NonZeroU64::new_unchecked(2)}),
trust_x_forwarded_for: false,
filter: Default::default(),
feed_bounds: "2..".to_owned(),
writer: Default::default(),
mask: Default::default(),
}
}
}
impl Config
{
/// Try to generate a config cache for this instance.
pub fn try_gen_cache(&self) -> Result<Cache, InvalidConfigError>
{
macro_rules! section {
($name:literal, $expr:expr) => {
match $expr {
Ok(v) => Ok(v),
Err(e) => Err(InvalidConfigError($name, Box::new(e))),
}
}
}
use std::ops::RangeBounds;
let feed_bounds = section!("feed_bounds", self.parse_feed_bounds()).and_then(|bounds| if bounds.contains(&0) {
Err(InvalidConfigError("feed_bounds", Box::new(opaque_error!("Bounds not allowed to contains 0 (they were `{}`)", bounds))))
} else {
Ok(bounds)
})?;
Ok(Cache {
handler_settings: self.writer.create_settings(feed_bounds.clone()),
feed_bounds,
inbound_filter: self.filter.get_inbound_filter(),
outbound_filter: self.filter.get_outbound_filter(),
})
}
/// Try to parse the `feed_bounds`
fn parse_feed_bounds(&self) -> Result<range::DynRange<usize>, range::ParseError>
{
if self.feed_bounds.len() == 0 {
Ok(feed::DEFAULT_FEED_BOUNDS.into())
} else {
self.feed_bounds.parse()
}
}
pub fn save_interval(&self) -> Option<Duration>
{
self.save_interval_secs.map(|x| Duration::from_secs(x.into()))
}
pub async fn load(from: impl AsRef<Path>) -> io::Result<Self>
{
let file = OpenOptions::new()
.read(true)
.open(from).await?;
let mut buffer= String::new();
let reader = BufReader::new(file);
let mut lines = reader.lines();
while let Some(line) = lines.next_line().await? {
buffer.push_str(&line[..]);
buffer.push('\n');
}
toml::de::from_str(&buffer[..]).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))
}
pub async fn save(&self, to: impl AsRef<Path>) -> io::Result<()>
{
let config = toml::ser::to_string_pretty(self).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(to).await?;
file.write_all(config.as_bytes()).await?;
file.shutdown().await?;
Ok(())
}
}
/// Try to load config file specified by args, or default config file
pub fn load() -> impl futures::future::Future<Output =Option<Config>>
{
load_args(std::env::args().skip(1))
}
async fn load_args<I: Iterator<Item=String>>(mut from: I) -> Option<Config>
{
let place = if let Some(arg) = from.next() {
trace!("File {:?} provided", arg);
Cow::Owned(arg)
} else {
warn!("No config file provided. Using default location {:?}", DEFAULT_FILE_LOCATION);
Cow::Borrowed(DEFAULT_FILE_LOCATION)
};
match Config::load(place.as_ref()).await {
Ok(cfg) => {
info!("Loaded config file {:?}", place);
Some(cfg)
},
Err(err) => {
error!("Failed to load config file from {:?}: {}", place, err);
None
},
}
}
#[derive(Debug)]
pub struct InvalidConfigError(&'static str, Box<dyn error::Error+ 'static>);
impl InvalidConfigError
{
pub fn field(&self) -> &str
{
&self.0[..]
}
}
impl error::Error for InvalidConfigError
{
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
Some(self.1.as_ref())
}
}
impl fmt::Display for InvalidConfigError
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f,"failed to parse field `{}`: {}", self.0, self.1)
}
}
/// Caches some parsed config arguments
#[derive(Clone, PartialEq)]
pub struct Cache
{
pub feed_bounds: range::DynRange<usize>,
pub inbound_filter: sanitise::filter::Filter,
pub outbound_filter: sanitise::filter::Filter,
pub handler_settings: handle::Settings,
}
impl fmt::Debug for Cache
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
f.debug_struct("Cache")
.field("feed_bounds", &self.feed_bounds)
.field("inbound_filter", &self.inbound_filter.iter().collect::<String>())
.field("outbound_filter", &self.outbound_filter.iter().collect::<String>())
.field("handler_settings", &self.handler_settings)
.finish()
}
}