Skip to content

Commit a574e88

Browse files
committed
Deserialize series using field names, not order
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 0566811 commit a574e88

File tree

4 files changed

+378
-3
lines changed

4 files changed

+378
-3
lines changed

influxdb/Cargo.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ serde = { version = "1.0.104", features = ["derive"], optional = true }
2525
serde_json = { version = "1.0.48", optional = true }
2626
regex = "1.3.5"
2727
lazy_static = "1.4.0"
28+
smol_str = { version = "0.1.16", optional = true, features = ["serde"] }
2829

2930
[features]
30-
use-serde = ["serde", "serde_json"]
31+
use-serde = ["serde", "serde_json", "smol_str"]
3132
default = ["use-serde"]
3233
derive = ["influxdb_derive"]
3334

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

0 commit comments

Comments
 (0)