// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use std::marker::PhantomData; use glean_core::metrics::JsonValue; use glean_core::traits; use crate::ErrorType; // We need to wrap the glean-core type: otherwise if we try to implement // the trait for the metric in `glean_core::metrics` we hit error[E0117]: // only traits defined in the current crate can be implemented for arbitrary // types. /// Developer-facing API for recording object metrics. /// /// Instances of this class type are automatically generated by the parsers /// at build time, allowing developers to record values that were previously /// registered in the metrics.yaml file. #[derive(Clone)] pub struct ObjectMetric { pub(crate) inner: glean_core::metrics::ObjectMetric, object_type: PhantomData, } impl ObjectMetric { /// The public constructor used by automatically generated metrics. pub fn new(meta: glean_core::CommonMetricData) -> Self { let inner = glean_core::metrics::ObjectMetric::new(meta); Self { inner, object_type: PhantomData, } } /// Sets to the specified structure. /// /// # Arguments /// /// * `object` - the object to set. pub fn set(&self, object: K) { let obj = object .into_serialized_object() .expect("failed to serialize object. This should be impossible."); self.inner.set(obj); } /// Sets to the specified structure. /// /// Parses the passed JSON string. /// If it can't be parsed into a valid object it records an invalid value error. /// /// # Arguments /// /// * `object` - JSON representation of the object to set. pub fn set_string(&self, object: String) { let data = match K::from_str(&object) { Ok(data) => data, Err(_) => { self.inner.record_schema_error(); return; } }; self.set(data) } /// **Test-only API (exported for FFI purposes).** /// /// Gets the currently stored value as JSON-encoded string. /// /// This doesn't clear the stored value. pub fn test_get_value<'a, S: Into>>(&self, ping_name: S) -> Option { let ping_name = ping_name.into().map(|s| s.to_string()); self.inner.test_get_value(ping_name) } /// **Exported for test purposes.** /// /// Gets the number of recorded errors for the given metric and error type. /// /// # Arguments /// /// * `error` - The type of error /// /// # Returns /// /// The number of errors reported. pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 { self.inner.test_get_num_recorded_errors(error) } } #[cfg(test)] mod test { use super::*; use crate::common_test::{lock_test, new_glean}; use crate::CommonMetricData; use serde_json::json; #[test] fn simple_array() { let _lock = lock_test(); let _t = new_glean(None, true); type SimpleArray = Vec; let metric: ObjectMetric = ObjectMetric::new(CommonMetricData { name: "object".into(), category: "test".into(), send_in_pings: vec!["test1".into()], ..Default::default() }); let arr = SimpleArray::from([1, 2, 3]); metric.set(arr); let data = metric.test_get_value(None).expect("no object recorded"); let expected = json!([1, 2, 3]); assert_eq!(expected, data); } #[test] fn complex_nested_object() { let _lock = lock_test(); let _t = new_glean(None, true); type BalloonsObject = Vec; #[derive( Debug, Hash, Eq, PartialEq, traits::__serde::Deserialize, traits::__serde::Serialize, )] #[serde(crate = "traits::__serde")] #[serde(deny_unknown_fields)] struct BalloonsObjectItem { #[serde(skip_serializing_if = "Option::is_none")] colour: Option, #[serde(skip_serializing_if = "Option::is_none")] diameter: Option, } let metric: ObjectMetric = ObjectMetric::new(CommonMetricData { name: "object".into(), category: "test".into(), send_in_pings: vec!["test1".into()], ..Default::default() }); let balloons = BalloonsObject::from([ BalloonsObjectItem { colour: Some("red".to_string()), diameter: Some(5), }, BalloonsObjectItem { colour: Some("green".to_string()), diameter: None, }, ]); metric.set(balloons); let data = metric.test_get_value(None).expect("no object recorded"); let expected = json!([ { "colour": "red", "diameter": 5 }, { "colour": "green" }, ]); assert_eq!(expected, data); } #[test] fn set_string_api() { let _lock = lock_test(); let _t = new_glean(None, true); type SimpleArray = Vec; let metric: ObjectMetric = ObjectMetric::new(CommonMetricData { name: "object".into(), category: "test".into(), send_in_pings: vec!["test1".into()], ..Default::default() }); let arr_str = String::from("[1, 2, 3]"); metric.set_string(arr_str); let data = metric.test_get_value(None).expect("no object recorded"); let expected = json!([1, 2, 3]); assert_eq!(expected, data); } }