Skip to main content

glean/private/
object.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use std::marker::PhantomData;
6
7use malloc_size_of::MallocSizeOf;
8
9use glean_core::metrics::{JsonValue, MetricIdentifier};
10use glean_core::{traits, TestGetValue};
11
12use crate::ErrorType;
13
14// We need to wrap the glean-core type: otherwise if we try to implement
15// the trait for the metric in `glean_core::metrics` we hit error[E0117]:
16// only traits defined in the current crate can be implemented for arbitrary
17// types.
18
19/// Developer-facing API for recording object metrics.
20///
21/// Instances of this class type are automatically generated by the parsers
22/// at build time, allowing developers to record values that were previously
23/// registered in the metrics.yaml file.
24#[derive(Clone)]
25pub struct ObjectMetric<K> {
26    pub(crate) inner: glean_core::metrics::ObjectMetric,
27    object_type: PhantomData<K>,
28}
29
30impl<K> MallocSizeOf for ObjectMetric<K> {
31    fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
32        self.inner.size_of(ops)
33    }
34}
35
36impl<'a, K> MetricIdentifier<'a> for ObjectMetric<K> {
37    fn get_identifiers(&'a self) -> (&'a str, &'a str, Option<&'a str>) {
38        self.inner.get_identifiers()
39    }
40}
41
42impl<K> TestGetValue for ObjectMetric<K> {
43    type Output = JsonValue;
44
45    /// **Test-only API (exported for FFI purposes).**
46    ///
47    /// Gets the currently stored value as JSON-encoded string.
48    ///
49    /// This doesn't clear the stored value.
50    fn test_get_value(&self, ping_name: Option<String>) -> Option<JsonValue> {
51        self.inner.test_get_value(ping_name)
52    }
53}
54
55impl<K: traits::ObjectSerialize> ObjectMetric<K> {
56    /// The public constructor used by automatically generated metrics.
57    pub fn new(meta: glean_core::CommonMetricData) -> Self {
58        let inner = glean_core::metrics::ObjectMetric::new(meta);
59        Self {
60            inner,
61            object_type: PhantomData,
62        }
63    }
64
65    /// Sets to the specified structure.
66    ///
67    /// # Arguments
68    ///
69    /// * `object` - the object to set.
70    pub fn set(&self, object: K) {
71        let obj = object
72            .into_serialized_object()
73            .expect("failed to serialize object. This should be impossible.");
74        self.inner.set(obj);
75    }
76
77    /// Sets to the specified structure.
78    ///
79    /// Parses the passed JSON string.
80    /// If it can't be parsed into a valid object it records an invalid value error.
81    ///
82    /// # Arguments
83    ///
84    /// * `object` - JSON representation of the object to set.
85    pub fn set_string(&self, object: String) {
86        let data = match K::from_str(&object) {
87            Ok(data) => data,
88            Err(_) => {
89                self.inner.record_schema_error();
90                return;
91            }
92        };
93        self.set(data)
94    }
95
96    /// **Exported for test purposes.**
97    ///
98    /// Gets the number of recorded errors for the given metric and error type.
99    ///
100    /// # Arguments
101    ///
102    /// * `error` - The type of error
103    ///
104    /// # Returns
105    ///
106    /// The number of errors reported.
107    pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
108        self.inner.test_get_num_recorded_errors(error)
109    }
110}
111
112#[cfg(test)]
113mod test {
114    use super::*;
115    use crate::common_test::{lock_test, new_glean};
116    use crate::CommonMetricData;
117
118    use serde_json::json;
119
120    #[test]
121    fn simple_array() {
122        let _lock = lock_test();
123        let _t = new_glean(None, true);
124
125        type SimpleArray = Vec<i64>;
126
127        let metric: ObjectMetric<SimpleArray> = ObjectMetric::new(CommonMetricData {
128            name: "object".into(),
129            category: "test".into(),
130            send_in_pings: vec!["store1".into()],
131            ..Default::default()
132        });
133
134        let arr = SimpleArray::from([1, 2, 3]);
135        metric.set(arr);
136
137        let data = metric.test_get_value(None).expect("no object recorded");
138        let expected = json!([1, 2, 3]);
139        assert_eq!(expected, data);
140    }
141
142    #[test]
143    fn complex_nested_object() {
144        let _lock = lock_test();
145        let _t = new_glean(None, true);
146
147        type BalloonsObject = Vec<BalloonsObjectItem>;
148
149        #[derive(
150            Debug, Hash, Eq, PartialEq, traits::__serde::Deserialize, traits::__serde::Serialize,
151        )]
152        #[serde(crate = "traits::__serde")]
153        #[serde(deny_unknown_fields)]
154        struct BalloonsObjectItem {
155            #[serde(skip_serializing_if = "Option::is_none")]
156            colour: Option<String>,
157            #[serde(skip_serializing_if = "Option::is_none")]
158            diameter: Option<i64>,
159        }
160
161        let metric: ObjectMetric<BalloonsObject> = ObjectMetric::new(CommonMetricData {
162            name: "object".into(),
163            category: "test".into(),
164            send_in_pings: vec!["store1".into()],
165            ..Default::default()
166        });
167
168        let balloons = BalloonsObject::from([
169            BalloonsObjectItem {
170                colour: Some("red".to_string()),
171                diameter: Some(5),
172            },
173            BalloonsObjectItem {
174                colour: Some("green".to_string()),
175                diameter: None,
176            },
177        ]);
178        metric.set(balloons);
179
180        let data = metric.test_get_value(None).expect("no object recorded");
181        let expected = json!([
182            { "colour": "red", "diameter": 5 },
183            { "colour": "green" },
184        ]);
185        assert_eq!(expected, data);
186    }
187
188    #[test]
189    fn set_string_api() {
190        let _lock = lock_test();
191        let _t = new_glean(None, true);
192
193        type SimpleArray = Vec<i64>;
194
195        let metric: ObjectMetric<SimpleArray> = ObjectMetric::new(CommonMetricData {
196            name: "object".into(),
197            category: "test".into(),
198            send_in_pings: vec!["store1".into()],
199            ..Default::default()
200        });
201
202        let arr_str = String::from("[1, 2, 3]");
203        metric.set_string(arr_str);
204
205        let data = metric.test_get_value(None).expect("no object recorded");
206        let expected = json!([1, 2, 3]);
207        assert_eq!(expected, data);
208    }
209
210    #[test]
211    fn set_string_api_complex() {
212        let _lock = lock_test();
213        let _t = new_glean(None, true);
214
215        #[derive(
216            Debug, Hash, Eq, PartialEq, traits::__serde::Deserialize, traits::__serde::Serialize,
217        )]
218        #[serde(crate = "traits::__serde")]
219        #[serde(deny_unknown_fields)]
220        struct StackTrace {
221            #[serde(skip_serializing_if = "Option::is_none")]
222            error: Option<String>,
223            #[serde(
224                skip_serializing_if = "Vec::is_empty",
225                default = "Vec::new",
226                deserialize_with = "traits::__serde_helper::vec_null"
227            )]
228            modules: Vec<String>,
229            #[serde(skip_serializing_if = "Option::is_none")]
230            thread_info: Option<StackTraceThreadInfo>,
231        }
232
233        #[derive(
234            Debug, Hash, Eq, PartialEq, traits::__serde::Serialize, traits::__serde::Deserialize,
235        )]
236        #[serde(crate = "traits::__serde")]
237        #[serde(deny_unknown_fields)]
238        struct StackTraceThreadInfo {
239            base_address: Option<String>,
240        }
241
242        let metric: ObjectMetric<StackTrace> = ObjectMetric::new(CommonMetricData {
243            name: "object".into(),
244            category: "test".into(),
245            send_in_pings: vec!["store1".into()],
246            ..Default::default()
247        });
248
249        let arr_str = json!({
250            "error": "error",
251            "modules": null,
252            "thread_info": null,
253        })
254        .to_string();
255        metric.set_string(arr_str);
256
257        let data = metric.test_get_value(None).expect("no object recorded");
258        let expected = json!({
259            "error": "error"
260        });
261        assert_eq!(expected, data);
262        assert_eq!(
263            0,
264            metric.test_get_num_recorded_errors(ErrorType::InvalidValue)
265        );
266    }
267}