histdb-rs 2.0.1

Better history management for zsh. Based on ideas from [https://github.com/larkery/zsh-histdb](https://github.com/larkery/zsh-histdb).
use chrono::{
    DateTime,
    Utc,
};
use serde::{
    Deserialize,
    Serialize,
};
use std::{
    env,
    path::PathBuf,
};
use thiserror::Error;
use uuid::Uuid;

#[derive(Debug, Serialize, Deserialize)]
pub enum Message {
    Stop,

    Disable(Uuid),

    Enable(Uuid),

    CommandStart(CommandStart),

    CommandFinished(CommandFinished),
}

#[derive(Error, Debug)]
pub enum Error {
    #[error("can not get hostname: {0}")]
    GetHostname(std::io::Error),

    #[error("can not get current directory: {0}")]
    GetCurrentDir(std::io::Error),

    #[error("can not get current user: {0}")]
    GetUser(env::VarError),

    #[error("invalid session id in environment variable: {0}")]
    InvalidSessionIDEnvVar(env::VarError),

    #[error("invalid session id: {0}")]
    InvalidSessionID(uuid::Error),

    #[error("session id is missing")]
    MissingSessionID,

    #[error("retval is missing")]
    MissingRetval(std::env::VarError),

    #[error("invalid result: {0}")]
    InvalidResult(std::num::ParseIntError),
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CommandStart {
    pub command: String,
    pub pwd: PathBuf,
    pub session_id: Uuid,
    pub time_stamp: DateTime<Utc>,
    pub user: String,
    pub hostname: String,
}

impl CommandStart {
    pub fn from_env(command: String) -> Result<Self, Error> {
        let pwd = env::current_dir().map_err(Error::GetCurrentDir)?;

        let time_stamp = Utc::now();

        let user = env::var("USER").map_err(Error::GetUser)?;

        let session_id = session_id_from_env()?;

        let hostname = hostname::get()
            .map_err(Error::GetHostname)?
            .to_string_lossy()
            .to_string();

        Ok(Self {
            command,
            pwd,
            session_id,
            time_stamp,
            user,
            hostname,
        })
    }
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CommandFinished {
    pub session_id: Uuid,
    pub time_stamp: DateTime<Utc>,
    pub result: u16,
}

impl CommandFinished {
    pub fn from_env() -> Result<Self, Error> {
        let time_stamp = Utc::now();

        let session_id = session_id_from_env()?;

        let result = env::var("HISTDB_RS_RETVAL")
            .map_err(Error::MissingRetval)?
            .parse()
            .map_err(Error::InvalidResult)?;

        Ok(Self {
            session_id,
            time_stamp,
            result,
        })
    }
}

pub fn session_id_from_env() -> Result<Uuid, Error> {
    match env::var("HISTDB_RS_SESSION_ID") {
        Err(err) => match err {
            env::VarError::NotPresent => Err(Error::MissingSessionID),
            env::VarError::NotUnicode(_) => Err(Error::InvalidSessionIDEnvVar(err)),
        },

        Ok(s) => Uuid::parse_str(&s).map_err(Error::InvalidSessionID),
    }
}