relay_event_schema/protocol/
fingerprint.rs1use relay_protocol::{
2 Annotated, Empty, Error, ErrorKind, FromValue, IntoValue, SkipSerialization, Value,
3};
4
5use crate::processor::ProcessValue;
6use crate::protocol::LenientString;
7
8#[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 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
114impl 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 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 assert_eq!(
210 Annotated::new(vec![].into()),
211 Annotated::<Fingerprint>::from_json("[]").unwrap()
212 );
213 }
214}