i18n_codegen 0.1.1

Safe localization library using code generation
use crate::{Key, LocaleName};
use std::{collections::HashSet, fmt};

pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;

#[derive(Debug)]
pub(crate) enum Error {
    JsonParsing(serde_json::error::Error),
    ProcMacroInput(syn::Error),
    Io(std::io::Error),
    MissingEnvVar {
        name: String,
        inner_error: std::env::VarError,
    },
    DirectoryInLocalesFolder,
    NoFileStem,
    InvalidUtf8InFileName,
    UnbalancedPlaceholders {
        locale_name: LocaleName,
        string: String,
    },
    MissingKeysInLocale(Vec<MissingKeysInLocale>),
}

#[derive(Debug)]
pub(crate) struct MissingKeysInLocale {
    pub(crate) locale_name: LocaleName,
    pub(crate) keys: HashSet<Key>,
}

impl std::error::Error for Error {}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Error::JsonParsing(e) => write!(f, "{}", e),
            Error::ProcMacroInput(e) => write!(f, "{}", e),
            Error::Io(e) => write!(f, "{}", e),
            Error::MissingEnvVar { name, .. } => {
                write!(f, "Missing environment variable `{}`", name)
            }
            Error::DirectoryInLocalesFolder => {
                write!(f, "The locales directory should not contain other folders")
            }
            Error::NoFileStem => write!(f, "Failed to get file stem of locale file"),
            Error::InvalidUtf8InFileName => write!(f, "File name contained invalid UTF-8"),
            Error::UnbalancedPlaceholders {
                locale_name,
                string,
            } => {
                writeln!(f, "Unbalanced placeholders in string")?;
                writeln!(f, "Locale: {}", locale_name.0)?;
                write!(f, "String: {:?}", string)?;
                Ok(())
            }
            Error::MissingKeysInLocale(errors) => {
                let mut errors = errors.iter().collect::<Vec<_>>();
                errors.sort_by_key(|error| &error.locale_name.0);

                writeln!(f, "Some locales are missing keys")?;
                for error in errors {
                    writeln!(f, "`{}` is missing:", error.locale_name.0)?;
                    for key in &error.keys {
                        writeln!(f, "  `{}`", key.0)?;
                    }
                    writeln!(f)?;
                }
                Ok(())
            }
        }
    }
}

macro_rules! impl_from {
    ($from:ty => $to:ident :: $variant:ident) => {
        impl From<$from> for $to {
            fn from(from: $from) -> Self {
                $to::$variant(from)
            }
        }
    };
}

impl_from!(serde_json::error::Error => Error::JsonParsing);
impl_from!(syn::Error => Error::ProcMacroInput);
impl_from!(std::io::Error => Error::Io);

impl Error {
    pub fn missing_env_var<S: Into<String>>(name: S) -> impl FnOnce(std::env::VarError) -> Self {
        let name = name.into();
        move |var_error| Error::MissingEnvVar {
            name,
            inner_error: var_error,
        }
    }
}