fancier error handling for config
This commit is contained in:
		
							parent
							
								
									e4be6d9588
								
							
						
					
					
						commit
						9a90b18caf
					
				
					 2 changed files with 67 additions and 39 deletions
				
			
		| 
						 | 
					@ -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)),
 | 
				
			||||||
fn parse_var<E: Debug, T: FromStr<Err = E>>(name: &str) -> Result<T, VarError> {
 | 
					        Err(VarError::NotPresent) => Ok(None),
 | 
				
			||||||
    get_var(name).map(|s| {
 | 
					        Err(e) => Err(e).with_context(|| format!("Failed to read variable {}", var_name)),
 | 
				
			||||||
        s.parse()
 | 
					 | 
				
			||||||
            .unwrap_or_else(|e| panic!("Failed to parse {}{}: {:?}", VAR_PREFIX, name, e))
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn expect_var(name: &str) -> String {
 | 
					 | 
				
			||||||
    get_var(name).unwrap_or_else(|_| panic!("{}{} must be set", VAR_PREFIX, name))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn load() -> Config {
 | 
					 | 
				
			||||||
    let capture_images = parse_var("CAPTURE_IMAGES").unwrap_or(true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Config {
 | 
					 | 
				
			||||||
        capture_images,
 | 
					 | 
				
			||||||
        capture_audio_duration: parse_var("CAPTURE_AUDIO_DURATION").ok(),
 | 
					 | 
				
			||||||
        shows_file: parse_var("SHOWS_FILE").unwrap_or(PathBuf::from("./shows.yaml")),
 | 
					 | 
				
			||||||
        global_tags: get_var("GLOBAL_TAGS")
 | 
					 | 
				
			||||||
            .map(|s| s.split(',').map(String::from).collect())
 | 
					 | 
				
			||||||
            .unwrap_or_default(),
 | 
					 | 
				
			||||||
        post_interval: Duration::from_secs(parse_var("POST_INTERVAL").unwrap_or_default()),
 | 
					 | 
				
			||||||
        cohost_email: expect_var("COHOST_EMAIL"),
 | 
					 | 
				
			||||||
        cohost_password: expect_var("COHOST_PASSWORD"),
 | 
					 | 
				
			||||||
        cohost_page: expect_var("COHOST_PAGE"),
 | 
					 | 
				
			||||||
        cohost_draft: parse_var("COHOST_DRAFT").unwrap_or(false),
 | 
					 | 
				
			||||||
        cohost_cw: parse_var("COHOST_CW").unwrap_or(capture_images),
 | 
					 | 
				
			||||||
        eighteen_plus: parse_var("18PLUS").unwrap_or(false),
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_var<E: Debug + Send + Sync + Error + 'static, T: FromStr<Err = E>>(
 | 
				
			||||||
 | 
					    name: &str,
 | 
				
			||||||
 | 
					) -> anyhow::Result<Option<T>> {
 | 
				
			||||||
 | 
					    get_var(name)?
 | 
				
			||||||
 | 
					        .map(|s| {
 | 
				
			||||||
 | 
					            s.parse()
 | 
				
			||||||
 | 
					                .with_context(|| format!("Failed to parse variable {}{}", VAR_PREFIX, name))
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .transpose()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn require_var(name: &str) -> anyhow::Result<String> {
 | 
				
			||||||
 | 
					    get_var(name)?.ok_or_else(|| anyhow!("{}{} must be set", VAR_PREFIX, name))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn load() -> anyhow::Result<Config> {
 | 
				
			||||||
 | 
					    let capture_images = parse_var("CAPTURE_IMAGES")?.unwrap_or(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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_audio_duration,
 | 
				
			||||||
 | 
					        shows_file: parse_var("SHOWS_FILE")?.unwrap_or_else(|| PathBuf::from("./shows.yaml")),
 | 
				
			||||||
 | 
					        global_tags: get_var("GLOBAL_TAGS")?
 | 
				
			||||||
 | 
					            .map(|s| s.split(',').map(String::from).collect())
 | 
				
			||||||
 | 
					            .unwrap_or_default(),
 | 
				
			||||||
 | 
					        post_interval: Duration::from_secs(parse_var("POST_INTERVAL")?.unwrap_or_default()),
 | 
				
			||||||
 | 
					        cohost_email: require_var("COHOST_EMAIL")?,
 | 
				
			||||||
 | 
					        cohost_password: require_var("COHOST_PASSWORD")?,
 | 
				
			||||||
 | 
					        cohost_page: require_var("COHOST_PAGE")?,
 | 
				
			||||||
 | 
					        cohost_draft: parse_var("COHOST_DRAFT")?.unwrap_or(false),
 | 
				
			||||||
 | 
					        cohost_cw: parse_var("COHOST_CW")?.unwrap_or(capture_images),
 | 
				
			||||||
 | 
					        eighteen_plus: parse_var("18PLUS")?.unwrap_or(false),
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										14
									
								
								src/main.rs
									
										
									
									
									
								
							
							
						
						
									
										14
									
								
								src/main.rs
									
										
									
									
									
								
							| 
						 | 
					@ -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);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue