[web] Add support for reading from a configuration file

This commit is contained in:
Emi Simpson 2021-10-29 12:48:20 -04:00
parent 5087d4738d
commit 7f3b9ef2ec
Signed by: Emi
GPG Key ID: A12F2C2FFDC3D847
3 changed files with 112 additions and 5 deletions

View File

@ -12,6 +12,8 @@ log = "0.4"
env_logger = "0.9"
askama = "0.10"
argh = "0.1.6"
serde = "1.0"
serde_yaml = "0.8"
rusttype = { version = "0.9.2", optional = true }
image = { version = "0.23.14", optional = true }
lazy_static = { version = "1.4.0", optional = true }

View File

@ -1,4 +1,9 @@
use std::fs::OpenOptions;
use std::fs::File;
use std::io::Write;
use pronouns_today::InstanceSettings;
use argh::FromArgs;
use serde::{Deserialize, Serialize};
#[derive(FromArgs, PartialEq, Debug)]
/// Everything you need to selfhost a pronouns.today server
@ -45,12 +50,14 @@ pub struct Run {
option,
default = "String::from(\"/etc/pronouns_today.yml\")",
short = 'c'))]
/// path to the config file (generated if needed)
/// path to the config file (generated if needed). Defaults to
/// /etc/pronouns_today.yml
pub config: String,
#[argh(switch)]
/// don't generate a config file if not present
pub no_generate_cfg: bool,
/// don't attempt to read or generate a config file, just use command line args. Missing
/// options will be filled in using sane defaults
pub no_read_cfg: bool,
#[argh(option, short = 'p')]
/// the port to listen on
@ -61,3 +68,81 @@ pub struct Run {
/// used in the custom URLs, e.g. "acaqeawdym")
pub default_prefstr: Option<String>,
}
impl Run {
/// Read the config from the provided file, and apply overrides from args
///
/// If `--no-read-cfg` was passed, no config file is present, and there aren't
/// enough args to populate the configuration, returns an [`Err`] with a list of
/// arguments that were missing.
pub fn load_config(&self) -> Result<Conf, ConfigError> {
if !self.no_read_cfg {
match File::open(&self.config) {
Ok(raw_cfg) => {
serde_yaml::from_reader(raw_cfg)
.map_err(ConfigError::MalformedConfig)
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
OpenOptions::new()
.write(true)
.create(true)
.open(&self.config)
.and_then(|mut c|
c.write_all(include_bytes!("../assets/default_config.yml"))
)
.map_err(ConfigError::IoError)?;
Err(ConfigError::ConfigCreated(self.config.clone()))
}
Err(e) => {
Err(ConfigError::IoError(e))
}
}
} else {
Ok(Conf::default())
}.map(|config| config.update_with(&self))
}
}
#[derive(Clone, Serialize, Deserialize, Debug)]
/// A parsed config file for Pronouns.Today web
pub struct Conf {
#[serde(flatten)]
/// General settings for the pronouns.today library
pub instance_settings: InstanceSettings,
/// The port for the server to bind to. Defaults to 1312
pub port: u16,
}
impl Conf {
fn update_with(mut self, args: &Run) -> Conf {
self.port = args.port.unwrap_or(self.port);
self
}
}
impl Default for Conf {
fn default() -> Self {
Conf {
instance_settings: InstanceSettings::default(),
port: 1312,
}
}
}
/// An error occured while trying to load the configuration
pub enum ConfigError {
/// There wase a problem reading/writing the config file
IoError(std::io::Error),
/// There wase a problem deserializing the config file
MalformedConfig(serde_yaml::Error),
/// The config file was missing, but has been created
///
/// The program should now prompt the user to fill the config in, and then exit. The
/// provided [`String`] contains the path to the new config file
ConfigCreated(String),
}

View File

@ -3,6 +3,9 @@ pub mod contrast;
pub mod statics;
pub mod configuration;
use configuration::ConfigError;
use std::process::exit;
use std::collections::HashMap;
use std::fmt::{self, Display};
@ -142,6 +145,7 @@ async fn not_found() -> impl Responder {
#[actix_web::main]
async fn main() -> std::io::Result<()> {
env_logger::init();
let args: configuration::PronounsTodayArgs = argh::from_env();
match args.command {
@ -149,14 +153,30 @@ async fn main() -> std::io::Result<()> {
println!("Support for dumping statics not yet implemented");
Ok(())
}
configuration::SubCommand::Run(_subargs) => {
configuration::SubCommand::Run(subargs) => {
let config = match subargs.load_config() {
Ok(config) => config,
Err(ConfigError::IoError(e)) => {
return Err(e);
}
Err(ConfigError::MalformedConfig(e)) => {
eprintln!("Error parsing config file:\n{}", e);
exit(1001);
}
Err(ConfigError::ConfigCreated(path)) => {
println!("A config file has been generated at {}! Please check it out
and modify it to your liking, and then run this command
again", path);
exit(0);
}
};
log::info!("Starting with configuration {:?}", config);
start_server().await
}
}
}
async fn start_server() -> std::io::Result<()> {
env_logger::init();
println!("Starting pronouns-today-web on 127.0.0.1:8080");
HttpServer::new(|| {
let logger = Logger::default();