use qubit_datatype::DataType;
use qubit_value::Value;
use qubit_value::ValueError;
use qubit_value::ValueMissing;
#[test]
fn test_value_getter_is_strict_and_default_is_empty_only() {
let value = Value::Int32(5);
assert_eq!(value.get::<i32>().unwrap(), 5);
assert!(matches!(
value.get::<String>(),
Err(ValueError::TypeMismatch {
expected: DataType::String,
actual: DataType::Int32,
})
));
let empty = Value::Unset(DataType::String);
assert_eq!(
empty.get::<String>(),
Err(ValueError::Missing(ValueMissing::unset_scalar(
DataType::String,
DataType::String
))),
);
assert_eq!(empty.get_or::<String>("missing").unwrap(), "missing");
}
#[test]
fn test_value_try_from_borrowed_value_reports_each_storage_state() {
let value = Value::Bool(true);
assert_eq!(<&bool>::try_from(&value), Ok(&true));
assert_eq!(
<&bool>::try_from(&Value::Unset(DataType::Bool)),
Err(ValueError::Missing(ValueMissing::unset_scalar(
DataType::Bool,
DataType::Bool
))),
);
assert_eq!(
<&bool>::try_from(&Value::Int32(1)),
Err(ValueError::TypeMismatch {
expected: DataType::Bool,
actual: DataType::Int32,
}),
);
}
#[test]
fn test_value_try_from_owned_value_reports_each_storage_state() {
assert_eq!(bool::try_from(Value::Bool(true)), Ok(true));
assert_eq!(
bool::try_from(Value::Unset(DataType::Bool)),
Err(ValueError::Missing(ValueMissing::unset_scalar(
DataType::Bool,
DataType::Bool
))),
);
assert_eq!(
bool::try_from(Value::Int32(1)),
Err(ValueError::TypeMismatch {
expected: DataType::Bool,
actual: DataType::Int32,
}),
);
}
#[test]
fn test_value_try_from_borrowed_string_reports_each_storage_state() {
let value = Value::String("text".to_owned());
assert_eq!(<&str>::try_from(&value), Ok("text"));
assert_eq!(
<&str>::try_from(&Value::Unset(DataType::String)),
Err(ValueError::Missing(ValueMissing::unset_scalar(
DataType::String,
DataType::String
))),
);
assert_eq!(
<&str>::try_from(&Value::Int32(1)),
Err(ValueError::TypeMismatch {
expected: DataType::String,
actual: DataType::Int32,
}),
);
}
macro_rules! assert_scalar_getter_contract {
($value:expr, $getter:ident, $expected:expr, $data_type:expr, $wrong_type:expr) => {{
let value = $value;
assert_eq!(value.$getter().unwrap(), $expected);
assert_eq!(
Value::Unset($data_type).$getter(),
Err(ValueError::Missing(ValueMissing::unset_scalar($data_type, $data_type)))
);
assert!(matches!(
Value::Unset($wrong_type).$getter(),
Err(ValueError::TypeMismatch { expected, actual })
if expected == $data_type && actual == $wrong_type
));
}};
}
#[test]
fn test_value_scalar_getters_cover_all_builtin_types() {
assert_scalar_getter_contract!(Value::Bool(true), get_bool, true, DataType::Bool, DataType::String);
assert_scalar_getter_contract!(Value::Char('x'), get_char, 'x', DataType::Char, DataType::String);
assert_scalar_getter_contract!(Value::Int8(-8), get_int8, -8, DataType::Int8, DataType::String);
assert_scalar_getter_contract!(Value::Int16(-16), get_int16, -16, DataType::Int16, DataType::String);
assert_scalar_getter_contract!(Value::Int32(-32), get_int32, -32, DataType::Int32, DataType::String);
assert_scalar_getter_contract!(Value::Int64(-64), get_int64, -64, DataType::Int64, DataType::String);
assert_scalar_getter_contract!(
Value::Int128(-128),
get_int128,
-128,
DataType::Int128,
DataType::String
);
assert_scalar_getter_contract!(Value::UInt8(8), get_uint8, 8, DataType::UInt8, DataType::String);
assert_scalar_getter_contract!(Value::UInt16(16), get_uint16, 16, DataType::UInt16, DataType::String);
assert_scalar_getter_contract!(Value::UInt32(32), get_uint32, 32, DataType::UInt32, DataType::String);
assert_scalar_getter_contract!(Value::UInt64(64), get_uint64, 64, DataType::UInt64, DataType::String);
assert_scalar_getter_contract!(
Value::UInt128(128),
get_uint128,
128,
DataType::UInt128,
DataType::String
);
assert_scalar_getter_contract!(
Value::Float32(1.5),
get_float32,
1.5,
DataType::Float32,
DataType::String
);
assert_scalar_getter_contract!(
Value::Float64(2.5),
get_float64,
2.5,
DataType::Float64,
DataType::String
);
assert_scalar_getter_contract!(
Value::String("text".to_string()),
get_string,
"text",
DataType::String,
DataType::Bool
);
assert_scalar_getter_contract!(
Value::Duration(std::time::Duration::from_secs(3)),
get_duration,
std::time::Duration::from_secs(3),
DataType::Duration,
DataType::String
);
}
#[cfg(feature = "chrono")]
#[test]
fn test_value_chrono_getters_cover_unset_and_values() {
use chrono::DateTime;
use chrono::NaiveDate;
use chrono::NaiveDateTime;
use chrono::NaiveTime;
use chrono::Utc;
assert_eq!(
Value::Date(NaiveDate::from_ymd_opt(2025, 1, 2).unwrap())
.get_date()
.unwrap(),
NaiveDate::from_ymd_opt(2025, 1, 2).unwrap()
);
assert_eq!(
Value::Time(NaiveTime::from_hms_opt(3, 4, 5).unwrap())
.get_time()
.unwrap(),
NaiveTime::from_hms_opt(3, 4, 5).unwrap()
);
let datetime = NaiveDateTime::new(
NaiveDate::from_ymd_opt(2025, 1, 2).unwrap(),
NaiveTime::from_hms_opt(3, 4, 5).unwrap(),
);
assert_eq!(Value::DateTime(datetime).get_datetime().unwrap(), datetime);
let instant = DateTime::<Utc>::from_timestamp(1_735_776_000, 0).unwrap();
assert_eq!(Value::Instant(instant).get_instant().unwrap(), instant);
}
#[cfg(feature = "big-integer")]
#[test]
fn test_value_big_integer_getters_cover_owned_and_borrowed_access() {
let value = Value::BigInteger(num_bigint::BigInt::from(123));
assert_eq!(value.get_biginteger().unwrap(), num_bigint::BigInt::from(123));
assert_eq!(value.get_biginteger_ref().unwrap(), &num_bigint::BigInt::from(123));
}
#[cfg(feature = "big-decimal")]
#[test]
fn test_value_big_decimal_getters_cover_owned_and_borrowed_access() {
let decimal = "123.45".parse::<bigdecimal::BigDecimal>().unwrap();
let value = Value::BigDecimal(decimal.clone());
assert_eq!(value.get_bigdecimal().unwrap(), decimal);
assert_eq!(value.get_bigdecimal_ref().unwrap(), &decimal);
}
#[cfg(feature = "url")]
#[test]
fn test_value_url_getters_cover_owned_and_borrowed_access() {
let url = url::Url::parse("https://example.com/path").unwrap();
let value = Value::Url(url.clone());
assert_eq!(value.get_url().unwrap(), url);
assert_eq!(value.get_url_ref().unwrap(), &url);
}
#[test]
fn test_value_map_getters_cover_owned_and_borrowed_access() {
let map = std::collections::HashMap::from([("key".to_string(), "value".to_string())]);
let value = Value::StringMap(map.clone());
assert_eq!(value.get_string_map().unwrap(), map);
assert_eq!(value.get_string_map_ref().unwrap(), &map);
}
#[cfg(feature = "json")]
#[test]
fn test_value_json_getters_cover_owned_and_borrowed_access() {
let json = serde_json::json!({"key": 1});
let value = Value::Json(json.clone());
assert_eq!(value.get_json().unwrap(), json);
assert_eq!(value.get_json_ref().unwrap(), &json);
}
#[cfg(feature = "big-integer")]
#[test]
fn test_value_big_integer_borrowed_getter_reports_storage_errors() {
assert!(matches!(
Value::Unset(DataType::BigInteger).get_biginteger_ref(),
Err(ValueError::Missing(ref missing)) if *missing == ValueMissing::unset_scalar(DataType::BigInteger, DataType::BigInteger)
));
assert!(matches!(
Value::Int32(1).get_biginteger_ref(),
Err(ValueError::TypeMismatch {
expected: DataType::BigInteger,
actual: DataType::Int32,
})
));
}
#[cfg(feature = "big-decimal")]
#[test]
fn test_value_big_decimal_borrowed_getter_reports_storage_errors() {
assert!(matches!(
Value::Unset(DataType::BigDecimal).get_bigdecimal_ref(),
Err(ValueError::Missing(ref missing)) if *missing == ValueMissing::unset_scalar(DataType::BigDecimal, DataType::BigDecimal)
));
assert!(matches!(
Value::Int32(1).get_bigdecimal_ref(),
Err(ValueError::TypeMismatch {
expected: DataType::BigDecimal,
actual: DataType::Int32,
})
));
}
#[cfg(feature = "url")]
#[test]
fn test_value_url_borrowed_getter_reports_storage_errors() {
assert!(matches!(
Value::Unset(DataType::Url).get_url_ref(),
Err(ValueError::Missing(ref missing)) if *missing == ValueMissing::unset_scalar(DataType::Url, DataType::Url)
));
assert!(matches!(
Value::Int32(1).get_url_ref(),
Err(ValueError::TypeMismatch {
expected: DataType::Url,
actual: DataType::Int32,
})
));
}
#[test]
fn test_value_string_map_borrowed_getter_reports_storage_errors() {
assert!(matches!(
Value::Unset(DataType::StringMap).get_string_map_ref(),
Err(ValueError::Missing(ref missing)) if *missing == ValueMissing::unset_scalar(DataType::StringMap, DataType::StringMap)
));
assert!(matches!(
Value::Int32(1).get_string_map_ref(),
Err(ValueError::TypeMismatch {
expected: DataType::StringMap,
actual: DataType::Int32,
})
));
}
#[cfg(feature = "json")]
#[test]
fn test_value_json_borrowed_getter_reports_storage_errors() {
assert!(matches!(
Value::Unset(DataType::Json).get_json_ref(),
Err(ValueError::Missing(ref missing)) if *missing == ValueMissing::unset_scalar(DataType::Json, DataType::Json)
));
assert!(matches!(
Value::Int32(1).get_json_ref(),
Err(ValueError::TypeMismatch {
expected: DataType::Json,
actual: DataType::Int32,
})
));
}
#[cfg(all(feature = "converter", feature = "json"))]
#[test]
fn test_value_json_accessors_cover_serialization_and_deserialization() {
let value = Value::from_serializable(&serde_json::json!({"answer": 42})).expect("serialize JSON value");
let decoded: std::collections::HashMap<String, i32> = value.deserialize_json().expect("deserialize JSON value");
assert_eq!(decoded.get("answer"), Some(&42));
assert!(matches!(
Value::Unset(DataType::Json).deserialize_json::<serde_json::Value>(),
Err(ValueError::Missing(ref missing)) if *missing == ValueMissing::unset_scalar(DataType::Json, DataType::Json)
));
assert!(matches!(
Value::Int32(1).deserialize_json::<serde_json::Value>(),
Err(ValueError::TypeMismatch {
expected: DataType::Json,
actual: DataType::Int32,
})
));
assert!(matches!(
Value::Json(serde_json::json!("not an integer")).deserialize_json::<i32>(),
Err(ValueError::Conversion(_))
));
}