shared/domain/
ser.rs

1//! Serialization helpers
2
3use serde::de::{DeserializeOwned, Visitor};
4use std::fmt::Display;
5use std::{
6    fmt::{self, Write},
7    marker::PhantomData,
8    str::FromStr,
9};
10
11use uuid::Uuid;
12
13/// Hack to deserialize an Optional [`Option<T>`]
14///
15/// This is to differentiate between "missing" values and null values.
16/// For example in json `{"v": null}` and `{}` are different things, in the first one, `v` is `null`, but in the second, v is `undefined`.
17///
18/// [`Option<T>`]: Option
19pub fn deserialize_optional_field<'de, T, D>(deserializer: D) -> Result<Option<Option<T>>, D::Error>
20where
21    D: serde::Deserializer<'de>,
22    T: serde::Deserialize<'de>,
23{
24    Ok(Some(serde::Deserialize::deserialize(deserializer)?))
25}
26
27/// Serializes a slice of hyphenated UUIDs into CSV format
28///
29/// ## Note:
30/// * Algolia takes CSV format arrays: https://www.algolia.com/doc/rest-api/search/#arrays
31pub fn csv_encode_uuids<T: Into<Uuid> + Copy, S>(
32    uuids: &[T],
33    serializer: S,
34) -> Result<S::Ok, S::Error>
35where
36    S: serde::Serializer,
37{
38    // todo: use a `Display` struct to use `collect_str`
39    // but for now, pre-allocate the whole string.
40
41    // a hyphenated uuid is 36 bytes long, we have `n` of those, then we also have `n - 1` 1 byte separators.
42    let len = uuids.len() * 36 + uuids.len().saturating_sub(1);
43
44    let mut out = String::with_capacity(len);
45    let mut iter = uuids.iter().copied().map(<T as Into<Uuid>>::into);
46    if let Some(item) = iter.next() {
47        write!(&mut out, "{}", item.hyphenated())
48            .expect("`String` call to `write!` shouldn't fail.");
49    }
50
51    for item in iter {
52        write!(&mut out, ",{}", item.hyphenated())
53            .expect("`String` call to `write!` shouldn't fail");
54    }
55
56    serializer.serialize_str(&out)
57}
58
59/// ASSUMING this is only going to be used for resources identified by index, which is
60/// a non-negative integer which begins counting up from 0.
61///
62/// In most cases for this project, i16 is used instead of u16 because PostgreSQL does not have
63/// unsigned integer types.
64pub fn csv_encode_i16_indices<T: Into<i16> + Copy, S>(
65    values: &[T],
66    serializer: S,
67) -> Result<S::Ok, S::Error>
68where
69    S: serde::Serializer,
70{
71    // i16 has range [-32768, 32767].
72    // We are making the assumption here that the values are >= 0.
73    let len = values.len() * 5 + values.len().saturating_sub(1);
74
75    let mut out = String::with_capacity(len);
76    let mut iter = values.iter().copied().map(<T as Into<i16>>::into);
77    if let Some(item) = iter.next() {
78        write!(&mut out, "{}", item.to_string())
79            .expect("`String` call to `write!` shouldn't fail.");
80    }
81
82    for item in iter {
83        write!(&mut out, ",{}", item.to_string())
84            .expect("`String` call to `write!` shouldn't fail");
85    }
86
87    serializer.serialize_str(&out)
88}
89
90/// Deserializes a slice of hyphenated UUIDs into CSV format
91///
92/// ## Note:
93/// * Algolia takes CSV format arrays: https://www.algolia.com/doc/rest-api/search/#arrays
94pub fn from_csv<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
95where
96    D: serde::Deserializer<'de>,
97    T: DeserializeOwned,
98{
99    deserializer.deserialize_str(CSVVecVisitor::<T>::default())
100}
101
102/// Visits a string value of the form "v1,v2,v3" into a vector of bytes Vec<u8>
103struct CSVVecVisitor<T: DeserializeOwned>(std::marker::PhantomData<T>);
104
105impl<T: DeserializeOwned> Default for CSVVecVisitor<T> {
106    fn default() -> Self {
107        Self(std::marker::PhantomData)
108    }
109}
110
111impl<'de, T: DeserializeOwned> serde::de::Visitor<'de> for CSVVecVisitor<T> {
112    type Value = Vec<T>;
113
114    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        write!(formatter, "a str")
116    }
117
118    fn visit_str<E>(self, s: &str) -> std::result::Result<Self::Value, E>
119    where
120        E: serde::de::Error,
121    {
122        csv::ReaderBuilder::new()
123            .has_headers(false)
124            .from_reader(s.as_bytes())
125            .into_deserialize()
126            .next()
127            .unwrap_or_else(|| Ok(Vec::new()))
128            .map_err(|e| E::custom(format!("could not deserialize sequence value: {:?}", e)))
129    }
130}
131
132/// Stringify a list
133pub fn to_csv<T: Display, S>(values: &[T], serializer: S) -> Result<S::Ok, S::Error>
134where
135    S: serde::Serializer,
136{
137    let out = values
138        .iter()
139        .map(std::string::ToString::to_string)
140        .collect::<Vec<String>>()
141        .join(",");
142
143    serializer.serialize_str(&out)
144}
145
146// // I think this is commented out to avoid repeated writer re-allocations? the csv_encode_* functions
147// // above preallocate. Not 100% certain if this is the reason, or if the performance impact is noticeable
148// pub(super) fn vec_encode_csv<T: Serialize, S>(v: &Vec<T>, serializer: S) -> Result<S::Ok, S::Error>
149// where
150//     S: serde::Serializer,
151// {
152//     let mut writer = csv::WriterBuilder::new()
153//         .has_headers(false)
154//         .from_writer(vec![]);
155
156//     writer.serialize(v).map_err(Error::custom)?;
157
158//     // This error might not be triggerable.
159//     let raw = writer.into_inner().map_err(Error::custom)?;
160
161//     let s = std::str::from_utf8(&raw).map_err(Error::custom)?;
162
163//     serializer.serialize_str(s)
164// }
165
166pub(super) struct FromStrVisitor<T>(pub PhantomData<T>);
167
168impl<'de, TErr: std::fmt::Debug, T: FromStr<Err = TErr>> Visitor<'de> for FromStrVisitor<T> {
169    type Value = T;
170
171    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
172        formatter.write_str("string")
173    }
174
175    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
176    where
177        E: serde::de::Error,
178    {
179        FromStr::from_str(value)
180            .map_err(|e| E::custom(format!("could not deserialize string: {:?}", e)))
181    }
182}