1use 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#[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 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 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 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 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 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}