Skip to main content

glean_core/metrics/
string_list.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::sync::Arc;
6
7use crate::common_metric_data::CommonMetricDataInternal;
8use crate::error_recording::{record_error, test_get_num_recorded_errors, ErrorType};
9use crate::metrics::Metric;
10use crate::metrics::MetricType;
11use crate::util::truncate_string_at_boundary_with_error;
12use crate::Glean;
13use crate::{CommonMetricData, TestGetValue};
14
15// Maximum length of any list
16const MAX_LIST_LENGTH: usize = 100;
17// Maximum length of any string in the list
18const MAX_STRING_LENGTH: usize = 100;
19
20/// A string list metric.
21///
22/// This allows appending a string value with arbitrary content to a list.
23#[derive(Clone, Debug)]
24pub struct StringListMetric {
25    meta: Arc<CommonMetricDataInternal>,
26}
27
28impl MetricType for StringListMetric {
29    fn meta(&self) -> &CommonMetricDataInternal {
30        &self.meta
31    }
32}
33
34// IMPORTANT:
35//
36// When changing this implementation, make sure all the operations are
37// also declared in the related trait in `../traits/`.
38impl StringListMetric {
39    /// Creates a new string list metric.
40    pub fn new(meta: CommonMetricData) -> Self {
41        Self {
42            meta: Arc::new(meta.into()),
43        }
44    }
45
46    /// Adds a new string to the list.
47    ///
48    /// # Arguments
49    ///
50    /// * `value` - The string to add.
51    ///
52    /// ## Notes
53    ///
54    /// Truncates the value if it is longer than `MAX_STRING_LENGTH` bytes and logs an error.
55    pub fn add(&self, value: String) {
56        let metric = self.clone();
57        crate::launch_with_glean(move |glean| metric.add_sync(glean, value))
58    }
59
60    /// Adds a new string to the list synchronously
61    #[doc(hidden)]
62    pub fn add_sync<S: Into<String>>(&self, glean: &Glean, value: S) {
63        if !self.should_record(glean) {
64            return;
65        }
66
67        let value =
68            truncate_string_at_boundary_with_error(glean, &self.meta, value, MAX_STRING_LENGTH);
69        let mut error = None;
70        glean
71            .storage()
72            .record_with(glean, &self.meta, |old_value| match old_value {
73                Some(Metric::StringList(mut old_value)) => {
74                    if old_value.len() == MAX_LIST_LENGTH {
75                        let msg = format!(
76                            "String list length of {} exceeds maximum of {}",
77                            old_value.len() + 1,
78                            MAX_LIST_LENGTH
79                        );
80                        error = Some(msg);
81                    } else {
82                        old_value.push(value.clone());
83                    }
84                    Metric::StringList(old_value)
85                }
86                _ => Metric::StringList(vec![value.clone()]),
87            });
88
89        if let Some(msg) = error {
90            record_error(glean, &self.meta, ErrorType::InvalidValue, msg, None);
91        }
92    }
93
94    /// Sets to a specific list of strings.
95    ///
96    /// # Arguments
97    ///
98    /// * `value` - The list of string to set the metric to.
99    ///
100    /// ## Notes
101    ///
102    /// If passed an empty list, records an error and returns.
103    ///
104    /// Truncates the list if it is longer than `MAX_LIST_LENGTH` and logs an error.
105    ///
106    /// Truncates any value in the list if it is longer than `MAX_STRING_LENGTH` and logs an error.
107    pub fn set(&self, values: Vec<String>) {
108        let metric = self.clone();
109        crate::launch_with_glean(move |glean| metric.set_sync(glean, values))
110    }
111
112    /// Sets to a specific list of strings synchronously.
113    #[doc(hidden)]
114    pub fn set_sync(&self, glean: &Glean, value: Vec<String>) {
115        if !self.should_record(glean) {
116            return;
117        }
118
119        let value = if value.len() > MAX_LIST_LENGTH {
120            let msg = format!(
121                "StringList length {} exceeds maximum of {}",
122                value.len(),
123                MAX_LIST_LENGTH
124            );
125            record_error(glean, &self.meta, ErrorType::InvalidValue, msg, None);
126            value[0..MAX_LIST_LENGTH].to_vec()
127        } else {
128            value
129        };
130
131        let value = value
132            .into_iter()
133            .map(|elem| {
134                truncate_string_at_boundary_with_error(glean, &self.meta, elem, MAX_STRING_LENGTH)
135            })
136            .collect();
137
138        let value = Metric::StringList(value);
139        glean.storage().record(glean, &self.meta, &value);
140    }
141
142    /// **Test-only API (exported for FFI purposes).**
143    ///
144    /// Gets the currently-stored values.
145    ///
146    /// This doesn't clear the stored value.
147    #[doc(hidden)]
148    pub fn get_value<'a, S: Into<Option<&'a str>>>(
149        &self,
150        glean: &Glean,
151        ping_name: S,
152    ) -> Option<Vec<String>> {
153        let queried_ping_name = ping_name
154            .into()
155            .unwrap_or_else(|| &self.meta().inner.send_in_pings[0]);
156
157        match glean.storage().get_metric(
158            #[cfg(not(feature = "sqlite"))]
159            glean,
160            self.meta(),
161            queried_ping_name,
162        ) {
163            Some(Metric::StringList(values)) => Some(values),
164            _ => None,
165        }
166    }
167
168    /// **Exported for test purposes.**
169    ///
170    /// Gets the number of recorded errors for the given metric and error type.
171    ///
172    /// # Arguments
173    ///
174    /// * `error` - The type of error
175    ///
176    /// # Returns
177    ///
178    /// The number of errors reported.
179    pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
180        crate::block_on_dispatcher();
181
182        crate::core::with_glean(|glean| {
183            test_get_num_recorded_errors(glean, self.meta(), error).unwrap_or(0)
184        })
185    }
186}
187
188impl TestGetValue for StringListMetric {
189    type Output = Vec<String>;
190
191    /// **Test-only API (exported for FFI purposes).**
192    ///
193    /// Gets the currently-stored values.
194    ///
195    /// This doesn't clear the stored value.
196    ///
197    /// # Arguments
198    ///
199    /// * `ping_name` - the optional name of the ping to retrieve the metric
200    ///                 for. Defaults to the first value in `send_in_pings`.
201    ///
202    /// # Returns
203    ///
204    /// The stored value or `None` if nothing stored.
205    fn test_get_value(&self, ping_name: Option<String>) -> Option<Vec<String>> {
206        crate::block_on_dispatcher();
207        crate::core::with_glean(|glean| self.get_value(glean, ping_name.as_deref()))
208    }
209}