diff --git a/lib/src/bolt/structs/node.rs b/lib/src/bolt/structs/node.rs index dcbc0809..542ac07c 100644 --- a/lib/src/bolt/structs/node.rs +++ b/lib/src/bolt/structs/node.rs @@ -188,7 +188,10 @@ mod tests { use serde_test::{assert_de_tokens, Token}; use test_case::{test_case, test_matrix}; - use crate::packstream::{bolt, from_bytes_ref, BoltBytesBuilder, Data}; + use crate::{ + bolt::LegacyDateTime, + packstream::{bolt, from_bytes_ref, BoltBytesBuilder, Data}, + }; use super::*; @@ -335,4 +338,33 @@ mod tests { .tiny_string("Alice") .build() } + + fn deserialize_properties_with_structs() { + let mut data = Data::new( + bolt() + .structure(3, 0x4E) + .tiny_int(42) + .tiny_list(1) + .tiny_string("Label") + .tiny_map(1) + .tiny_string("p") + .structure(3, b'F') + .int32(42) + .tiny_int(0) + .tiny_int(0) + .build(), + ); + let mut node: NodeRef = from_bytes_ref(&mut data).unwrap(); + + assert_eq!(node.id(), 42); + assert_eq!(node.labels(), &["Label"]); + assert_eq!(node.element_id(), None); + + assert_eq!(node.keys(), &["p"]); + + let p = node.get::("p").unwrap().unwrap(); + assert_eq!(p.seconds_since_epoch(), 42); + assert_eq!(p.nanoseconds(), 0); + assert_eq!(p.timezone_offset_seconds(), 0); + } } diff --git a/lib/src/packstream/de.rs b/lib/src/packstream/de.rs index f723448d..3e6503f8 100644 --- a/lib/src/packstream/de.rs +++ b/lib/src/packstream/de.rs @@ -1,10 +1,10 @@ -use std::{fmt, marker::PhantomData}; +use std::{any::type_name, fmt, marker::PhantomData}; use bytes::{Buf, Bytes}; use serde::{ de::{ - self, value::SeqDeserializer, DeserializeSeed, EnumAccess, IntoDeserializer as _, - MapAccess, SeqAccess, VariantAccess, Visitor, + self, value::SeqDeserializer, DeserializeSeed, EnumAccess, IgnoredAny, + IntoDeserializer as _, MapAccess, SeqAccess, VariantAccess, Visitor, }, forward_to_deserialize_any, }; @@ -207,8 +207,10 @@ impl<'de> Deserializer<'de> { let start = full_bytes.as_ptr(); let end = self.bytes.as_ptr(); + // SAFETY: both pointers are from the same allocation and end is >= start let len = unsafe { end.offset_from(start) }; full_bytes.truncate(len.unsigned_abs()); + Ok(full_bytes) } @@ -358,9 +360,17 @@ impl<'a> ItemsParser<'a> { self.excess = excess; self } + + fn drain(&mut self) -> Result<(), Error> { + let bytes = self.bytes.get(); + for _ in 0..(self.len + self.excess) { + Deserializer { bytes }.skip_next_item()?; + } + Ok(()) + } } -impl<'a, 'de> SeqAccess<'de> for ItemsParser<'a> { +impl<'de> SeqAccess<'de> for ItemsParser<'_> { type Error = Error; fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> @@ -368,10 +378,7 @@ impl<'a, 'de> SeqAccess<'de> for ItemsParser<'a> { T: DeserializeSeed<'de>, { if self.len == 0 { - let bytes = self.bytes.get(); - for _ in 0..self.excess { - Deserializer { bytes }.skip_next_item()?; - } + self.drain()?; return Ok(None); } self.len -= 1; @@ -388,7 +395,7 @@ impl<'a, 'de> SeqAccess<'de> for ItemsParser<'a> { } } -impl<'a, 'de> MapAccess<'de> for ItemsParser<'a> { +impl<'de> MapAccess<'de> for ItemsParser<'_> { type Error = Error; fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> @@ -420,7 +427,7 @@ impl<'a, 'de> MapAccess<'de> for ItemsParser<'a> { } } -impl<'a, 'de> VariantAccess<'de> for ItemsParser<'a> { +impl<'de> VariantAccess<'de> for ItemsParser<'_> { type Error = Error; fn unit_variant(mut self) -> Result<(), Self::Error> { @@ -431,6 +438,11 @@ impl<'a, 'de> VariantAccess<'de> for ItemsParser<'a> { where T: DeserializeSeed<'de>, { + if type_name::() == type_name::>() { + self.drain()?; + return seed.deserialize(().into_deserializer()); + } + self.next_value_seed(seed) } @@ -498,14 +510,14 @@ struct SharedBytes<'a> { } #[cfg(all(test, debug_assertions))] -impl<'a> fmt::Debug for SharedBytes<'a> { +impl fmt::Debug for SharedBytes<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { crate::packstream::Dbg(unsafe { &*self.bytes }).fmt(f) } } #[cfg(not(all(test, debug_assertions)))] -impl<'a> fmt::Debug for SharedBytes<'a> { +impl fmt::Debug for SharedBytes<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("SharedBytes").finish_non_exhaustive() } diff --git a/lib/tests/node_property_parsing.rs b/lib/tests/node_property_parsing.rs new file mode 100644 index 00000000..aa86beef --- /dev/null +++ b/lib/tests/node_property_parsing.rs @@ -0,0 +1,23 @@ +use chrono::{DateTime, FixedOffset}; +use neo4rs::{query, Node}; + +mod container; + +#[tokio::test] +async fn node_property_parsing() { + let neo4j = container::Neo4jContainer::new().await; + let graph = neo4j.graph(); + + graph + .run(query("CREATE (:A {p1:DATETIME('2024-12-31T08:10:35')})")) + .await + .unwrap(); + + let mut result = graph.execute(query("MATCH (p:A) RETURN p")).await.unwrap(); + + while let Ok(Some(row)) = result.next().await { + let node: Node = row.get("p").unwrap(); + let p1 = node.get::>("p1").unwrap(); + assert_eq!(p1.timestamp(), 1735632635); + } +}