1use relay_protocol::{Annotated, Empty, Error, FromValue, IntoValue, Object, Value};
2
3use crate::processor::ProcessValue;
4
5#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
11pub struct CError {
12 pub number: Annotated<i64>,
14
15 pub name: Annotated<String>,
17}
18
19#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
21pub struct MachException {
22 #[metastructure(field = "exception")]
24 pub ty: Annotated<i64>,
25
26 pub code: Annotated<u64>,
28
29 pub subcode: Annotated<u64>,
31
32 pub name: Annotated<String>,
34}
35
36#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
38pub struct NsError {
39 pub code: Annotated<i64>,
41
42 pub domain: Annotated<String>,
44}
45
46#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
51pub struct PosixSignal {
52 pub number: Annotated<i64>,
54
55 pub code: Annotated<i64>,
57
58 pub name: Annotated<String>,
60
61 pub code_name: Annotated<String>,
63}
64
65#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
73pub struct MechanismMeta {
74 pub errno: Annotated<CError>,
76
77 pub signal: Annotated<PosixSignal>,
79
80 pub mach_exception: Annotated<MachException>,
82
83 pub ns_error: Annotated<NsError>,
85
86 #[metastructure(additional_properties)]
88 pub other: Object<Value>,
89}
90
91#[derive(Clone, Debug, Default, PartialEq, Empty, IntoValue, ProcessValue)]
98pub struct Mechanism {
99 #[metastructure(field = "type", required = true, nonempty = true, max_chars = 128)]
107 pub ty: Annotated<String>,
108
109 pub synthetic: Annotated<bool>,
114
115 #[metastructure(pii = "true", max_chars = 8192, max_chars_allowance = 200)]
119 pub description: Annotated<String>,
120
121 #[metastructure(
123 required = false,
124 nonempty = true,
125 max_chars = 256,
126 max_chars_allowance = 40
127 )]
128 pub help_link: Annotated<String>,
129
130 pub handled: Annotated<bool>,
142
143 #[metastructure(
155 required = false,
156 nonempty = true,
157 max_chars = 128,
158 deny_chars = " \t\r\n"
159 )]
160 pub source: Annotated<String>,
161
162 pub is_exception_group: Annotated<bool>,
169
170 pub exception_id: Annotated<u64>,
177
178 pub parent_id: Annotated<u64>,
184
185 #[metastructure(pii = "true", max_depth = 5, max_bytes = 2048)]
187 #[metastructure(skip_serialization = "empty")]
188 pub data: Annotated<Object<Value>>,
189
190 #[metastructure(skip_serialization = "empty")]
192 pub meta: Annotated<MechanismMeta>,
193
194 #[metastructure(additional_properties)]
196 pub other: Object<Value>,
197}
198
199impl FromValue for Mechanism {
200 fn from_value(annotated: Annotated<Value>) -> Annotated<Self> {
201 #[derive(Debug, FromValue)]
202 struct NewMechanism {
203 #[metastructure(field = "type", required = true)]
204 pub ty: Annotated<String>,
205 pub synthetic: Annotated<bool>,
206 pub description: Annotated<String>,
207 pub help_link: Annotated<String>,
208 pub handled: Annotated<bool>,
209 pub source: Annotated<String>,
210 pub is_exception_group: Annotated<bool>,
211 pub exception_id: Annotated<u64>,
212 pub parent_id: Annotated<u64>,
213 pub data: Annotated<Object<Value>>,
214 pub meta: Annotated<MechanismMeta>,
215 #[metastructure(additional_properties)]
216 pub other: Object<Value>,
217 }
218
219 #[derive(Debug, FromValue)]
220 struct LegacyPosixSignal {
221 pub signal: Annotated<i64>,
222 pub code: Annotated<i64>,
223 pub name: Annotated<String>,
224 pub code_name: Annotated<String>,
225 }
226
227 #[derive(Debug, FromValue)]
228 struct LegacyMachException {
229 pub exception: Annotated<i64>,
230 pub code: Annotated<u64>,
231 pub subcode: Annotated<u64>,
232 pub exception_name: Annotated<String>,
233 }
234
235 #[derive(Debug, FromValue)]
236 struct LegacyMechanism {
237 posix_signal: Annotated<LegacyPosixSignal>,
238 mach_exception: Annotated<LegacyMachException>,
239 #[metastructure(additional_properties)]
240 pub other: Object<Value>,
241 }
242
243 match annotated {
244 Annotated(Some(Value::Object(object)), meta) => {
245 if object.is_empty() {
246 Annotated(None, meta)
247 } else if object.contains_key("type") {
248 let annotated = Annotated(Some(Value::Object(object)), meta);
249 NewMechanism::from_value(annotated).map_value(|mechanism| Mechanism {
250 ty: mechanism.ty,
251 synthetic: mechanism.synthetic,
252 description: mechanism.description,
253 help_link: mechanism.help_link,
254 handled: mechanism.handled,
255 source: mechanism.source,
256 is_exception_group: mechanism.is_exception_group,
257 exception_id: mechanism.exception_id,
258 parent_id: mechanism.parent_id,
259 data: mechanism.data,
260 meta: mechanism.meta,
261 other: mechanism.other,
262 })
263 } else {
264 let annotated = Annotated(Some(Value::Object(object)), meta);
265 LegacyMechanism::from_value(annotated).map_value(|legacy| Mechanism {
266 ty: Annotated::new("generic".to_string()),
267 synthetic: Annotated::empty(),
268 description: Annotated::empty(),
269 help_link: Annotated::empty(),
270 handled: Annotated::empty(),
271 source: Annotated::empty(),
272 is_exception_group: Annotated::empty(),
273 exception_id: Annotated::empty(),
274 parent_id: Annotated::empty(),
275 data: Annotated::new(legacy.other),
276 meta: Annotated::new(MechanismMeta {
277 errno: Annotated::empty(),
278 signal: legacy.posix_signal.map_value(|legacy| PosixSignal {
279 number: legacy.signal,
280 code: legacy.code,
281 name: legacy.name,
282 code_name: legacy.code_name,
283 }),
284 mach_exception: legacy.mach_exception.map_value(|legacy| {
285 MachException {
286 ty: legacy.exception,
287 code: legacy.code,
288 subcode: legacy.subcode,
289 name: legacy.exception_name,
290 }
291 }),
292 ns_error: Annotated::empty(),
293 other: Object::default(),
294 }),
295 other: Object::default(),
296 })
297 }
298 }
299 Annotated(Some(value), mut meta) => {
300 meta.add_error(Error::expected("exception mechanism"));
301 meta.set_original_value(Some(value));
302 Annotated(None, meta)
303 }
304 Annotated(None, meta) => Annotated(None, meta),
305 }
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use relay_protocol::Map;
312 use similar_asserts::assert_eq;
313
314 use super::*;
315
316 #[test]
317 fn test_mechanism_roundtrip() {
318 let json = r#"{
319 "type": "mytype",
320 "description": "mydescription",
321 "help_link": "https://developer.apple.com/library/content/qa/qa1367/_index.html",
322 "handled": false,
323 "source": "errors[0]",
324 "is_exception_group": false,
325 "exception_id": 1,
326 "parent_id": 0,
327 "data": {
328 "relevant_address": "0x1"
329 },
330 "meta": {
331 "errno": {
332 "number": 2,
333 "name": "ENOENT"
334 },
335 "signal": {
336 "number": 11,
337 "code": 0,
338 "name": "SIGSEGV",
339 "code_name": "SEGV_NOOP"
340 },
341 "mach_exception": {
342 "exception": 1,
343 "code": 1,
344 "subcode": 8,
345 "name": "EXC_BAD_ACCESS"
346 },
347 "ns_error": {
348 "code": -42,
349 "domain": "SqlException"
350 },
351 "other": "value"
352 },
353 "other": "value"
354}"#;
355 let mechanism = Annotated::new(Mechanism {
356 ty: Annotated::new("mytype".to_string()),
357 synthetic: Annotated::empty(),
358 description: Annotated::new("mydescription".to_string()),
359 help_link: Annotated::new(
360 "https://developer.apple.com/library/content/qa/qa1367/_index.html".to_string(),
361 ),
362 handled: Annotated::new(false),
363 source: Annotated::new("errors[0]".to_string()),
364 is_exception_group: Annotated::new(false),
365 exception_id: Annotated::new(1),
366 parent_id: Annotated::new(0),
367 data: {
368 let mut map = Map::new();
369 map.insert(
370 "relevant_address".to_string(),
371 Annotated::new(Value::String("0x1".to_string())),
372 );
373 Annotated::new(map)
374 },
375 meta: Annotated::new(MechanismMeta {
376 errno: Annotated::new(CError {
377 number: Annotated::new(2),
378 name: Annotated::new("ENOENT".to_string()),
379 }),
380 mach_exception: Annotated::new(MachException {
381 ty: Annotated::new(1),
382 code: Annotated::new(1),
383 subcode: Annotated::new(8),
384 name: Annotated::new("EXC_BAD_ACCESS".to_string()),
385 }),
386 signal: Annotated::new(PosixSignal {
387 number: Annotated::new(11),
388 code: Annotated::new(0),
389 name: Annotated::new("SIGSEGV".to_string()),
390 code_name: Annotated::new("SEGV_NOOP".to_string()),
391 }),
392 ns_error: Annotated::new(NsError {
393 code: Annotated::new(-42),
394 domain: Annotated::new("SqlException".to_string()),
395 }),
396 other: {
397 let mut map = Object::new();
398 map.insert(
399 "other".to_string(),
400 Annotated::new(Value::String("value".to_string())),
401 );
402 map
403 },
404 }),
405 other: {
406 let mut map = Object::new();
407 map.insert(
408 "other".to_string(),
409 Annotated::new(Value::String("value".to_string())),
410 );
411 map
412 },
413 });
414
415 assert_eq!(mechanism, Annotated::from_json(json).unwrap());
416 assert_eq!(json, mechanism.to_json_pretty().unwrap());
417 }
418
419 #[test]
420 fn test_mechanism_default_values() {
421 let json = r#"{"type":"mytype"}"#;
422 let mechanism = Annotated::new(Mechanism {
423 ty: Annotated::new("mytype".to_string()),
424 ..Default::default()
425 });
426
427 assert_eq!(mechanism, Annotated::from_json(json).unwrap());
428 assert_eq!(json, mechanism.to_json().unwrap());
429 }
430
431 #[test]
432 fn test_mechanism_empty() {
433 let mechanism = Annotated::<Mechanism>::empty();
434 assert_eq!(mechanism, Annotated::from_json("{}").unwrap());
435 }
436
437 #[test]
438 fn test_mechanism_legacy_conversion() {
439 let input = r#"{
440 "posix_signal": {
441 "name": "SIGSEGV",
442 "code_name": "SEGV_NOOP",
443 "signal": 11,
444 "code": 0
445 },
446 "relevant_address": "0x1",
447 "mach_exception": {
448 "exception": 1,
449 "exception_name": "EXC_BAD_ACCESS",
450 "subcode": 8,
451 "code": 1
452 }
453}"#;
454
455 let output = r#"{
456 "type": "generic",
457 "data": {
458 "relevant_address": "0x1"
459 },
460 "meta": {
461 "signal": {
462 "number": 11,
463 "code": 0,
464 "name": "SIGSEGV",
465 "code_name": "SEGV_NOOP"
466 },
467 "mach_exception": {
468 "exception": 1,
469 "code": 1,
470 "subcode": 8,
471 "name": "EXC_BAD_ACCESS"
472 }
473 }
474}"#;
475 let mechanism = Annotated::new(Mechanism {
476 ty: Annotated::new("generic".to_string()),
477 synthetic: Annotated::empty(),
478 description: Annotated::empty(),
479 help_link: Annotated::empty(),
480 handled: Annotated::empty(),
481 source: Annotated::empty(),
482 is_exception_group: Annotated::empty(),
483 exception_id: Annotated::empty(),
484 parent_id: Annotated::empty(),
485 data: {
486 let mut map = Map::new();
487 map.insert(
488 "relevant_address".to_string(),
489 Annotated::new(Value::String("0x1".to_string())),
490 );
491 Annotated::new(map)
492 },
493 meta: Annotated::new(MechanismMeta {
494 errno: Annotated::empty(),
495 mach_exception: Annotated::new(MachException {
496 ty: Annotated::new(1),
497 code: Annotated::new(1),
498 subcode: Annotated::new(8),
499 name: Annotated::new("EXC_BAD_ACCESS".to_string()),
500 }),
501 signal: Annotated::new(PosixSignal {
502 number: Annotated::new(11),
503 code: Annotated::new(0),
504 name: Annotated::new("SIGSEGV".to_string()),
505 code_name: Annotated::new("SEGV_NOOP".to_string()),
506 }),
507 ns_error: Annotated::empty(),
508 other: Object::default(),
509 }),
510 other: Object::default(),
511 });
512
513 assert_eq!(mechanism, Annotated::from_json(input).unwrap());
514 assert_eq!(output, mechanism.to_json_pretty().unwrap());
515 }
516}