Skip to main content

lora_store/
codec.rs

1//! Dependency-free binary codecs for store-owned values that must cross
2//! crate boundaries.
3//!
4//! `lora-wal` and `lora-snapshot` own their container formats, but the
5//! byte shape for core store types belongs here so catalog DDL, property
6//! values, and snapshot index metadata do not drift apart.
7
8use std::collections::BTreeMap;
9use std::fmt;
10
11use crate::{
12    ConstraintDefinition, ConstraintRequest, IndexConfigValue, IndexDefinition, IndexRequest,
13    LoraBinary, LoraDate, LoraDateTime, LoraDuration, LoraLocalDateTime, LoraLocalTime, LoraPoint,
14    LoraTime, LoraVector, PropertyValue, StoredConstraintKind, StoredIndexEntity, StoredIndexKind,
15    StoredIndexState, StoredPropertyType, StoredPropertyTypeTerm, StoredScalarType,
16    StoredVectorCoordType, VectorValues, ZoneId,
17};
18
19type Result<T> = std::result::Result<T, StoreCodecError>;
20
21/// The most lists and maps a property or index-config value may nest.
22/// Decoding recurses once per level, so a crafted value nested a hundred
23/// thousand deep would overflow the stack; encoding refuses the same
24/// depth so nothing written becomes unreadable.
25pub const MAX_VALUE_NESTING: usize = lora_ast::limits::MAX_VALUE_NESTING;
26
27fn nesting_error(kind: fn(String) -> StoreCodecError) -> StoreCodecError {
28    kind(format!(
29        "value nests lists and maps more than {MAX_VALUE_NESTING} deep"
30    ))
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum StoreCodecError {
35    Encode(String),
36    Decode(String),
37}
38
39impl fmt::Display for StoreCodecError {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        match self {
42            Self::Encode(message) => write!(f, "store value encode failed: {message}"),
43            Self::Decode(message) => write!(f, "store value decode failed: {message}"),
44        }
45    }
46}
47
48impl std::error::Error for StoreCodecError {}
49
50pub fn encode_property_value(value: &PropertyValue) -> Result<Vec<u8>> {
51    let mut out = Vec::new();
52    write_property_value(&mut out, value, 0, MAX_VALUE_NESTING)?;
53    Ok(out)
54}
55
56pub fn decode_property_value(bytes: &[u8]) -> Result<PropertyValue> {
57    let mut reader = Reader::new(bytes);
58    let value = reader.read_property_value()?;
59    reader.finish()?;
60    Ok(value)
61}
62
63/// [`encode_property_value`] without the nesting limit, for the in-memory
64/// record encoding: a value the graph accepted is stored however deep it
65/// is, as it was before the limit, and read back with
66/// [`decode_stored_property_value`].
67pub(crate) fn encode_stored_property_value(value: &PropertyValue) -> Result<Vec<u8>> {
68    let mut out = Vec::new();
69    write_property_value(&mut out, value, 0, usize::MAX)?;
70    Ok(out)
71}
72
73/// [`decode_property_value`] without the nesting limit, for bytes this
74/// process encoded with [`encode_stored_property_value`].
75pub(crate) fn decode_stored_property_value(bytes: &[u8]) -> Result<PropertyValue> {
76    let mut reader = Reader::new(bytes);
77    reader.max_depth = usize::MAX;
78    let value = reader.read_property_value()?;
79    reader.finish()?;
80    Ok(value)
81}
82
83pub fn encode_index_request(request: &IndexRequest) -> Result<Vec<u8>> {
84    let mut out = Vec::new();
85    write_index_request(&mut out, request)?;
86    Ok(out)
87}
88
89pub fn decode_index_request(bytes: &[u8]) -> Result<IndexRequest> {
90    let mut reader = Reader::new(bytes);
91    let request = reader.read_index_request()?;
92    reader.finish()?;
93    Ok(request)
94}
95
96pub fn encode_constraint_request(request: &ConstraintRequest) -> Result<Vec<u8>> {
97    let mut out = Vec::new();
98    write_constraint_request(&mut out, request)?;
99    Ok(out)
100}
101
102pub fn decode_constraint_request(bytes: &[u8]) -> Result<ConstraintRequest> {
103    let mut reader = Reader::new(bytes);
104    let request = reader.read_constraint_request()?;
105    reader.finish()?;
106    Ok(request)
107}
108
109pub fn encode_constraint_definitions(defs: &[ConstraintDefinition]) -> Result<Vec<u8>> {
110    let mut out = Vec::new();
111    write_len(&mut out, defs.len())?;
112    for def in defs {
113        write_constraint_definition(&mut out, def)?;
114    }
115    Ok(out)
116}
117
118pub fn decode_constraint_definitions(bytes: &[u8]) -> Result<Vec<ConstraintDefinition>> {
119    let mut reader = Reader::new(bytes);
120    let len = reader.read_len_bounded("constraint definition")?;
121    let mut defs = reader.vec_with_capacity(len, "constraint definition")?;
122    for _ in 0..len {
123        defs.push(reader.read_constraint_definition()?);
124    }
125    reader.finish()?;
126    Ok(defs)
127}
128
129pub fn encode_index_definitions(defs: &[IndexDefinition]) -> Result<Vec<u8>> {
130    let mut out = Vec::new();
131    write_len(&mut out, defs.len())?;
132    for def in defs {
133        write_index_definition(&mut out, def)?;
134    }
135    Ok(out)
136}
137
138pub fn decode_index_definitions(bytes: &[u8]) -> Result<Vec<IndexDefinition>> {
139    let mut reader = Reader::new(bytes);
140    let len = reader.read_len_bounded("index definition")?;
141    let mut defs = reader.vec_with_capacity(len, "index definition")?;
142    for _ in 0..len {
143        defs.push(reader.read_index_definition()?);
144    }
145    reader.finish()?;
146    Ok(defs)
147}
148
149const VALUE_NULL: u8 = 0;
150const VALUE_BOOL: u8 = 1;
151const VALUE_INT: u8 = 2;
152const VALUE_FLOAT: u8 = 3;
153const VALUE_STRING: u8 = 4;
154const VALUE_LIST: u8 = 5;
155const VALUE_MAP: u8 = 6;
156const VALUE_DATE: u8 = 7;
157const VALUE_TIME: u8 = 8;
158const VALUE_LOCAL_TIME: u8 = 9;
159const VALUE_DATE_TIME: u8 = 10;
160const VALUE_LOCAL_DATE_TIME: u8 = 11;
161const VALUE_DURATION: u8 = 12;
162const VALUE_POINT: u8 = 13;
163const VALUE_VECTOR: u8 = 14;
164const VALUE_BINARY: u8 = 15;
165/// A DATETIME with a named zone: the `VALUE_DATE_TIME` fields, then the
166/// zone's name. A DATETIME without one keeps `VALUE_DATE_TIME`, so data
167/// written before zones existed decodes unchanged.
168const VALUE_ZONED_DATE_TIME: u8 = 16;
169
170const VECTOR_FLOAT64: u8 = 1;
171const VECTOR_FLOAT32: u8 = 2;
172const VECTOR_INTEGER64: u8 = 3;
173const VECTOR_INTEGER32: u8 = 4;
174const VECTOR_INTEGER16: u8 = 5;
175const VECTOR_INTEGER8: u8 = 6;
176
177const INDEX_KIND_RANGE: u8 = 1;
178const INDEX_KIND_TEXT: u8 = 2;
179const INDEX_KIND_POINT: u8 = 3;
180const INDEX_KIND_LOOKUP: u8 = 4;
181const INDEX_KIND_VECTOR: u8 = 5;
182const INDEX_KIND_FULLTEXT: u8 = 6;
183
184const INDEX_ENTITY_NODE: u8 = 1;
185const INDEX_ENTITY_RELATIONSHIP: u8 = 2;
186
187const INDEX_STATE_ONLINE: u8 = 1;
188const INDEX_STATE_POPULATING: u8 = 2;
189
190const CONFIG_NUMBER: u8 = 1;
191const CONFIG_INTEGER: u8 = 2;
192const CONFIG_STRING: u8 = 3;
193const CONFIG_BOOL: u8 = 4;
194const CONFIG_LIST: u8 = 5;
195const CONFIG_MAP: u8 = 6;
196const CONFIG_NULL: u8 = 7;
197
198const CONSTRAINT_KIND_UNIQUE: u8 = 1;
199const CONSTRAINT_KIND_EXISTENCE: u8 = 2;
200const CONSTRAINT_KIND_NODE_KEY: u8 = 3;
201const CONSTRAINT_KIND_RELATIONSHIP_KEY: u8 = 4;
202const CONSTRAINT_KIND_PROPERTY_TYPE: u8 = 5;
203
204const PROPERTY_TYPE_TERM_SCALAR: u8 = 1;
205const PROPERTY_TYPE_TERM_LIST: u8 = 2;
206const PROPERTY_TYPE_TERM_VECTOR: u8 = 3;
207
208const SCALAR_BOOLEAN: u8 = 1;
209const SCALAR_STRING: u8 = 2;
210const SCALAR_INTEGER: u8 = 3;
211const SCALAR_FLOAT: u8 = 4;
212const SCALAR_DATE: u8 = 5;
213const SCALAR_LOCAL_TIME: u8 = 6;
214const SCALAR_ZONED_TIME: u8 = 7;
215const SCALAR_LOCAL_DATETIME: u8 = 8;
216const SCALAR_ZONED_DATETIME: u8 = 9;
217const SCALAR_DURATION: u8 = 10;
218const SCALAR_POINT: u8 = 11;
219const SCALAR_MAP: u8 = 12;
220const SCALAR_ANY: u8 = 13;
221
222const VCOORD_INT8: u8 = 1;
223const VCOORD_INT16: u8 = 2;
224const VCOORD_INT32: u8 = 3;
225const VCOORD_INT64: u8 = 4;
226const VCOORD_FLOAT32: u8 = 5;
227const VCOORD_FLOAT64: u8 = 6;
228
229fn write_index_request(out: &mut Vec<u8>, request: &IndexRequest) -> Result<()> {
230    write_optional_string(out, request.explicit_name.as_deref())?;
231    write_index_kind(out, request.kind);
232    write_index_entity(out, request.entity);
233    write_optional_string(out, request.label.as_deref())?;
234    write_string_vec(out, &request.additional_labels)?;
235    write_string_vec(out, &request.properties)?;
236    write_config_map(out, &request.options)
237}
238
239fn write_index_definition(out: &mut Vec<u8>, def: &IndexDefinition) -> Result<()> {
240    write_string(out, &def.name)?;
241    write_index_kind(out, def.kind);
242    write_index_entity(out, def.entity);
243    write_optional_string(out, def.label.as_deref())?;
244    write_string_vec(out, &def.additional_labels)?;
245    write_string_vec(out, &def.properties)?;
246    write_config_map(out, &def.options)?;
247    write_index_state(out, def.state);
248    Ok(())
249}
250
251fn write_index_kind(out: &mut Vec<u8>, kind: StoredIndexKind) {
252    out.push(match kind {
253        StoredIndexKind::Range => INDEX_KIND_RANGE,
254        StoredIndexKind::Text => INDEX_KIND_TEXT,
255        StoredIndexKind::Point => INDEX_KIND_POINT,
256        StoredIndexKind::Lookup => INDEX_KIND_LOOKUP,
257        StoredIndexKind::Vector => INDEX_KIND_VECTOR,
258        StoredIndexKind::Fulltext => INDEX_KIND_FULLTEXT,
259    });
260}
261
262fn write_index_entity(out: &mut Vec<u8>, entity: StoredIndexEntity) {
263    out.push(match entity {
264        StoredIndexEntity::Node => INDEX_ENTITY_NODE,
265        StoredIndexEntity::Relationship => INDEX_ENTITY_RELATIONSHIP,
266    });
267}
268
269fn write_index_state(out: &mut Vec<u8>, state: StoredIndexState) {
270    out.push(match state {
271        StoredIndexState::Online => INDEX_STATE_ONLINE,
272        StoredIndexState::Populating => INDEX_STATE_POPULATING,
273    });
274}
275
276fn write_constraint_request(out: &mut Vec<u8>, request: &ConstraintRequest) -> Result<()> {
277    write_string(out, &request.name)?;
278    write_constraint_kind(out, &request.kind)?;
279    write_index_entity(out, request.entity);
280    write_string(out, &request.label)?;
281    write_string_vec(out, &request.properties)
282}
283
284fn write_constraint_definition(out: &mut Vec<u8>, def: &ConstraintDefinition) -> Result<()> {
285    write_string(out, &def.name)?;
286    write_constraint_kind(out, &def.kind)?;
287    write_index_entity(out, def.entity);
288    write_string(out, &def.label)?;
289    write_string_vec(out, &def.properties)?;
290    write_optional_string(out, def.owned_index.as_deref())
291}
292
293fn write_constraint_kind(out: &mut Vec<u8>, kind: &StoredConstraintKind) -> Result<()> {
294    match kind {
295        StoredConstraintKind::Unique => out.push(CONSTRAINT_KIND_UNIQUE),
296        StoredConstraintKind::Existence => out.push(CONSTRAINT_KIND_EXISTENCE),
297        StoredConstraintKind::NodeKey => out.push(CONSTRAINT_KIND_NODE_KEY),
298        StoredConstraintKind::RelationshipKey => out.push(CONSTRAINT_KIND_RELATIONSHIP_KEY),
299        StoredConstraintKind::PropertyType(t) => {
300            out.push(CONSTRAINT_KIND_PROPERTY_TYPE);
301            write_property_type(out, t)?;
302        }
303    }
304    Ok(())
305}
306
307fn write_property_type(out: &mut Vec<u8>, t: &StoredPropertyType) -> Result<()> {
308    write_len(out, t.alternatives.len())?;
309    for term in &t.alternatives {
310        write_property_type_term(out, term)?;
311    }
312    Ok(())
313}
314
315fn write_property_type_term(out: &mut Vec<u8>, term: &StoredPropertyTypeTerm) -> Result<()> {
316    match term {
317        StoredPropertyTypeTerm::Scalar(s) => {
318            out.push(PROPERTY_TYPE_TERM_SCALAR);
319            out.push(scalar_tag(*s));
320        }
321        StoredPropertyTypeTerm::List { inner, not_null } => {
322            out.push(PROPERTY_TYPE_TERM_LIST);
323            out.push(u8::from(*not_null));
324            write_property_type_term(out, inner)?;
325        }
326        StoredPropertyTypeTerm::Vector { coord, dimension } => {
327            out.push(PROPERTY_TYPE_TERM_VECTOR);
328            out.push(vector_coord_tag(*coord));
329            write_u32(out, *dimension);
330        }
331    }
332    Ok(())
333}
334
335fn scalar_tag(s: StoredScalarType) -> u8 {
336    match s {
337        StoredScalarType::Boolean => SCALAR_BOOLEAN,
338        StoredScalarType::String => SCALAR_STRING,
339        StoredScalarType::Integer => SCALAR_INTEGER,
340        StoredScalarType::Float => SCALAR_FLOAT,
341        StoredScalarType::Date => SCALAR_DATE,
342        StoredScalarType::LocalTime => SCALAR_LOCAL_TIME,
343        StoredScalarType::ZonedTime => SCALAR_ZONED_TIME,
344        StoredScalarType::LocalDateTime => SCALAR_LOCAL_DATETIME,
345        StoredScalarType::ZonedDateTime => SCALAR_ZONED_DATETIME,
346        StoredScalarType::Duration => SCALAR_DURATION,
347        StoredScalarType::Point => SCALAR_POINT,
348        StoredScalarType::Map => SCALAR_MAP,
349        StoredScalarType::Any => SCALAR_ANY,
350    }
351}
352
353fn vector_coord_tag(c: StoredVectorCoordType) -> u8 {
354    match c {
355        StoredVectorCoordType::Int8 => VCOORD_INT8,
356        StoredVectorCoordType::Int16 => VCOORD_INT16,
357        StoredVectorCoordType::Int32 => VCOORD_INT32,
358        StoredVectorCoordType::Int64 => VCOORD_INT64,
359        StoredVectorCoordType::Float32 => VCOORD_FLOAT32,
360        StoredVectorCoordType::Float64 => VCOORD_FLOAT64,
361    }
362}
363
364fn write_config_map(out: &mut Vec<u8>, values: &BTreeMap<String, IndexConfigValue>) -> Result<()> {
365    write_config_map_at(out, values, 0)
366}
367
368fn write_config_map_at(
369    out: &mut Vec<u8>,
370    values: &BTreeMap<String, IndexConfigValue>,
371    depth: usize,
372) -> Result<()> {
373    write_len(out, values.len())?;
374    for (key, value) in values {
375        write_string(out, key)?;
376        write_config_value(out, value, depth)?;
377    }
378    Ok(())
379}
380
381/// `depth`: lists and maps around `value`.
382fn write_config_value(out: &mut Vec<u8>, value: &IndexConfigValue, depth: usize) -> Result<()> {
383    if matches!(value, IndexConfigValue::List(_) | IndexConfigValue::Map(_))
384        && depth >= MAX_VALUE_NESTING
385    {
386        return Err(nesting_error(StoreCodecError::Encode));
387    }
388    match value {
389        IndexConfigValue::Number(value) => {
390            out.push(CONFIG_NUMBER);
391            write_f64(out, *value);
392        }
393        IndexConfigValue::Integer(value) => {
394            out.push(CONFIG_INTEGER);
395            write_i64(out, *value);
396        }
397        IndexConfigValue::String(value) => {
398            out.push(CONFIG_STRING);
399            write_string(out, value)?;
400        }
401        IndexConfigValue::Bool(value) => {
402            out.push(CONFIG_BOOL);
403            out.push(u8::from(*value));
404        }
405        IndexConfigValue::List(values) => {
406            out.push(CONFIG_LIST);
407            write_len(out, values.len())?;
408            for value in values {
409                write_config_value(out, value, depth + 1)?;
410            }
411        }
412        IndexConfigValue::Map(values) => {
413            out.push(CONFIG_MAP);
414            write_config_map_at(out, values, depth + 1)?;
415        }
416        IndexConfigValue::Null => out.push(CONFIG_NULL),
417    }
418    Ok(())
419}
420
421/// `depth`: lists and maps around `value`; at most `max_depth`.
422fn write_property_value(
423    out: &mut Vec<u8>,
424    value: &PropertyValue,
425    depth: usize,
426    max_depth: usize,
427) -> Result<()> {
428    if matches!(value, PropertyValue::List(_) | PropertyValue::Map(_)) && depth >= max_depth {
429        return Err(nesting_error(StoreCodecError::Encode));
430    }
431    match value {
432        PropertyValue::Null => out.push(VALUE_NULL),
433        PropertyValue::Bool(value) => {
434            out.push(VALUE_BOOL);
435            out.push(u8::from(*value));
436        }
437        PropertyValue::Int(value) => {
438            out.push(VALUE_INT);
439            write_i64(out, *value);
440        }
441        PropertyValue::Float(value) => {
442            out.push(VALUE_FLOAT);
443            write_f64(out, *value);
444        }
445        PropertyValue::String(value) => {
446            out.push(VALUE_STRING);
447            write_string(out, value)?;
448        }
449        PropertyValue::List(values) => {
450            out.push(VALUE_LIST);
451            write_len(out, values.len())?;
452            for value in values {
453                write_property_value(out, value, depth + 1, max_depth)?;
454            }
455        }
456        PropertyValue::Map(values) => {
457            out.push(VALUE_MAP);
458            write_len(out, values.len())?;
459            for (key, value) in values {
460                write_string(out, key)?;
461                write_property_value(out, value, depth + 1, max_depth)?;
462            }
463        }
464        PropertyValue::Date(value) => {
465            out.push(VALUE_DATE);
466            write_i32(out, value.year);
467            write_u32(out, value.month);
468            write_u32(out, value.day);
469        }
470        PropertyValue::Time(value) => {
471            out.push(VALUE_TIME);
472            write_time_fields(
473                out,
474                value.hour,
475                value.minute,
476                value.second,
477                value.nanosecond,
478            );
479            write_i32(out, value.offset_seconds);
480        }
481        PropertyValue::LocalTime(value) => {
482            out.push(VALUE_LOCAL_TIME);
483            write_time_fields(
484                out,
485                value.hour,
486                value.minute,
487                value.second,
488                value.nanosecond,
489            );
490        }
491        PropertyValue::DateTime(value) => {
492            out.push(if value.zone.is_some() {
493                VALUE_ZONED_DATE_TIME
494            } else {
495                VALUE_DATE_TIME
496            });
497            write_date_fields(out, value.year, value.month, value.day);
498            write_time_fields(
499                out,
500                value.hour,
501                value.minute,
502                value.second,
503                value.nanosecond,
504            );
505            write_i32(out, value.offset_seconds);
506            if let Some(zone) = value.zone {
507                write_string(out, zone.name())?;
508            }
509        }
510        PropertyValue::LocalDateTime(value) => {
511            out.push(VALUE_LOCAL_DATE_TIME);
512            write_date_fields(out, value.year, value.month, value.day);
513            write_time_fields(
514                out,
515                value.hour,
516                value.minute,
517                value.second,
518                value.nanosecond,
519            );
520        }
521        PropertyValue::Duration(value) => {
522            out.push(VALUE_DURATION);
523            write_i64(out, value.months);
524            write_i64(out, value.days);
525            write_i64(out, value.seconds);
526            write_i64(out, value.nanoseconds);
527        }
528        PropertyValue::Point(value) => {
529            out.push(VALUE_POINT);
530            write_f64(out, value.x);
531            write_f64(out, value.y);
532            match value.z {
533                Some(z) => {
534                    out.push(1);
535                    write_f64(out, z);
536                }
537                None => out.push(0),
538            }
539            write_u32(out, value.srid);
540        }
541        PropertyValue::Vector(value) => {
542            out.push(VALUE_VECTOR);
543            write_vector(out, value)?;
544        }
545        PropertyValue::Binary(value) => {
546            out.push(VALUE_BINARY);
547            write_len(out, value.segments().len())?;
548            for segment in value.segments() {
549                write_bytes(out, segment)?;
550            }
551        }
552    }
553    Ok(())
554}
555
556fn write_date_fields(out: &mut Vec<u8>, year: i32, month: u32, day: u32) {
557    write_i32(out, year);
558    write_u32(out, month);
559    write_u32(out, day);
560}
561
562fn write_time_fields(out: &mut Vec<u8>, hour: u32, minute: u32, second: u32, nanosecond: u32) {
563    write_u32(out, hour);
564    write_u32(out, minute);
565    write_u32(out, second);
566    write_u32(out, nanosecond);
567}
568
569fn write_vector(out: &mut Vec<u8>, vector: &LoraVector) -> Result<()> {
570    // The coordinates decide the dimension. A vector built with another
571    // `dimension` (its fields are public) would otherwise encode bytes the
572    // decoder rejects, and a stored record would fail on every read.
573    write_len(out, vector.values.len())?;
574    match &vector.values {
575        VectorValues::Float64(values) => {
576            out.push(VECTOR_FLOAT64);
577            write_len(out, values.len())?;
578            for value in values {
579                write_f64(out, *value);
580            }
581        }
582        VectorValues::Float32(values) => {
583            out.push(VECTOR_FLOAT32);
584            write_len(out, values.len())?;
585            for value in values {
586                write_f32(out, *value);
587            }
588        }
589        VectorValues::Integer64(values) => {
590            out.push(VECTOR_INTEGER64);
591            write_len(out, values.len())?;
592            for value in values {
593                write_i64(out, *value);
594            }
595        }
596        VectorValues::Integer32(values) => {
597            out.push(VECTOR_INTEGER32);
598            write_len(out, values.len())?;
599            for value in values {
600                write_i32(out, *value);
601            }
602        }
603        VectorValues::Integer16(values) => {
604            out.push(VECTOR_INTEGER16);
605            write_len(out, values.len())?;
606            for value in values {
607                write_i16(out, *value);
608            }
609        }
610        VectorValues::Integer8(values) => {
611            out.push(VECTOR_INTEGER8);
612            write_len(out, values.len())?;
613            for value in values {
614                out.push(*value as u8);
615            }
616        }
617    }
618    Ok(())
619}
620
621fn write_optional_string(out: &mut Vec<u8>, value: Option<&str>) -> Result<()> {
622    match value {
623        Some(value) => {
624            out.push(1);
625            write_string(out, value)?;
626        }
627        None => out.push(0),
628    }
629    Ok(())
630}
631
632fn write_len(out: &mut Vec<u8>, len: usize) -> Result<()> {
633    write_u64(
634        out,
635        u64::try_from(len)
636            .map_err(|_| StoreCodecError::Encode("length does not fit in u64".into()))?,
637    );
638    Ok(())
639}
640
641fn write_bytes(out: &mut Vec<u8>, bytes: &[u8]) -> Result<()> {
642    write_len(out, bytes.len())?;
643    out.extend_from_slice(bytes);
644    Ok(())
645}
646
647fn write_string(out: &mut Vec<u8>, value: &str) -> Result<()> {
648    write_bytes(out, value.as_bytes())
649}
650
651fn write_string_vec(out: &mut Vec<u8>, values: &[String]) -> Result<()> {
652    write_len(out, values.len())?;
653    for value in values {
654        write_string(out, value)?;
655    }
656    Ok(())
657}
658
659fn write_i16(out: &mut Vec<u8>, value: i16) {
660    out.extend_from_slice(&value.to_le_bytes());
661}
662
663fn write_i32(out: &mut Vec<u8>, value: i32) {
664    out.extend_from_slice(&value.to_le_bytes());
665}
666
667fn write_u32(out: &mut Vec<u8>, value: u32) {
668    out.extend_from_slice(&value.to_le_bytes());
669}
670
671fn write_i64(out: &mut Vec<u8>, value: i64) {
672    out.extend_from_slice(&value.to_le_bytes());
673}
674
675fn write_u64(out: &mut Vec<u8>, value: u64) {
676    out.extend_from_slice(&value.to_le_bytes());
677}
678
679fn write_f32(out: &mut Vec<u8>, value: f32) {
680    out.extend_from_slice(&value.to_bits().to_le_bytes());
681}
682
683fn write_f64(out: &mut Vec<u8>, value: f64) {
684    out.extend_from_slice(&value.to_bits().to_le_bytes());
685}
686
687struct Reader<'a> {
688    bytes: &'a [u8],
689    offset: usize,
690    /// Lists and maps open around the value being read.
691    depth: usize,
692    /// The most `depth` may reach.
693    max_depth: usize,
694}
695
696impl<'a> Reader<'a> {
697    fn new(bytes: &'a [u8]) -> Self {
698        Self {
699            bytes,
700            offset: 0,
701            depth: 0,
702            max_depth: MAX_VALUE_NESTING,
703        }
704    }
705
706    /// Open a list or map; [`MAX_VALUE_NESTING`] bounds the recursion.
707    fn enter(&mut self) -> Result<()> {
708        if self.depth >= self.max_depth {
709            return Err(nesting_error(StoreCodecError::Decode));
710        }
711        self.depth += 1;
712        Ok(())
713    }
714
715    fn finish(&self) -> Result<()> {
716        if self.offset == self.bytes.len() {
717            Ok(())
718        } else {
719            Err(StoreCodecError::Decode(format!(
720                "trailing bytes: {}",
721                self.bytes.len() - self.offset
722            )))
723        }
724    }
725
726    fn read_exact(&mut self, len: usize) -> Result<&'a [u8]> {
727        let end = self
728            .offset
729            .checked_add(len)
730            .ok_or_else(|| StoreCodecError::Decode("offset overflow".into()))?;
731        if end > self.bytes.len() {
732            return Err(StoreCodecError::Decode("truncated input".into()));
733        }
734        let out = &self.bytes[self.offset..end];
735        self.offset = end;
736        Ok(out)
737    }
738
739    fn read_array<const N: usize>(&mut self) -> Result<[u8; N]> {
740        self.read_exact(N)?
741            .try_into()
742            .map_err(|_| StoreCodecError::Decode("fixed-width field truncated".into()))
743    }
744
745    fn read_u8(&mut self) -> Result<u8> {
746        Ok(self.read_exact(1)?[0])
747    }
748
749    fn read_i8(&mut self) -> Result<i8> {
750        Ok(self.read_u8()? as i8)
751    }
752
753    fn read_i16(&mut self) -> Result<i16> {
754        Ok(i16::from_le_bytes(self.read_array()?))
755    }
756
757    fn read_i32(&mut self) -> Result<i32> {
758        Ok(i32::from_le_bytes(self.read_array()?))
759    }
760
761    fn read_u32(&mut self) -> Result<u32> {
762        Ok(u32::from_le_bytes(self.read_array()?))
763    }
764
765    fn read_i64(&mut self) -> Result<i64> {
766        Ok(i64::from_le_bytes(self.read_array()?))
767    }
768
769    fn read_u64(&mut self) -> Result<u64> {
770        Ok(u64::from_le_bytes(self.read_array()?))
771    }
772
773    fn read_f32(&mut self) -> Result<f32> {
774        Ok(f32::from_bits(self.read_u32()?))
775    }
776
777    fn read_f64(&mut self) -> Result<f64> {
778        Ok(f64::from_bits(self.read_u64()?))
779    }
780
781    fn read_len(&mut self) -> Result<usize> {
782        usize::try_from(self.read_u64()?)
783            .map_err(|_| StoreCodecError::Decode("length overflows usize".into()))
784    }
785
786    fn remaining(&self) -> usize {
787        self.bytes.len().saturating_sub(self.offset)
788    }
789
790    fn read_len_bounded(&mut self, label: &str) -> Result<usize> {
791        let len = self.read_len()?;
792        if len > self.remaining() {
793            return Err(StoreCodecError::Decode(format!(
794                "{label} count {len} exceeds remaining input"
795            )));
796        }
797        Ok(len)
798    }
799
800    fn vec_with_capacity<T>(&self, len: usize, label: &str) -> Result<Vec<T>> {
801        let mut values = Vec::new();
802        values.try_reserve(len).map_err(|_| {
803            StoreCodecError::Decode(format!("{label} count {len} is too large to allocate"))
804        })?;
805        Ok(values)
806    }
807
808    fn read_bytes(&mut self) -> Result<&'a [u8]> {
809        let len = self.read_len()?;
810        self.read_exact(len)
811    }
812
813    fn read_string(&mut self) -> Result<String> {
814        let bytes = self.read_bytes()?;
815        std::str::from_utf8(bytes)
816            .map(|value| value.to_string())
817            .map_err(|e| StoreCodecError::Decode(format!("invalid UTF-8 string: {e}")))
818    }
819
820    fn read_string_vec(&mut self) -> Result<Vec<String>> {
821        let len = self.read_len_bounded("string")?;
822        let mut values = self.vec_with_capacity(len, "string")?;
823        for _ in 0..len {
824            values.push(self.read_string()?);
825        }
826        Ok(values)
827    }
828
829    fn read_optional_string(&mut self) -> Result<Option<String>> {
830        match self.read_u8()? {
831            0 => Ok(None),
832            1 => Ok(Some(self.read_string()?)),
833            tag => Err(StoreCodecError::Decode(format!(
834                "invalid optional string tag {tag}"
835            ))),
836        }
837    }
838
839    fn read_index_request(&mut self) -> Result<IndexRequest> {
840        Ok(IndexRequest {
841            explicit_name: self.read_optional_string()?,
842            kind: self.read_index_kind()?,
843            entity: self.read_index_entity()?,
844            label: self.read_optional_string()?,
845            additional_labels: self.read_string_vec()?,
846            properties: self.read_string_vec()?,
847            options: self.read_config_map()?,
848        })
849    }
850
851    fn read_index_definition(&mut self) -> Result<IndexDefinition> {
852        Ok(IndexDefinition {
853            name: self.read_string()?,
854            kind: self.read_index_kind()?,
855            entity: self.read_index_entity()?,
856            label: self.read_optional_string()?,
857            additional_labels: self.read_string_vec()?,
858            properties: self.read_string_vec()?,
859            options: self.read_config_map()?,
860            state: self.read_index_state()?,
861        })
862    }
863
864    fn read_index_kind(&mut self) -> Result<StoredIndexKind> {
865        match self.read_u8()? {
866            INDEX_KIND_RANGE => Ok(StoredIndexKind::Range),
867            INDEX_KIND_TEXT => Ok(StoredIndexKind::Text),
868            INDEX_KIND_POINT => Ok(StoredIndexKind::Point),
869            INDEX_KIND_LOOKUP => Ok(StoredIndexKind::Lookup),
870            INDEX_KIND_VECTOR => Ok(StoredIndexKind::Vector),
871            INDEX_KIND_FULLTEXT => Ok(StoredIndexKind::Fulltext),
872            tag => Err(StoreCodecError::Decode(format!(
873                "invalid index kind tag {tag}"
874            ))),
875        }
876    }
877
878    fn read_index_entity(&mut self) -> Result<StoredIndexEntity> {
879        match self.read_u8()? {
880            INDEX_ENTITY_NODE => Ok(StoredIndexEntity::Node),
881            INDEX_ENTITY_RELATIONSHIP => Ok(StoredIndexEntity::Relationship),
882            tag => Err(StoreCodecError::Decode(format!(
883                "invalid index entity tag {tag}"
884            ))),
885        }
886    }
887
888    fn read_index_state(&mut self) -> Result<StoredIndexState> {
889        match self.read_u8()? {
890            INDEX_STATE_ONLINE => Ok(StoredIndexState::Online),
891            INDEX_STATE_POPULATING => Ok(StoredIndexState::Populating),
892            tag => Err(StoreCodecError::Decode(format!(
893                "invalid index state tag {tag}"
894            ))),
895        }
896    }
897
898    fn read_constraint_request(&mut self) -> Result<ConstraintRequest> {
899        Ok(ConstraintRequest {
900            name: self.read_string()?,
901            kind: self.read_constraint_kind()?,
902            entity: self.read_index_entity()?,
903            label: self.read_string()?,
904            properties: self.read_string_vec()?,
905        })
906    }
907
908    fn read_constraint_definition(&mut self) -> Result<ConstraintDefinition> {
909        Ok(ConstraintDefinition {
910            name: self.read_string()?,
911            kind: self.read_constraint_kind()?,
912            entity: self.read_index_entity()?,
913            label: self.read_string()?,
914            properties: self.read_string_vec()?,
915            owned_index: self.read_optional_string()?,
916        })
917    }
918
919    fn read_constraint_kind(&mut self) -> Result<StoredConstraintKind> {
920        match self.read_u8()? {
921            CONSTRAINT_KIND_UNIQUE => Ok(StoredConstraintKind::Unique),
922            CONSTRAINT_KIND_EXISTENCE => Ok(StoredConstraintKind::Existence),
923            CONSTRAINT_KIND_NODE_KEY => Ok(StoredConstraintKind::NodeKey),
924            CONSTRAINT_KIND_RELATIONSHIP_KEY => Ok(StoredConstraintKind::RelationshipKey),
925            CONSTRAINT_KIND_PROPERTY_TYPE => Ok(StoredConstraintKind::PropertyType(
926                self.read_property_type()?,
927            )),
928            tag => Err(StoreCodecError::Decode(format!(
929                "invalid constraint kind tag {tag}"
930            ))),
931        }
932    }
933
934    fn read_property_type(&mut self) -> Result<StoredPropertyType> {
935        let len = self.read_len_bounded("property type alternative")?;
936        let mut alternatives = self.vec_with_capacity(len, "property type alternative")?;
937        for _ in 0..len {
938            alternatives.push(self.read_property_type_term()?);
939        }
940        Ok(StoredPropertyType { alternatives })
941    }
942
943    fn read_property_type_term(&mut self) -> Result<StoredPropertyTypeTerm> {
944        match self.read_u8()? {
945            PROPERTY_TYPE_TERM_SCALAR => {
946                Ok(StoredPropertyTypeTerm::Scalar(self.read_scalar_type()?))
947            }
948            PROPERTY_TYPE_TERM_LIST => {
949                let not_null = self.read_u8()? != 0;
950                let inner = Box::new(self.read_property_type_term()?);
951                Ok(StoredPropertyTypeTerm::List { inner, not_null })
952            }
953            PROPERTY_TYPE_TERM_VECTOR => {
954                let coord = self.read_vector_coord_type()?;
955                let dimension = self.read_u32()?;
956                Ok(StoredPropertyTypeTerm::Vector { coord, dimension })
957            }
958            tag => Err(StoreCodecError::Decode(format!(
959                "invalid property-type term tag {tag}"
960            ))),
961        }
962    }
963
964    fn read_scalar_type(&mut self) -> Result<StoredScalarType> {
965        match self.read_u8()? {
966            SCALAR_BOOLEAN => Ok(StoredScalarType::Boolean),
967            SCALAR_STRING => Ok(StoredScalarType::String),
968            SCALAR_INTEGER => Ok(StoredScalarType::Integer),
969            SCALAR_FLOAT => Ok(StoredScalarType::Float),
970            SCALAR_DATE => Ok(StoredScalarType::Date),
971            SCALAR_LOCAL_TIME => Ok(StoredScalarType::LocalTime),
972            SCALAR_ZONED_TIME => Ok(StoredScalarType::ZonedTime),
973            SCALAR_LOCAL_DATETIME => Ok(StoredScalarType::LocalDateTime),
974            SCALAR_ZONED_DATETIME => Ok(StoredScalarType::ZonedDateTime),
975            SCALAR_DURATION => Ok(StoredScalarType::Duration),
976            SCALAR_POINT => Ok(StoredScalarType::Point),
977            SCALAR_MAP => Ok(StoredScalarType::Map),
978            SCALAR_ANY => Ok(StoredScalarType::Any),
979            tag => Err(StoreCodecError::Decode(format!(
980                "invalid scalar type tag {tag}"
981            ))),
982        }
983    }
984
985    fn read_vector_coord_type(&mut self) -> Result<StoredVectorCoordType> {
986        match self.read_u8()? {
987            VCOORD_INT8 => Ok(StoredVectorCoordType::Int8),
988            VCOORD_INT16 => Ok(StoredVectorCoordType::Int16),
989            VCOORD_INT32 => Ok(StoredVectorCoordType::Int32),
990            VCOORD_INT64 => Ok(StoredVectorCoordType::Int64),
991            VCOORD_FLOAT32 => Ok(StoredVectorCoordType::Float32),
992            VCOORD_FLOAT64 => Ok(StoredVectorCoordType::Float64),
993            tag => Err(StoreCodecError::Decode(format!(
994                "invalid vector coord type tag {tag}"
995            ))),
996        }
997    }
998
999    fn read_config_map(&mut self) -> Result<BTreeMap<String, IndexConfigValue>> {
1000        let len = self.read_len_bounded("index config map entry")?;
1001        let mut values = BTreeMap::new();
1002        for _ in 0..len {
1003            values.insert(self.read_string()?, self.read_config_value()?);
1004        }
1005        Ok(values)
1006    }
1007
1008    fn read_config_value(&mut self) -> Result<IndexConfigValue> {
1009        Ok(match self.read_u8()? {
1010            CONFIG_NUMBER => IndexConfigValue::Number(self.read_f64()?),
1011            CONFIG_INTEGER => IndexConfigValue::Integer(self.read_i64()?),
1012            CONFIG_STRING => IndexConfigValue::String(self.read_string()?),
1013            CONFIG_BOOL => IndexConfigValue::Bool(self.read_u8()? != 0),
1014            CONFIG_LIST => {
1015                self.enter()?;
1016                let len = self.read_len_bounded("index config list value")?;
1017                let mut values = self.vec_with_capacity(len, "index config list value")?;
1018                for _ in 0..len {
1019                    values.push(self.read_config_value()?);
1020                }
1021                self.depth -= 1;
1022                IndexConfigValue::List(values)
1023            }
1024            CONFIG_MAP => {
1025                self.enter()?;
1026                let values = self.read_config_map()?;
1027                self.depth -= 1;
1028                IndexConfigValue::Map(values)
1029            }
1030            CONFIG_NULL => IndexConfigValue::Null,
1031            tag => {
1032                return Err(StoreCodecError::Decode(format!(
1033                    "invalid index config value tag {tag}"
1034                )));
1035            }
1036        })
1037    }
1038
1039    fn read_property_value(&mut self) -> Result<PropertyValue> {
1040        Ok(match self.read_u8()? {
1041            VALUE_NULL => PropertyValue::Null,
1042            VALUE_BOOL => PropertyValue::Bool(self.read_u8()? != 0),
1043            VALUE_INT => PropertyValue::Int(self.read_i64()?),
1044            VALUE_FLOAT => PropertyValue::Float(self.read_f64()?),
1045            VALUE_STRING => PropertyValue::String(self.read_string()?),
1046            VALUE_LIST => {
1047                self.enter()?;
1048                let len = self.read_len_bounded("property list value")?;
1049                let mut values = self.vec_with_capacity(len, "property list value")?;
1050                for _ in 0..len {
1051                    values.push(self.read_property_value()?);
1052                }
1053                self.depth -= 1;
1054                PropertyValue::List(values)
1055            }
1056            VALUE_MAP => {
1057                self.enter()?;
1058                let len = self.read_len_bounded("property map entry")?;
1059                let mut values = BTreeMap::new();
1060                for _ in 0..len {
1061                    values.insert(self.read_string()?, self.read_property_value()?);
1062                }
1063                self.depth -= 1;
1064                PropertyValue::Map(values)
1065            }
1066            VALUE_DATE => PropertyValue::Date(LoraDate {
1067                year: self.read_i32()?,
1068                month: self.read_u32()?,
1069                day: self.read_u32()?,
1070            }),
1071            VALUE_TIME => PropertyValue::Time(LoraTime {
1072                hour: self.read_u32()?,
1073                minute: self.read_u32()?,
1074                second: self.read_u32()?,
1075                nanosecond: self.read_u32()?,
1076                offset_seconds: self.read_i32()?,
1077            }),
1078            VALUE_LOCAL_TIME => PropertyValue::LocalTime(LoraLocalTime {
1079                hour: self.read_u32()?,
1080                minute: self.read_u32()?,
1081                second: self.read_u32()?,
1082                nanosecond: self.read_u32()?,
1083            }),
1084            tag @ (VALUE_DATE_TIME | VALUE_ZONED_DATE_TIME) => {
1085                let mut value = LoraDateTime {
1086                    year: self.read_i32()?,
1087                    month: self.read_u32()?,
1088                    day: self.read_u32()?,
1089                    hour: self.read_u32()?,
1090                    minute: self.read_u32()?,
1091                    second: self.read_u32()?,
1092                    nanosecond: self.read_u32()?,
1093                    offset_seconds: self.read_i32()?,
1094                    zone: None,
1095                };
1096                if tag == VALUE_ZONED_DATE_TIME {
1097                    let name = self.read_string()?;
1098                    value.zone = Some(ZoneId::lookup(&name).ok_or_else(|| {
1099                        StoreCodecError::Decode(format!("unknown time zone `{name}`"))
1100                    })?);
1101                }
1102                PropertyValue::DateTime(value)
1103            }
1104            VALUE_LOCAL_DATE_TIME => PropertyValue::LocalDateTime(LoraLocalDateTime {
1105                year: self.read_i32()?,
1106                month: self.read_u32()?,
1107                day: self.read_u32()?,
1108                hour: self.read_u32()?,
1109                minute: self.read_u32()?,
1110                second: self.read_u32()?,
1111                nanosecond: self.read_u32()?,
1112            }),
1113            VALUE_DURATION => PropertyValue::Duration(LoraDuration {
1114                months: self.read_i64()?,
1115                days: self.read_i64()?,
1116                seconds: self.read_i64()?,
1117                nanoseconds: self.read_i64()?,
1118            }),
1119            VALUE_POINT => {
1120                let x = self.read_f64()?;
1121                let y = self.read_f64()?;
1122                let z = match self.read_u8()? {
1123                    0 => None,
1124                    1 => Some(self.read_f64()?),
1125                    tag => {
1126                        return Err(StoreCodecError::Decode(format!(
1127                            "invalid point z-presence tag {tag}"
1128                        )));
1129                    }
1130                };
1131                PropertyValue::Point(LoraPoint {
1132                    x,
1133                    y,
1134                    z,
1135                    srid: self.read_u32()?,
1136                })
1137            }
1138            VALUE_VECTOR => PropertyValue::Vector(self.read_vector()?),
1139            VALUE_BINARY => PropertyValue::Binary(self.read_binary()?),
1140            tag => {
1141                return Err(StoreCodecError::Decode(format!(
1142                    "invalid property value tag {tag}"
1143                )));
1144            }
1145        })
1146    }
1147
1148    fn read_vector(&mut self) -> Result<LoraVector> {
1149        let dimension = self.read_len()?;
1150        let values = match self.read_u8()? {
1151            VECTOR_FLOAT64 => {
1152                read_vec(self, |reader| reader.read_f64()).map(VectorValues::Float64)?
1153            }
1154            VECTOR_FLOAT32 => {
1155                read_vec(self, |reader| reader.read_f32()).map(VectorValues::Float32)?
1156            }
1157            VECTOR_INTEGER64 => {
1158                read_vec(self, |reader| reader.read_i64()).map(VectorValues::Integer64)?
1159            }
1160            VECTOR_INTEGER32 => {
1161                read_vec(self, |reader| reader.read_i32()).map(VectorValues::Integer32)?
1162            }
1163            VECTOR_INTEGER16 => {
1164                read_vec(self, |reader| reader.read_i16()).map(VectorValues::Integer16)?
1165            }
1166            VECTOR_INTEGER8 => {
1167                read_vec(self, |reader| reader.read_i8()).map(VectorValues::Integer8)?
1168            }
1169            tag => {
1170                return Err(StoreCodecError::Decode(format!(
1171                    "unknown vector value tag {tag}"
1172                )))
1173            }
1174        };
1175        if values.len() != dimension {
1176            return Err(StoreCodecError::Decode(format!(
1177                "vector dimension mismatch: declared {dimension}, got {}",
1178                values.len()
1179            )));
1180        }
1181        Ok(LoraVector { dimension, values })
1182    }
1183
1184    fn read_binary(&mut self) -> Result<LoraBinary> {
1185        let len = self.read_len_bounded("binary segment")?;
1186        let mut segments = self.vec_with_capacity(len, "binary segment")?;
1187        for _ in 0..len {
1188            segments.push(self.read_bytes()?.to_vec());
1189        }
1190        Ok(LoraBinary::from_segments(segments))
1191    }
1192}
1193
1194fn read_vec<T>(
1195    reader: &mut Reader<'_>,
1196    mut read_one: impl FnMut(&mut Reader<'_>) -> Result<T>,
1197) -> Result<Vec<T>> {
1198    let len = reader.read_len()?;
1199    if len > reader.remaining() {
1200        return Err(StoreCodecError::Decode(format!(
1201            "vector value count {len} exceeds remaining input"
1202        )));
1203    }
1204    let mut values = reader.vec_with_capacity(len, "vector value")?;
1205    for _ in 0..len {
1206        values.push(read_one(reader)?);
1207    }
1208    Ok(values)
1209}
1210
1211#[cfg(test)]
1212mod tests {
1213    use super::*;
1214    use crate::{IndexConfigValue, StoredIndexEntity, StoredIndexKind};
1215
1216    fn nested_list(depth: usize) -> PropertyValue {
1217        (0..depth).fold(PropertyValue::Null, |inner, _| {
1218            PropertyValue::List(vec![inner])
1219        })
1220    }
1221
1222    /// Bytes for `depth` one-element containers around a null, written
1223    /// directly so they can exceed what the encoder accepts.
1224    fn nested_bytes(depth: usize, tag: u8, null: u8, map: bool) -> Vec<u8> {
1225        let mut out = Vec::new();
1226        for _ in 0..depth {
1227            out.push(tag);
1228            write_len(&mut out, 1).unwrap();
1229            if map {
1230                write_string(&mut out, "k").unwrap();
1231            }
1232        }
1233        out.push(null);
1234        out
1235    }
1236
1237    /// A crafted value nested far past the limit fails to decode instead
1238    /// of overflowing the stack; the limit itself round-trips, and the
1239    /// encoder refuses one level more.
1240    #[test]
1241    fn nesting_is_bounded_on_decode_and_encode() {
1242        for map in [false, true] {
1243            let tag = if map { VALUE_MAP } else { VALUE_LIST };
1244            let err =
1245                decode_property_value(&nested_bytes(100_000, tag, VALUE_NULL, map)).unwrap_err();
1246            assert!(err.to_string().contains("more than 128 deep"), "{err}");
1247            assert!(
1248                decode_property_value(&nested_bytes(MAX_VALUE_NESTING, tag, VALUE_NULL, map))
1249                    .is_ok()
1250            );
1251            assert!(decode_property_value(&nested_bytes(
1252                MAX_VALUE_NESTING + 1,
1253                tag,
1254                VALUE_NULL,
1255                map
1256            ))
1257            .is_err());
1258        }
1259        let deepest = nested_list(MAX_VALUE_NESTING);
1260        let bytes = encode_property_value(&deepest).unwrap();
1261        assert_eq!(decode_property_value(&bytes).unwrap(), deepest);
1262        let err = encode_property_value(&nested_list(MAX_VALUE_NESTING + 1)).unwrap_err();
1263        assert!(matches!(err, StoreCodecError::Encode(_)), "{err}");
1264
1265        for map in [false, true] {
1266            let tag = if map { CONFIG_MAP } else { CONFIG_LIST };
1267            let mut reader = Reader::new(&[]);
1268            let bytes = nested_bytes(100_000, tag, CONFIG_NULL, map);
1269            reader.bytes = &bytes;
1270            assert!(reader.read_config_value().is_err());
1271        }
1272        let config = (0..MAX_VALUE_NESTING + 1).fold(IndexConfigValue::Null, |inner, _| {
1273            IndexConfigValue::List(vec![inner])
1274        });
1275        let mut out = Vec::new();
1276        assert!(write_config_value(&mut out, &config, 0).is_err());
1277    }
1278
1279    /// The in-memory record encoding keeps no limit: a deep value the
1280    /// graph accepted stays readable instead of failing (or panicking) on
1281    /// its way in or out.
1282    #[test]
1283    fn stored_values_nest_without_a_limit() {
1284        let deep = nested_list(MAX_VALUE_NESTING + 50);
1285        let bytes = encode_stored_property_value(&deep).unwrap();
1286        assert_eq!(decode_stored_property_value(&bytes).unwrap(), deep);
1287
1288        let mut g = crate::InMemoryGraph::new();
1289        let mut props = crate::Properties::new();
1290        props.insert(crate::intern("deep"), deep.clone());
1291        let id = crate::GraphStorageMut::create_node(&mut g, vec!["N".into()], props).id;
1292        assert_eq!(
1293            crate::GraphStorage::node_property(&g, id, "deep"),
1294            Some(deep)
1295        );
1296    }
1297
1298    #[test]
1299    fn property_value_roundtrips_nested_values() {
1300        let value = PropertyValue::Map(BTreeMap::from([
1301            ("name".into(), PropertyValue::String("Ada".into())),
1302            (
1303                "scores".into(),
1304                PropertyValue::List(vec![PropertyValue::Int(1), PropertyValue::Float(-2.5)]),
1305            ),
1306        ]));
1307
1308        let bytes = encode_property_value(&value).unwrap();
1309        assert_eq!(decode_property_value(&bytes).unwrap(), value);
1310    }
1311
1312    #[test]
1313    fn datetimes_roundtrip_with_and_without_a_zone() {
1314        let fixed = LoraDateTime::parse("2026-07-01T12:00:00+02:00").unwrap();
1315        let zoned = LoraDateTime::parse("2026-07-01T12:00:00+02:00[Europe/Amsterdam]").unwrap();
1316        for value in [fixed.clone(), zoned] {
1317            let value = PropertyValue::DateTime(value);
1318            let bytes = encode_property_value(&value).unwrap();
1319            assert_eq!(decode_property_value(&bytes).unwrap(), value);
1320        }
1321        // A DATETIME without a zone keeps its pre-zone encoding, so data
1322        // written by earlier versions decodes unchanged.
1323        let bytes = encode_property_value(&PropertyValue::DateTime(fixed)).unwrap();
1324        assert_eq!(bytes[0], VALUE_DATE_TIME);
1325        assert_eq!(bytes.len(), 1 + 8 * 4);
1326        let mut unknown = vec![VALUE_ZONED_DATE_TIME];
1327        unknown.extend_from_slice(&bytes[1..]);
1328        write_string(&mut unknown, "Mars/Olympus").unwrap();
1329        assert!(decode_property_value(&unknown)
1330            .unwrap_err()
1331            .to_string()
1332            .contains("Mars/Olympus"));
1333    }
1334
1335    #[test]
1336    fn property_value_rejects_oversized_list_length() {
1337        let mut bytes = vec![VALUE_LIST];
1338        bytes.extend_from_slice(&u64::MAX.to_le_bytes());
1339
1340        let err = decode_property_value(&bytes).unwrap_err();
1341        assert!(matches!(err, StoreCodecError::Decode(_)));
1342        assert!(err.to_string().contains("exceeds remaining input"));
1343    }
1344
1345    #[test]
1346    fn index_request_roundtrips_options() {
1347        let request = IndexRequest {
1348            explicit_name: Some("idx_person_name".into()),
1349            kind: StoredIndexKind::Text,
1350            entity: StoredIndexEntity::Node,
1351            label: Some("Person".into()),
1352            additional_labels: Vec::new(),
1353            properties: vec!["name".into()],
1354            options: BTreeMap::from([(
1355                "indexConfig".into(),
1356                IndexConfigValue::Map(BTreeMap::from([(
1357                    "trigram.min".into(),
1358                    IndexConfigValue::Integer(3),
1359                )])),
1360            )]),
1361        };
1362
1363        let bytes = encode_index_request(&request).unwrap();
1364        assert_eq!(decode_index_request(&bytes).unwrap(), request);
1365    }
1366
1367    #[test]
1368    fn index_definition_vec_roundtrips_state() {
1369        let defs = vec![IndexDefinition {
1370            name: "idx_person_age".into(),
1371            kind: StoredIndexKind::Range,
1372            entity: StoredIndexEntity::Node,
1373            label: Some("Person".into()),
1374            additional_labels: Vec::new(),
1375            properties: vec!["age".into()],
1376            options: BTreeMap::new(),
1377            state: StoredIndexState::Online,
1378        }];
1379
1380        let bytes = encode_index_definitions(&defs).unwrap();
1381        assert_eq!(decode_index_definitions(&bytes).unwrap(), defs);
1382    }
1383}