Skip to content

Commit 2d648f3

Browse files
authored
Deserialize series using field names, not order (#62)
Add a custom Deserialize impl for Series which uses the field names in the columns field of the response to allow the inner type to deserialize itself as a map instead of relying on the field order.
1 parent 55d497f commit 2d648f3

File tree

3 files changed

+352
-2
lines changed

3 files changed

+352
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
use super::Series;
2+
use serde::de::{
3+
value, DeserializeSeed, Deserializer, Error, IntoDeserializer, MapAccess, SeqAccess, Visitor,
4+
};
5+
use serde::Deserialize;
6+
use std::fmt;
7+
use std::marker::PhantomData;
8+
9+
// Based on https://serde.rs/deserialize-struct.html
10+
impl<'de, T> Deserialize<'de> for Series<T>
11+
where
12+
T: Deserialize<'de>,
13+
{
14+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
15+
where
16+
D: Deserializer<'de>,
17+
{
18+
// Field name deserializer
19+
#[derive(Deserialize)]
20+
#[serde(field_identifier, rename_all = "lowercase")]
21+
enum Field {
22+
Name,
23+
Columns,
24+
Values,
25+
};
26+
27+
struct SeriesVisitor<T> {
28+
_inner_type: PhantomData<T>,
29+
};
30+
31+
impl<'de, T> Visitor<'de> for SeriesVisitor<T>
32+
where
33+
T: Deserialize<'de>,
34+
{
35+
type Value = Series<T>;
36+
37+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
38+
formatter.write_str("struct Series")
39+
}
40+
41+
fn visit_map<V>(self, mut map: V) -> Result<Series<T>, V::Error>
42+
where
43+
V: MapAccess<'de>,
44+
{
45+
let mut name = None;
46+
let mut columns: Option<Vec<String>> = None;
47+
let mut values: Option<Vec<T>> = None;
48+
while let Some(key) = map.next_key()? {
49+
match key {
50+
Field::Name => {
51+
if name.is_some() {
52+
return Err(Error::duplicate_field("name"));
53+
}
54+
name = Some(map.next_value()?);
55+
}
56+
Field::Columns => {
57+
if columns.is_some() {
58+
return Err(Error::duplicate_field("columns"));
59+
}
60+
columns = Some(map.next_value()?);
61+
}
62+
Field::Values => {
63+
if values.is_some() {
64+
return Err(Error::duplicate_field("values"));
65+
}
66+
// Error out if "values" is encountered before "columns"
67+
// Hopefully, InfluxDB never does this.
68+
if columns.is_none() {
69+
return Err(Error::custom(
70+
"series values encountered before columns",
71+
));
72+
}
73+
// Deserialize using a HeaderVec deserializer
74+
// seeded with the headers from the "columns" field
75+
values = Some(map.next_value_seed(HeaderVec::<T> {
76+
header: columns.as_ref().unwrap(),
77+
_inner_type: PhantomData,
78+
})?);
79+
}
80+
}
81+
}
82+
let name = name.ok_or_else(|| Error::missing_field("name"))?;
83+
let values = values.ok_or_else(|| Error::missing_field("values"))?;
84+
Ok(Series { name, values })
85+
}
86+
}
87+
88+
const FIELDS: &[&str] = &["name", "values"];
89+
deserializer.deserialize_struct(
90+
"Series",
91+
FIELDS,
92+
SeriesVisitor::<T> {
93+
_inner_type: PhantomData,
94+
},
95+
)
96+
}
97+
}
98+
99+
// Deserializer that takes a header as a seed
100+
// and deserializes an array of arrays into a
101+
// Vec of map-like values using the header as
102+
// keys and the values as values.
103+
struct HeaderVec<'h, T> {
104+
header: &'h [String],
105+
_inner_type: PhantomData<T>,
106+
}
107+
108+
impl<'de, 'h, T> DeserializeSeed<'de> for HeaderVec<'h, T>
109+
where
110+
T: Deserialize<'de>,
111+
{
112+
type Value = Vec<T>;
113+
114+
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
115+
where
116+
D: Deserializer<'de>,
117+
{
118+
struct HeaderVecVisitor<'h, T> {
119+
header: &'h [String],
120+
_inner_type: PhantomData<T>,
121+
}
122+
impl<'de, 'h, T> Visitor<'de> for HeaderVecVisitor<'h, T>
123+
where
124+
T: Deserialize<'de>,
125+
{
126+
type Value = Vec<T>;
127+
128+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
129+
write!(formatter, "an array of arrays")
130+
}
131+
132+
fn visit_seq<A>(self, mut seq: A) -> Result<Vec<T>, A::Error>
133+
where
134+
A: SeqAccess<'de>,
135+
{
136+
let mut vec = Vec::new();
137+
138+
while let Some(v) = seq.next_element_seed(RowWithHeader {
139+
header: self.header,
140+
_inner_type: PhantomData,
141+
})? {
142+
vec.push(v);
143+
}
144+
145+
Ok(vec)
146+
}
147+
}
148+
deserializer.deserialize_seq(HeaderVecVisitor {
149+
header: self.header,
150+
_inner_type: PhantomData,
151+
})
152+
}
153+
}
154+
155+
// Deserializer that takes a header as a seed
156+
// and deserializes an array into a map-like
157+
// value using the header as keys and the values
158+
// as values.
159+
struct RowWithHeader<'h, T> {
160+
header: &'h [String],
161+
_inner_type: PhantomData<T>,
162+
}
163+
164+
impl<'de, 'h, T> DeserializeSeed<'de> for RowWithHeader<'h, T>
165+
where
166+
T: Deserialize<'de>,
167+
{
168+
type Value = T;
169+
170+
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
171+
where
172+
D: Deserializer<'de>,
173+
{
174+
struct RowWithHeaderVisitor<'h, T> {
175+
header: &'h [String],
176+
_inner: PhantomData<fn() -> T>,
177+
}
178+
179+
impl<'de, 'h, T> Visitor<'de> for RowWithHeaderVisitor<'h, T>
180+
where
181+
T: Deserialize<'de>,
182+
{
183+
type Value = T;
184+
185+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
186+
formatter.write_str("array")
187+
}
188+
189+
fn visit_seq<A>(self, seq: A) -> Result<T, A::Error>
190+
where
191+
A: SeqAccess<'de>,
192+
{
193+
// `MapAccessDeserializer` is a wrapper that turns a `MapAccess`
194+
// into a `Deserializer`, allowing it to be used as the input to T's
195+
// `Deserialize` implementation. T then deserializes itself using
196+
// the entries from the map visitor.
197+
Deserialize::deserialize(value::MapAccessDeserializer::new(HeaderMapAccess {
198+
header: self.header,
199+
field: 0,
200+
data: seq,
201+
}))
202+
}
203+
}
204+
205+
deserializer.deserialize_seq(RowWithHeaderVisitor {
206+
header: self.header,
207+
_inner: PhantomData,
208+
})
209+
}
210+
}
211+
212+
// MapAccess implementation that holds a reference to
213+
// the header for keys and a serde sequence for values.
214+
// When asked for a key, it returns the next header and
215+
// advances its header field index. When asked for a value,
216+
// it tries to deserialize the next element in the serde
217+
// sequence into the desired type, and returns an error
218+
// if no element is returned (the sequence is exhausted).
219+
struct HeaderMapAccess<'h, A> {
220+
header: &'h [String],
221+
field: usize,
222+
data: A,
223+
}
224+
225+
impl<'de, 'h, A> MapAccess<'de> for HeaderMapAccess<'h, A>
226+
where
227+
A: SeqAccess<'de>,
228+
{
229+
type Error = <A as SeqAccess<'de>>::Error;
230+
231+
fn next_key_seed<K: DeserializeSeed<'de>>(
232+
&mut self,
233+
seed: K,
234+
) -> Result<Option<K::Value>, Self::Error> {
235+
let field = match self.header.get(self.field) {
236+
None => return Ok(None),
237+
Some(field) => field,
238+
};
239+
self.field += 1;
240+
seed.deserialize(field.clone().into_deserializer())
241+
.map(Some)
242+
}
243+
244+
fn next_value_seed<K: DeserializeSeed<'de>>(
245+
&mut self,
246+
seed: K,
247+
) -> Result<K::Value, Self::Error> {
248+
match self.data.next_element_seed(seed)? {
249+
Some(value) => Ok(value),
250+
None => Err(Error::custom("next_value_seed called but no value")),
251+
}
252+
}
253+
}
254+
255+
#[cfg(test)]
256+
mod tests {
257+
use super::Series;
258+
use std::borrow::Cow;
259+
use std::collections::HashMap;
260+
261+
const TEST_DATA: &str = r#"
262+
{
263+
"name": "series_name",
264+
"columns": ["foo", "bar"],
265+
"values": [
266+
["foo_a", "bar_a"],
267+
["foo_b", "bar_b"]
268+
]
269+
}
270+
"#;
271+
272+
// we can derive all the impls we want here
273+
#[derive(Debug, PartialEq, Eq)]
274+
struct EqSeries<T> {
275+
pub name: String,
276+
pub values: Vec<T>,
277+
}
278+
279+
impl<T> From<Series<T>> for EqSeries<T> {
280+
fn from(Series { name, values }: Series<T>) -> Self {
281+
EqSeries { name, values }
282+
}
283+
}
284+
285+
#[test]
286+
fn test_deserialize_cow() {
287+
// Unfortunately, Cow is not automatically borrowed,
288+
// so this is basically equivalent to String, String
289+
let result = serde_json::from_str::<Series<HashMap<Cow<str>, Cow<str>>>>(TEST_DATA);
290+
assert!(result.is_ok());
291+
assert_eq!(
292+
EqSeries::from(result.unwrap()),
293+
EqSeries {
294+
name: "series_name".into(),
295+
values: vec![
296+
{
297+
let mut h = std::collections::HashMap::new();
298+
h.insert("foo".into(), "foo_a".into());
299+
h.insert("bar".into(), "bar_a".into());
300+
h
301+
},
302+
{
303+
let mut h = std::collections::HashMap::new();
304+
h.insert("foo".into(), "foo_b".into());
305+
h.insert("bar".into(), "bar_b".into());
306+
h
307+
},
308+
],
309+
},
310+
);
311+
}
312+
313+
#[test]
314+
fn test_deserialize_borrowed() {
315+
use serde::Deserialize;
316+
317+
// Deserializing a string that cannot be passed through
318+
// without escaping will result in an error like this:
319+
// `invalid type: string "\n", expected a borrowed string at line 6 column 43`
320+
// but if it doesn't need escaping it's fine.
321+
#[derive(Deserialize, Debug, PartialEq, Eq)]
322+
struct BorrowingStruct<'a> {
323+
foo: &'a str,
324+
bar: &'a str,
325+
}
326+
327+
let result = serde_json::from_str::<Series<BorrowingStruct>>(TEST_DATA);
328+
assert!(result.is_ok(), "{}", result.unwrap_err());
329+
assert_eq!(
330+
EqSeries::from(result.unwrap()),
331+
EqSeries {
332+
name: "series_name".into(),
333+
values: vec![
334+
BorrowingStruct {
335+
foo: "foo_a",
336+
bar: "bar_a",
337+
},
338+
BorrowingStruct {
339+
foo: "foo_b",
340+
bar: "bar_b",
341+
},
342+
],
343+
},
344+
);
345+
}
346+
}

influxdb/src/integrations/serde_integration.rs renamed to influxdb/src/integrations/serde_integration/mod.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
//! # }
4747
//! ```
4848
49+
mod de;
50+
4951
use reqwest::{Client as ReqwestClient, StatusCode, Url};
5052

5153
use serde::{de::DeserializeOwned, Deserialize};
@@ -84,7 +86,7 @@ pub struct Return<T> {
8486
pub series: Vec<Series<T>>,
8587
}
8688

87-
#[derive(Deserialize, Debug)]
89+
#[derive(Debug)]
8890
/// Represents a returned series from InfluxDB
8991
pub struct Series<T> {
9092
pub name: String,

influxdb/tests/integration_tests.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,10 @@ async fn test_write_and_read_option() {
274274
#[derive(Deserialize, Debug, PartialEq)]
275275
struct Weather {
276276
time: String,
277-
temperature: i32,
277+
// different order to verify field names
278+
// are being used instead of just order
278279
wind_strength: Option<u64>,
280+
temperature: i32,
279281
}
280282

281283
let query =

0 commit comments

Comments
 (0)