aam-rs 2.0.2

A Rust implementation of the Abstract Alias Mapping (AAM) framework for aliasing and maping aam files.
Documentation
//! `@type` directive — registers a named type alias or built-in type reference.
//!
//! # Syntax
//! ```text
//! @type alias_name = primitive_type
//! @type alias_name = module::type_name
//! ```
//!
//! # Examples
//! ```text
//! @type age     = i32
//! @type ratio   = f64
//! @type pos     = math::vector3
//! @type mass    = physics::kilogram
//! @type created = time::datetime
//! ```
//!
//! After registration the alias can be used as a field type in `@schema`
//! definitions and validated via [`AAML::validate_value`].
//!

use crate::aaml::AAML;
use crate::commands::Command;
use crate::error::AamlError;
use crate::types::primitive_type::PrimitiveType;
use crate::types::{Type, resolve_builtin};

/// A resolved type definition stored in the [`AAML`](AAML) type registry.
///
/// Variants correspond to the three ways a type can be declared:
/// - [`TypeDefinition::Primitive`] — a primitive name such as `i32` or `bool`.
/// - [`TypeDefinition::Builtin`] — a module-qualified path such as `math::vector3`.
/// - [`TypeDefinition::Alias`] — an opaque alias (currently always passes validation).
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TypeDefinition {
    /// A primitive type identified by name (e.g. `"i32"`, `"f64"`).
    Primitive(String),
    /// An alias that doesn't map to a concrete type — always valid.
    Alias(String),
    /// A built-in module path (e.g. `"math::vector3"`).
    Builtin(String),
}

impl Type for TypeDefinition {
    fn from_name(_name: &str) -> Result<Self, AamlError>
    where
        Self: Sized,
    {
        Err(AamlError::NotFound {
            key: "TypeDefinition".to_string(),
            context: "TypeDefinition::from_name is not supported".to_string(),
            diagnostics: None,
        })
    }

    /// Returns the underlying [`PrimitiveType`] that best represents this type.
    fn base_type(&self) -> PrimitiveType {
        match self {
            TypeDefinition::Builtin(path) => resolve_builtin(path)
                .map(|t| t.base_type())
                .unwrap_or(PrimitiveType::String),
            TypeDefinition::Primitive(name) => PrimitiveType::from_name(name)
                .unwrap_or(PrimitiveType::String)
                .base_type(),
            TypeDefinition::Alias(_) => PrimitiveType::String,
        }
    }

    /// Validates `value` according to the underlying type definition.
    ///
    /// - `Builtin` — delegates to the corresponding module type.
    /// - `Primitive` — delegates to [`PrimitiveType`].
    /// - `Alias` — always returns `Ok(())`.
    fn validate(&self, value: &str, aaml: &AAML) -> Result<(), AamlError> {
        match self {
            TypeDefinition::Builtin(path) => resolve_builtin(path)?.validate(value, aaml),
            TypeDefinition::Primitive(name) => {
                PrimitiveType::from_name(name)?.validate(value, aaml)
            }
            TypeDefinition::Alias(_) => Ok(()),
        }
    }
}

/// Command handler for the `@type` directive.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TypeCommand;

impl Command for TypeCommand {
    fn name(&self) -> &str {
        "type"
    }

    /// Parses `name = definition` and registers the resulting [`TypeDefinition`].
    ///
    /// Built-in paths (containing `::`) become [`TypeDefinition::Builtin`];
    /// all other definitions become [`TypeDefinition::Primitive`].
    ///
    /// # Errors
    /// [`AamlError::ParseError`] if the format is invalid or name/definition is empty.
    fn execute(&self, aaml: &mut AAML, args: &str) -> Result<(), AamlError> {
        let (name, definition) =
            args.split_once('=')
                .ok_or_else(|| AamlError::DirectiveSyntaxError {
                    directive: "type".to_string(),
                    provided_syntax: args.to_string(),
                    expected_syntax: "name = definition".to_string(),
                    diagnostics: Some(crate::error::ErrorDiagnostics::new(
                        "Invalid @type syntax",
                        "Type definition must have an '=' sign",
                        "Use format: @type name = primitive_type or @type name = module::type",
                    )),
                })?;

        let name = name.trim();
        let definition = definition.trim();

        if name.is_empty() {
            return Err(AamlError::InvalidValue {
                details: "Type name is empty".to_string(),
                expected: "non-empty type name".to_string(),
                diagnostics: Some(crate::error::ErrorDiagnostics::new(
                    "Empty type name",
                    "Type name cannot be empty before '='",
                    "Provide a type name: @type myType = ...",
                )),
            });
        }
        if definition.is_empty() {
            return Err(AamlError::InvalidValue {
                details: "Type definition is empty".to_string(),
                expected: "type definition (primitive or module path)".to_string(),
                diagnostics: Some(crate::error::ErrorDiagnostics::new(
                    "Empty type definition",
                    "Type definition cannot be empty after '='",
                    "Provide a type: @type myType = i32, f64, string, etc.",
                )),
            });
        }

        let type_def = if definition.contains("::") {
            TypeDefinition::Builtin(definition.to_string())
        } else {
            TypeDefinition::Primitive(definition.to_string())
        };

        aaml.register_type(name.to_string(), type_def);

        Ok(())
    }
}