use chrono::{DateTime, FixedOffset, Local};
use crate::common_metric_data::CommonMetricDataInternal;
use crate::error_recording::{record_error, ErrorType};
use crate::metrics::TimeUnit;
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.to_ascii_lowercase())
}
_ => {
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();
now.with_timezone(now.offset())
}
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: &CommonMetricDataInternal,
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::InvalidOverflow, msg, None);
truncate_string_at_boundary(s, length)
} else {
s
}
}
#[cfg(all(target_os = "windows", target_env = "gnu"))]
pub mod floating_point_context {
#[allow(non_camel_case_types)]
type size_t = usize;
#[link(name = "m")]
extern "C" {
fn _controlfp_s(current: *mut size_t, new: size_t, mask: size_t) -> size_t;
}
const MCW_RC: size_t = 0x00000300;
const RC_CHOP: size_t = 0x00000300;
const MCW_PC: size_t = 0x00030000;
const PC_64: size_t = 0x00000000;
pub struct FloatingPointContext {
original_value: size_t,
}
impl FloatingPointContext {
pub fn new() -> Self {
let mut current: size_t = 0;
let _err = unsafe { _controlfp_s(&mut current, PC_64 | RC_CHOP, MCW_PC | MCW_RC) };
FloatingPointContext {
original_value: current,
}
}
}
impl Drop for FloatingPointContext {
fn drop(&mut self) {
let mut current: size_t = 0;
let _err = unsafe { _controlfp_s(&mut current, self.original_value, MCW_PC | MCW_RC) };
}
}
}
#[cfg(not(all(target_os = "windows", target_env = "gnu")))]
pub mod floating_point_context {
pub struct FloatingPointContext {}
impl FloatingPointContext {
pub fn new() -> Self {
FloatingPointContext {}
}
}
}
#[cfg(test)]
mod test {
use super::*;
use chrono::{offset::TimeZone, Timelike};
#[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")
);
assert_eq!(
"org-mozilla-test-app",
sanitize_application_id("org.mozilla.Test.App")
);
}
#[test]
fn test_get_iso_time_string() {
let dt = FixedOffset::east_opt(3600)
.unwrap()
.with_ymd_and_hms(1985, 7, 3, 12, 9, 14)
.unwrap()
.with_nanosecond(1_560_274)
.unwrap();
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();
}
}