AkurAI Build
Menu

Public repository / master

AkurAI-Framework / crates/schema/src/error.rs

Latest change baca1a74b0ca9dcd770206285f8e51230b34d130 - feat(schema): add akurai-schema crate — JSON-shape validator for tool-UI matching by Ólafur Búi Ólafsson

//! [`SchemaError`] — a single validation failure with its JSON path and message.

use std::fmt;

/// A single validation failure.
///
/// `path` uses dot-notation for object fields (`"user.address.city"`) and
/// bracket-notation for array indices (`"tags[2]"`).  The root value is
/// reported with an empty path string.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SchemaError {
    /// Dot/bracket path to the offending value.  Empty means the root.
    pub path: String,
    /// Human-readable reason.
    pub message: String,
}

impl SchemaError {
    pub(crate) fn new(path: impl Into<String>, message: impl Into<String>) -> Self {
        Self {
            path: path.into(),
            message: message.into(),
        }
    }

    /// Prepend a parent segment to the path.
    pub(crate) fn prepend(mut self, segment: &str) -> Self {
        if self.path.is_empty() {
            self.path = segment.to_owned();
        } else if self.path.starts_with('[') {
            // array index — attach directly: "items[0]"
            self.path = format!("{segment}{}", self.path);
        } else {
            self.path = format!("{segment}.{}", self.path);
        }
        self
    }
}

impl fmt::Display for SchemaError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.path.is_empty() {
            write!(f, "{}", self.message)
        } else {
            write!(f, "{}: {}", self.path, self.message)
        }
    }
}

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

    #[test]
    fn display_with_path() {
        let e = SchemaError::new("user.name", "expected string");
        assert_eq!(e.to_string(), "user.name: expected string");
    }

    #[test]
    fn display_without_path() {
        let e = SchemaError::new("", "expected object");
        assert_eq!(e.to_string(), "expected object");
    }

    #[test]
    fn prepend_field_to_empty() {
        let e = SchemaError::new("", "expected string").prepend("name");
        assert_eq!(e.path, "name");
    }

    #[test]
    fn prepend_field_to_existing() {
        let e = SchemaError::new("city", "expected string").prepend("address");
        assert_eq!(e.path, "address.city");
    }

    #[test]
    fn prepend_array_index() {
        let e = SchemaError::new("[1]", "expected string").prepend("tags");
        assert_eq!(e.path, "tags[1]");
    }
}