Skip to main content

wide_log/
key.rs

1/// Trait for wide-event keys.
2///
3/// Each variant of a key enum represents a JSON key in the wide event.
4/// The `wide_log!` macro generates the enum and this trait impl
5/// automatically from the JSON structure — users do not implement this
6/// trait manually.
7///
8/// # Path-Based Duration / Timestamp / ID
9///
10/// [`DURATION_PATH`](Key::DURATION_PATH) specifies the nested path to the
11/// duration field (e.g. `[Duration, TotalMs]`). The guard auto-sets this
12/// field to the elapsed time in milliseconds on drop.
13///
14/// [`TIMESTAMP_PATH`](Key::TIMESTAMP_PATH) specifies the nested path to the
15/// timestamp field (e.g. `[Event, Timestamp]`). The guard auto-sets this
16/// field to the current time as an RFC 3339 string on drop.
17///
18/// [`ID_PATH`](Key::ID_PATH) specifies the nested path to the id field
19/// (e.g. `[Event, Id]`). The builder auto-sets this field to the generated
20/// ID string on `build()`.
21///
22/// The user does **not** define `Log`, `Level`, or `Message` variants. Log
23/// entries are handled entirely internally by a `LogEntry` type and the
24/// `log_entries` field on [`WideEvent`].
25///
26/// [`WideEvent`]: crate::WideEvent
27pub trait Key: Copy + Eq + 'static {
28    /// Returns the JSON string representation of this key.
29    fn as_str(self) -> &'static str;
30
31    /// The total number of keys in the enum.
32    const MAX_KEYS: usize;
33
34    /// All keys in enum order, indexed by `as_index`.
35    const KEYS: &'static [Self];
36
37    /// All key strings in enum order, indexed by `as_index`.
38    /// Used for O(1) `as_str()` via array index instead of a match.
39    const KEY_STRS: &'static [&'static str];
40
41    /// Returns the discriminant index of this key.
42    fn as_index(self) -> usize;
43
44    /// Path to the duration field (e.g. `[Duration, TotalMs]`).
45    /// Auto-set by the guard on drop. The value is in milliseconds (u64).
46    const DURATION_PATH: &'static [Self];
47
48    /// Path to the timestamp field (e.g. `[Event, Timestamp]`).
49    /// Auto-set by the guard on drop. The value is an RFC 3339 string.
50    const TIMESTAMP_PATH: &'static [Self];
51
52    /// Path to the id field (e.g. `[Event, Id]`).
53    /// Auto-set by the builder on `build()`. The value is a string.
54    const ID_PATH: &'static [Self];
55
56    /// JSON key for the log entries array.
57    /// Override path: `Log`. Default: "log".
58    const LOG_KEY: &'static str;
59
60    /// JSON key for the level field within each log entry.
61    /// Override path: `Log.Level`. Default: "level".
62    const LEVEL_KEY: &'static str;
63
64    /// JSON key for the message field within each log entry.
65    /// Override path: `Log.Message`. Default: "message".
66    const MESSAGE_KEY: &'static str;
67}
68
69#[cfg(test)]
70pub(crate) mod test_support {
71    use super::Key;
72
73    #[derive(Copy, Clone, PartialEq, Eq, Debug)]
74    #[repr(u8)]
75    pub enum TestKey {
76        Duration,
77        TotalMs,
78        Event,
79        Timestamp,
80        Id,
81        Service,
82        Name,
83        Version,
84        Requests,
85        Status,
86        Details,
87        Tag,
88        Count,
89        Flag,
90    }
91
92    impl Key for TestKey {
93        fn as_str(self) -> &'static str {
94            match self {
95                TestKey::Duration => "duration",
96                TestKey::TotalMs => "total_ms",
97                TestKey::Event => "event",
98                TestKey::Timestamp => "timestamp",
99                TestKey::Id => "id",
100                TestKey::Service => "service",
101                TestKey::Name => "name",
102                TestKey::Version => "version",
103                TestKey::Requests => "requests",
104                TestKey::Status => "status",
105                TestKey::Details => "details",
106                TestKey::Tag => "tag",
107                TestKey::Count => "count",
108                TestKey::Flag => "flag",
109            }
110        }
111
112        const MAX_KEYS: usize = 14;
113
114        const KEYS: &'static [Self] = &[
115            TestKey::Duration,
116            TestKey::TotalMs,
117            TestKey::Event,
118            TestKey::Timestamp,
119            TestKey::Id,
120            TestKey::Service,
121            TestKey::Name,
122            TestKey::Version,
123            TestKey::Requests,
124            TestKey::Status,
125            TestKey::Details,
126            TestKey::Tag,
127            TestKey::Count,
128            TestKey::Flag,
129        ];
130
131        const KEY_STRS: &'static [&'static str] = &[
132            "duration",
133            "total_ms",
134            "event",
135            "timestamp",
136            "id",
137            "service",
138            "name",
139            "version",
140            "requests",
141            "status",
142            "details",
143            "tag",
144            "count",
145            "flag",
146        ];
147
148        fn as_index(self) -> usize {
149            self as usize
150        }
151
152        const DURATION_PATH: &'static [Self] = &[TestKey::Duration, TestKey::TotalMs];
153        const TIMESTAMP_PATH: &'static [Self] = &[TestKey::Event, TestKey::Timestamp];
154        const ID_PATH: &'static [Self] = &[TestKey::Event, TestKey::Id];
155        const LOG_KEY: &'static str = "log";
156        const LEVEL_KEY: &'static str = "level";
157        const MESSAGE_KEY: &'static str = "message";
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164    use test_support::TestKey;
165
166    #[test]
167    fn key_as_str() {
168        assert_eq!(TestKey::Duration.as_str(), "duration");
169        assert_eq!(TestKey::TotalMs.as_str(), "total_ms");
170        assert_eq!(TestKey::Event.as_str(), "event");
171        assert_eq!(TestKey::Timestamp.as_str(), "timestamp");
172        assert_eq!(TestKey::Id.as_str(), "id");
173        assert_eq!(TestKey::Service.as_str(), "service");
174        assert_eq!(TestKey::Name.as_str(), "name");
175        assert_eq!(TestKey::Version.as_str(), "version");
176        assert_eq!(TestKey::Requests.as_str(), "requests");
177        assert_eq!(TestKey::Status.as_str(), "status");
178        assert_eq!(TestKey::Details.as_str(), "details");
179        assert_eq!(TestKey::Tag.as_str(), "tag");
180        assert_eq!(TestKey::Count.as_str(), "count");
181        assert_eq!(TestKey::Flag.as_str(), "flag");
182    }
183
184    #[test]
185    fn key_as_index() {
186        assert_eq!(TestKey::Duration.as_index(), 0);
187        assert_eq!(TestKey::TotalMs.as_index(), 1);
188        assert_eq!(TestKey::Event.as_index(), 2);
189        assert_eq!(TestKey::Timestamp.as_index(), 3);
190        assert_eq!(TestKey::Id.as_index(), 4);
191        assert_eq!(TestKey::Service.as_index(), 5);
192        assert_eq!(TestKey::Name.as_index(), 6);
193        assert_eq!(TestKey::Version.as_index(), 7);
194        assert_eq!(TestKey::Requests.as_index(), 8);
195        assert_eq!(TestKey::Status.as_index(), 9);
196        assert_eq!(TestKey::Details.as_index(), 10);
197        assert_eq!(TestKey::Tag.as_index(), 11);
198        assert_eq!(TestKey::Count.as_index(), 12);
199        assert_eq!(TestKey::Flag.as_index(), 13);
200    }
201
202    #[test]
203    fn max_keys_correct() {
204        assert_eq!(TestKey::MAX_KEYS, 14);
205        assert_eq!(TestKey::KEYS.len(), 14);
206    }
207
208    #[test]
209    fn duration_path_correct() {
210        assert_eq!(
211            TestKey::DURATION_PATH,
212            &[TestKey::Duration, TestKey::TotalMs]
213        );
214    }
215
216    #[test]
217    fn timestamp_path_correct() {
218        assert_eq!(
219            TestKey::TIMESTAMP_PATH,
220            &[TestKey::Event, TestKey::Timestamp]
221        );
222    }
223
224    #[test]
225    fn id_path_correct() {
226        assert_eq!(TestKey::ID_PATH, &[TestKey::Event, TestKey::Id]);
227    }
228}