use crate::error::BridgeError;
use crate::traits::ToCore;
use tidepool_eval::Value;
use tidepool_repr::{DataConTable, Literal};
impl ToCore for serde_json::Value {
fn to_value(&self, table: &DataConTable) -> Result<Value, BridgeError> {
match self {
serde_json::Value::Null => {
let id = table
.get_by_name_arity("Null", 0)
.ok_or_else(|| BridgeError::UnknownDataConName("Null".into()))?;
Ok(Value::Con(id, vec![]))
}
serde_json::Value::Bool(b) => {
let id = table
.get_by_name_arity("Bool", 1)
.ok_or_else(|| BridgeError::UnknownDataConName("Bool".into()))?;
let inner = (*b).to_value(table)?;
Ok(Value::Con(id, vec![inner]))
}
serde_json::Value::Number(n) => {
let id = table
.get_by_name_arity("Number", 1)
.ok_or_else(|| BridgeError::UnknownDataConName("Number".into()))?;
let f = n.as_f64().unwrap_or(0.0);
Ok(Value::Con(
id,
vec![Value::Lit(Literal::LitDouble(f.to_bits()))],
))
}
serde_json::Value::String(s) => {
let id = table
.get_by_name_arity("String", 1)
.ok_or_else(|| BridgeError::UnknownDataConName("String".into()))?;
let inner = s.clone().to_value(table)?;
Ok(Value::Con(id, vec![inner]))
}
serde_json::Value::Array(arr) => {
let id = table
.get_by_name_arity("Array", 1)
.ok_or_else(|| BridgeError::UnknownDataConName("Array".into()))?;
let elements: Result<Vec<Value>, BridgeError> =
arr.iter().map(|v| v.to_value(table)).collect();
let list = elements?.to_value(table)?;
Ok(Value::Con(id, vec![list]))
}
serde_json::Value::Object(map) => {
let id = table
.get_by_name_arity("Object", 1)
.ok_or_else(|| BridgeError::UnknownDataConName("Object".into()))?;
let map_val = keymap_to_value(map, table)?;
Ok(Value::Con(id, vec![map_val]))
}
}
}
}
fn keymap_to_value(
map: &serde_json::Map<std::string::String, serde_json::Value>,
table: &DataConTable,
) -> Result<Value, BridgeError> {
let bin_id = table
.get_by_qualified_name("Data.Map.Bin")
.or_else(|| table.get_by_name_arity("Bin", 5))
.ok_or_else(|| BridgeError::UnknownDataConName("Bin".into()))?;
let tip_id = table
.get_by_qualified_name("Data.Map.Tip")
.or_else(|| table.get_companion(bin_id, "Tip", 0))
.or_else(|| table.get_by_name_arity("Tip", 0))
.ok_or_else(|| BridgeError::UnknownDataConName("Tip".into()))?;
let i_hash_id = table
.get_by_name_arity("I#", 1)
.ok_or_else(|| BridgeError::UnknownDataConName("I#".into()))?;
let mut entries: Vec<(&std::string::String, &serde_json::Value)> = map.iter().collect();
entries.sort_by(|a, b| a.0.cmp(b.0));
fn build_tree(
entries: &[(&std::string::String, &serde_json::Value)],
bin_id: tidepool_repr::DataConId,
tip_id: tidepool_repr::DataConId,
i_hash_id: tidepool_repr::DataConId,
table: &DataConTable,
) -> Result<Value, BridgeError> {
if entries.is_empty() {
return Ok(Value::Con(tip_id, vec![]));
}
let mid = entries.len() / 2;
let (k, v) = entries[mid];
let left = build_tree(&entries[..mid], bin_id, tip_id, i_hash_id, table)?;
let right = build_tree(&entries[mid + 1..], bin_id, tip_id, i_hash_id, table)?;
let key_val = k.clone().to_value(table)?;
let json_val = v.to_value(table)?;
let size = Value::Con(
i_hash_id,
vec![Value::Lit(Literal::LitInt(entries.len() as i64))],
);
Ok(Value::Con(
bin_id,
vec![size, key_val, json_val, left, right],
))
}
build_tree(&entries, bin_id, tip_id, i_hash_id, table)
}
#[cfg(test)]
mod tests {
use super::*;
use tidepool_repr::{DataCon, DataConId};
fn json_test_table() -> DataConTable {
let mut t = DataConTable::new();
let cons = [
("Object", 0, 1),
("Array", 1, 1),
("String", 2, 1),
("Number", 3, 1),
("Bool", 4, 1),
("Null", 5, 0),
("Bin", 6, 5),
("Tip", 7, 0),
("True", 8, 0),
("False", 9, 0),
("[]", 10, 0),
(":", 11, 2),
("Text", 12, 3),
("I#", 13, 1),
];
for (i, (name, tag, arity)) in cons.iter().enumerate() {
t.insert(DataCon {
id: DataConId(i as u64),
name: (*name).into(),
tag: *tag,
rep_arity: *arity,
field_bangs: vec![],
qualified_name: None,
});
}
t
}
#[test]
fn test_null_to_value() {
let table = json_test_table();
let json = serde_json::Value::Null;
let val = json.to_value(&table).unwrap();
match val {
Value::Con(id, fields) => {
assert_eq!(table.name_of(id), Some("Null"));
assert!(fields.is_empty());
}
_ => panic!("Expected Con(Null)"),
}
}
#[test]
fn test_bool_to_value() {
let table = json_test_table();
let json = serde_json::json!(true);
let val = json.to_value(&table).unwrap();
match &val {
Value::Con(id, fields) => {
assert_eq!(table.name_of(*id), Some("Bool"));
assert_eq!(fields.len(), 1);
match &fields[0] {
Value::Con(inner_id, _) => {
assert_eq!(table.name_of(*inner_id), Some("True"));
}
_ => panic!("Expected Con(True)"),
}
}
_ => panic!("Expected Con(Bool)"),
}
}
#[test]
fn test_string_to_value() {
let table = json_test_table();
let json = serde_json::json!("hello");
let val = json.to_value(&table).unwrap();
match &val {
Value::Con(id, fields) => {
assert_eq!(table.name_of(*id), Some("String"));
assert_eq!(fields.len(), 1);
match &fields[0] {
Value::Con(inner_id, _) => {
assert_eq!(table.name_of(*inner_id), Some("Text"));
}
_ => panic!("Expected Con(Text), got {:?}", fields[0]),
}
}
_ => panic!("Expected Con(String)"),
}
}
#[test]
fn test_number_to_value() {
let table = json_test_table();
let json = serde_json::json!(42);
let val = json.to_value(&table).unwrap();
match &val {
Value::Con(id, fields) => {
assert_eq!(table.name_of(*id), Some("Number"));
assert_eq!(fields.len(), 1);
match &fields[0] {
Value::Lit(Literal::LitDouble(bits)) => {
assert_eq!(f64::from_bits(*bits), 42.0);
}
_ => panic!("Expected Lit(LitDouble), got {:?}", fields[0]),
}
}
_ => panic!("Expected Con(Number)"),
}
}
#[test]
fn test_number_double_to_value() {
let table = json_test_table();
let json = serde_json::json!(3.14);
let val = json.to_value(&table).unwrap();
match &val {
Value::Con(id, fields) => {
assert_eq!(table.name_of(*id), Some("Number"));
assert_eq!(fields.len(), 1);
match &fields[0] {
Value::Lit(Literal::LitDouble(bits)) => {
let f = f64::from_bits(*bits);
assert!((f - 3.14).abs() < 1e-10);
}
_ => panic!("Expected Lit(LitDouble), got {:?}", fields[0]),
}
}
_ => panic!("Expected Con(Number)"),
}
}
#[test]
fn test_array_to_value() {
let table = json_test_table();
let json = serde_json::json!([1, 2, 3]);
let val = json.to_value(&table).unwrap();
match &val {
Value::Con(id, fields) => {
assert_eq!(table.name_of(*id), Some("Array"));
assert_eq!(fields.len(), 1);
}
_ => panic!("Expected Con(Array)"),
}
}
#[test]
fn test_object_to_value() {
let table = json_test_table();
let json = serde_json::json!({"name": "Alice", "age": 30});
let val = json.to_value(&table).unwrap();
match &val {
Value::Con(id, fields) => {
assert_eq!(table.name_of(*id), Some("Object"));
assert_eq!(fields.len(), 1);
}
_ => panic!("Expected Con(Object)"),
}
}
#[test]
fn test_nested_json() {
let table = json_test_table();
let json = serde_json::json!({
"users": [
{"name": "Alice", "active": true},
{"name": "Bob", "active": false}
],
"count": 2
});
let val = json.to_value(&table).unwrap();
match &val {
Value::Con(id, _) => {
assert_eq!(table.name_of(*id), Some("Object"));
}
_ => panic!("Expected Con(Object)"),
}
}
#[test]
fn test_bool_false_to_value() {
let table = json_test_table();
let val = serde_json::json!(false).to_value(&table).unwrap();
match &val {
Value::Con(id, fields) => {
assert_eq!(table.name_of(*id), Some("Bool"));
match &fields[0] {
Value::Con(inner_id, _) => assert_eq!(table.name_of(*inner_id), Some("False")),
_ => panic!("Expected Con(False)"),
}
}
_ => panic!("Expected Con(Bool)"),
}
}
#[test]
fn test_number_negative() {
let table = json_test_table();
let val = serde_json::json!(-1).to_value(&table).unwrap();
match &val {
Value::Con(id, fields) => {
assert_eq!(table.name_of(*id), Some("Number"));
match &fields[0] {
Value::Lit(Literal::LitDouble(bits)) => {
assert_eq!(f64::from_bits(*bits), -1.0);
}
_ => panic!("Expected LitDouble"),
}
}
_ => panic!("Expected Con(Number)"),
}
}
#[test]
fn test_number_zero() {
let table = json_test_table();
let val = serde_json::json!(0).to_value(&table).unwrap();
match &val {
Value::Con(id, fields) => {
assert_eq!(table.name_of(*id), Some("Number"));
match &fields[0] {
Value::Lit(Literal::LitDouble(bits)) => {
assert_eq!(f64::from_bits(*bits), 0.0);
}
_ => panic!("Expected LitDouble"),
}
}
_ => panic!("Expected Con(Number)"),
}
}
#[test]
fn test_string_empty() {
let table = json_test_table();
let val = serde_json::json!("").to_value(&table).unwrap();
match &val {
Value::Con(id, fields) => {
assert_eq!(table.name_of(*id), Some("String"));
assert_eq!(fields.len(), 1);
match &fields[0] {
Value::Con(inner_id, _) => {
assert_eq!(table.name_of(*inner_id), Some("Text"));
}
_ => panic!("Expected Con(Text)"),
}
}
_ => panic!("Expected Con(String)"),
}
}
#[test]
fn test_string_unicode() {
let table = json_test_table();
let val = serde_json::json!("héllo 🌊").to_value(&table).unwrap();
match &val {
Value::Con(id, fields) => {
assert_eq!(table.name_of(*id), Some("String"));
assert_eq!(fields.len(), 1);
}
_ => panic!("Expected Con(String)"),
}
}
#[test]
fn test_array_empty() {
let table = json_test_table();
let val = serde_json::json!([]).to_value(&table).unwrap();
match &val {
Value::Con(id, fields) => {
assert_eq!(table.name_of(*id), Some("Array"));
assert_eq!(fields.len(), 1);
match &fields[0] {
Value::Con(nil_id, nil_fields) => {
assert_eq!(table.name_of(*nil_id), Some("[]"));
assert!(nil_fields.is_empty());
}
_ => panic!("Expected Con([])"),
}
}
_ => panic!("Expected Con(Array)"),
}
}
#[test]
fn test_array_single() {
let table = json_test_table();
let val = serde_json::json!([1]).to_value(&table).unwrap();
match &val {
Value::Con(id, fields) => {
assert_eq!(table.name_of(*id), Some("Array"));
match &fields[0] {
Value::Con(cons_id, cons_fields) => {
assert_eq!(table.name_of(*cons_id), Some(":"));
assert_eq!(cons_fields.len(), 2);
match &cons_fields[0] {
Value::Con(num_id, _) => {
assert_eq!(table.name_of(*num_id), Some("Number"));
}
_ => panic!("Expected Con(Number)"),
}
match &cons_fields[1] {
Value::Con(nil_id, _) => {
assert_eq!(table.name_of(*nil_id), Some("[]"));
}
_ => panic!("Expected Con([])"),
}
}
_ => panic!("Expected Con(:)"),
}
}
_ => panic!("Expected Con(Array)"),
}
}
#[test]
fn test_array_nested() {
let table = json_test_table();
let val = serde_json::json!([[1, 2], [3]]).to_value(&table).unwrap();
match &val {
Value::Con(id, _) => assert_eq!(table.name_of(*id), Some("Array")),
_ => panic!("Expected Con(Array)"),
}
}
#[test]
fn test_object_empty() {
let table = json_test_table();
let val = serde_json::json!({}).to_value(&table).unwrap();
match &val {
Value::Con(id, fields) => {
assert_eq!(table.name_of(*id), Some("Object"));
assert_eq!(fields.len(), 1);
match &fields[0] {
Value::Con(tip_id, tip_fields) => {
assert_eq!(table.name_of(*tip_id), Some("Tip"));
assert!(tip_fields.is_empty());
}
_ => panic!("Expected Con(Tip)"),
}
}
_ => panic!("Expected Con(Object)"),
}
}
#[test]
fn test_object_single_key() {
let table = json_test_table();
let val = serde_json::json!({"a": 1}).to_value(&table).unwrap();
match &val {
Value::Con(id, fields) => {
assert_eq!(table.name_of(*id), Some("Object"));
match &fields[0] {
Value::Con(bin_id, bin_fields) => {
assert_eq!(table.name_of(*bin_id), Some("Bin"));
assert_eq!(bin_fields.len(), 5);
match &bin_fields[0] {
Value::Con(i_id, i_fields) => {
assert_eq!(table.name_of(*i_id), Some("I#"));
match &i_fields[0] {
Value::Lit(Literal::LitInt(n)) => assert_eq!(*n, 1),
_ => panic!("Expected LitInt(1)"),
}
}
_ => panic!("Expected Con(I#)"),
}
match &bin_fields[3] {
Value::Con(tip_id, _) => {
assert_eq!(table.name_of(*tip_id), Some("Tip"));
}
_ => panic!("Expected Con(Tip)"),
}
match &bin_fields[4] {
Value::Con(tip_id, _) => {
assert_eq!(table.name_of(*tip_id), Some("Tip"));
}
_ => panic!("Expected Con(Tip)"),
}
}
_ => panic!("Expected Con(Bin)"),
}
}
_ => panic!("Expected Con(Object)"),
}
}
#[test]
fn test_object_two_keys() {
let table = json_test_table();
let val = serde_json::json!({"a": 1, "b": 2})
.to_value(&table)
.unwrap();
match &val {
Value::Con(id, fields) => {
assert_eq!(table.name_of(*id), Some("Object"));
match &fields[0] {
Value::Con(bin_id, bin_fields) => {
assert_eq!(table.name_of(*bin_id), Some("Bin"));
assert_eq!(bin_fields.len(), 5);
match &bin_fields[0] {
Value::Con(_, i_fields) => match &i_fields[0] {
Value::Lit(Literal::LitInt(n)) => assert_eq!(*n, 2),
_ => panic!("Expected LitInt(2)"),
},
_ => panic!("Expected I#"),
}
}
_ => panic!("Expected Con(Bin)"),
}
}
_ => panic!("Expected Con(Object)"),
}
}
#[test]
fn test_object_three_keys_balanced() {
let table = json_test_table();
let val = serde_json::json!({"a": 1, "b": 2, "c": 3})
.to_value(&table)
.unwrap();
match &val {
Value::Con(id, fields) => {
assert_eq!(table.name_of(*id), Some("Object"));
match &fields[0] {
Value::Con(bin_id, bin_fields) => {
assert_eq!(table.name_of(*bin_id), Some("Bin"));
let left_is_bin = matches!(&bin_fields[3], Value::Con(id, _) if table.name_of(*id) == Some("Bin"));
let right_is_bin = matches!(&bin_fields[4], Value::Con(id, _) if table.name_of(*id) == Some("Bin"));
assert!(
left_is_bin || right_is_bin,
"Expected at least one Bin child for 3-entry map"
);
}
_ => panic!("Expected Con(Bin)"),
}
}
_ => panic!("Expected Con(Object)"),
}
}
#[test]
fn test_object_large() {
let table = json_test_table();
let mut map = serde_json::Map::new();
for i in 0..12 {
map.insert(format!("key_{}", i), serde_json::json!(i));
}
let json = serde_json::Value::Object(map);
let val = json.to_value(&table).unwrap();
match &val {
Value::Con(id, _) => assert_eq!(table.name_of(*id), Some("Object")),
_ => panic!("Expected Con(Object)"),
}
}
#[test]
fn test_deep_nesting() {
let table = json_test_table();
let json = serde_json::json!({"a": {"b": {"c": 1}}});
let val = json.to_value(&table).unwrap();
match &val {
Value::Con(id, fields) => {
assert_eq!(table.name_of(*id), Some("Object"));
assert_eq!(fields.len(), 1);
}
_ => panic!("Expected Con(Object)"),
}
}
#[test]
fn test_mixed_types() {
let table = json_test_table();
let json = serde_json::json!({
"s": "str",
"n": 42,
"b": true,
"a": [1, 2],
"o": {"x": 1},
"null": null
});
let val = json.to_value(&table).unwrap();
match &val {
Value::Con(id, _) => assert_eq!(table.name_of(*id), Some("Object")),
_ => panic!("Expected Con(Object)"),
}
}
#[test]
fn test_ambiguous_tip_with_companion() {
let mut table = json_test_table();
table.insert(DataCon {
id: DataConId(500),
name: "Tip".into(),
tag: 1,
rep_arity: 0,
field_bangs: vec![],
qualified_name: None,
});
let json = serde_json::json!({"key": "value"});
let val = json.to_value(&table).unwrap();
match &val {
Value::Con(id, _) => assert_eq!(table.name_of(*id), Some("Object")),
_ => panic!("Expected Con(Object)"),
}
}
#[test]
fn test_qualified_name_resolves_ambiguous_map_constructors() {
let mut t = DataConTable::new();
let cons: &[(&str, u32, u32)] = &[
("Object", 0, 1),
("Array", 1, 1),
("String", 2, 1),
("Number", 3, 1),
("Bool", 4, 1),
("Null", 5, 0),
("True", 8, 0),
("False", 9, 0),
("[]", 10, 0),
(":", 11, 2),
("Text", 12, 3),
("I#", 13, 1),
];
for (i, (name, tag, arity)) in cons.iter().enumerate() {
t.insert(DataCon {
id: DataConId(i as u64),
name: (*name).into(),
tag: *tag,
rep_arity: *arity,
field_bangs: vec![],
qualified_name: None,
});
}
t.insert(DataCon {
id: DataConId(100),
name: "Bin".into(),
tag: 1,
rep_arity: 5,
field_bangs: vec![],
qualified_name: Some("Data.Map.Bin".into()),
});
t.insert(DataCon {
id: DataConId(101),
name: "Tip".into(),
tag: 2,
rep_arity: 0,
field_bangs: vec![],
qualified_name: Some("Data.Map.Tip".into()),
});
t.insert(DataCon {
id: DataConId(200),
name: "Bin".into(),
tag: 1,
rep_arity: 3,
field_bangs: vec![],
qualified_name: Some("Data.Set.Bin".into()),
});
t.insert(DataCon {
id: DataConId(201),
name: "Tip".into(),
tag: 2,
rep_arity: 0,
field_bangs: vec![],
qualified_name: Some("Data.Set.Tip".into()),
});
let json = serde_json::json!({"a": 1, "b": 2});
let val = json.to_value(&t).unwrap();
match &val {
Value::Con(id, fields) => {
assert_eq!(t.name_of(*id), Some("Object"));
match &fields[0] {
Value::Con(bin_id, _) => {
assert_eq!(*bin_id, DataConId(100));
}
_ => panic!("Expected Con(Bin)"),
}
}
_ => panic!("Expected Con(Object)"),
}
}
}