1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//! Sentry breadcrumb implementation.

#[cfg(doc)]
use crate::Event;
use crate::{Object, RToC, Value};
use std::{
    collections::BTreeMap,
    ffi::CStr,
    ops::{Deref, DerefMut},
    ptr,
};

/// A Sentry breadcrumb.
///
/// # Examples
/// ```
/// # use sentry_contrib_native::Breadcrumb;
/// # use std::collections::BTreeMap;
/// let mut breadcrumb = Breadcrumb::new(None, Some("test message".into()));
/// breadcrumb.insert("data", vec!["some extra data", "test data"]);
/// breadcrumb.add();
/// ```
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub struct Breadcrumb {
    /// Breadcrumb type.
    pub ty: Option<String>,
    /// Breadcrumb message.
    pub message: Option<String>,
    /// Breadcrumb content.
    pub map: BTreeMap<String, Value>,
}

impl Default for Breadcrumb {
    fn default() -> Self {
        Self::new(None, None)
    }
}

impl Object for Breadcrumb {
    fn into_parts(self) -> (sys::Value, BTreeMap<String, Value>) {
        let ty = self.ty.map(RToC::into_cstring);
        let ty = ty.as_deref().map_or(ptr::null(), CStr::as_ptr);
        let message = self.message.map(RToC::into_cstring);
        let message = message.as_deref().map_or(ptr::null(), CStr::as_ptr);

        (unsafe { sys::value_new_breadcrumb(ty, message) }, self.map)
    }
}

impl Deref for Breadcrumb {
    type Target = BTreeMap<String, Value>;

    fn deref(&self) -> &Self::Target {
        &self.map
    }
}

impl DerefMut for Breadcrumb {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.map
    }
}

impl Breadcrumb {
    /// Creates a new Sentry breadcrumb.
    ///
    /// # Examples
    /// ```
    /// # use sentry_contrib_native::Breadcrumb;
    /// let mut breadcrumb = Breadcrumb::new(None, Some("test message".into()));
    /// ```
    #[must_use = "`Breadcrumb` doesn't do anything without `Breadcrumb::add`"]
    #[allow(clippy::missing_const_for_fn)]
    pub fn new(r#type: Option<String>, message: Option<String>) -> Self {
        Self {
            ty: r#type,
            message,
            map: BTreeMap::new(),
        }
    }

    /// Inserts a key-value pair into the [`Breadcrumb`].
    ///
    /// # Examples
    /// ```
    /// # use sentry_contrib_native::Breadcrumb;
    /// let mut breadcrumb = Breadcrumb::new(None, None);
    /// breadcrumb.insert("data", vec![("data", "test data")]);
    /// ```
    pub fn insert<S: Into<String>, V: Into<Value>>(&mut self, key: S, value: V) {
        self.deref_mut().insert(key.into(), value.into());
    }

    /// Adds the [`Breadcrumb`] to be sent in case of an [`Event::capture`].
    ///
    /// # Examples
    /// ```
    /// # use sentry_contrib_native::Breadcrumb;
    /// Breadcrumb::new(None, Some("test message".into())).add();
    /// ```
    pub fn add(self) {
        let breadcrumb = self.into_raw();
        unsafe { sys::add_breadcrumb(breadcrumb) }
    }
}

#[test]
fn breadcrumb() {
    let breadcrumb = Breadcrumb::new(Some("test".into()), Some("test".into()));
    assert_eq!(Some("test".into()), breadcrumb.ty);
    assert_eq!(Some("test".into()), breadcrumb.message);
    breadcrumb.add();

    let mut breadcrumb = Breadcrumb::new(None, None);
    breadcrumb.insert("test", "test");
    assert_eq!(Some("test"), breadcrumb.get("test").and_then(Value::as_str));
    breadcrumb.add();
}