use jmap_types::{Id, JmapError};
use serde_json::Value;
pub fn ser<T: serde::Serialize>(val: T) -> Result<serde_json::Value, JmapError> {
serde_json::to_value(val).map_err(|e| JmapError::server_fail(e.to_string()))
}
pub fn not_found_json(ids: &[Id]) -> Value {
if ids.is_empty() {
Value::Null
} else {
Value::Array(
ids.iter()
.map(|id| Value::String(id.as_ref().to_owned()))
.collect(),
)
}
}
pub fn extract_account_id(args: &Value) -> Result<Id, JmapError> {
match args.get("accountId").and_then(|v| v.as_str()) {
Some(s) => Ok(Id::from(s)),
None => Err(JmapError::invalid_arguments("accountId is required")),
}
}
pub fn now_utc_string() -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as i64;
let s = secs % 60;
let m = (secs / 60) % 60;
let h = (secs / 3600) % 24;
let days = secs / 86400;
let (year, month, day) = civil_from_days(days);
format!("{year:04}-{month:02}-{day:02}T{h:02}:{m:02}:{s:02}Z")
}
fn civil_from_days(z: i64) -> (i32, u8, u8) {
let z = z + 719_468;
let era: i64 = if z >= 0 { z } else { z - 146_096 } / 146_097;
let doe = z - era * 146_097; let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); let mp = (5 * doy + 2) / 153; let d = doy - (153 * mp + 2) / 5 + 1; let mo = if mp < 10 { mp + 3 } else { mp - 9 }; let yr = if mo <= 2 { y + 1 } else { y };
(yr as i32, mo as u8, d as u8)
}
#[cfg(test)]
mod tests {
use super::{civil_from_days, now_utc_string};
#[test]
fn civil_from_days_known_dates() {
let cases: &[(i64, (i32, u8, u8))] = &[
(0, (1970, 1, 1)), (365, (1971, 1, 1)), (10957, (2000, 1, 1)), (11016, (2000, 2, 29)), (11017, (2000, 3, 1)), (19358, (2023, 1, 1)), (19722, (2023, 12, 31)), (19782, (2024, 2, 29)), (19783, (2024, 3, 1)), ];
for &(days, expected) in cases {
assert_eq!(
civil_from_days(days),
expected,
"civil_from_days({days}) mismatch"
);
}
}
#[test]
fn now_utc_string_format() {
let s = now_utc_string();
assert_eq!(s.len(), 20, "unexpected length: {s}");
assert!(s.ends_with('Z'), "must end with Z: {s}");
assert_eq!(&s[4..5], "-", "missing year-month separator: {s}");
assert_eq!(&s[7..8], "-", "missing month-day separator: {s}");
assert_eq!(&s[10..11], "T", "missing date-time separator: {s}");
assert_eq!(&s[13..14], ":", "missing hour-minute separator: {s}");
assert_eq!(&s[16..17], ":", "missing minute-second separator: {s}");
assert!(
s.starts_with("20"),
"year should start with 20 in 21st century: {s}"
);
}
}