errsight 0.1.1

Rust client for ErrSight error tracking — captures panics, errors, and log/tracing events and ships them to the ErrSight API from a background thread.
Documentation
//! Tiny shared helpers: UUIDs and timestamps.

use time::OffsetDateTime;
use time::format_description::well_known::Rfc3339;

/// A fresh v4 UUID as a 32-hex-char string (no hyphens), matching the
/// `ingestion_id` shape the backend stores. Hyphenated would work too, but the
/// backend caps fingerprints/ids at 32 chars in places, so the compact form is
/// the safe default.
pub fn new_uuid() -> String {
    uuid::Uuid::new_v4().simple().to_string()
}

/// Current UTC time as an RFC-3339 / ISO-8601 string with millisecond
/// precision (e.g. `2026-06-03T14:30:45.123Z`).
///
/// The backend parses ISO-8601 and clamps out-of-range timestamps, so exact
/// precision isn't load-bearing — but milliseconds keep breadcrumbs ordering
/// stable, since the backend sorts a merged crumb stream lexicographically.
pub fn now_iso8601() -> String {
    let now = OffsetDateTime::now_utc();
    // Round to milliseconds: nanosecond noise bloats the string and the
    // backend doesn't need it. If formatting ever fails (it shouldn't for a
    // valid `now_utc`), fall back to a coarse manual encoding so an event is
    // never dropped over a timestamp.
    let millis = now.millisecond();
    let truncated = now
        .replace_nanosecond(millis as u32 * 1_000_000)
        .unwrap_or(now);
    truncated
        .format(&Rfc3339)
        // Effectively unreachable for a valid `now_utc`, but keep the fallback
        // ISO-8601 too so the contract holds unconditionally — never a bare
        // unix-seconds string that the backend can't parse as a timestamp.
        .unwrap_or_else(|_| "1970-01-01T00:00:00.000Z".to_string())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn uuid_is_32_hex_chars() {
        let u = new_uuid();
        assert_eq!(u.len(), 32);
        assert!(u.chars().all(|c| c.is_ascii_hexdigit()));
        assert_ne!(new_uuid(), new_uuid());
    }

    #[test]
    fn timestamp_is_rfc3339_ish() {
        let ts = now_iso8601();
        // Looks like a date-time with a 'T' separator and ends in Z or offset.
        assert!(ts.contains('T'), "got {ts}");
        assert!(ts.len() >= 20, "got {ts}");
    }
}