fancier error handling for config

This commit is contained in:
xenofem 2023-07-30 02:47:55 -04:00
parent e4be6d9588
commit 9a90b18caf
2 changed files with 67 additions and 39 deletions

View file

@ -1,11 +1,14 @@
use std::{ use std::{
env::{self, VarError}, env::{self, VarError},
error::Error,
fmt::Debug, fmt::Debug,
path::PathBuf, path::PathBuf,
str::FromStr, str::FromStr,
time::Duration, time::Duration,
}; };
use anyhow::{anyhow, Context};
pub struct Config { pub struct Config {
pub capture_images: bool, pub capture_images: bool,
pub capture_audio_duration: Option<f64>, pub capture_audio_duration: Option<f64>,
@ -22,37 +25,62 @@ pub struct Config {
const VAR_PREFIX: &str = "SCREENCAP_BOT_"; const VAR_PREFIX: &str = "SCREENCAP_BOT_";
fn get_var(name: &str) -> Result<String, VarError> { fn get_var(name: &str) -> anyhow::Result<Option<String>> {
env::var(VAR_PREFIX.to_string() + name) let var_name = VAR_PREFIX.to_string() + name;
match env::var(&var_name) {
Ok(v) => Ok(Some(v)),
Err(VarError::NotPresent) => Ok(None),
Err(e) => Err(e).with_context(|| format!("Failed to read variable {}", var_name)),
}
} }
fn parse_var<E: Debug, T: FromStr<Err = E>>(name: &str) -> Result<T, VarError> { fn parse_var<E: Debug + Send + Sync + Error + 'static, T: FromStr<Err = E>>(
get_var(name).map(|s| { name: &str,
) -> anyhow::Result<Option<T>> {
get_var(name)?
.map(|s| {
s.parse() s.parse()
.unwrap_or_else(|e| panic!("Failed to parse {}{}: {:?}", VAR_PREFIX, name, e)) .with_context(|| format!("Failed to parse variable {}{}", VAR_PREFIX, name))
}) })
.transpose()
} }
fn expect_var(name: &str) -> String { fn require_var(name: &str) -> anyhow::Result<String> {
get_var(name).unwrap_or_else(|_| panic!("{}{} must be set", VAR_PREFIX, name)) get_var(name)?.ok_or_else(|| anyhow!("{}{} must be set", VAR_PREFIX, name))
} }
pub fn load() -> Config { pub fn load() -> anyhow::Result<Config> {
let capture_images = parse_var("CAPTURE_IMAGES").unwrap_or(true); let capture_images = parse_var("CAPTURE_IMAGES")?.unwrap_or(true);
Config { let capture_audio_duration = parse_var("CAPTURE_AUDIO_DURATION")?;
if let Some(d) = capture_audio_duration {
if d <= 0.0 {
return Err(anyhow!(
"{}CAPTURE_AUDIO_DURATION cannot be <= 0",
VAR_PREFIX
));
}
}
if let (false, None) = (capture_images, capture_audio_duration) {
return Err(anyhow!(
"At least one of image capture and audio capture must be enabled!"
));
}
Ok(Config {
capture_images, capture_images,
capture_audio_duration: parse_var("CAPTURE_AUDIO_DURATION").ok(), capture_audio_duration,
shows_file: parse_var("SHOWS_FILE").unwrap_or(PathBuf::from("./shows.yaml")), shows_file: parse_var("SHOWS_FILE")?.unwrap_or_else(|| PathBuf::from("./shows.yaml")),
global_tags: get_var("GLOBAL_TAGS") global_tags: get_var("GLOBAL_TAGS")?
.map(|s| s.split(',').map(String::from).collect()) .map(|s| s.split(',').map(String::from).collect())
.unwrap_or_default(), .unwrap_or_default(),
post_interval: Duration::from_secs(parse_var("POST_INTERVAL").unwrap_or_default()), post_interval: Duration::from_secs(parse_var("POST_INTERVAL")?.unwrap_or_default()),
cohost_email: expect_var("COHOST_EMAIL"), cohost_email: require_var("COHOST_EMAIL")?,
cohost_password: expect_var("COHOST_PASSWORD"), cohost_password: require_var("COHOST_PASSWORD")?,
cohost_page: expect_var("COHOST_PAGE"), cohost_page: require_var("COHOST_PAGE")?,
cohost_draft: parse_var("COHOST_DRAFT").unwrap_or(false), cohost_draft: parse_var("COHOST_DRAFT")?.unwrap_or(false),
cohost_cw: parse_var("COHOST_CW").unwrap_or(capture_images), cohost_cw: parse_var("COHOST_CW")?.unwrap_or(capture_images),
eighteen_plus: parse_var("18PLUS").unwrap_or(false), eighteen_plus: parse_var("18PLUS")?.unwrap_or(false),
} })
} }

View file

@ -27,13 +27,7 @@ async fn main() -> anyhow::Result<()> {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let conf = config::load(); let conf = config::load()?;
if let (false, None) = (conf.capture_images, conf.capture_audio_duration) {
return Err(anyhow!(
"At least one of image capture and audio capture must be enabled!"
));
}
info!("Loading shows from {}", conf.shows_file.display()); info!("Loading shows from {}", conf.shows_file.display());
let shows = shows::load(&conf.shows_file).with_context(|| { let shows = shows::load(&conf.shows_file).with_context(|| {
@ -110,6 +104,12 @@ async fn post_random_capture<R: Rng>(
Some(d) => media_info.duration_secs - d, Some(d) => media_info.duration_secs - d,
None => media_info.duration_secs, None => media_info.duration_secs,
}; };
if max_timestamp < 0.0 {
return Err(anyhow!(
"Media file {} is too short to take audio clip!",
file.display()
));
}
let timestamp = max_timestamp * rng.sample::<f64, _>(Standard); let timestamp = max_timestamp * rng.sample::<f64, _>(Standard);
let formatted_timestamp = format_timestamp(timestamp, Some(media_info.duration_secs)); let formatted_timestamp = format_timestamp(timestamp, Some(media_info.duration_secs));
info!("Taking capture at {}", formatted_timestamp); info!("Taking capture at {}", formatted_timestamp);