qubit-value 0.13.1

Type-safe containers for single, multi-valued, and named runtime values
Documentation
// =============================================================================
//    Copyright (c) 2025 - 2026 Haixing Hu.
//
//    SPDX-License-Identifier: Apache-2.0
//
//    Licensed under the Apache License, Version 2.0.
// =============================================================================

#[cfg(feature = "converter")]
use qubit_datatype::ConversionLimits;
#[cfg(feature = "converter")]
use qubit_datatype::ConversionPolicy;
#[cfg(feature = "converter")]
use qubit_datatype::ConversionSession;
use qubit_datatype::DataType;
use qubit_value::MultiValues;
use qubit_value::ValueError;
use qubit_value::ValueWireEncodeError;
use qubit_value::ValueWireV1;
use serde_json::to_string;

use crate::decode_value_wire_str;

#[test]
fn test_multi_values_len_and_is_empty_distinguish_unset_from_concrete_empty_values() {
    let unset = MultiValues::Unset(DataType::Int32);
    let empty = MultiValues::Int32(Vec::new());
    let values = MultiValues::Int32(vec![1, 2]);

    assert_eq!(unset.len(), 0);
    assert!(unset.is_empty());
    assert!(unset.is_unset());
    assert_eq!(empty.len(), 0);
    assert!(empty.is_empty());
    assert!(!empty.is_unset());
    assert_eq!(values.len(), 2);
    assert!(!values.is_empty());
}

#[test]
fn test_multi_values_core_tracks_len_and_type_changes() {
    let mut values = MultiValues::Int32(vec![1, 2, 3]);
    assert_eq!(values.len(), 3);
    assert_eq!(values.data_type(), DataType::Int32);

    values.clear();
    assert!(!values.is_unset());
    assert_eq!(values.len(), 0);
    assert_eq!(values.data_type(), DataType::Int32);

    values.set_type(DataType::String);
    assert!(values.is_unset());
    assert_eq!(values.data_type(), DataType::String);
    assert!(matches!(
        values.get_first::<String>(),
        Err(ValueError::Missing(_))
    ));
}

#[test]
fn test_multi_values_core_unset_preserves_declared_type() {
    let mut values = MultiValues::Int32(Vec::new());
    assert!(!values.is_unset());

    values.unset();

    assert!(values.is_unset());
    assert_eq!(values.data_type(), DataType::Int32);
    assert_eq!(values.len(), 0);
}

/// Checks eager and lazy fallbacks with strict reads, conversions, explicit
/// policy, and a caller-owned conversion session.
#[cfg(feature = "converter")]
#[test]
fn test_multi_values_default_and_session_conversion_paths() {
    let unset = MultiValues::Unset(DataType::Int32);
    let values = MultiValues::String(vec![String::from("42"), String::from("43")]);
    let empty = MultiValues::Int32(Vec::new());

    assert_eq!(unset.get_or::<i32>(vec![1, 2]), Ok(vec![1, 2]));
    assert_eq!(unset.get_or_else::<i32, _>(|| vec![3]), Ok(vec![3]));
    assert_eq!(unset.get_first_or::<i32>(4), Ok(4));
    assert_eq!(unset.get_first_or_else::<i32, _>(|| 5), Ok(5));
    assert_eq!(
        values.get::<String>(),
        Ok(vec![String::from("42"), String::from("43")])
    );
    assert_eq!(values.get_first::<String>(), Ok(String::from("42")));

    assert_eq!(unset.to_first_or::<i32>(6), Ok(6));
    assert_eq!(unset.to_first_or_else::<i32, _>(|| 7), Ok(7));
    assert_eq!(unset.to_list_or::<i32>(vec![8]), Ok(vec![8]));
    assert_eq!(unset.to_list_or_else::<i32, _>(|| vec![9]), Ok(vec![9]));
    assert!(empty.to_first_or::<i32>(10).is_err());
    assert_eq!(empty.to_list_or::<i32>(vec![11]), Ok(Vec::new()));

    let policy = ConversionPolicy::default();
    let limits = ConversionLimits::default();
    assert_eq!(values.to_first_with::<i32>(&policy, &limits), Ok(42));
    assert_eq!(
        values.to_list_with::<i32>(&policy, &limits),
        Ok(vec![42, 43])
    );
    assert_eq!(unset.to_first_or_with::<i32>(12, &policy, &limits), Ok(12));
    assert_eq!(
        unset.to_first_or_else_with::<i32, _>(|| 13, &policy, &limits),
        Ok(13)
    );
    assert_eq!(
        unset.to_list_or_with::<i32>(vec![14], &policy, &limits),
        Ok(vec![14])
    );
    assert_eq!(
        unset.to_list_or_else_with::<i32, _>(|| vec![15], &policy, &limits),
        Ok(vec![15])
    );

    let mut session = ConversionSession::new(&policy, &limits);
    assert_eq!(values.to_first_in::<i32>(&mut session), Ok(42));
    assert_eq!(values.to_list_in::<i32>(&mut session), Ok(vec![42, 43]));
}

#[test]
fn test_multi_values_is_numeric_is_state_aware() {
    assert!(MultiValues::Int32(vec![1]).is_numeric());
    assert!(MultiValues::Float64(Vec::new()).is_numeric());
    assert!(!MultiValues::Unset(DataType::Int32).is_numeric());
    assert!(!MultiValues::String(Vec::new()).is_numeric());
    assert!(!MultiValues::Bool(vec![true]).is_numeric());
}

#[test]
fn test_multi_values_wire_rejects_non_finite_floats() {
    for finite in [
        MultiValues::Float32(vec![1.0, 2.5]),
        MultiValues::Float64(vec![1.0, 2.5]),
    ] {
        let wire = ValueWireV1::try_from(finite.clone())
            .expect("finite collection should fit the V1 wire contract");
        let json = to_string(&wire).expect("serialize V1 wire");
        let decoded = decode_value_wire_str(&json).expect("deserialize V1 wire");
        assert_eq!(decoded.into_container(), finite.into());
    }

    assert!(matches!(
        ValueWireV1::try_from(MultiValues::Float32(vec![1.0, f32::NEG_INFINITY,])),
        Err(ValueWireEncodeError::NonFiniteFloat { .. })
    ));
    assert!(matches!(
        ValueWireV1::try_from(MultiValues::Float64(vec![1.0, f64::NEG_INFINITY,])),
        Err(ValueWireEncodeError::NonFiniteFloat { .. })
    ));
}

#[test]
fn test_multi_values_core_get_is_strict() {
    let values = MultiValues::Int32(vec![1, 2]);
    assert_eq!(values.get::<i32>().unwrap(), vec![1, 2]);
    assert!(matches!(
        values.get::<String>(),
        Err(ValueError::TypeMismatch {
            expected: DataType::String,
            actual: DataType::Int32,
        })
    ));
}

#[test]
fn test_multi_values_core_get_first_reads_first_or_default() {
    let values = MultiValues::Int32(vec![8, 13]);
    assert_eq!(values.get_first::<i32>().unwrap(), 8);
    assert_eq!(values.get_first_or::<i32>(99).unwrap(), 8);

    let empty = MultiValues::Unset(DataType::Int32);
    assert_eq!(empty.get_first_or::<i32>(99).unwrap(), 99);
    assert!(matches!(
        empty.get_first::<i32>(),
        Err(ValueError::Missing(_))
    ));
}

#[test]
fn test_multi_values_core_fallbacks_and_merge_errors() {
    let unset = MultiValues::Unset(DataType::Int32);
    assert_eq!(unset.get_or::<i32>(vec![9]).unwrap(), vec![9]);
    assert_eq!(unset.get_or_else::<i32, _>(|| vec![10]).unwrap(), vec![10]);
    assert_eq!(unset.get_first_or::<i32>(11).unwrap(), 11);
    assert_eq!(unset.get_first_or_else::<i32, _>(|| 12).unwrap(), 12);

    let mut values = MultiValues::Int32(vec![1]);
    assert!(values.merge(&MultiValues::Int32(Vec::new())).is_ok());
    assert!(matches!(
        values.merge(&MultiValues::String(vec!["wrong".to_string()])),
        Err(ValueError::TypeMismatch { .. })
    ));
}

#[test]
fn test_multi_values_core_set_replaces_with_vec_slice_and_single() {
    let mut values = MultiValues::Unset(DataType::Int32);
    values.set(vec![1, 2]);
    assert_eq!(values.get_int32s().unwrap(), &[1, 2]);

    values.set(&[3, 4][..]);
    assert_eq!(values.get_int32s().unwrap(), &[3, 4]);

    values.set(5);
    assert_eq!(values.get_int32s().unwrap(), &[5]);
}

#[test]
fn test_multi_values_core_set_clones_slice_values() {
    let mut values = MultiValues::Unset(DataType::Int64);
    let source = [10i64, 20, 30];
    values.set(&source[..]);

    assert_eq!(values.get_int64s().unwrap(), &[10, 20, 30]);
    assert_eq!(source, [10, 20, 30]);
}

#[test]
fn test_multi_values_core_set_sets_owned_vectors() {
    let mut values = MultiValues::Unset(DataType::String);
    values.set(vec!["alpha".to_string(), "beta".to_string()]);

    assert_eq!(values.data_type(), DataType::String);
    assert_eq!(values.get_strings().unwrap(), &["alpha", "beta"]);
}

#[test]
fn test_multi_values_core_set_single_replaces_existing_list() {
    let mut values = MultiValues::Int32(vec![1, 2, 3]);
    values.set(9);

    assert_eq!(values.data_type(), DataType::Int32);
    assert_eq!(values.get_int32s().unwrap(), &[9]);
}

#[test]
fn test_multi_values_core_add_appends_single_vec_and_slice() {
    let mut values = MultiValues::Int32(vec![1]);
    values.add(2).unwrap();
    values.add(vec![3, 4]).unwrap();
    values.add(&[5, 6][..]).unwrap();

    assert_eq!(values.get_int32s().unwrap(), &[1, 2, 3, 4, 5, 6]);
}

#[test]
fn test_multi_values_core_add_appends_matching_single_value() {
    let mut values = MultiValues::Bool(vec![true]);
    values.add(false).unwrap();
    assert_eq!(values.get_bools().unwrap(), &[true, false]);

    let mut strings = MultiValues::String(vec!["x".to_string()]);
    assert!(matches!(
        strings.add(1i32),
        Err(ValueError::TypeMismatch { .. })
    ));
}

#[test]
fn test_multi_values_core_add_initializes_empty_and_extends_existing() {
    let mut values = MultiValues::Unset(DataType::Int32);
    values.add(vec![1, 2]).unwrap();
    values.add(vec![3, 4]).unwrap();

    assert_eq!(values.get_int32s().unwrap(), &[1, 2, 3, 4]);
}

#[test]
fn test_multi_values_core_add_slice_appends_without_consuming_source() {
    let source = [1u16, 2, 3];
    let mut values = MultiValues::Unset(DataType::UInt16);
    values.add(&source[..]).unwrap();
    values.add(&source[1..]).unwrap();

    assert_eq!(values.get_uint16s().unwrap(), &[1, 2, 3, 2, 3]);
    assert_eq!(source, [1, 2, 3]);
}