use chrono::offset::TimeZone;
use chrono::{DateTime, FixedOffset, Local};
use crate::error_recording::{record_error, ErrorType};
use crate::metrics::TimeUnit;
use crate::CommonMetricData;
use crate::Glean;
pub fn sanitize_application_id(application_id: &str) -> String {
let mut last_dash = false;
application_id
.chars()
.filter_map(|x| match x {
'A'..='Z' | 'a'..='z' | '0'..='9' => {
last_dash = false;
Some(x)
}
_ => {
let result = if last_dash { None } else { Some('-') };
last_dash = true;
result
}
})
.collect()
}
pub fn get_iso_time_string(datetime: DateTime<FixedOffset>, truncate_to: TimeUnit) -> String {
datetime.format(truncate_to.format_pattern()).to_string()
}
pub(crate) fn local_now_with_offset() -> DateTime<FixedOffset> {
let now: DateTime<Local> = Local::now();
let naive = now.naive_utc();
let fixed_tz = Local.offset_from_utc_datetime(&naive);
fixed_tz.from_utc_datetime(&naive)
}
pub(crate) fn truncate_string_at_boundary<S: Into<String>>(value: S, length: usize) -> String {
let s = value.into();
if s.len() > length {
for i in (0..=length).rev() {
if s.is_char_boundary(i) {
return s[0..i].to_string();
}
}
return "".to_string();
}
s
}
pub(crate) fn truncate_string_at_boundary_with_error<S: Into<String>>(
glean: &Glean,
meta: &CommonMetricData,
value: S,
length: usize,
) -> String {
let s = value.into();
if s.len() > length {
let msg = format!("Value length {} exceeds maximum of {}", s.len(), length);
record_error(glean, meta, ErrorType::InvalidValue, msg, None);
truncate_string_at_boundary(s, length)
} else {
s
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_sanitize_application_id() {
assert_eq!(
"org-mozilla-test-app",
sanitize_application_id("org.mozilla.test-app")
);
assert_eq!(
"org-mozilla-test-app",
sanitize_application_id("org.mozilla..test---app")
);
assert_eq!(
"org-mozilla-test-app",
sanitize_application_id("org-mozilla-test-app")
);
}
#[test]
fn test_get_iso_time_string() {
let dt = FixedOffset::east(3600)
.ymd(1985, 7, 3)
.and_hms_nano(12, 9, 14, 1_560_274);
assert_eq!(
"1985-07-03T12:09:14.001560274+01:00",
get_iso_time_string(dt, TimeUnit::Nanosecond)
);
assert_eq!(
"1985-07-03T12:09:14.001560+01:00",
get_iso_time_string(dt, TimeUnit::Microsecond)
);
assert_eq!(
"1985-07-03T12:09:14.001+01:00",
get_iso_time_string(dt, TimeUnit::Millisecond)
);
assert_eq!(
"1985-07-03T12:09:14+01:00",
get_iso_time_string(dt, TimeUnit::Second)
);
assert_eq!(
"1985-07-03T12:09+01:00",
get_iso_time_string(dt, TimeUnit::Minute)
);
assert_eq!(
"1985-07-03T12+01:00",
get_iso_time_string(dt, TimeUnit::Hour)
);
assert_eq!("1985-07-03+01:00", get_iso_time_string(dt, TimeUnit::Day));
}
#[test]
fn local_now_gets_the_time() {
let now = Local::now();
let fixed_now = local_now_with_offset();
assert!(
fixed_now.naive_utc() >= now.naive_utc(),
"Time mismatch. Local now: {:?}, Fixed now: {:?}",
now,
fixed_now
);
}
#[test]
fn truncate_safely_test() {
let value = "电脑坏了".to_string();
let truncated = truncate_string_at_boundary(value, 10);
assert_eq!("电脑坏", truncated);
let value = "0123456789abcdef".to_string();
let truncated = truncate_string_at_boundary(value, 10);
assert_eq!("0123456789", truncated);
}
#[test]
#[should_panic]
fn truncate_naive() {
let value = "电脑坏了".to_string();
value[0..10].to_string();
}
}