1use 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;
133const 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 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}