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 
5 use crate::error_recording::{record_error, ErrorType};
6 use crate::metrics::Metric;
7 use crate::metrics::MetricType;
8 use crate::storage::StorageManager;
9 use crate::util::truncate_string_at_boundary_with_error;
10 use crate::CommonMetricData;
11 use crate::Glean;
12 
13 // Maximum length of any list
14 const MAX_LIST_LENGTH: usize = 20;
15 // Maximum length of any string in the list
16 const MAX_STRING_LENGTH: usize = 50;
17 
18 /// A string list metric.
19 ///
20 /// This allows appending a string value with arbitrary content to a list.
21 #[derive(Clone, Debug)]
22 pub struct StringListMetric {
23     meta: CommonMetricData,
24 }
25 
26 impl MetricType for StringListMetric {
meta(&self) -> &CommonMetricData27     fn meta(&self) -> &CommonMetricData {
28         &self.meta
29     }
30 
meta_mut(&mut self) -> &mut CommonMetricData31     fn meta_mut(&mut self) -> &mut CommonMetricData {
32         &mut self.meta
33     }
34 }
35 
36 // IMPORTANT:
37 //
38 // When changing this implementation, make sure all the operations are
39 // also declared in the related trait in `../traits/`.
40 impl StringListMetric {
41     /// Creates a new string list metric.
new(meta: CommonMetricData) -> Self42     pub fn new(meta: CommonMetricData) -> Self {
43         Self { meta }
44     }
45 
46     /// Adds a new string to the list.
47     ///
48     /// # Arguments
49     ///
50     /// * `glean` - The Glean instance this metric belongs to.
51     /// * `value` - The string to add.
52     ///
53     /// ## Notes
54     ///
55     /// Truncates the value if it is longer than `MAX_STRING_LENGTH` bytes and logs an error.
add<S: Into<String>>(&self, glean: &Glean, value: S)56     pub fn add<S: Into<String>>(&self, glean: &Glean, value: S) {
57         if !self.should_record(glean) {
58             return;
59         }
60 
61         let value =
62             truncate_string_at_boundary_with_error(glean, &self.meta, value, MAX_STRING_LENGTH);
63         let mut error = None;
64         glean
65             .storage()
66             .record_with(glean, &self.meta, |old_value| match old_value {
67                 Some(Metric::StringList(mut old_value)) => {
68                     if old_value.len() == MAX_LIST_LENGTH {
69                         let msg = format!(
70                             "String list length of {} exceeds maximum of {}",
71                             old_value.len() + 1,
72                             MAX_LIST_LENGTH
73                         );
74                         error = Some(msg);
75                     } else {
76                         old_value.push(value.clone());
77                     }
78                     Metric::StringList(old_value)
79                 }
80                 _ => Metric::StringList(vec![value.clone()]),
81             });
82 
83         if let Some(msg) = error {
84             record_error(glean, &self.meta, ErrorType::InvalidValue, msg, None);
85         }
86     }
87 
88     /// Sets to a specific list of strings.
89     ///
90     /// # Arguments
91     ///
92     /// * `glean` - The Glean instance this metric belongs to.
93     /// * `value` - The list of string to set the metric to.
94     ///
95     /// ## Notes
96     ///
97     /// If passed an empty list, records an error and returns.
98     ///
99     /// Truncates the list if it is longer than `MAX_LIST_LENGTH` and logs an error.
100     ///
101     /// Truncates any value in the list if it is longer than `MAX_STRING_LENGTH` and logs an error.
set(&self, glean: &Glean, value: Vec<String>)102     pub fn set(&self, glean: &Glean, value: Vec<String>) {
103         if !self.should_record(glean) {
104             return;
105         }
106 
107         let value = if value.len() > MAX_LIST_LENGTH {
108             let msg = format!(
109                 "StringList length {} exceeds maximum of {}",
110                 value.len(),
111                 MAX_LIST_LENGTH
112             );
113             record_error(glean, &self.meta, ErrorType::InvalidValue, msg, None);
114             value[0..MAX_LIST_LENGTH].to_vec()
115         } else {
116             value
117         };
118 
119         let value = value
120             .into_iter()
121             .map(|elem| {
122                 truncate_string_at_boundary_with_error(glean, &self.meta, elem, MAX_STRING_LENGTH)
123             })
124             .collect();
125 
126         let value = Metric::StringList(value);
127         glean.storage().record(glean, &self.meta, &value);
128     }
129 
130     /// **Test-only API (exported for FFI purposes).**
131     ///
132     /// Gets the currently-stored values.
133     ///
134     /// This doesn't clear the stored value.
test_get_value(&self, glean: &Glean, storage_name: &str) -> Option<Vec<String>>135     pub fn test_get_value(&self, glean: &Glean, storage_name: &str) -> Option<Vec<String>> {
136         match StorageManager.snapshot_metric_for_test(
137             glean.storage(),
138             storage_name,
139             &self.meta.identifier(glean),
140             self.meta.lifetime,
141         ) {
142             Some(Metric::StringList(values)) => Some(values),
143             _ => None,
144         }
145     }
146 
147     /// **Test-only API (exported for FFI purposes).**
148     ///
149     /// Gets the currently-stored values as a JSON String of the format
150     /// ["string1", "string2", ...]
151     ///
152     /// This doesn't clear the stored value.
test_get_value_as_json_string( &self, glean: &Glean, storage_name: &str, ) -> Option<String>153     pub fn test_get_value_as_json_string(
154         &self,
155         glean: &Glean,
156         storage_name: &str,
157     ) -> Option<String> {
158         self.test_get_value(glean, storage_name)
159             .map(|values| serde_json::to_string(&values).unwrap())
160     }
161 }
162