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}