relay_event_schema/protocol/
user.rs1use relay_protocol::{Annotated, Empty, FromValue, IntoValue, Object, Value};
2
3use crate::processor::ProcessValue;
4use crate::protocol::{IpAddr, LenientString};
5
6#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
8#[metastructure(process_func = "process_geo")]
9pub struct Geo {
10 #[metastructure(pii = "true", max_chars = 102, max_chars_allowance = 1004)]
12 pub country_code: Annotated<String>,
13
14 #[metastructure(pii = "true", max_chars = 1024, max_chars_allowance = 100)]
16 pub city: Annotated<String>,
17
18 #[metastructure(pii = "true", max_chars = 1024, max_chars_allowance = 100)]
20 pub subdivision: Annotated<String>,
21
22 #[metastructure(pii = "true", max_chars = 1024, max_chars_allowance = 100)]
24 pub region: Annotated<String>,
25
26 #[metastructure(additional_properties)]
28 pub other: Object<Value>,
29}
30
31#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
45#[metastructure(process_func = "process_user", value_type = "User")]
46pub struct User {
47 #[metastructure(pii = "true", max_chars = 128, skip_serialization = "empty")]
49 pub id: Annotated<LenientString>,
50
51 #[metastructure(pii = "true", max_chars = 75, skip_serialization = "empty")]
53 pub email: Annotated<String>,
54
55 #[metastructure(pii = "true", skip_serialization = "empty")]
57 pub ip_address: Annotated<IpAddr>,
58
59 #[metastructure(pii = "true", max_chars = 128, skip_serialization = "empty")]
61 pub username: Annotated<LenientString>,
62
63 #[metastructure(pii = "true", max_chars = 128, skip_serialization = "empty")]
65 pub name: Annotated<String>,
66
67 #[metastructure(pii = "true", skip_serialization = "empty")]
73 pub sentry_user: Annotated<String>,
74
75 #[metastructure(skip_serialization = "empty")]
77 pub geo: Annotated<Geo>,
78
79 #[metastructure(skip_serialization = "empty")]
81 pub segment: Annotated<String>,
82
83 #[metastructure(pii = "true", skip_serialization = "empty")]
86 pub data: Annotated<Object<Value>>,
87
88 #[metastructure(additional_properties, pii = "true")]
90 pub other: Object<Value>,
91}
92
93#[cfg(test)]
94mod tests {
95 use similar_asserts::assert_eq;
96
97 use super::*;
98 use relay_protocol::{Error, Map};
99
100 #[test]
101 fn test_geo_roundtrip() {
102 let json = r#"{
103 "country_code": "US",
104 "city": "San Francisco",
105 "subdivision": "California",
106 "region": "CA",
107 "other": "value"
108}"#;
109 let geo = Annotated::new(Geo {
110 country_code: Annotated::new("US".to_string()),
111 city: Annotated::new("San Francisco".to_string()),
112 subdivision: Annotated::new("California".to_string()),
113 region: Annotated::new("CA".to_string()),
114 other: {
115 let mut map = Map::new();
116 map.insert(
117 "other".to_string(),
118 Annotated::new(Value::String("value".to_string())),
119 );
120 map
121 },
122 });
123
124 assert_eq!(geo, Annotated::from_json(json).unwrap());
125 assert_eq!(json, geo.to_json_pretty().unwrap());
126 }
127
128 #[test]
129 fn test_geo_default_values() {
130 let json = "{}";
131 let geo = Annotated::new(Geo {
132 country_code: Annotated::empty(),
133 city: Annotated::empty(),
134 subdivision: Annotated::empty(),
135 region: Annotated::empty(),
136 other: Object::default(),
137 });
138
139 assert_eq!(geo, Annotated::from_json(json).unwrap());
140 assert_eq!(json, geo.to_json_pretty().unwrap());
141 }
142
143 #[test]
144 fn test_user_roundtrip() {
145 let json = r#"{
146 "id": "e4e24881-8238-4539-a32b-d3c3ecd40568",
147 "email": "mail@example.org",
148 "ip_address": "{{auto}}",
149 "username": "john_doe",
150 "name": "John Doe",
151 "segment": "vip",
152 "data": {
153 "data": "value"
154 },
155 "other": "value"
156}"#;
157 let user = Annotated::new(User {
158 id: Annotated::new("e4e24881-8238-4539-a32b-d3c3ecd40568".to_string().into()),
159 email: Annotated::new("mail@example.org".to_string()),
160 ip_address: Annotated::new(IpAddr::auto()),
161 name: Annotated::new("John Doe".to_string()),
162 username: Annotated::new(LenientString("john_doe".to_owned())),
163 geo: Annotated::empty(),
164 segment: Annotated::new("vip".to_string()),
165 data: {
166 let mut map = Object::new();
167 map.insert(
168 "data".to_string(),
169 Annotated::new(Value::String("value".to_string())),
170 );
171 Annotated::new(map)
172 },
173 other: {
174 let mut map = Object::new();
175 map.insert(
176 "other".to_string(),
177 Annotated::new(Value::String("value".to_string())),
178 );
179 map
180 },
181 ..Default::default()
182 });
183
184 assert_eq!(user, Annotated::from_json(json).unwrap());
185 assert_eq!(json, user.to_json_pretty().unwrap());
186 }
187
188 #[test]
189 fn test_user_lenient_id() {
190 let input = r#"{"id":42}"#;
191 let output = r#"{"id":"42"}"#;
192 let user = Annotated::new(User {
193 id: Annotated::new("42".to_string().into()),
194 ..User::default()
195 });
196
197 assert_eq!(user, Annotated::from_json(input).unwrap());
198 assert_eq!(output, user.to_json().unwrap());
199 }
200
201 #[test]
202 fn test_user_lenient_username() {
203 let input = r#"{"username":42}"#;
204 let output = r#"{"username":"42"}"#;
205 let user = Annotated::new(User {
206 username: Annotated::new("42".to_string().into()),
207 ..User::default()
208 });
209
210 assert_eq!(user, Annotated::from_json(input).unwrap());
211 assert_eq!(output, user.to_json().unwrap());
212 }
213
214 #[test]
215 fn test_user_invalid_id() {
216 let json = r#"{"id":[]}"#;
217 let user = Annotated::new(User {
218 id: Annotated::from_error(
219 Error::expected("a primitive value"),
220 Some(Value::Array(vec![])),
221 ),
222 ..User::default()
223 });
224
225 assert_eq!(user, Annotated::from_json(json).unwrap());
226 }
227
228 #[test]
229 fn test_explicit_none() {
230 let json = r#"{
231 "id": null
232}"#;
233
234 let user = Annotated::new(User::default());
235
236 assert_eq!(user, Annotated::from_json(json).unwrap());
237 assert_eq!("{}", user.to_json_pretty().unwrap());
238 }
239}