use nodedb_types::columnar::StrictSchema;
use nodedb_types::value::Value;
use super::coerce::coerce_value;
pub fn bytes_to_binary_tuple(bytes: &[u8], schema: &StrictSchema) -> crate::Result<Vec<u8>> {
let value =
nodedb_types::value_from_msgpack(bytes).map_err(|e| crate::Error::Serialization {
format: "msgpack".to_string(),
detail: format!("zerompk decode: {e}"),
})?;
value_to_binary_tuple(&value, schema)
}
pub fn value_to_binary_tuple(value: &Value, schema: &StrictSchema) -> crate::Result<Vec<u8>> {
let map = match value {
Value::Object(m) => m,
_ => {
return Err(crate::Error::BadRequest {
detail: "strict value must be an Object".to_string(),
});
}
};
let schema_columns: std::collections::HashSet<&str> =
schema.columns.iter().map(|c| c.name.as_str()).collect();
if let Some(unknown) = map.keys().find(|k| !schema_columns.contains(k.as_str())) {
return Err(crate::Error::BadRequest {
detail: format!("unknown field '{unknown}' not present in strict schema"),
});
}
let encoder = nodedb_strict::TupleEncoder::new(schema);
let mut values = Vec::with_capacity(schema.columns.len());
for col in &schema.columns {
let field_val = map.get(&col.name);
let typed = match field_val {
None | Some(Value::Null) => {
if !col.nullable {
return Err(crate::Error::BadRequest {
detail: format!("column '{}' is NOT NULL but no value provided", col.name),
});
}
Value::Null
}
Some(v) => coerce_value(v, &col.column_type, &col.name)?,
};
values.push(typed);
}
encoder
.encode(&values)
.map_err(|e| crate::Error::BadRequest {
detail: format!("Binary Tuple encode: {e}"),
})
}
pub fn bytes_to_binary_tuple_bitemporal(
bytes: &[u8],
schema: &StrictSchema,
system_from_ms: i64,
valid_from_ms: i64,
valid_until_ms: i64,
) -> crate::Result<Vec<u8>> {
let value =
nodedb_types::value_from_msgpack(bytes).map_err(|e| crate::Error::Serialization {
format: "msgpack".to_string(),
detail: format!("zerompk decode: {e}"),
})?;
value_to_binary_tuple_bitemporal(
&value,
schema,
system_from_ms,
valid_from_ms,
valid_until_ms,
)
}
pub fn value_to_binary_tuple_bitemporal(
value: &Value,
schema: &StrictSchema,
system_from_ms: i64,
valid_from_ms: i64,
valid_until_ms: i64,
) -> crate::Result<Vec<u8>> {
if !schema.bitemporal {
return Err(crate::Error::BadRequest {
detail: "schema is not bitemporal".to_string(),
});
}
let map = match value {
Value::Object(m) => m,
_ => {
return Err(crate::Error::BadRequest {
detail: "strict value must be an Object".to_string(),
});
}
};
let user_columns = &schema.columns[3..];
let user_names: std::collections::HashSet<&str> =
user_columns.iter().map(|c| c.name.as_str()).collect();
if let Some(unknown) = map.keys().find(|k| {
!user_names.contains(k.as_str())
&& !nodedb_types::columnar::BITEMPORAL_RESERVED_COLUMNS.contains(&k.as_str())
}) {
return Err(crate::Error::BadRequest {
detail: format!("unknown field '{unknown}' not present in strict schema"),
});
}
let mut user_values = Vec::with_capacity(user_columns.len());
for col in user_columns {
let field_val = map.get(&col.name);
let typed = match field_val {
None | Some(Value::Null) => {
if !col.nullable {
return Err(crate::Error::BadRequest {
detail: format!("column '{}' is NOT NULL but no value provided", col.name),
});
}
Value::Null
}
Some(v) => coerce_value(v, &col.column_type, &col.name)?,
};
user_values.push(typed);
}
let encoder = nodedb_strict::TupleEncoder::new(schema);
encoder
.encode_bitemporal(system_from_ms, valid_from_ms, valid_until_ms, &user_values)
.map_err(|e| crate::Error::BadRequest {
detail: format!("Binary Tuple encode: {e}"),
})
}