Skip to content

Commit c51482e

Browse files
committed
Improve annotation deserialization in Wrapper
Enhanced the Wrapper struct to support custom deserialization logic that iterates through all child elements in an annotation and attempts to deserialize each into the target type, using the first successful match. Added a new test to verify complex annotation deserialization and updated documentation for clarity.
1 parent 30c4e53 commit c51482e

File tree

2 files changed

+132
-8
lines changed

2 files changed

+132
-8
lines changed

src/model.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,26 @@ mod tests {
11001100
);
11011101
}
11021102

1103+
#[test]
1104+
fn test_get_complex_annotation() {
1105+
#[derive(Serialize, Deserialize)]
1106+
#[serde(rename = "customAnnotation")]
1107+
struct TestAnnotation {
1108+
test: String,
1109+
}
1110+
1111+
let doc = SBMLDocument::default();
1112+
let model = Model::new(&doc, "test");
1113+
model
1114+
.set_annotation(
1115+
"<customAnnotation><test>test</test></customAnnotation><test2>test2</test2>",
1116+
)
1117+
.unwrap();
1118+
1119+
let annotation: TestAnnotation = model.get_annotation_serde().unwrap();
1120+
assert_eq!(annotation.test, "test");
1121+
}
1122+
11031123
#[test]
11041124
fn test_set_annotation_serde() {
11051125
#[derive(Serialize, Deserialize)]

src/wrapper.rs

Lines changed: 112 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,130 @@
33
//! This module defines a generic wrapper that allows for flexible deserialization
44
//! of annotations with custom types in SBML-related data structures.
55
6-
use serde::Deserialize;
6+
use serde::de::{self, MapAccess, Visitor};
7+
use serde::{Deserialize, Deserializer, Serialize};
8+
use std::fmt;
9+
use std::marker::PhantomData;
710

811
/// A generic wrapper struct for deserializing XML annotations.
912
///
1013
/// This struct allows for flexible deserialization of annotations by wrapping
1114
/// a generic type `T` with a specific XML structure. It is particularly useful
1215
/// when working with serialized metadata in SBML models.
1316
///
17+
/// The custom deserializer iterates through all child elements within the
18+
/// `<annotation>` tag and attempts to deserialize each one into type `T`.
19+
/// If multiple elements can be parsed into `T`, the first successful one is used.
20+
/// Elements that cannot be parsed into `T` are silently ignored.
21+
///
1422
/// # Type Parameters
1523
/// * `T` - The type of the annotation content to be deserialized
1624
///
17-
/// # Serde Configuration
18-
/// * Renames the root XML element to "annotation"
19-
/// * Uses "$value" to capture the inner content
20-
#[derive(Debug, Deserialize, Clone)]
25+
/// # Behavior
26+
/// * Expects XML with root element named "annotation"
27+
/// * Iterates through all child elements
28+
/// * Attempts to deserialize each child element into type `T`
29+
/// * Returns the first successful match
30+
/// * Ignores elements that cannot be parsed into `T`
31+
///
32+
/// # Example
33+
/// ```xml
34+
/// <annotation>
35+
/// <test>some_value</test>
36+
/// <other_field>ignored</other_field>
37+
/// <name>also_ignored</name>
38+
/// </annotation>
39+
/// ```
40+
///
41+
/// When deserializing into `Wrapper<TestStruct>` where `TestStruct` has a `test` field,
42+
/// only the `<test>` element would be successfully parsed, while others are ignored.
43+
#[derive(Debug, Clone, Serialize)]
2144
#[serde(rename = "annotation")]
2245
pub(crate) struct Wrapper<T> {
2346
/// The actual annotation content
24-
///
25-
/// Uses a special serde rename to capture the inner XML value
26-
#[serde(rename = "$value")]
2747
pub(crate) annotation: T,
2848
}
49+
50+
impl<'de, T> Deserialize<'de> for Wrapper<T>
51+
where
52+
T: Deserialize<'de>,
53+
{
54+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
55+
where
56+
D: Deserializer<'de>,
57+
{
58+
struct WrapperVisitor<T> {
59+
marker: PhantomData<T>,
60+
}
61+
62+
impl<'de, T> Visitor<'de> for WrapperVisitor<T>
63+
where
64+
T: Deserialize<'de>,
65+
{
66+
type Value = Wrapper<T>;
67+
68+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
69+
formatter.write_str("an annotation element with parseable content")
70+
}
71+
72+
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
73+
where
74+
A: MapAccess<'de>,
75+
{
76+
let mut last_error: Option<String> = None;
77+
78+
// Iterate through all key-value pairs
79+
while let Some(key) = map.next_key::<String>()? {
80+
// Try to deserialize the next value into T
81+
// This handles both single values and nested structures
82+
match map.next_value::<T>() {
83+
Ok(parsed_value) => {
84+
// Successfully parsed this element into T
85+
return Ok(Wrapper {
86+
annotation: parsed_value,
87+
});
88+
}
89+
Err(err) => {
90+
// This element couldn't be parsed into T, store error and continue
91+
last_error = Some(format!("Failed to parse '{}': {}", key, err));
92+
continue;
93+
}
94+
}
95+
}
96+
97+
// If we get here, no element could be parsed into T
98+
match last_error {
99+
Some(err) => Err(de::Error::custom(err)),
100+
None => Err(de::Error::custom(
101+
"no elements found that could be parsed into the target type",
102+
)),
103+
}
104+
}
105+
}
106+
107+
// Use a map deserializer since XML elements are treated as key-value pairs
108+
deserializer.deserialize_map(WrapperVisitor {
109+
marker: PhantomData,
110+
})
111+
}
112+
}
113+
114+
impl<T> Wrapper<T> {
115+
/// Creates a new wrapper with the given annotation content.
116+
#[allow(dead_code)]
117+
pub(crate) fn new(annotation: T) -> Self {
118+
Self { annotation }
119+
}
120+
121+
/// Gets a reference to the annotation content.
122+
#[allow(dead_code)]
123+
pub(crate) fn get(&self) -> &T {
124+
&self.annotation
125+
}
126+
127+
/// Consumes the wrapper and returns the annotation content.
128+
#[allow(dead_code)]
129+
pub(crate) fn into_inner(self) -> T {
130+
self.annotation
131+
}
132+
}

0 commit comments

Comments
 (0)