1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
use std::collections::HashMap;

use chrono::{DateTime, Utc};
use relay_common::time::UnixTimestamp;
use serde::{Deserialize, Serialize};

use crate::MetricResourceIdentifier;

/// A metric metadata item.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct MetricMeta {
    /// Timestamp scope for the contained metadata.
    ///
    /// Metric metadata is collected in daily intervals, so this may be truncated
    /// to the start of the day (UTC) already.
    pub timestamp: StartOfDayUnixTimestamp,

    /// The contained metadata mapped by MRI.
    pub mapping: HashMap<MetricResourceIdentifier<'static>, Vec<Item>>,
}

/// A metadata item.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum Item {
    /// A location metadata pointing to the code location where the metric originates from.
    Location(Location),
    /// Unknown item.
    #[serde(other)]
    Unknown,
}

/// A code location.
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct Location {
    /// The relative file path.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub filename: Option<String>,
    /// The absolute file path.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub abs_path: Option<String>,
    /// The containing module name or path.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub module: Option<String>,
    /// The containing function name.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub function: Option<String>,
    /// The line number.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub lineno: Option<u64>,
    /// Source code leading up to `lineno`.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub pre_context: Vec<String>,
    /// Source code of the current line (`lineno`).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub context_line: Option<String>,
    /// Source code of the lines after `lineno`.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub post_context: Vec<String>,
}

/// A Unix timestamp that is truncated to the start of the day.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct StartOfDayUnixTimestamp(UnixTimestamp);

impl StartOfDayUnixTimestamp {
    /// Creates a new `StartOfDayUnixTimestamp` from a timestamp by truncating it.
    ///
    /// May return none when passed an invalid date, but in practice this never fails
    /// since the [`UnixTimestamp`] is already sufficiently validated.
    pub fn new(ts: UnixTimestamp) -> Option<Self> {
        let dt: DateTime<Utc> = DateTime::from_timestamp(ts.as_secs().try_into().ok()?, 0)?;
        let beginning_of_day = dt.date_naive().and_hms_opt(0, 0, 0)?.and_utc();
        Some(Self(UnixTimestamp::from_datetime(beginning_of_day)?))
    }

    /// Returns the underlying unix timestamp, truncated to the start of the day.
    pub fn as_timestamp(&self) -> UnixTimestamp {
        self.0
    }
}

impl std::ops::Deref for StartOfDayUnixTimestamp {
    type Target = UnixTimestamp;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl Serialize for StartOfDayUnixTimestamp {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.0.serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for StartOfDayUnixTimestamp {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let ts = UnixTimestamp::deserialize(deserializer)?;
        StartOfDayUnixTimestamp::new(ts)
            .ok_or_else(|| serde::de::Error::custom("invalid timestamp"))
    }
}