Skip to main content

nodedb_types/value/
raw_bytes.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! The one rule for turning a scalar into a raw KV body.
4//!
5//! A KV row written through the single-`value` SQL column, or through RESP
6//! `SET`, stores its scalar as raw bytes rather than a msgpack map. The SQL
7//! lowering and every KV read-modify-write encode through this function, so
8//! a merged row re-encodes byte-for-byte as a fresh insert of the same
9//! scalar.
10
11use crate::value::core::Value;
12
13/// The value is not a scalar, so it has no raw KV body form.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
15#[error("{kind} has no raw KV body form; only a scalar does")]
16pub struct NotScalar {
17    /// `Value::type_name()` of the offending value.
18    pub kind: &'static str,
19}
20
21/// Raw KV body bytes for a scalar.
22///
23/// - string, UUID, ULID: the UTF-8 bytes
24/// - bytes: verbatim
25/// - integer, float, decimal, bool: decimal / `true` / `false` text
26/// - timestamps: ISO-8601 text
27/// - null: empty
28pub fn scalar_to_raw_bytes(value: &Value) -> Result<Vec<u8>, NotScalar> {
29    match value {
30        Value::Null => Ok(Vec::new()),
31        Value::Bool(b) => Ok(b.to_string().into_bytes()),
32        Value::Integer(i) => Ok(i.to_string().into_bytes()),
33        Value::Float(f) => Ok(f.to_string().into_bytes()),
34        Value::Decimal(d) => Ok(d.to_string().into_bytes()),
35        Value::String(s) | Value::Uuid(s) | Value::Ulid(s) => Ok(s.as_bytes().to_vec()),
36        Value::Bytes(b) => Ok(b.clone()),
37        Value::DateTime(dt) | Value::NaiveDateTime(dt) => Ok(dt.to_iso8601().into_bytes()),
38        Value::Duration(_)
39        | Value::Array(_)
40        | Value::Object(_)
41        | Value::Set(_)
42        | Value::Regex(_)
43        | Value::Geometry(_)
44        | Value::Range { .. }
45        | Value::Record { .. }
46        | Value::ArrayCell(_)
47        | Value::Vector(_) => Err(NotScalar {
48            kind: value.type_name(),
49        }),
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56    use std::collections::HashMap;
57
58    #[test]
59    fn string_is_its_utf8_bytes() {
60        assert_eq!(
61            scalar_to_raw_bytes(&Value::String("v1".into())).unwrap(),
62            b"v1".to_vec()
63        );
64    }
65
66    #[test]
67    fn integer_is_decimal_text() {
68        assert_eq!(
69            scalar_to_raw_bytes(&Value::Integer(-42)).unwrap(),
70            b"-42".to_vec()
71        );
72    }
73
74    #[test]
75    fn bool_and_null() {
76        assert_eq!(
77            scalar_to_raw_bytes(&Value::Bool(true)).unwrap(),
78            b"true".to_vec()
79        );
80        assert!(scalar_to_raw_bytes(&Value::Null).unwrap().is_empty());
81    }
82
83    #[test]
84    fn object_is_not_scalar() {
85        let err = scalar_to_raw_bytes(&Value::Object(HashMap::new())).unwrap_err();
86        assert_eq!(err.kind, Value::Object(HashMap::new()).type_name());
87    }
88}