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