bistun-core 2.0.3

The authoritative Linguistic DNA models and DTOs for the Bistun LMS. Provides a high-performance, immutable contract layer for BCP 47 locale resolution, typographic traits, and linguistic metadata.
Documentation
// Copyright (C) 2026 Francis Xavier Wazeter IV
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This file is part of the Bistun Linguistic Metadata Service (LMS).
// See the LICENSE file in the workspace root for full license information.

//! # Capability Manifest `DTO`
//! **Crate**: `bistun-core`
//! **Ref**: `[011-LMS-DTO]`
//! **Domain**: `[Delivery]`
//! **Location**: `crates/bistun-core/src/manifest.rs`
//!
//! **Why**: This module defines the primary `CapabilityManifest` (`DTO`) generated at the end of the `Resolution Pipeline`.
//! **Impact**: If this module or its serialization is compromised, downstream software components will receive malformed rendering/processing instructions, causing widespread `UI` and algorithmic failures.
//!
//! ### Architectural Topology
//! * **Tier**: `0`
//! * **Dependencies**: `crate::traits`, `hashbrown`, `serde`
//! * **Consumers**: Downstream `UI` and `NLP` systems, `Delivery` layer microservices
//! * **State Model**: `Stateless`
//! * **Design Patterns**: `Data Transfer Object`
//!
//! ### Local Definitions
//! * **`CapabilityManifest`**: The primary, immutable Data Transfer Object returned to the client containing the finalized configuration traits, rules, and telemetry metadata.
//! * **Untagged Serialization**: A `Serde` configuration where enums are serialized as their underlying value rather than explicitly wrapping them in their variant name.

use crate::traits::{Direction, LmsRule, MorphType, SegType, TraitKey};
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};

/// Strongly typed wrapper for the heterogeneous values in the traits dictionary.
///
/// Using `Untagged Serialization` allows us to serialize standard `JSON` types while maintaining Rust's exhaustiveness checks.
///
/// ### `OpenAPI` Schema
/// ```yaml
/// schema: TraitValue
/// description: Strongly typed wrapper for the heterogeneous values in the traits dictionary.
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum TraitValue {
    // --- Orthographic Enums ---
    /// Text directionality layout directive.
    Direction(Direction),
    /// Word and sentence boundary detection strategy.
    SegType(SegType),

    // --- Typological Enums ---
    /// Linguistic morphology classification.
    MorphType(MorphType),

    // -- Primitives ---
    /// A boolean flag (e.g., true/false).
    Boolean(bool),
    /// An array of string values (e.g., `["one", "other"]`).
    StringArray(Vec<String>),
    /// A catch-all string value (e.g., "arab").
    /// Note: Must be last to prevent greedy matching over specialized enums.
    String(String),
}

/// The immutable `CapabilityManifest` delivered to consuming services.
///
/// ### `OpenAPI` Schema
/// ```yaml
/// schema: CapabilityManifest
/// description: The primary, immutable Data Transfer Object returned to the client containing the finalized configuration traits, rules, and telemetry metadata.
/// ```
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CapabilityManifest {
    // --- Identity ---
    /// The canonical `BCP 47` tag resulting from taxonomic resolution.
    pub resolved_locale: String,

    // --- Linguistic Domains ---
    /// The dictionary of "Linguistic DNA" traits (`Typology` & `Orthography`).
    pub traits: HashMap<TraitKey, TraitValue>,
    /// Logical directives for algorithmic execution (`Rule Engine Aggregation`).
    pub rules: HashMap<String, LmsRule>,
    /// Mappings of Logical Resource IDs to Physical Paths.
    pub resources: HashMap<String, String>,

    // --- Overrides & Observability
    /// User-requested `BCP 47` extensions (e.g., `-u-nu-latn`).
    pub extensions: HashMap<String, String>,
    /// Observability data (registry version, `SLI` latency).
    pub metadata: HashMap<String, String>,
}

impl CapabilityManifest {
    /// Constructs a new, empty `CapabilityManifest`.
    ///
    /// **Time**: `O(1)` | **Space**: `O(1)`
    /// **SLI Target**: `< 1ms`
    ///
    /// # Algorithmic Execution Trace (Internal)
    /// 1. Ingest `resolved_locale` as the authoritative `BCP 47` tag.
    /// 2. Initialize high-performance `HashMap` structures for traits, rules, resources, extensions, and metadata.
    /// 3. Return the populated instance.
    ///
    /// # Examples
    /// ```rust
    /// # use bistun_core::manifest::CapabilityManifest;
    /// let manifest = CapabilityManifest::new("ar-EG".to_string());
    /// assert_eq!(manifest.resolved_locale, "ar-EG");
    /// ```
    ///
    /// # Arguments
    /// * `resolved_locale` (`String`): The fully resolved and validated `BCP 47` language tag (e.g., "ar-EG").
    ///
    /// # Returns
    /// * `CapabilityManifest`: An empty `CapabilityManifest`, ready to be hydrated by the `Resolution Pipeline`.
    ///
    /// # Golden Master Tests
    /// * **Input**: `"ar-EG".to_string()`
    /// * **Output**: A valid `CapabilityManifest` with `resolved_locale` set to `"ar-EG"`.
    ///
    /// # Errors
    /// This function cannot fail and does not return an error variant.
    ///
    /// # Panics
    /// This function does not panic.
    ///
    /// # Safety
    /// This function does not contain unsafe blocks.
    ///
    /// # Side Effects
    /// Allocates internal `HashMap` collections on the heap upon initialization.
    ///
    /// # Observability & Security
    /// * **Spans**: None.
    /// * **Metrics**: None.
    /// * **Auth Required**: None.
    #[must_use]
    pub fn new(resolved_locale: String) -> Self {
        // [STEP 1]: Ingest `resolved_locale` as the authoritative `BCP 47` tag.
        // [STEP 2]: Initialize high-performance `HashMap` structures for traits, rules, resources, extensions, and metadata.
        // [STEP 3]: Return the populated instance.
        Self {
            resolved_locale,
            traits: HashMap::new(),
            rules: HashMap::new(),
            resources: HashMap::new(),
            extensions: HashMap::new(),
            metadata: HashMap::new(),
        }
    }
}

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

    /// **What is being proven**: Verifies that the `CapabilityManifest` (`DTO`) properly implements `Untagged Serialization` so that internal Rust enums (`TraitValue`) flatten into native `JSON` primitives without generating structural errors downstream.
    #[test]
    fn test_manifest_serialization() {
        // [Algorithmic Execution Trace Mapping]
        // [STEP 1]: Setup: Instantiate a manifest for Egyptian Arabic with Latin numeral override.
        // [STEP 2]: Execute: Populate the categorized maps with specific v2.0.0 test data.
        // [STEP 3]: Assert: Verify that Serde correctly flattens the untagged enums into the JSON output.

        let mut manifest = CapabilityManifest::new("ar-EG-u-nu-latn".to_string());

        // Inject Phase 2 Aggregation Traits (Immutable DNA)
        manifest.traits.insert(TraitKey::PrimaryDirection, TraitValue::Direction(Direction::RTL));
        manifest.traits.insert(TraitKey::HasBidiElements, TraitValue::Boolean(true));
        manifest
            .traits
            .insert(TraitKey::MorphologyType, TraitValue::MorphType(MorphType::TEMPLATIC));
        manifest
            .traits
            .insert(TraitKey::DefaultNumberingSystem, TraitValue::String("arab".to_string()));

        // Inject Phase 2 Synthesis Rules (Algorithmic Logic)
        manifest.rules.insert(
            "TRANSLITERATION_DEFAULT".to_string(),
            LmsRule::Trans(crate::traits::TransRule::ICU_TRANSFORM),
        );

        // Inject Phase 2.5 Resource Paths
        manifest.resources.insert(
            "icu_arab".to_string(),
            "https://cdn.bistun.io/v1/data/icu_arab.postcard".to_string(),
        );

        // Inject Phase 3 Overrides
        manifest.extensions.insert("nu".to_string(), "latn".to_string());

        // Inject Phase 5 Telemetry
        manifest.metadata.insert("registry_version".to_string(), "2.0.0".to_string());

        let json_output =
            serde_json::to_string(&manifest).expect("LMS-TEST: Failed to serialize manifest");

        // Parse the raw string back into a dynamic JSON Value for structural assertions
        let parsed: serde_json::Value =
            serde_json::from_str(&json_output).expect("LMS-TEST: Failed to parse JSON");

        // Asserts ensure untagged serialization maps correctly to raw JSON primitives
        assert_eq!(parsed["resolved_locale"], "ar-EG-u-nu-latn");
        assert_eq!(parsed["traits"]["PRIMARY_DIRECTION"], "RTL");
        assert_eq!(parsed["traits"]["DEFAULT_NUMBERING_SYSTEM"], "arab");
        assert_eq!(parsed["rules"]["TRANSLITERATION_DEFAULT"], "ICU_TRANSFORM");
        assert_eq!(
            parsed["resources"]["icu_arab"],
            "https://cdn.bistun.io/v1/data/icu_arab.postcard"
        );
        assert_eq!(parsed["extensions"]["nu"], "latn");
        assert_eq!(parsed["metadata"]["registry_version"], "2.0.0");
    }
}