geam 0.1.1

Experimental Rust-embedded execution runtime for typed Gleam programs
use ecow::EcoString;
use std::fmt::{self, Display, Formatter};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HostFailure {
    message: EcoString,
}

#[derive(Debug, PartialEq)]
pub struct HostCallError {
    kind: HostCallErrorKind,
}

#[derive(Debug, PartialEq)]
pub(crate) enum HostCallErrorKind {
    Failure(HostFailure),
    Nested(crate::ExecutionError),
}

impl HostFailure {
    pub fn new(message: impl Into<EcoString>) -> Self {
        Self {
            message: message.into(),
        }
    }

    pub fn message(&self) -> &EcoString {
        &self.message
    }
}

impl HostCallError {
    pub(crate) fn nested(error: crate::ExecutionError) -> Self {
        Self {
            kind: HostCallErrorKind::Nested(error),
        }
    }

    pub(crate) fn into_kind(self) -> HostCallErrorKind {
        self.kind
    }
}

impl Display for HostFailure {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
        formatter.write_str(&self.message)
    }
}

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

impl Display for HostCallError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
        match &self.kind {
            HostCallErrorKind::Failure(failure) => Display::fmt(failure, formatter),
            HostCallErrorKind::Nested(error) => Display::fmt(error, formatter),
        }
    }
}

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

impl From<HostFailure> for HostCallError {
    fn from(error: HostFailure) -> Self {
        Self {
            kind: HostCallErrorKind::Failure(error),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{HostCallError, HostFailure};
    use crate::{ExecutionError, InvariantError, ValueType};

    #[test]
    fn host_failure_owns_and_displays_its_message() {
        let failure = HostFailure::new("database unavailable");

        assert_eq!(failure.message(), "database unavailable");
        assert_eq!(failure.to_string(), "database unavailable");
    }

    #[test]
    fn host_call_error_preserves_the_owned_host_failure() {
        let local = HostCallError::from(HostFailure::new("invalid input"));

        assert_eq!(local.to_string(), "invalid input");
        assert_eq!(
            local.into_kind(),
            super::HostCallErrorKind::Failure(HostFailure::new("invalid input")),
        );
    }

    #[test]
    fn host_call_error_preserves_a_nested_execution_failure() {
        let execution = ExecutionError::Invariant(InvariantError::ListIndexOutOfBounds {
            item_type: ValueType::Int,
            index: 1,
            length: 0,
        });
        let nested = HostCallError::nested(execution.clone());

        assert_eq!(
            nested.to_string(),
            "list index out of bounds for Int list (index 1, length 0)",
        );
        assert_eq!(
            nested.into_kind(),
            super::HostCallErrorKind::Nested(execution),
        );
    }
}