genson-core 1.2.2

Core library for JSON schema inference using genson-rs
Documentation
#![cfg(feature = "avro")]

use genson_core::normalise::{normalise_value, normalise_values, MapEncoding, NormaliseConfig};
use serde_json::json;

/// Arrays: empty → null (with flag), empty → [] (without flag).
#[test]
fn test_array_empty_behavior() {
    let schema = json!({"type":"array","items":"string"});

    // empty_as_null = true
    let cfg = NormaliseConfig {
        empty_as_null: true,
        ..NormaliseConfig::default()
    };
    assert_eq!(normalise_value(json!([]), &schema, &cfg, None), json!(null));

    // empty_as_null = false
    let cfg = NormaliseConfig {
        empty_as_null: false,
        ..NormaliseConfig::default()
    };
    assert_eq!(normalise_value(json!([]), &schema, &cfg, None), json!([]));

    // scalar coerced into array
    assert_eq!(
        normalise_value(json!("foo"), &schema, &cfg, None),
        json!(["foo"])
    );
}

/// Maps: empty → null (with flag), empty → {} (without flag).
#[test]
fn test_map_empty_behavior() {
    let schema = json!({"type":"map","values":"string"});

    let cfg = NormaliseConfig {
        empty_as_null: true,
        ..NormaliseConfig::default()
    };
    assert_eq!(normalise_value(json!({}), &schema, &cfg, None), json!(null));

    let cfg = NormaliseConfig {
        empty_as_null: false,
        ..NormaliseConfig::default()
    };
    assert_eq!(normalise_value(json!({}), &schema, &cfg, None), json!({}));

    // Fallback scalar coerced into map
    let val = normalise_value(json!("foo"), &schema, &cfg, None);
    assert_eq!(val, json!({"__string":"foo"}));
}

/// Nested record with array field, ensures row is preserved.
#[test]
fn test_nested_record_array_field() {
    let schema = json!({
        "type":"record",
        "name":"doc",
        "fields":[
            {"name":"id","type":"string"},
            {"name":"tags","type":{"type":"array","items":"string"}}
        ]
    });

    let cfg = NormaliseConfig {
        empty_as_null: true,
        ..NormaliseConfig::default()
    };
    let input = json!({"id":"1","tags":[]});
    let norm = normalise_value(input, &schema, &cfg, None);
    assert_eq!(norm, json!({"id":"1","tags":null}));

    let cfg = NormaliseConfig {
        empty_as_null: false,
        ..NormaliseConfig::default()
    };
    let input = json!({"id":"1","tags":[]});
    let norm = normalise_value(input, &schema, &cfg, None);
    assert_eq!(norm, json!({"id":"1","tags":[]}));
}

/// Union precedence: ["null","array"] should pick array branch if not null.
#[test]
fn test_union_precedence_array() {
    let schema = json!(["null", {"type":"array","items":"string"}]);
    let cfg = NormaliseConfig {
        empty_as_null: true,
        ..NormaliseConfig::default()
    };

    // null stays null
    assert_eq!(
        normalise_value(json!(null), &schema, &cfg, None),
        json!(null)
    );

    // scalar coerced to array
    let val = normalise_value(json!("x"), &schema, &cfg, None);
    assert_eq!(val, json!(["x"]));
}

/// Union precedence: ["null","map","string"] should pick map branch.
#[test]
fn test_union_precedence_map() {
    let schema = json!(["null", {"type":"map","values":"string"}, "string"]);
    let cfg = NormaliseConfig {
        empty_as_null: true,
        ..NormaliseConfig::default()
    };

    // scalar coerced into map, because map branch is first non-null
    let val = normalise_value(json!("foo"), &schema, &cfg, None);
    assert_eq!(val, json!({"__string":"foo"}));
}

/// End-to-end: vector of values stays same length (no row loss).
#[test]
fn test_normalise_values_preserves_length() {
    let schema = json!({"type":"array","items":"string"});

    let cfg = NormaliseConfig {
        empty_as_null: true,
        ..NormaliseConfig::default()
    };
    let inputs = vec![
        json!(["a", "b"]),
        json!([]), // becomes null, but row stays
        json!(null),
    ];
    let outputs = normalise_values(inputs.clone(), &schema, &cfg);

    assert_eq!(outputs.len(), inputs.len());
    assert_eq!(outputs[0], json!(["a", "b"]));
    assert_eq!(outputs[1], json!(null));
    assert_eq!(outputs[2], json!(null));
}

/// Maps: scalar fallback respects map encoding.
#[test]
fn test_map_scalar_fallback_encodings() {
    let schema = json!({"type":"map","values":"string"});

    // Default Mapping
    let cfg = NormaliseConfig {
        map_encoding: MapEncoding::Mapping,
        ..NormaliseConfig::default()
    };
    let val = normalise_value(json!("foo"), &schema, &cfg, None);
    assert_eq!(val, json!({"__string": "foo"}));

    // Entries
    let cfg = NormaliseConfig {
        map_encoding: MapEncoding::Entries,
        ..NormaliseConfig::default()
    };
    let val = normalise_value(json!("foo"), &schema, &cfg, None);
    assert_eq!(val, json!([{"__string": "foo"}]));

    // KeyValueEntries
    let cfg = NormaliseConfig {
        map_encoding: MapEncoding::KeyValueEntries,
        ..NormaliseConfig::default()
    };
    let val = normalise_value(json!("foo"), &schema, &cfg, None);
    assert_eq!(val, json!([{"key": "__string", "value": "foo"}]));
}

/// Floats: a JSON integer in a float field widens instead of becoming null.
#[test]
fn test_integer_in_float_field() {
    let cfg = NormaliseConfig::default();
    for t in ["double", "float"] {
        let schema = json!(t);
        assert_eq!(normalise_value(json!(1), &schema, &cfg, None), json!(1.0));
        assert_eq!(normalise_value(json!(1.5), &schema, &cfg, None), json!(1.5));
        assert_eq!(
            normalise_value(json!("x"), &schema, &cfg, None),
            json!(null)
        );
    }
}

/// Promoted records: each number lands in exactly one promoted field.
#[test]
fn test_promoted_number_routing() {
    let cfg = NormaliseConfig::default();
    let both = json!({"type": "record", "name": "precision", "fields": [
        {"name": "precision__integer", "type": ["null", "long"]},
        {"name": "precision__number", "type": ["null", "double"]}
    ]});
    assert_eq!(
        normalise_value(json!(11), &both, &cfg, None),
        json!({"precision__integer": 11, "precision__number": null})
    );
    assert_eq!(
        normalise_value(json!(1.5), &both, &cfg, None),
        json!({"precision__integer": null, "precision__number": 1.5})
    );

    let number_only = json!({"type": "record", "name": "precision", "fields": [
        {"name": "precision__number", "type": ["null", "double"]}
    ]});
    assert_eq!(
        normalise_value(json!(11), &number_only, &cfg, None),
        json!({"precision__number": 11.0})
    );
}