1use std::fmt;
2
3use relay_protocol::{
4 Annotated, Empty, Error, ErrorKind, FromValue, IntoValue, Object, SkipSerialization, Value,
5};
6use serde::{Deserialize, Serialize, Serializer};
7
8use crate::processor::ProcessValue;
9use crate::protocol::{RawStacktrace, Stacktrace};
10
11#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
13#[serde(untagged)]
14pub enum ThreadId {
15 Int(u64),
17 String(String),
19}
20
21impl FromValue for ThreadId {
22 fn from_value(value: Annotated<Value>) -> Annotated<Self> {
23 match value {
24 Annotated(Some(Value::String(value)), meta) => {
25 Annotated(Some(ThreadId::String(value)), meta)
26 }
27 Annotated(Some(Value::U64(value)), meta) => Annotated(Some(ThreadId::Int(value)), meta),
28 Annotated(Some(Value::I64(value)), meta) => {
29 Annotated(Some(ThreadId::Int(value as u64)), meta)
30 }
31 Annotated(None, meta) => Annotated(None, meta),
32 Annotated(Some(value), mut meta) => {
33 meta.add_error(Error::expected("a thread id"));
34 meta.set_original_value(Some(value));
35 Annotated(None, meta)
36 }
37 }
38 }
39}
40
41impl IntoValue for ThreadId {
42 fn into_value(self) -> Value {
43 match self {
44 ThreadId::String(value) => Value::String(value),
45 ThreadId::Int(value) => Value::U64(value),
46 }
47 }
48
49 fn serialize_payload<S>(&self, s: S, _behavior: SkipSerialization) -> Result<S::Ok, S::Error>
50 where
51 Self: Sized,
52 S: Serializer,
53 {
54 match *self {
55 ThreadId::String(ref value) => Serialize::serialize(value, s),
56 ThreadId::Int(value) => Serialize::serialize(&value, s),
57 }
58 }
59}
60
61impl ProcessValue for ThreadId {}
62
63impl Empty for ThreadId {
64 #[inline]
65 fn is_empty(&self) -> bool {
66 match self {
67 ThreadId::Int(_) => false,
68 ThreadId::String(string) => string.is_empty(),
69 }
70 }
71}
72
73impl fmt::Display for ThreadId {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 match self {
76 ThreadId::Int(id) => write!(f, "{}", id),
77 ThreadId::String(id) => write!(f, "{}", id),
78 }
79 }
80}
81
82#[derive(Debug, Copy, Clone, Eq, PartialEq, ProcessValue, Empty)]
84pub enum LockReasonType {
85 Locked = 1,
87 Waiting = 2,
89 Sleeping = 4,
91 Blocked = 8,
93 }
96
97impl LockReasonType {
98 fn from_android_lock_reason_type(value: u64) -> Option<LockReasonType> {
99 Some(match value {
100 1 => LockReasonType::Locked,
101 2 => LockReasonType::Waiting,
102 4 => LockReasonType::Sleeping,
103 8 => LockReasonType::Blocked,
104 _ => return None,
105 })
106 }
107}
108
109impl FromValue for LockReasonType {
110 fn from_value(value: Annotated<Value>) -> Annotated<Self> {
111 match value {
112 Annotated(Some(Value::U64(val)), mut meta) => {
113 match LockReasonType::from_android_lock_reason_type(val) {
114 Some(value) => Annotated(Some(value), meta),
115 None => {
116 meta.add_error(ErrorKind::InvalidData);
117 meta.set_original_value(Some(val));
118 Annotated(None, meta)
119 }
120 }
121 }
122 Annotated(Some(Value::I64(val)), mut meta) => {
123 match LockReasonType::from_android_lock_reason_type(val as u64) {
124 Some(value) => Annotated(Some(value), meta),
125 None => {
126 meta.add_error(ErrorKind::InvalidData);
127 meta.set_original_value(Some(val));
128 Annotated(None, meta)
129 }
130 }
131 }
132 Annotated(None, meta) => Annotated(None, meta),
133 Annotated(Some(value), mut meta) => {
134 meta.add_error(Error::expected("lock reason type"));
135 meta.set_original_value(Some(value));
136 Annotated(None, meta)
137 }
138 }
139 }
140}
141
142impl IntoValue for LockReasonType {
143 fn into_value(self) -> Value {
144 Value::U64(self as u64)
145 }
146
147 fn serialize_payload<S>(&self, s: S, _behavior: SkipSerialization) -> Result<S::Ok, S::Error>
148 where
149 Self: Sized,
150 S: Serializer,
151 {
152 Serialize::serialize(&(*self as u64), s)
153 }
154}
155
156#[derive(Clone, Debug, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
158pub struct LockReason {
159 #[metastructure(field = "type", required = true)]
161 pub ty: Annotated<LockReasonType>,
162
163 #[metastructure(skip_serialization = "empty")]
165 pub address: Annotated<String>,
166
167 #[metastructure(skip_serialization = "empty")]
169 pub package_name: Annotated<String>,
170
171 #[metastructure(skip_serialization = "empty")]
173 pub class_name: Annotated<String>,
174
175 #[metastructure(skip_serialization = "empty")]
177 pub thread_id: Annotated<ThreadId>,
178
179 #[metastructure(additional_properties)]
181 pub other: Object<Value>,
182}
183
184#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
207#[metastructure(process_func = "process_thread", value_type = "Thread")]
208pub struct Thread {
209 #[metastructure(max_chars = 256, max_chars_allowance = 20)]
213 pub id: Annotated<ThreadId>,
214
215 #[metastructure(max_chars = 1024, max_chars_allowance = 100)]
217 pub name: Annotated<String>,
218
219 #[metastructure(skip_serialization = "empty")]
223 pub stacktrace: Annotated<Stacktrace>,
224
225 #[metastructure(skip_serialization = "empty", omit_from_schema)]
227 pub raw_stacktrace: Annotated<RawStacktrace>,
228
229 pub crashed: Annotated<bool>,
231
232 pub current: Annotated<bool>,
234
235 pub main: Annotated<bool>,
237
238 #[metastructure(skip_serialization = "empty")]
240 pub state: Annotated<String>,
241
242 pub held_locks: Annotated<Object<LockReason>>,
246
247 #[metastructure(additional_properties)]
249 pub other: Object<Value>,
250}
251
252#[cfg(test)]
253mod tests {
254 use relay_protocol::Map;
255 use similar_asserts::assert_eq;
256
257 use super::*;
258
259 #[test]
260 fn test_thread_id() {
261 assert_eq!(
262 ThreadId::String("testing".into()),
263 Annotated::<ThreadId>::from_json("\"testing\"")
264 .unwrap()
265 .0
266 .unwrap()
267 );
268 assert_eq!(
269 ThreadId::String("42".into()),
270 Annotated::<ThreadId>::from_json("\"42\"")
271 .unwrap()
272 .0
273 .unwrap()
274 );
275 assert_eq!(
276 ThreadId::Int(42),
277 Annotated::<ThreadId>::from_json("42").unwrap().0.unwrap()
278 );
279 }
280
281 #[test]
282 fn test_thread_roundtrip() {
283 let json = r#"{
285 "id": 42,
286 "name": "myname",
287 "crashed": true,
288 "current": true,
289 "main": true,
290 "state": "RUNNABLE",
291 "other": "value"
292}"#;
293 let thread = Annotated::new(Thread {
294 id: Annotated::new(ThreadId::Int(42)),
295 name: Annotated::new("myname".to_string()),
296 stacktrace: Annotated::empty(),
297 raw_stacktrace: Annotated::empty(),
298 crashed: Annotated::new(true),
299 current: Annotated::new(true),
300 main: Annotated::new(true),
301 state: Annotated::new("RUNNABLE".to_string()),
302 held_locks: Annotated::empty(),
303 other: {
304 let mut map = Map::new();
305 map.insert(
306 "other".to_string(),
307 Annotated::new(Value::String("value".to_string())),
308 );
309 map
310 },
311 });
312
313 assert_eq!(thread, Annotated::from_json(json).unwrap());
314 assert_eq!(json, thread.to_json_pretty().unwrap());
315 }
316
317 #[test]
318 fn test_thread_default_values() {
319 let json = "{}";
320 let thread = Annotated::new(Thread::default());
321
322 assert_eq!(thread, Annotated::from_json(json).unwrap());
323 assert_eq!(json, thread.to_json_pretty().unwrap());
324 }
325
326 #[test]
327 fn test_thread_lock_reason_roundtrip() {
328 let input = r#"{
330 "id": 42,
331 "name": "myname",
332 "crashed": true,
333 "current": true,
334 "main": true,
335 "state": "BLOCKED",
336 "held_locks": {
337 "0x07d7437b": {
338 "type": 2,
339 "package_name": "io.sentry.samples",
340 "class_name": "MainActivity",
341 "thread_id": 7
342 },
343 "0x0d3a2f0a": {
344 "type": 1,
345 "package_name": "android.database.sqlite",
346 "class_name": "SQLiteConnection",
347 "thread_id": 2
348 }
349 },
350 "other": "value"
351}"#;
352 let thread = Annotated::new(Thread {
353 id: Annotated::new(ThreadId::Int(42)),
354 name: Annotated::new("myname".to_string()),
355 stacktrace: Annotated::empty(),
356 raw_stacktrace: Annotated::empty(),
357 crashed: Annotated::new(true),
358 current: Annotated::new(true),
359 main: Annotated::new(true),
360 state: Annotated::new("BLOCKED".to_string()),
361 held_locks: {
362 let mut locks = Object::new();
363 locks.insert(
364 "0x07d7437b".to_string(),
365 Annotated::new(LockReason {
366 ty: Annotated::new(LockReasonType::Waiting),
367 address: Annotated::empty(),
368 package_name: Annotated::new("io.sentry.samples".to_string()),
369 class_name: Annotated::new("MainActivity".to_string()),
370 thread_id: Annotated::new(ThreadId::Int(7)),
371 other: Default::default(),
372 }),
373 );
374 locks.insert(
375 "0x0d3a2f0a".to_string(),
376 Annotated::new(LockReason {
377 ty: Annotated::new(LockReasonType::Locked),
378 address: Annotated::empty(),
379 package_name: Annotated::new("android.database.sqlite".to_string()),
380 class_name: Annotated::new("SQLiteConnection".to_string()),
381 thread_id: Annotated::new(ThreadId::Int(2)),
382 other: Default::default(),
383 }),
384 );
385 Annotated::new(locks)
386 },
387 other: {
388 let mut map = Map::new();
389 map.insert(
390 "other".to_string(),
391 Annotated::new(Value::String("value".to_string())),
392 );
393 map
394 },
395 });
396
397 assert_eq!(thread, Annotated::from_json(input).unwrap());
398
399 assert_eq!(input, thread.to_json_pretty().unwrap());
400 }
401}