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(skip_serialization = "empty")]
69 pub geo: Annotated<Geo>,
70
71 #[metastructure(skip_serialization = "empty")]
73 pub segment: Annotated<String>,
74
75 #[metastructure(pii = "true", skip_serialization = "empty")]
78 pub data: Annotated<Object<Value>>,
79
80 #[metastructure(additional_properties, pii = "true")]
82 pub other: Object<Value>,
83}
84
85#[cfg(test)]
86mod tests {
87 use similar_asserts::assert_eq;
88
89 use super::*;
90 use relay_protocol::{Error, Map};
91
92 #[test]
93 fn test_geo_roundtrip() {
94 let json = r#"{
95 "country_code": "US",
96 "city": "San Francisco",
97 "subdivision": "California",
98 "region": "CA",
99 "other": "value"
100}"#;
101 let geo = Annotated::new(Geo {
102 country_code: Annotated::new("US".to_owned()),
103 city: Annotated::new("San Francisco".to_owned()),
104 subdivision: Annotated::new("California".to_owned()),
105 region: Annotated::new("CA".to_owned()),
106 other: {
107 let mut map = Map::new();
108 map.insert(
109 "other".to_owned(),
110 Annotated::new(Value::String("value".to_owned())),
111 );
112 map
113 },
114 });
115
116 assert_eq!(geo, Annotated::from_json(json).unwrap());
117 assert_eq!(json, geo.to_json_pretty().unwrap());
118 }
119
120 #[test]
121 fn test_geo_default_values() {
122 let json = "{}";
123 let geo = Annotated::new(Geo {
124 country_code: Annotated::empty(),
125 city: Annotated::empty(),
126 subdivision: Annotated::empty(),
127 region: Annotated::empty(),
128 other: Object::default(),
129 });
130
131 assert_eq!(geo, Annotated::from_json(json).unwrap());
132 assert_eq!(json, geo.to_json_pretty().unwrap());
133 }
134
135 #[test]
136 fn test_user_roundtrip() {
137 let json = r#"{
138 "id": "e4e24881-8238-4539-a32b-d3c3ecd40568",
139 "email": "mail@example.org",
140 "ip_address": "{{auto}}",
141 "username": "john_doe",
142 "name": "John Doe",
143 "segment": "vip",
144 "data": {
145 "data": "value"
146 },
147 "other": "value"
148}"#;
149 let user = Annotated::new(User {
150 id: Annotated::new("e4e24881-8238-4539-a32b-d3c3ecd40568".to_owned().into()),
151 email: Annotated::new("mail@example.org".to_owned()),
152 ip_address: Annotated::new(IpAddr::auto()),
153 name: Annotated::new("John Doe".to_owned()),
154 username: Annotated::new(LenientString("john_doe".to_owned())),
155 geo: Annotated::empty(),
156 segment: Annotated::new("vip".to_owned()),
157 data: {
158 let mut map = Object::new();
159 map.insert(
160 "data".to_owned(),
161 Annotated::new(Value::String("value".to_owned())),
162 );
163 Annotated::new(map)
164 },
165 other: {
166 let mut map = Object::new();
167 map.insert(
168 "other".to_owned(),
169 Annotated::new(Value::String("value".to_owned())),
170 );
171 map
172 },
173 });
174
175 assert_eq!(user, Annotated::from_json(json).unwrap());
176 assert_eq!(json, user.to_json_pretty().unwrap());
177 }
178
179 #[test]
180 fn test_user_lenient_id() {
181 let input = r#"{"id":42}"#;
182 let output = r#"{"id":"42"}"#;
183 let user = Annotated::new(User {
184 id: Annotated::new("42".to_owned().into()),
185 ..User::default()
186 });
187
188 assert_eq!(user, Annotated::from_json(input).unwrap());
189 assert_eq!(output, user.to_json().unwrap());
190 }
191
192 #[test]
193 fn test_user_lenient_username() {
194 let input = r#"{"username":42}"#;
195 let output = r#"{"username":"42"}"#;
196 let user = Annotated::new(User {
197 username: Annotated::new("42".to_owned().into()),
198 ..User::default()
199 });
200
201 assert_eq!(user, Annotated::from_json(input).unwrap());
202 assert_eq!(output, user.to_json().unwrap());
203 }
204
205 #[test]
206 fn test_user_invalid_id() {
207 let json = r#"{"id":[]}"#;
208 let user = Annotated::new(User {
209 id: Annotated::from_error(
210 Error::expected("a primitive value"),
211 Some(Value::Array(vec![])),
212 ),
213 ..User::default()
214 });
215
216 assert_eq!(user, Annotated::from_json(json).unwrap());
217 }
218
219 #[test]
220 fn test_explicit_none() {
221 let json = r#"{
222 "id": null
223}"#;
224
225 let user = Annotated::new(User::default());
226
227 assert_eq!(user, Annotated::from_json(json).unwrap());
228 assert_eq!("{}", user.to_json_pretty().unwrap());
229 }
230}