seedfaker-core 0.1.0-alpha.2

Core library for seedfaker — deterministic synthetic generator for realistic, correlated, and noisy test records
Documentation
pub mod corrupt;
pub mod ctx;
pub mod field;
pub mod gen;
pub mod locale;
pub mod pipeline;
pub mod rng;
pub mod script;
pub mod temporal;
pub mod tz;
pub mod validate;

pub const DEFAULT_TZ_OFFSET: i32 = 0;

// Domain keys for sub-seed derivation. Must match across CLI, npm, pip.
pub const DOMAIN_IDENTITY: &str = "__identity__";
pub const DOMAIN_CORRUPT: &str = "__corrupt__";
pub const DOMAIN_SCRIPT: &str = "__script__";
pub const DOMAIN_LOCALE: &str = "__locale__";
pub const DOMAIN_TPL: &str = "__tpl__";

pub fn hash_seed(s: &str) -> u64 {
    fnv1a(s.as_bytes())
}

fn fnv1a(bytes: &[u8]) -> u64 {
    let mut h: u64 = 0xcbf2_9ce4_8422_2325;
    for &b in bytes {
        h ^= b as u64;
        h = h.wrapping_mul(0x0100_0000_01b3);
    }
    h
}

/// Generator fingerprint: identifies the current deterministic algorithm version.
///
/// If this value changes between releases, seeded output has changed —
/// users must regenerate fixtures. Computed by hashing one canonical value
/// per registered field.
/// Format: `sf0-<16 hex digits>`.
pub fn fingerprint() -> String {
    use field::REGISTRY;

    const CANONICAL_SEED: &str = "__determinism__";
    let master = hash_seed(CANONICAL_SEED);
    let locales: Vec<&locale::Locale> = locale::get("en").into_iter().collect();

    // Fixed temporal bounds — fingerprint must be stable across years.
    let since = temporal::parse("1900").unwrap_or(temporal::DEFAULT_SINCE);
    let until = temporal::parse("2038").unwrap_or(0);

    let mut buf = String::new();
    let mut val_buf = String::new();
    for f in REGISTRY {
        let mut ctx = ctx::GenContext {
            rng: rng::Rng::derive(master, 0, f.id),
            locales: &locales,
            modifier: "",
            identity: None,
            tz_offset_minutes: DEFAULT_TZ_OFFSET,
            since,
            until,
            range: None,
            ordering: crate::field::Ordering::None,
            numeric: None,
        };
        val_buf.clear();
        f.generate(&mut ctx, &mut val_buf);
        buf.push_str(&val_buf);
        buf.push('\0');
    }

    let h = fnv1a(buf.as_bytes());
    format!("sf0-{h:016x}")
}