#![allow(clippy::float_cmp, reason = "the tests use exact binary fractions")]
use std::collections::{BTreeMap, HashMap};
use std::sync::Arc;
use serde::Deserialize;
use crate::value::{Row, Value};
fn text(value: &str) -> Value {
Value::Text(value.to_owned())
}
fn row(pairs: &[(&str, Value)]) -> Row {
let columns: Vec<String> = pairs.iter().map(|(k, _)| (*k).to_owned()).collect();
let values = pairs.iter().map(|(_, v)| v.clone()).collect();
Row::new(Arc::from(columns), values)
}
#[derive(Debug, Deserialize, PartialEq)]
struct User {
id: i32,
name: String,
email: Option<String>,
}
#[test]
fn a_row_decodes_into_a_struct_by_column_name() {
let row = row(&[
("name", text("ada")),
("extra", Value::Bool(true)),
("id", Value::Int(7)),
("email", Value::Null),
]);
assert_eq!(
row.decode::<User>().unwrap(),
User {
id: 7,
name: "ada".into(),
email: None
}
);
}
#[test]
fn a_row_decodes_into_borrowed_text() {
#[derive(Deserialize)]
struct Name<'a> {
name: &'a str,
}
let row = row(&[("name", text("ada"))]);
assert_eq!(row.decode::<Name<'_>>().unwrap().name, "ada");
}
#[test]
fn a_missing_column_gives_an_error() {
let row = row(&[("id", Value::Int(1))]);
let err = row.decode::<User>().unwrap_err();
assert!(err.to_string().contains("name"), "{err}");
}
#[test]
fn a_row_decodes_into_a_tuple_by_position() {
let row = row(&[("a", Value::Int(1)), ("b", text("x"))]);
assert_eq!(row.decode::<(i64, String)>().unwrap(), (1, "x".into()));
let err = row.decode::<(i64,)>().unwrap_err();
assert!(err.to_string().contains("fewer columns"), "{err}");
let err = row.decode::<(i64, String, bool)>().unwrap_err();
assert!(err.to_string().contains("tuple of size 3"), "{err}");
}
#[test]
fn a_row_decodes_into_a_map() {
let row = row(&[("a", Value::Int(1)), ("b", Value::Int(2))]);
let map: BTreeMap<String, i64> = row.decode().unwrap();
assert_eq!(map, BTreeMap::from([("a".into(), 1), ("b".into(), 2)]));
}
#[test]
fn a_one_column_row_decodes_into_a_scalar() {
assert_eq!(
row(&[("n", Value::HugeInt(42))]).decode::<i64>().unwrap(),
42
);
assert_eq!(row(&[("s", text("x"))]).decode::<String>().unwrap(), "x");
assert_eq!(
row(&[("n", Value::Null)]).decode::<Option<i64>>().unwrap(),
None
);
assert_eq!(
row(&[("n", Value::Int(1))])
.decode::<Option<i64>>()
.unwrap(),
Some(1)
);
let two = row(&[("a", Value::Int(1)), ("b", Value::Int(2))]);
assert!(two.decode::<i64>().is_err());
}
#[test]
fn an_option_of_a_struct_decodes_from_a_row() {
let row = row(&[
("id", Value::Int(1)),
("name", text("a")),
("email", text("e")),
]);
assert!(row.decode::<Option<User>>().unwrap().is_some());
}
#[test]
fn numbers_decode_into_rust_numbers() {
assert_eq!(Value::Int(-1).decode::<i8>().unwrap(), -1);
assert_eq!(Value::UInt(255).decode::<u8>().unwrap(), 255);
assert_eq!(Value::HugeInt(5).decode::<i64>().unwrap(), 5);
assert_eq!(Value::UHugeInt(6).decode::<u32>().unwrap(), 6);
assert_eq!(
Value::HugeInt(i128::MIN).decode::<i128>().unwrap(),
i128::MIN
);
assert_eq!(Value::Int(2).decode::<f64>().unwrap(), 2.0);
assert_eq!(Value::Float(0.5).decode::<f32>().unwrap(), 0.5);
assert!(Value::Int(300).decode::<u8>().is_err());
assert!(Value::Int(-1).decode::<u64>().is_err());
}
#[test]
fn decimals_decode_into_floats_and_text() {
let decimal = Value::Decimal("12.50".into());
assert_eq!(decimal.decode::<f64>().unwrap(), 12.5);
assert_eq!(decimal.decode::<f32>().unwrap(), 12.5);
assert_eq!(decimal.decode::<String>().unwrap(), "12.50");
}
#[test]
fn text_decodes_into_strings_and_enums() {
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
enum Mood {
Happy,
Sad,
}
assert_eq!(text("sad").decode::<Mood>().unwrap(), Mood::Sad);
assert!(text("angry").decode::<Mood>().is_err());
assert_eq!(
Value::Date("2024-01-01".into()).decode::<String>().unwrap(),
"2024-01-01"
);
assert_eq!(
Value::Time("10:00:00".into()).decode::<String>().unwrap(),
"10:00:00"
);
assert_eq!(
Value::Timestamp("t".into()).decode::<String>().unwrap(),
"t"
);
assert!(Value::Int(1).decode::<Mood>().is_err());
let _ = Mood::Happy;
}
#[test]
fn blobs_decode_into_bytes() {
assert_eq!(
Value::Blob(vec![1, 2]).decode::<Vec<u8>>().unwrap(),
vec![1, 2]
);
}
#[test]
fn null_decodes_into_none_and_unit() {
Value::Null.decode::<()>().unwrap();
assert_eq!(Value::Null.decode::<Option<String>>().unwrap(), None);
assert!(Value::Null.decode::<String>().is_err());
assert!(Value::Int(1).decode::<()>().is_err());
}
#[test]
fn nested_values_decode() {
#[derive(Debug, Deserialize, PartialEq)]
struct Point {
x: i64,
y: i64,
}
let list = Value::List(vec![Value::Int(1), Value::Int(2)]);
assert_eq!(list.decode::<Vec<i64>>().unwrap(), vec![1, 2]);
assert!(list.decode::<(i64,)>().is_err());
let point = Value::Struct(vec![
("x".into(), Value::Int(1)),
("y".into(), Value::Int(2)),
]);
assert_eq!(point.decode::<Point>().unwrap(), Point { x: 1, y: 2 });
let fields: HashMap<String, i64> = point.decode().unwrap();
assert_eq!(fields.len(), 2);
let map = Value::Map(vec![(text("k"), Value::Bool(true))]);
let decoded: BTreeMap<String, bool> = map.decode().unwrap();
assert_eq!(decoded, BTreeMap::from([("k".into(), true)]));
}
#[test]
fn intervals_decode_into_a_struct() {
#[derive(Debug, Deserialize, PartialEq)]
struct Interval {
months: i32,
days: i32,
nanos: i64,
}
let value = Value::Interval {
months: 1,
days: 2,
nanos: 3,
};
assert_eq!(
value.decode::<Interval>().unwrap(),
Interval {
months: 1,
days: 2,
nanos: 3
}
);
}
#[test]
fn newtypes_and_ignored_values_decode() {
#[derive(Debug, Deserialize, PartialEq)]
struct Id(i64);
#[derive(Debug, Deserialize, PartialEq)]
struct Only {
id: Id,
}
let row = row(&[("id", Value::Int(3)), ("skip", Value::List(vec![]))]);
assert_eq!(row.decode::<Only>().unwrap(), Only { id: Id(3) });
assert_eq!(Value::Int(4).decode::<Id>().unwrap(), Id(4));
}
#[test]
fn an_error_names_the_column_and_hides_the_value() {
#[derive(Debug, Deserialize)]
struct Wrong {
#[allow(dead_code)]
name: i64,
}
let row = row(&[
("id", Value::Int(1)),
("name", text("secret@example.com")),
("email", Value::Null),
]);
let err = row.decode::<Wrong>().unwrap_err();
assert_eq!(err.column(), Some("name"));
let message = err.to_string();
assert!(message.contains("`name`"), "{message}");
assert!(!message.contains("secret"), "{message}");
}
#[test]
fn an_unknown_variant_error_hides_the_value() {
#[derive(Debug, Deserialize)]
enum Kind {
A,
}
let err = text("secret").decode::<Kind>().unwrap_err();
assert!(!err.to_string().contains("secret"), "{err}");
let _ = Kind::A;
}
#[test]
fn an_out_of_range_error_hides_the_value() {
let err = Value::Int(123_456).decode::<u8>().unwrap_err();
assert!(!err.to_string().contains("123456"), "{err}");
let err = Value::Decimal("9e999x".into()).decode::<f64>().unwrap_err();
assert!(!err.to_string().contains("9e999x"), "{err}");
}
#[test]
fn large_128_bit_values_decode_into_128_bit_types() {
let huge = Value::HugeInt(i128::MAX);
assert_eq!(huge.decode::<i128>().unwrap(), i128::MAX);
assert!(huge.decode::<i64>().is_err());
assert_eq!(Value::HugeInt(1 << 64).decode::<u128>().unwrap(), 1 << 64);
assert_eq!(
Value::HugeInt(u64::MAX.into()).decode::<u64>().unwrap(),
u64::MAX
);
assert_eq!(
Value::UHugeInt(u128::MAX).decode::<u128>().unwrap(),
u128::MAX
);
}
#[test]
fn an_unknown_field_error_hides_the_key() {
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct Only {
#[allow(dead_code)]
a: i64,
}
let value = Value::Map(vec![(text("alice@example.com"), Value::Int(1))]);
let err = value.decode::<Only>().unwrap_err();
assert!(!err.to_string().contains("alice"), "{err}");
}
#[test]
fn whole_decimals_decode_into_integers() {
assert_eq!(Value::Decimal("5".into()).decode::<i64>().unwrap(), 5);
assert_eq!(
Value::Decimal("-12.00".into()).decode::<i32>().unwrap(),
-12
);
assert_eq!(Value::Decimal("7.0".into()).decode::<u64>().unwrap(), 7);
assert!(Value::Decimal("5.5".into()).decode::<i64>().is_err());
assert!(Value::Decimal("300".into()).decode::<u8>().is_err());
}
#[test]
fn a_one_column_list_or_map_row_decodes_into_its_value() {
let list = row(&[(
"l",
Value::List(vec![Value::Float(1.5), Value::Float(2.25)]),
)]);
assert_eq!(list.decode::<Vec<f64>>().unwrap(), vec![1.5, 2.25]);
let map = row(&[("m", Value::Map(vec![(text("a"), Value::Int(1))]))]);
let decoded: HashMap<String, i64> = map.decode().unwrap();
assert_eq!(decoded, HashMap::from([("a".into(), 1)]));
let scalar = row(&[("n", Value::Int(3))]);
assert_eq!(scalar.decode::<Vec<i64>>().unwrap(), vec![3]);
let by_name: HashMap<String, i64> = scalar.decode().unwrap();
assert_eq!(by_name, HashMap::from([("n".into(), 3)]));
}
#[test]
fn a_one_column_scalar_error_names_the_column() {
let err = row(&[("n", text("x"))]).decode::<i64>().unwrap_err();
assert_eq!(err.column(), Some("n"));
}
#[test]
fn a_row_decodes_into_json() {
let row = row(&[
("id", Value::Int(1)),
("tags", Value::List(vec![text("a")])),
]);
let json: serde_json::Value = row.decode().unwrap();
assert_eq!(json, serde_json::json!({"id": 1, "tags": ["a"]}));
}
#[test]
fn a_row_decodes_into_a_newtype_or_an_enum() {
#[derive(Debug, Deserialize, PartialEq)]
struct Count(i64);
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
enum Mood {
Sad,
}
assert_eq!(
row(&[("n", Value::Int(4))]).decode::<Count>().unwrap(),
Count(4)
);
assert_eq!(
row(&[("m", text("sad"))]).decode::<Mood>().unwrap(),
Mood::Sad
);
let err = row(&[("a", text("sad")), ("b", text("sad"))])
.decode::<Mood>()
.unwrap_err();
assert!(err.to_string().contains("one column"), "{err}");
}