turtle-build 0.3.8

Ninja-compatible build system for high-level programming languages
use crate::{compile::CompileError, ir::Build, parse::ParseError, validation::ValidationError};
use fnv::FnvHashMap;
use itertools::Itertools;
use std::{
    error::Error,
    fmt::{self, Display, Formatter},
    path::Path,
    sync::Arc,
};
use tokio::{io, sync::AcquireError, task::JoinError};

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum InfrastructureError {
    Build,
    Compile(CompileError),
    DefaultOutputNotFound(String),
    DynamicDependencyNotFound(Arc<Build>),
    InputNotFound(String),
    Other(String),
    Parse(ParseError),
    Sled(sled::Error),
    Validation(ValidationError),
}

impl InfrastructureError {
    pub fn with_path(error: io::Error, path: impl AsRef<Path>) -> Self {
        Self::Other(format!("{}: {}", error, path.as_ref().display()))
    }

    pub fn map_outputs(self, source_map: &FnvHashMap<String, String>) -> Self {
        match self {
            Self::Validation(ValidationError::CircularBuildDependency(outputs)) => {
                Self::Validation(ValidationError::CircularBuildDependency(
                    outputs
                        .into_iter()
                        .map(|output| source_map.get(&output).cloned().unwrap_or(output))
                        .dedup()
                        .collect(),
                ))
            }
            _ => self,
        }
    }
}

impl Error for InfrastructureError {}

impl Display for InfrastructureError {
    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
        match self {
            Self::Build => write!(formatter, "build failed"),
            Self::Compile(error) => write!(formatter, "{}", error),
            Self::DefaultOutputNotFound(output) => {
                write!(formatter, "default output \"{}\" not found", output)
            }
            Self::DynamicDependencyNotFound(build) => {
                write!(
                    formatter,
                    "outputs {} not found in dynamic dependency file {}",
                    build.outputs().join(", "),
                    build.dynamic_module().unwrap()
                )
            }
            Self::InputNotFound(input) => {
                write!(formatter, "input \"{}\" not found", input)
            }
            Self::Other(message) => write!(formatter, "{}", message),
            Self::Parse(error) => write!(formatter, "{}", error),
            Self::Sled(error) => write!(formatter, "{}", error),
            Self::Validation(error) => write!(formatter, "{}", error),
        }
    }
}

impl From<AcquireError> for InfrastructureError {
    fn from(error: AcquireError) -> Self {
        Self::Other(format!("{}", &error))
    }
}

impl From<bincode::Error> for InfrastructureError {
    fn from(error: bincode::Error) -> Self {
        Self::Other(format!("{}", &error))
    }
}

impl From<CompileError> for InfrastructureError {
    fn from(error: CompileError) -> Self {
        Self::Compile(error)
    }
}

impl From<io::Error> for InfrastructureError {
    fn from(error: io::Error) -> Self {
        Self::Other(format!("{}", &error))
    }
}

impl From<JoinError> for InfrastructureError {
    fn from(error: JoinError) -> Self {
        Self::Other(format!("{}", &error))
    }
}

impl From<ParseError> for InfrastructureError {
    fn from(error: ParseError) -> Self {
        Self::Parse(error)
    }
}

impl From<sled::Error> for InfrastructureError {
    fn from(error: sled::Error) -> Self {
        Self::Sled(error)
    }
}

impl From<ValidationError> for InfrastructureError {
    fn from(error: ValidationError) -> Self {
        Self::Validation(error)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn map_dependency_cycle_error() {
        assert_eq!(
            InfrastructureError::from(ValidationError::CircularBuildDependency(vec![
                "foo.o".into(),
                "bar.o".into()
            ]))
            .map_outputs(
                &[
                    ("foo.o".into(), "foo.c".into()),
                    ("bar.o".into(), "bar.c".into())
                ]
                .into_iter()
                .collect()
            ),
            InfrastructureError::from(ValidationError::CircularBuildDependency(vec![
                "foo.c".into(),
                "bar.c".into()
            ]))
        );
    }

    #[test]
    fn map_dependency_cycle_error_with_duplicate_sources() {
        assert_eq!(
            InfrastructureError::from(ValidationError::CircularBuildDependency(vec![
                "foo.o".into(),
                "foo.h".into()
            ]))
            .map_outputs(
                &[
                    ("foo.o".into(), "foo.c".into()),
                    ("foo.h".into(), "foo.c".into())
                ]
                .into_iter()
                .collect()
            ),
            InfrastructureError::from(ValidationError::CircularBuildDependency(vec![
                "foo.c".into()
            ]))
        );
    }
}