Skip to main content

velr_types/
list.rs

1use crate::error::{DecodeError, EncodeError};
2use crate::property::{PropertyValue, PropertyValueRef};
3use crate::tag;
4use serde_json::Value;
5
6#[derive(Debug, Clone, PartialEq)]
7pub enum ListValue {
8    Generic(Vec<PropertyValue>),
9    String(Vec<Option<String>>),
10    Bool(Vec<Option<bool>>),
11    Int64(Vec<Option<i64>>),
12    Float64(Vec<Option<f64>>),
13}
14
15impl ListValue {
16    pub fn len(&self) -> usize {
17        match self {
18            Self::Generic(v) => v.len(),
19            Self::String(v) => v.len(),
20            Self::Bool(v) => v.len(),
21            Self::Int64(v) => v.len(),
22            Self::Float64(v) => v.len(),
23        }
24    }
25
26    pub fn is_empty(&self) -> bool {
27        self.len() == 0
28    }
29
30    pub fn iter(&self) -> ListIter<'_> {
31        match self {
32            Self::Generic(v) => ListIter::Generic(v.iter()),
33            Self::String(v) => ListIter::String(v.iter()),
34            Self::Bool(v) => ListIter::Bool(v.iter()),
35            Self::Int64(v) => ListIter::Int64(v.iter()),
36            Self::Float64(v) => ListIter::Float64(v.iter()),
37        }
38    }
39
40    pub fn encode_blob(&self) -> Result<Vec<u8>, EncodeError> {
41        match self {
42            Self::Generic(values) => {
43                let mut json_values = Vec::with_capacity(values.len());
44                for value in values {
45                    json_values.push(property_to_json_value(value)?);
46                }
47                let payload = serde_json::to_vec(&Value::Array(json_values))
48                    .map_err(|_| EncodeError::InvalidList("failed to encode generic list"))?;
49                let mut out = vec![tag::LIST_JSON];
50                out.extend_from_slice(&payload);
51                Ok(out)
52            }
53            Self::String(values) => {
54                let payload = serde_json::to_vec(values)
55                    .map_err(|_| EncodeError::InvalidList("failed to encode string list"))?;
56                let mut out = vec![tag::STRING_LIST_JSON];
57                out.extend_from_slice(&payload);
58                Ok(out)
59            }
60            Self::Bool(values) => encode_bool_list(values),
61            Self::Int64(values) => encode_int64_list(values),
62            Self::Float64(values) => encode_float64_list(values),
63        }
64    }
65
66    pub fn decode_blob(blob: &[u8]) -> Result<Self, DecodeError> {
67        if blob.is_empty() {
68            return Err(DecodeError::EmptyInput);
69        }
70        match blob[0] {
71            tag::LIST_JSON => decode_generic_list(&blob[1..]),
72            tag::STRING_LIST_JSON => decode_string_list(&blob[1..]),
73            tag::BOOL_LIST => decode_bool_list(blob),
74            tag::INT64_LIST => decode_int64_list(blob),
75            tag::FLOAT64_LIST => decode_float64_list(blob),
76            actual => Err(DecodeError::UnexpectedTag {
77                expected: "list tag",
78                actual,
79            }),
80        }
81    }
82}
83
84pub enum ListIter<'a> {
85    Generic(std::slice::Iter<'a, PropertyValue>),
86    String(std::slice::Iter<'a, Option<String>>),
87    Bool(std::slice::Iter<'a, Option<bool>>),
88    Int64(std::slice::Iter<'a, Option<i64>>),
89    Float64(std::slice::Iter<'a, Option<f64>>),
90}
91
92impl<'a> Iterator for ListIter<'a> {
93    type Item = PropertyValueRef<'a>;
94
95    fn next(&mut self) -> Option<Self::Item> {
96        match self {
97            Self::Generic(iter) => iter.next().map(PropertyValue::as_ref),
98            Self::String(iter) => iter.next().map(|v| match v {
99                None => PropertyValueRef::Null,
100                Some(value) => PropertyValueRef::String(value.as_str()),
101            }),
102            Self::Bool(iter) => iter.next().map(|v| match v {
103                None => PropertyValueRef::Null,
104                Some(value) => PropertyValueRef::Bool(*value),
105            }),
106            Self::Int64(iter) => iter.next().map(|v| match v {
107                None => PropertyValueRef::Null,
108                Some(value) => PropertyValueRef::Integer(*value),
109            }),
110            Self::Float64(iter) => iter.next().map(|v| match v {
111                None => PropertyValueRef::Null,
112                Some(value) => PropertyValueRef::Float(*value),
113            }),
114        }
115    }
116}
117
118fn property_to_json_value(value: &PropertyValue) -> Result<Value, EncodeError> {
119    match value {
120        PropertyValue::Null => Ok(Value::Null),
121        PropertyValue::Bool(v) => Ok(Value::Bool(*v)),
122        PropertyValue::Integer(v) => Ok(Value::Number((*v).into())),
123        PropertyValue::Float(v) if v.is_finite() => {
124            serde_json::Number::from_f64(*v).map(Value::Number).ok_or(
125                EncodeError::NonCanonicalValue("non-finite floats are not canonical"),
126            )
127        }
128        PropertyValue::Float(_) => Err(EncodeError::NonCanonicalValue(
129            "non-finite floats are not canonical",
130        )),
131        PropertyValue::String(v) => Ok(Value::String(v.clone())),
132        PropertyValue::List(ListValue::Generic(values)) => {
133            let mut out = Vec::with_capacity(values.len());
134            for value in values {
135                out.push(property_to_json_value(value)?);
136            }
137            Ok(Value::Array(out))
138        }
139        PropertyValue::List(ListValue::String(values)) => {
140            let mut out = Vec::with_capacity(values.len());
141            for value in values {
142                out.push(match value {
143                    None => Value::Null,
144                    Some(v) => Value::String(v.clone()),
145                });
146            }
147            Ok(Value::Array(out))
148        }
149        PropertyValue::List(ListValue::Bool(values)) => {
150            let mut out = Vec::with_capacity(values.len());
151            for value in values {
152                out.push(match value {
153                    None => Value::Null,
154                    Some(v) => Value::Bool(*v),
155                });
156            }
157            Ok(Value::Array(out))
158        }
159        PropertyValue::List(ListValue::Int64(values)) => {
160            let mut out = Vec::with_capacity(values.len());
161            for value in values {
162                out.push(match value {
163                    None => Value::Null,
164                    Some(v) => Value::Number((*v).into()),
165                });
166            }
167            Ok(Value::Array(out))
168        }
169        PropertyValue::List(ListValue::Float64(values)) => {
170            let mut out = Vec::with_capacity(values.len());
171            for value in values {
172                out.push(match value {
173                    None => Value::Null,
174                    Some(v) => serde_json::Number::from_f64(*v).map(Value::Number).ok_or(
175                        EncodeError::NonCanonicalValue("non-finite floats are not canonical"),
176                    )?,
177                });
178            }
179            Ok(Value::Array(out))
180        }
181        _ => Err(EncodeError::InvalidList(
182            "generic JSON-backed lists only support JSON-compatible property values",
183        )),
184    }
185}
186
187fn json_to_property_value(value: &Value) -> Result<PropertyValue, DecodeError> {
188    match value {
189        Value::Null => Ok(PropertyValue::Null),
190        Value::Bool(v) => Ok(PropertyValue::Bool(*v)),
191        Value::Number(v) => {
192            if let Some(i) = v.as_i64() {
193                Ok(PropertyValue::Integer(i))
194            } else if let Some(f) = v.as_f64() {
195                Ok(PropertyValue::Float(f))
196            } else {
197                Err(DecodeError::InvalidJson("unsupported JSON number".into()))
198            }
199        }
200        Value::String(v) => Ok(PropertyValue::String(v.clone())),
201        Value::Array(values) => {
202            let mut out = Vec::with_capacity(values.len());
203            for value in values {
204                out.push(json_to_property_value(value)?);
205            }
206            Ok(PropertyValue::List(ListValue::Generic(out)))
207        }
208        Value::Object(_) => Err(DecodeError::InvalidList(
209            "maps are not part of the persisted property-value contract".into(),
210        )),
211    }
212}
213
214fn decode_generic_list(payload: &[u8]) -> Result<ListValue, DecodeError> {
215    let value: Value =
216        serde_json::from_slice(payload).map_err(|e| DecodeError::InvalidJson(e.to_string()))?;
217    let Value::Array(values) = value else {
218        return Err(DecodeError::InvalidList(
219            "generic list payload must be a JSON array".into(),
220        ));
221    };
222    let mut out = Vec::with_capacity(values.len());
223    for value in &values {
224        out.push(json_to_property_value(value)?);
225    }
226    Ok(ListValue::Generic(out))
227}
228
229fn decode_string_list(payload: &[u8]) -> Result<ListValue, DecodeError> {
230    let values: Vec<Option<String>> =
231        serde_json::from_slice(payload).map_err(|e| DecodeError::InvalidJson(e.to_string()))?;
232    Ok(ListValue::String(values))
233}
234
235fn encode_bool_list(values: &[Option<bool>]) -> Result<Vec<u8>, EncodeError> {
236    let mut out = vec![tag::BOOL_LIST];
237    encode_list_header(
238        &mut out,
239        values.len(),
240        values.iter().filter(|v| v.is_none()).count(),
241    )?;
242    if values.iter().any(|v| v.is_none()) {
243        out.extend_from_slice(&bitmap(values.len(), |idx| values[idx].is_some()));
244    }
245    out.extend_from_slice(&bitmap(values.len(), |idx| {
246        matches!(values[idx], Some(true))
247    }));
248    Ok(out)
249}
250
251fn encode_int64_list(values: &[Option<i64>]) -> Result<Vec<u8>, EncodeError> {
252    let mut out = vec![tag::INT64_LIST];
253    encode_list_header(
254        &mut out,
255        values.len(),
256        values.iter().filter(|v| v.is_none()).count(),
257    )?;
258    if values.iter().any(|v| v.is_none()) {
259        out.extend_from_slice(&bitmap(values.len(), |idx| values[idx].is_some()));
260    }
261    for value in values {
262        out.extend_from_slice(&value.unwrap_or_default().to_le_bytes());
263    }
264    Ok(out)
265}
266
267fn encode_float64_list(values: &[Option<f64>]) -> Result<Vec<u8>, EncodeError> {
268    let mut out = vec![tag::FLOAT64_LIST];
269    encode_list_header(
270        &mut out,
271        values.len(),
272        values.iter().filter(|v| v.is_none()).count(),
273    )?;
274    if values.iter().any(|v| v.is_none()) {
275        out.extend_from_slice(&bitmap(values.len(), |idx| values[idx].is_some()));
276    }
277    for value in values {
278        let value = value.unwrap_or_default();
279        if !value.is_finite() {
280            return Err(EncodeError::NonCanonicalValue(
281                "non-finite float in Float64 list",
282            ));
283        }
284        out.extend_from_slice(&value.to_le_bytes());
285    }
286    Ok(out)
287}
288
289fn decode_bool_list(blob: &[u8]) -> Result<ListValue, DecodeError> {
290    if blob.first().copied() != Some(tag::BOOL_LIST) {
291        return Err(DecodeError::UnexpectedTag {
292            expected: "BoolList",
293            actual: blob.first().copied().unwrap_or(0),
294        });
295    }
296    let (length, null_count, validity, values, remainder) = decode_list_buffers(blob, 1)?;
297    if !remainder.is_empty() {
298        return Err(DecodeError::NonCanonical("trailing bytes after BoolList"));
299    }
300    if values.len() != bitmap_len(length) {
301        return Err(DecodeError::InvalidList(
302            "invalid BoolList value bitmap length".into(),
303        ));
304    }
305    let mut out = Vec::with_capacity(length);
306    for idx in 0..length {
307        if !is_valid(validity, idx, null_count) {
308            out.push(None);
309        } else {
310            out.push(Some(bit_is_set(values, idx)));
311        }
312    }
313    Ok(ListValue::Bool(out))
314}
315
316fn decode_int64_list(blob: &[u8]) -> Result<ListValue, DecodeError> {
317    if blob.first().copied() != Some(tag::INT64_LIST) {
318        return Err(DecodeError::UnexpectedTag {
319            expected: "Int64List",
320            actual: blob.first().copied().unwrap_or(0),
321        });
322    }
323    let (length, null_count, validity, values, remainder) = decode_list_buffers(blob, 8)?;
324    if !remainder.is_empty() {
325        return Err(DecodeError::NonCanonical("trailing bytes after Int64List"));
326    }
327    let mut out = Vec::with_capacity(length);
328    for idx in 0..length {
329        if !is_valid(validity, idx, null_count) {
330            out.push(None);
331            continue;
332        }
333        let start = idx * 8;
334        let value = i64::from_le_bytes(values[start..start + 8].try_into().unwrap());
335        out.push(Some(value));
336    }
337    Ok(ListValue::Int64(out))
338}
339
340fn decode_float64_list(blob: &[u8]) -> Result<ListValue, DecodeError> {
341    if blob.first().copied() != Some(tag::FLOAT64_LIST) {
342        return Err(DecodeError::UnexpectedTag {
343            expected: "Float64List",
344            actual: blob.first().copied().unwrap_or(0),
345        });
346    }
347    let (length, null_count, validity, values, remainder) = decode_list_buffers(blob, 8)?;
348    if !remainder.is_empty() {
349        return Err(DecodeError::NonCanonical(
350            "trailing bytes after Float64List",
351        ));
352    }
353    let mut out = Vec::with_capacity(length);
354    for idx in 0..length {
355        if !is_valid(validity, idx, null_count) {
356            out.push(None);
357            continue;
358        }
359        let start = idx * 8;
360        let value = f64::from_le_bytes(values[start..start + 8].try_into().unwrap());
361        out.push(Some(value));
362    }
363    Ok(ListValue::Float64(out))
364}
365
366fn encode_list_header(out: &mut Vec<u8>, len: usize, null_count: usize) -> Result<(), EncodeError> {
367    if len > u32::MAX as usize || null_count > u32::MAX as usize {
368        return Err(EncodeError::InvalidList("list length exceeds u32::MAX"));
369    }
370    out.extend_from_slice(&(len as u32).to_le_bytes());
371    out.extend_from_slice(&(null_count as u32).to_le_bytes());
372    Ok(())
373}
374
375fn decode_list_buffers<'a>(
376    blob: &'a [u8],
377    element_width: usize,
378) -> Result<(usize, usize, Option<&'a [u8]>, &'a [u8], &'a [u8]), DecodeError> {
379    if blob.len() < 9 {
380        return Err(DecodeError::Truncated);
381    }
382    let len = u32::from_le_bytes(blob[1..5].try_into().unwrap()) as usize;
383    let null_count = u32::from_le_bytes(blob[5..9].try_into().unwrap()) as usize;
384    let validity_len = if null_count == 0 { 0 } else { bitmap_len(len) };
385    let value_len = if element_width == 1 {
386        bitmap_len(len)
387    } else {
388        len.checked_mul(element_width)
389            .ok_or_else(|| DecodeError::InvalidList("list payload length overflow".into()))?
390    };
391    if blob.len() < 9 + validity_len + value_len {
392        return Err(DecodeError::Truncated);
393    }
394    let validity = if validity_len == 0 {
395        None
396    } else {
397        Some(&blob[9..9 + validity_len])
398    };
399    let values_start = 9 + validity_len;
400    let values = &blob[values_start..values_start + value_len];
401    let remainder = &blob[values_start + value_len..];
402    Ok((len, null_count, validity, values, remainder))
403}
404
405fn bitmap_len(len: usize) -> usize {
406    (len + 7) / 8
407}
408
409fn bitmap(len: usize, is_set: impl Fn(usize) -> bool) -> Vec<u8> {
410    let mut out = vec![0u8; bitmap_len(len)];
411    for idx in 0..len {
412        if is_set(idx) {
413            out[idx / 8] |= 1 << (idx % 8);
414        }
415    }
416    out
417}
418
419fn bit_is_set(bytes: &[u8], idx: usize) -> bool {
420    (bytes[idx / 8] & (1 << (idx % 8))) != 0
421}
422
423fn is_valid(validity: Option<&[u8]>, idx: usize, null_count: usize) -> bool {
424    if null_count == 0 {
425        true
426    } else {
427        bit_is_set(validity.unwrap(), idx)
428    }
429}