git-vet 0.1.1

Git-based review gate for tracked file contents
Documentation
use std::fmt;
use std::str::FromStr;

use serde::{Serialize, Serializer};
use thiserror::Error;

const NOTES_REF_PREFIX: &str = "refs/notes/vet";
pub const DEFAULT_REVIEW_CHANNEL: &str = "default";

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ReviewChannel {
    name: String,
    notes_ref: NotesRef,
}

impl ReviewChannel {
    pub(crate) const fn notes_ref(&self) -> &NotesRef {
        &self.notes_ref
    }
}

impl FromStr for ReviewChannel {
    type Err = ChannelError;

    fn from_str(input: &str) -> Result<Self, Self::Err> {
        if input.is_empty() {
            return Err(ChannelError {
                channel: input.to_owned(),
                details: "channel name must not be empty".to_owned(),
            });
        }

        let ref_name = format!("{NOTES_REF_PREFIX}/{input}");
        let notes_ref = NotesRef::new(ref_name).map_err(|details| ChannelError {
            channel: input.to_owned(),
            details,
        })?;

        Ok(Self {
            name: input.to_owned(),
            notes_ref,
        })
    }
}

impl Serialize for ReviewChannel {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&self.name)
    }
}

impl fmt::Display for ReviewChannel {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.name.fmt(f)
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NotesRef {
    name: String,
    full_name: gix::refs::FullName,
}

impl NotesRef {
    fn new(name: String) -> Result<Self, String> {
        let full_name =
            gix::refs::FullName::try_from(name.clone()).map_err(|error| error.to_string())?;
        Ok(Self { name, full_name })
    }

    pub(crate) fn as_str(&self) -> &str {
        &self.name
    }

    pub(crate) fn full_name(&self) -> gix::refs::FullName {
        self.full_name.clone()
    }
}

impl fmt::Display for NotesRef {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.name.fmt(f)
    }
}

#[derive(Clone, Debug, Eq, Error, PartialEq)]
#[error("invalid review channel {channel:?}: {details}")]
pub struct ChannelError {
    pub(crate) channel: String,
    pub(crate) details: String,
}