Skip to content

Commit

Permalink
Deserialize missing properties as none (#159)
Browse files Browse the repository at this point in the history
* Deserialize missing properties as none

fixed #147

* Don't use let..else
  • Loading branch information
knutwalker committed Dec 28, 2023
1 parent 0660928 commit 79200fa
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 4 deletions.
14 changes: 10 additions & 4 deletions lib/src/types/serde/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,18 +338,23 @@ impl<'de, T: ElementData<'de>> Deserializer<'de> for ElementDataDeserializer<'de
visitor.visit_unit()
}

fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_none()
}

forward_to_deserialize_any! {
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
bytes byte_buf option seq tuple map enum identifier
bytes byte_buf seq tuple map enum identifier
}

fn deserialize_any<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
Err(DeError::custom(
"deserializing additional data requires a struct",
))
Err(DeError::PropertyMissingButRequired)
}
}

Expand Down Expand Up @@ -499,6 +504,7 @@ impl<'de> IntoDeserializer<'de, DeError> for BorrowedStr<'de> {
}
}

#[derive(Copy, Clone, Debug)]
enum AdditionalData<'de, T> {
Property(&'de BoltType),
Element(T),
Expand Down
9 changes: 9 additions & 0 deletions lib/src/types/serde/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ pub enum DeError {
#[error("The property does not exist")]
NoSuchProperty,

#[error(
"The property is missing but the deserializer still expects a value. \
If you have an optional property with a default value, you need to \
use an Option<T> instead (the default attribute does not work in \
this particular instance). If you meant to extract additional data \
other than properties, you need to use the appropriate struct wrapper."
)]
PropertyMissingButRequired,

#[error("{0}")]
Other(String),

Expand Down
29 changes: 29 additions & 0 deletions lib/src/types/serde/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,35 @@ mod tests {
});
}

#[test]
fn extract_missing_properties_with_option() {
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
struct Person {
favorite_rust_crate: Option<String>,
}

test_extract_node(Person {
favorite_rust_crate: None,
});
}

#[test]
fn extract_missing_properties_with_default() {
fn favorite_rust_crate() -> String {
"graph".to_owned()
}

#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
struct Person {
#[serde(default = "favorite_rust_crate")]
favorite_rust_crate: String,
}

let node = test_node();
let actual = node.to::<Person>().unwrap_err();
assert!(matches!(actual, DeError::PropertyMissingButRequired));
}

#[test]
fn extract_node_id() {
test_extract_node_extra(Id(1337));
Expand Down
31 changes: 31 additions & 0 deletions lib/tests/missing_properties.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use neo4rs::{query, Node};
use serde::Deserialize;

mod container;

#[tokio::test]
async fn missing_properties() {
let neo4j = container::Neo4jContainer::new().await;
let graph = neo4j.graph();

let a_val = None::<String>;
let mut result = graph
.execute(query("CREATE (ts:TestStruct {a: $a}) RETURN ts").param("a", a_val))
.await
.unwrap();
let row = result.next().await.unwrap().unwrap();
let expected = StructWithOption { a: None, b: None };

let test_struct: StructWithOption = row.to().unwrap();
assert_eq!(test_struct, expected);

let node = row.get::<Node>("ts").unwrap();
let test_struct: StructWithOption = node.to().unwrap();
assert_eq!(test_struct, expected);
}

#[derive(Deserialize, PartialEq, Eq, Debug)]
struct StructWithOption {
a: Option<String>,
b: Option<String>,
}

0 comments on commit 79200fa

Please sign in to comment.