fluent-zero 0.1.4

A zero-allocation, high-performance Fluent localization loader optimized for GUIs and games via compile-time caching to return &'static str for static messages.
Documentation
use std::{borrow::Cow, collections::HashMap};

// Imports
use fluent_zero::{
    CacheEntry, CacheStore, ConcurrentFluentBundle, FluentArgs, FluentResource, LanguageIdentifier,
    lookup_dynamic, lookup_static, set_lang,
};

// =========================================================================
// TEST SUITE: STRICT TYPES
// =========================================================================
// These tests verify that `fluent-zero` behaves exactly as promised:
// 1. Static messages return Cow::Borrowed referencing static memory.
// 2. Dynamic messages return Cow::Owned strings.
// 3. Fallbacks occur in the correct order.
// =========================================================================

fn create_bundle(lang: &str, source: &str) -> ConcurrentFluentBundle<FluentResource> {
    let lang_id: LanguageIdentifier = lang.parse().unwrap();
    let mut bundle = ConcurrentFluentBundle::new_concurrent(vec![lang_id]);
    bundle.set_use_isolating(false);
    let res = FluentResource::try_new(source.to_string()).unwrap();
    bundle.add_resource(res).unwrap();
    bundle
}

/// Mock implementation of the combined cache used by `fluent-zero`.
/// This mocks the `phf` maps generated by the build script.
struct MockCache {
    // Map<LangString, Map<Key, CacheEntry>>
    data: HashMap<String, HashMap<&'static str, CacheEntry>>,
}

impl MockCache {
    fn new() -> Self {
        Self {
            data: HashMap::new(),
        }
    }

    fn insert_static(&mut self, lang: &str, key: &'static str, value: &'static str) {
        self.data
            .entry(lang.to_string())
            .or_default()
            .insert(key, CacheEntry::Static(value));
    }

    fn insert_dynamic(&mut self, lang: &str, key: &'static str) {
        self.data
            .entry(lang.to_string())
            .or_default()
            .insert(key, CacheEntry::Dynamic);
    }
}

// Implement the library trait for our mock
impl CacheStore for MockCache {
    fn get_entry(&self, lang: &str, key: &str) -> Option<CacheEntry> {
        self.data.get(lang).and_then(|c| c.get(key)).copied()
    }
}

struct MockLocales {
    bundles: HashMap<String, ConcurrentFluentBundle<FluentResource>>,
    cache: MockCache,
}

impl MockLocales {
    fn new() -> Self {
        let en_us: LanguageIdentifier = "en-US".parse().unwrap();
        let fr_fr: LanguageIdentifier = "fr-FR".parse().unwrap();

        let mut bundles = HashMap::new();
        bundles.insert(
            en_us.to_string(),
            create_bundle(
                "en-US",
                r#"
hello = Hello World
welcome = Welcome { $name }
emoji = { "😀" }
        "#,
            ),
        );
        bundles.insert(
            fr_fr.to_string(),
            create_bundle("fr-FR", "hello = Bonjour le monde"),
        );

        let mut cache = MockCache::new();
        // Setup static entries
        cache.insert_static("en-US", "hello", "Hello World");
        cache.insert_static("fr-FR", "hello", "Bonjour le monde");

        // Setup dynamic entries
        cache.insert_dynamic("en-US", "welcome");
        cache.insert_dynamic("en-US", "emoji");

        Self { bundles, cache }
    }
}

// --- TEST CASES ---

#[test]
fn t01_static_hit_returns_borrowed() {
    let mocks = MockLocales::new();
    set_lang("en-US".parse().unwrap());

    let result = lookup_static(&mocks.bundles, &mocks.cache, "hello");

    assert_eq!(result, "Hello World");

    // CRITICAL: Ensure we did not allocate a new string
    assert!(matches!(result, Cow::Borrowed(_)));
}

#[test]
fn t02_static_miss_hits_bundle_via_dynamic_entry() {
    let mocks = MockLocales::new();
    set_lang("en-US".parse().unwrap());

    // 'emoji' is in bundle and marked Dynamic in cache.
    // This tests the path where CacheEntry::Dynamic forces a bundle lookup.
    let result = lookup_static(&mocks.bundles, &mocks.cache, "emoji");

    assert_eq!(result, "😀");
}

#[test]
fn t03_dynamic_lookup_always_returns_owned_if_args_used() {
    let mocks = MockLocales::new();
    set_lang("en-US".parse().unwrap());

    let mut args = FluentArgs::new();
    args.set("name", "Alice");

    let result = lookup_dynamic(&mocks.bundles, &mocks.cache, "welcome", &args);

    assert_eq!(result, "Welcome Alice");
}

#[test]
fn t04_fallback_static_returns_borrowed() {
    let mocks = MockLocales::new();
    set_lang("de-DE".parse().unwrap()); // Unknown language

    // Fallback to en-US
    let result = lookup_static(&mocks.bundles, &mocks.cache, "hello");

    assert_eq!(result, "Hello World");
    assert!(matches!(result, Cow::Borrowed(_)));
}

#[test]
fn t06_language_switch_updates_static_return() {
    let mocks = MockLocales::new();

    set_lang("en-US".parse().unwrap());
    assert_eq!(
        lookup_static(&mocks.bundles, &mocks.cache, "hello"),
        "Hello World"
    );

    set_lang("fr-FR".parse().unwrap());
    assert_eq!(
        lookup_static(&mocks.bundles, &mocks.cache, "hello"),
        "Bonjour le monde"
    );
}

#[test]
fn t08_verify_zero_copy_pointer_address() {
    // This test performs pointer arithmetic to guarantee that the string returned
    // by lookup_static is the exact same memory address as the string stored
    // in the mock cache (simulating the .rodata segment).
    let mocks = MockLocales::new();
    set_lang("en-US".parse().unwrap());

    // 1. Get exact reference from cache
    let entry = mocks.cache.data.get("en-US").unwrap().get("hello").unwrap();
    let cached_str_ref = match entry {
        CacheEntry::Static(s) => s,
        CacheEntry::Dynamic => panic!("Expected static"),
    };

    let cached_ptr = std::ptr::from_ref::<str>(*cached_str_ref);

    // 2. Get result from lookup
    let result = lookup_static(&mocks.bundles, &mocks.cache, "hello");

    if let Cow::Borrowed(res_str) = result {
        let res_ptr = std::ptr::from_ref::<str>(res_str);

        // 3. Verify pointers are identical
        assert_eq!(
            cached_ptr, res_ptr,
            "Returned string should point to the exact same memory address."
        );
    } else {
        panic!("Expected Borrowed result");
    }
}

#[test]
fn t09_verify_charset_merging() {
    let app_charset = "abc😀";
    let dep_charset = "cde";
    let ui_charset = "zxa";

    let merged = fluent_zero::join_charsets(&[app_charset, dep_charset, ui_charset]);

    // Ensure it deduplicated successfully, included emoji, and deterministically sorted via BTreeSet.
    assert_eq!(merged, "abcdexz😀");
}