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