ic_timers/snapshot/
identity.rs1use std::{fmt, ops::Deref};
4use thiserror::Error;
5
6pub const MAX_TIMER_LABEL_BYTES: usize = 64;
11
12#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
14pub struct TimerLabel(Box<str>);
15
16impl TimerLabel {
17 pub fn new(value: impl AsRef<str>) -> Result<Self, TimerLabelError> {
19 let value = value.as_ref();
20 if value.is_empty() {
21 return Err(TimerLabelError::Empty);
22 }
23 if value.len() > MAX_TIMER_LABEL_BYTES {
24 return Err(TimerLabelError::TooLong {
25 actual_bytes: value.len(),
26 max_bytes: MAX_TIMER_LABEL_BYTES,
27 });
28 }
29 if value.trim() != value {
30 return Err(TimerLabelError::SurroundingWhitespace);
31 }
32 if let Some((byte_index, character)) = value.char_indices().find(|(_, ch)| ch.is_control())
33 {
34 return Err(TimerLabelError::ControlCharacter {
35 byte_index,
36 character,
37 });
38 }
39
40 Ok(Self(value.into()))
41 }
42
43 #[must_use]
45 pub fn as_str(&self) -> &str {
46 &self.0
47 }
48}
49
50impl AsRef<str> for TimerLabel {
51 fn as_ref(&self) -> &str {
52 self.as_str()
53 }
54}
55
56impl Deref for TimerLabel {
57 type Target = str;
58
59 fn deref(&self) -> &Self::Target {
60 self.as_str()
61 }
62}
63
64impl fmt::Display for TimerLabel {
65 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
66 formatter.write_str(self.as_str())
67 }
68}
69
70impl TryFrom<&str> for TimerLabel {
71 type Error = TimerLabelError;
72
73 fn try_from(value: &str) -> Result<Self, Self::Error> {
74 Self::new(value)
75 }
76}
77
78impl TryFrom<String> for TimerLabel {
79 type Error = TimerLabelError;
80
81 fn try_from(value: String) -> Result<Self, Self::Error> {
82 Self::new(value)
83 }
84}
85
86#[non_exhaustive]
88#[derive(Clone, Debug, Eq, Error, PartialEq)]
89pub enum TimerLabelError {
90 #[error("timer identity label must not be empty")]
92 Empty,
93 #[error("timer identity label is {actual_bytes} bytes; maximum is {max_bytes}")]
95 TooLong {
96 actual_bytes: usize,
98 max_bytes: usize,
100 },
101 #[error("timer identity label must not have leading or trailing whitespace")]
103 SurroundingWhitespace,
104 #[error("timer identity label contains control character {character:?} at byte {byte_index}")]
106 ControlCharacter {
107 byte_index: usize,
109 character: char,
111 },
112}
113
114#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
116pub enum TimerIdentityField {
117 Owner,
119 Subsystem,
121 Name,
123}
124
125impl fmt::Display for TimerIdentityField {
126 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
127 formatter.write_str(match self {
128 Self::Owner => "owner",
129 Self::Subsystem => "subsystem",
130 Self::Name => "name",
131 })
132 }
133}
134
135#[non_exhaustive]
137#[derive(Clone, Debug, Eq, Error, PartialEq)]
138#[error("invalid timer {field}: {source}")]
139pub struct TimerIdentityError {
140 pub field: TimerIdentityField,
142 #[source]
144 pub source: TimerLabelError,
145}
146
147#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
151pub struct TimerIdentity {
152 owner: TimerLabel,
153 subsystem: TimerLabel,
154 name: TimerLabel,
155}
156
157impl TimerIdentity {
158 #[must_use]
160 pub const fn new(owner: TimerLabel, subsystem: TimerLabel, name: TimerLabel) -> Self {
161 Self {
162 owner,
163 subsystem,
164 name,
165 }
166 }
167
168 pub fn try_new(
170 owner: impl AsRef<str>,
171 subsystem: impl AsRef<str>,
172 name: impl AsRef<str>,
173 ) -> Result<Self, TimerIdentityError> {
174 Ok(Self::new(
175 validate_component(TimerIdentityField::Owner, owner)?,
176 validate_component(TimerIdentityField::Subsystem, subsystem)?,
177 validate_component(TimerIdentityField::Name, name)?,
178 ))
179 }
180
181 #[must_use]
183 pub const fn owner(&self) -> &TimerLabel {
184 &self.owner
185 }
186
187 #[must_use]
189 pub const fn subsystem(&self) -> &TimerLabel {
190 &self.subsystem
191 }
192
193 #[must_use]
195 pub const fn name(&self) -> &TimerLabel {
196 &self.name
197 }
198}
199
200fn validate_component(
201 field: TimerIdentityField,
202 value: impl AsRef<str>,
203) -> Result<TimerLabel, TimerIdentityError> {
204 TimerLabel::new(value).map_err(|source| TimerIdentityError { field, source })
205}