use std::collections::HashMap;
use nodedb_query::msgpack_scan::{
KvBodyError, KvBodyShape, kv_body_shape, kv_body_to_row, kv_row_msgpack, kv_row_to_body_fields,
row_to_kv_body,
};
use nodedb_types::value::Value;
use crate::error::LiteError;
pub(crate) fn encode_kv_body(
fields: HashMap<String, Value>,
shape: KvBodyShape,
) -> Result<Vec<u8>, LiteError> {
row_to_kv_body(&Value::Object(fields), shape).map_err(kv_body_error)
}
pub(crate) fn kv_body_columns(
body: &[u8],
) -> Result<(HashMap<String, Value>, KvBodyShape), LiteError> {
let (row, shape) = kv_body_to_row(body).map_err(|e| kv_body_error(KvBodyError::from(e)))?;
match row {
Value::Object(columns) => Ok((columns, shape)),
other => Err(kv_body_error(KvBodyError::RowNotObject {
kind: other.type_name(),
})),
}
}
pub(crate) fn kv_body_error(e: KvBodyError) -> LiteError {
match e {
KvBodyError::RowNotObject { .. }
| KvBodyError::RawMissingValue
| KvBodyError::RawExtraKeys { .. }
| KvBodyError::RawNotScalar(_) => LiteError::BadRequest {
detail: e.to_string(),
},
KvBodyError::Decode(_) | KvBodyError::Encode(_) => LiteError::Serialization {
detail: e.to_string(),
},
}
}
pub(crate) fn encode_kv_map(fields: HashMap<String, Value>) -> Result<Vec<u8>, LiteError> {
encode_kv_body(fields, KvBodyShape::Map)
}
pub(crate) fn decode_kv_map(body: &[u8]) -> Result<Option<HashMap<String, Value>>, LiteError> {
if kv_body_shape(body) == KvBodyShape::Raw {
return Ok(None);
}
match nodedb_types::value_from_msgpack(body) {
Ok(Value::Object(columns)) => Ok(Some(columns)),
Ok(other) => Err(LiteError::Serialization {
detail: format!("KV body is a {}, not a row map", other.type_name()),
}),
Err(e) => Err(LiteError::Serialization {
detail: format!("KV body does not decode: {e}"),
}),
}
}
pub(crate) fn kv_read_row(key: &[u8], body: &[u8]) -> Result<HashMap<String, Value>, LiteError> {
let row = kv_row_msgpack(&String::from_utf8_lossy(key), body);
match nodedb_types::value_from_msgpack(&row) {
Ok(Value::Object(columns)) => Ok(columns),
Ok(other) => Err(LiteError::Serialization {
detail: format!("KV read row is a {}, not a row map", other.type_name()),
}),
Err(e) => Err(LiteError::Serialization {
detail: format!("KV read row does not decode: {e}"),
}),
}
}
pub(crate) fn kv_body_from_row(key: &str, row: &[u8]) -> Result<Vec<u8>, LiteError> {
let (fields, shape) =
kv_row_to_body_fields(key, row).map_err(|e| LiteError::Serialization {
detail: format!("KV row for key '{key}' is not a {{key, value}} row map: {e}"),
})?;
encode_kv_body(fields, shape)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_raw_row_stores_the_raw_value() {
let row = kv_row_msgpack("k1", b"v1");
assert_eq!(kv_body_from_row("k1", &row).expect("body"), b"v1".to_vec());
}
#[test]
fn a_typed_row_stores_the_body_origin_stores() {
let mut fields = HashMap::new();
fields.insert("n".to_string(), Value::Integer(7));
let origin_body =
row_to_kv_body(&Value::Object(fields.clone()), KvBodyShape::Map).expect("origin body");
let row = kv_row_msgpack("k2", &origin_body);
let stored = kv_body_from_row("k2", &row).expect("body");
assert_eq!(stored, origin_body, "Lite stores Origin's exact bytes");
assert_eq!(decode_kv_map(&stored).expect("decode"), Some(fields));
}
#[test]
fn a_raw_body_decodes_as_no_columns() {
assert_eq!(decode_kv_map(b"12").expect("decode"), None);
}
#[test]
fn a_payload_that_is_not_a_row_map_is_a_serialization_error() {
let scalar = nodedb_types::value_to_msgpack(&Value::Integer(118)).expect("encode");
assert!(matches!(
kv_body_from_row("k", &scalar),
Err(LiteError::Serialization { .. })
));
assert!(matches!(
kv_body_from_row("k", b"v1"),
Err(LiteError::Serialization { .. })
));
}
}