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