ic_timers/snapshot/
identity.rs1use std::{fmt, sync::Arc};
4use thiserror::Error;
5
6pub const MAX_TIMER_IDENTITY_COMPONENT_BYTES: usize = 64;
11
12#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
14pub enum TimerIdentityField {
15 Owner,
17 Subsystem,
19 Name,
21}
22
23impl fmt::Display for TimerIdentityField {
24 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
25 formatter.write_str(match self {
26 Self::Owner => "owner",
27 Self::Subsystem => "subsystem",
28 Self::Name => "name",
29 })
30 }
31}
32
33#[non_exhaustive]
35#[derive(Clone, Debug, Eq, Error, PartialEq)]
36pub enum TimerIdentityError {
37 #[error("invalid timer {field}: identity component must not be empty")]
39 Empty {
40 field: TimerIdentityField,
42 },
43 #[error(
45 "invalid timer {field}: identity component is {actual_bytes} bytes; maximum is {max_bytes}"
46 )]
47 TooLong {
48 field: TimerIdentityField,
50 actual_bytes: usize,
52 max_bytes: usize,
54 },
55 #[error("invalid timer {field}: identity component has leading or trailing whitespace")]
57 SurroundingWhitespace {
58 field: TimerIdentityField,
60 },
61 #[error(
63 "invalid timer {field}: identity component contains control character {character:?} at byte {byte_index}"
64 )]
65 ControlCharacter {
66 field: TimerIdentityField,
68 byte_index: usize,
70 character: char,
72 },
73}
74
75#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
80pub struct TimerIdentity {
81 components: Arc<IdentityComponents>,
82}
83
84#[derive(Eq, Hash, Ord, PartialEq, PartialOrd)]
85struct IdentityComponents {
86 owner: Box<str>,
87 subsystem: Box<str>,
88 name: Box<str>,
89}
90
91impl TimerIdentity {
92 pub fn try_new(
94 owner: impl AsRef<str>,
95 subsystem: impl AsRef<str>,
96 name: impl AsRef<str>,
97 ) -> Result<Self, TimerIdentityError> {
98 Ok(Self {
99 components: Arc::new(IdentityComponents {
100 owner: validate_component(TimerIdentityField::Owner, owner)?,
101 subsystem: validate_component(TimerIdentityField::Subsystem, subsystem)?,
102 name: validate_component(TimerIdentityField::Name, name)?,
103 }),
104 })
105 }
106
107 #[must_use]
109 pub fn owner(&self) -> &str {
110 &self.components.owner
111 }
112
113 #[must_use]
115 pub fn subsystem(&self) -> &str {
116 &self.components.subsystem
117 }
118
119 #[must_use]
121 pub fn name(&self) -> &str {
122 &self.components.name
123 }
124}
125
126impl fmt::Debug for TimerIdentity {
127 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
128 formatter
129 .debug_struct("TimerIdentity")
130 .field("owner", &self.owner())
131 .field("subsystem", &self.subsystem())
132 .field("name", &self.name())
133 .finish()
134 }
135}
136
137fn validate_component(
138 field: TimerIdentityField,
139 value: impl AsRef<str>,
140) -> Result<Box<str>, TimerIdentityError> {
141 let value = value.as_ref();
142 if value.is_empty() {
143 return Err(TimerIdentityError::Empty { field });
144 }
145 if value.len() > MAX_TIMER_IDENTITY_COMPONENT_BYTES {
146 return Err(TimerIdentityError::TooLong {
147 field,
148 actual_bytes: value.len(),
149 max_bytes: MAX_TIMER_IDENTITY_COMPONENT_BYTES,
150 });
151 }
152 if value.trim() != value {
153 return Err(TimerIdentityError::SurroundingWhitespace { field });
154 }
155 if let Some((byte_index, character)) = value.char_indices().find(|(_, ch)| ch.is_control()) {
156 return Err(TimerIdentityError::ControlCharacter {
157 field,
158 byte_index,
159 character,
160 });
161 }
162
163 Ok(value.into())
164}