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