dovemail 0.0.1

Use Microsoft's PNP CLI with email clients.
Documentation
mod single_or_list;

use std::io::{BufReader, BufWriter};
use std::path::{Path, PathBuf};

use color_eyre::eyre;
use color_eyre::eyre::bail;

use crate::single_or_list::{MaybeValid, SingleOrList};
use dovemail::outlook::OutlookMessage;
use dovemail::{write_message, MessageWriteError};

fn main() -> eyre::Result<()> {
    color_eyre::install()?;
    tracing::subscriber::set_global_default(
        tracing_subscriber::FmtSubscriber::builder()
            .with_max_level(tracing::Level::WARN)
            .finish(),
    )?;

    let arg = get_arg()?;
    if &arg == "-h" || &arg == "--help" || &arg == "-help" {
        println!("usage: {} output_dir/", std::env::args().nth(0).unwrap());
        return Ok(())
    }
    let output_dir = PathBuf::from(arg);
    let messages = read_messages_from_stdin()?;

    let mut had_error = false;
    for maybe_message in messages {
        match maybe_message {
            MaybeValid::Valid(message) => {
                if message.from.is_none() {
                    tracing::warn!(
                        "Skipping message because `From` is missing. id={} subject={}",
                        message.id,
                        message.subject
                    );
                } else if let Err(e) = write_message_to_file(message, &output_dir) {
                    tracing::error!("{:?}", e);
                    had_error = true;
                }
            }
            MaybeValid::Invalid(value) => {
                let result: Result<OutlookMessage, _> = serde_json::from_value(value.clone());
                tracing::error!(
                    "Invalid Outlook message JSON: {:?} --- Result: {:?}",
                    value,
                    result
                );
                had_error = true;
            }
        }
    }
    if had_error {
        Err(eyre::Error::msg("Errors occurred."))
    } else {
        Ok(())
    }
}

fn write_message_to_file(
    message: OutlookMessage,
    output_dir: &Path,
) -> Result<(), MessageWriteError> {
    let file_name = format!("{:x}", seahash::hash(message.id.as_bytes()));
    let output_path = output_dir.join(file_name);

    let file = std::fs::File::create(output_path)?;
    let mut writer = BufWriter::new(file);
    write_message(&message, &mut writer)
}

// helper functions for input and argument parsing
// -----------------------------------------------

fn get_arg() -> eyre::Result<String> {
    let mut args = std::env::args();
    if args.len() != 2 {
        bail!("expected 1 positional argument.")
    }
    args.nth(1).ok_or_else(|| eyre::Error::msg("missing operand"))
}

fn read_messages_from_stdin() -> eyre::Result<Vec<MaybeValid<OutlookMessage>>> {
    let reader = BufReader::new(std::io::stdin());
    let data: SingleOrList<_> = serde_json::from_reader(reader)?;
    Ok(data.into_vec())
}