relay_event_schema/protocol/
fingerprint.rs

1use relay_protocol::{
2    Annotated, Empty, Error, ErrorKind, FromValue, IntoValue, SkipSerialization, Value,
3};
4
5use crate::processor::ProcessValue;
6use crate::protocol::LenientString;
7
8/// A fingerprint value.
9#[derive(Debug, Clone, PartialEq)]
10pub struct Fingerprint(Vec<String>);
11
12impl std::ops::Deref for Fingerprint {
13    type Target = Vec<String>;
14
15    fn deref(&self) -> &Self::Target {
16        &self.0
17    }
18}
19
20impl std::ops::DerefMut for Fingerprint {
21    fn deref_mut(&mut self) -> &mut Self::Target {
22        &mut self.0
23    }
24}
25
26impl From<Vec<String>> for Fingerprint {
27    fn from(vec: Vec<String>) -> Fingerprint {
28        Fingerprint(vec)
29    }
30}
31
32impl Empty for Fingerprint {
33    fn is_empty(&self) -> bool {
34        self.0.is_empty()
35    }
36
37    fn is_deep_empty(&self) -> bool {
38        self.0.iter().all(Empty::is_deep_empty)
39    }
40}
41
42impl FromValue for Fingerprint {
43    fn from_value(value: Annotated<Value>) -> Annotated<Self>
44    where
45        Self: Sized,
46    {
47        match value {
48            // TODO: check error reporting here, this seems wrong
49            Annotated(Some(Value::Array(array)), mut meta) => {
50                let mut fingerprint = vec![];
51                let mut bad_values = vec![];
52
53                for elem in array {
54                    let Annotated(value, mut elem_meta) = LenientString::from_value(elem);
55                    if let (Some(value), false) = (value, elem_meta.has_errors()) {
56                        fingerprint.push(value.0);
57                    }
58                    if let Some(bad_value) = elem_meta.take_original_value() {
59                        bad_values.push(Annotated::new(bad_value));
60                    }
61                }
62
63                if !bad_values.is_empty() {
64                    if meta.original_length().is_none() {
65                        meta.set_original_length(Some(fingerprint.len() + bad_values.len()));
66                    }
67
68                    meta.add_error(Error::with(ErrorKind::InvalidData, |error| {
69                        error.insert("value", bad_values);
70                    }));
71                }
72
73                Annotated(
74                    if fingerprint.is_empty() && meta.has_errors() {
75                        None
76                    } else {
77                        Some(Fingerprint(fingerprint))
78                    },
79                    meta,
80                )
81            }
82            Annotated(Some(value), mut meta) => {
83                meta.add_error(Error::expected("an array"));
84                meta.set_original_value(Some(value));
85                Annotated(None, meta)
86            }
87            Annotated(None, meta) => Annotated(None, meta),
88        }
89    }
90}
91
92impl IntoValue for Fingerprint {
93    fn into_value(self) -> Value
94    where
95        Self: Sized,
96    {
97        Value::Array(
98            self.0
99                .into_iter()
100                .map(|x| Annotated::new(Value::String(x)))
101                .collect(),
102        )
103    }
104
105    fn serialize_payload<S>(&self, s: S, _behavior: SkipSerialization) -> Result<S::Ok, S::Error>
106    where
107        Self: Sized,
108        S: serde::Serializer,
109    {
110        serde::Serialize::serialize(&self.0, s)
111    }
112}
113
114// Fingerprints must not be trimmed.
115impl ProcessValue for Fingerprint {}
116
117#[cfg(test)]
118mod tests {
119    use relay_protocol::Meta;
120    use similar_asserts::assert_eq;
121
122    use super::*;
123
124    #[test]
125    fn test_fingerprint_string() {
126        assert_eq!(
127            Annotated::new(vec!["fingerprint".to_string()].into()),
128            Annotated::<Fingerprint>::from_json("[\"fingerprint\"]").unwrap()
129        );
130    }
131
132    #[test]
133    fn test_fingerprint_bool() {
134        assert_eq!(
135            Annotated::new(vec!["True".to_string(), "False".to_string()].into()),
136            Annotated::<Fingerprint>::from_json("[true, false]").unwrap()
137        );
138    }
139
140    #[test]
141    fn test_fingerprint_number() {
142        assert_eq!(
143            Annotated::new(vec!["-22".to_string()].into()),
144            Annotated::<Fingerprint>::from_json("[-22]").unwrap()
145        );
146    }
147
148    #[test]
149    fn test_fingerprint_float() {
150        assert_eq!(
151            Annotated::new(vec!["3".to_string()].into()),
152            Annotated::<Fingerprint>::from_json("[3.0]").unwrap()
153        );
154    }
155
156    #[test]
157    fn test_fingerprint_float_trunc() {
158        assert_eq!(
159            Annotated::new(vec!["3".to_string()].into()),
160            Annotated::<Fingerprint>::from_json("[3.5]").unwrap()
161        );
162    }
163
164    #[test]
165    fn test_fingerprint_float_strip() {
166        let bad_values = vec![Annotated::new(Value::F64(-1e100))];
167
168        let mut meta = Meta::from_error(Error::with(ErrorKind::InvalidData, |e| {
169            e.insert("value", bad_values);
170        }));
171        meta.set_original_length(Some(1));
172
173        assert_eq!(
174            Annotated(None, meta),
175            Annotated::<Fingerprint>::from_json("[-1e100]").unwrap()
176        );
177    }
178
179    #[test]
180    fn test_fingerprint_float_bounds() {
181        let bad_values = vec![Annotated::new(Value::F64(
182            #[allow(clippy::excessive_precision)]
183            1.797_693_134_862_315_7e+308,
184        ))];
185
186        let mut meta = Meta::from_error(Error::with(ErrorKind::InvalidData, |e| {
187            e.insert("value", bad_values);
188        }));
189        meta.set_original_length(Some(1));
190
191        assert_eq!(
192            Annotated(None, meta),
193            Annotated::<Fingerprint>::from_json("[1.7976931348623157e+308]").unwrap()
194        );
195    }
196
197    #[test]
198    fn test_fingerprint_invalid_fallback() {
199        // XXX: review, this was changed after refactor
200        assert_eq!(
201            Annotated::new(Fingerprint(vec!["a".to_string(), "d".to_string()])),
202            Annotated::<Fingerprint>::from_json("[\"a\", null, \"d\"]").unwrap()
203        );
204    }
205
206    #[test]
207    fn test_fingerprint_empty() {
208        // XXX: review, this was changed after refactor
209        assert_eq!(
210            Annotated::new(vec![].into()),
211            Annotated::<Fingerprint>::from_json("[]").unwrap()
212        );
213    }
214}