Decode a Binary Tuple to `nodedb_types::Value::Object` using the schema. Returns `None` if the bytes are not a valid binary tuple (e.g., if they are already msgpack — detected by checking for msgpack map headers).
(tuple_bytes: &[u8], schema: &StrictSchema)
| 12 | /// Returns `None` if the bytes are not a valid binary tuple (e.g., if they |
| 13 | /// are already msgpack — detected by checking for msgpack map headers). |
| 14 | pub fn binary_tuple_to_value(tuple_bytes: &[u8], schema: &StrictSchema) -> Option<Value> { |
| 15 | // Reject bytes that look like msgpack maps (fixmap 0x80-0x8F, map16 0xDE, map32 0xDF). |
| 16 | // Binary tuples start with a u32 LE schema version — the low byte (first byte) |
| 17 | // of any realistic version is well below 0x80. This catches the common case |
| 18 | // where data is already stored as msgpack. |
| 19 | if let Some(&first) = tuple_bytes.first() |
| 20 | && ((0x80..=0x8F).contains(&first) || first == 0xDE || first == 0xDF) |
| 21 | { |
| 22 | return None; |
| 23 | } |
| 24 | |
| 25 | let decoder = nodedb_strict::TupleDecoder::new(schema); |
| 26 | |
| 27 | // Validate schema version matches before decoding. |
| 28 | let version = decoder.schema_version(tuple_bytes).ok()?; |
| 29 | if version == 0 || version > schema.version { |
| 30 | return None; |
| 31 | } |
| 32 | |
| 33 | // Version-aware decoding: if the tuple was written with an older schema |
| 34 | // (fewer columns due to ADD COLUMN), build a sub-schema decoder matching |
| 35 | // the physical layout and fill defaults for new columns. |
| 36 | let mut map = std::collections::HashMap::with_capacity(schema.columns.len()); |
| 37 | if version < schema.version { |
| 38 | let old_schema = schema.schema_for_version(version); |
| 39 | let old_decoder = nodedb_strict::TupleDecoder::new(&old_schema); |
| 40 | let old_values = old_decoder.extract_all(tuple_bytes).ok()?; |
| 41 | |
| 42 | // Map old columns by name. |
| 43 | for (i, col) in old_schema.columns.iter().enumerate() { |
| 44 | map.insert(col.name.clone(), old_values[i].clone()); |
| 45 | } |
| 46 | // Fill defaults for columns added after this tuple's version. |
| 47 | for col in &schema.columns { |
| 48 | if col.added_at_version > version { |
| 49 | let default_val = col |
| 50 | .default |
| 51 | .as_deref() |
| 52 | .map(StrictSchema::parse_default_literal) |
| 53 | .unwrap_or(Value::Null); |
| 54 | map.insert(col.name.clone(), default_val); |
| 55 | } |
| 56 | } |
| 57 | } else { |
| 58 | let values = decoder.extract_all(tuple_bytes).ok()?; |
| 59 | for (i, col) in schema.columns.iter().enumerate() { |
| 60 | map.insert(col.name.clone(), values[i].clone()); |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | Some(Value::Object(map)) |
| 65 | } |
| 66 | |
| 67 | /// Decode a Binary Tuple to standard msgpack bytes. |
| 68 | pub fn binary_tuple_to_msgpack(tuple_bytes: &[u8], schema: &StrictSchema) -> Option<Vec<u8>> { |
no test coverage detected