ic_timers/snapshot/
identity.rs1use std::fmt;
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, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
79pub struct TimerIdentity {
80 owner: Box<str>,
81 subsystem: Box<str>,
82 name: Box<str>,
83}
84
85impl TimerIdentity {
86 pub fn try_new(
88 owner: impl AsRef<str>,
89 subsystem: impl AsRef<str>,
90 name: impl AsRef<str>,
91 ) -> Result<Self, TimerIdentityError> {
92 Ok(Self {
93 owner: validate_component(TimerIdentityField::Owner, owner)?,
94 subsystem: validate_component(TimerIdentityField::Subsystem, subsystem)?,
95 name: validate_component(TimerIdentityField::Name, name)?,
96 })
97 }
98
99 #[must_use]
101 pub fn owner(&self) -> &str {
102 &self.owner
103 }
104
105 #[must_use]
107 pub fn subsystem(&self) -> &str {
108 &self.subsystem
109 }
110
111 #[must_use]
113 pub fn name(&self) -> &str {
114 &self.name
115 }
116}
117
118fn validate_component(
119 field: TimerIdentityField,
120 value: impl AsRef<str>,
121) -> Result<Box<str>, TimerIdentityError> {
122 let value = value.as_ref();
123 if value.is_empty() {
124 return Err(TimerIdentityError::Empty { field });
125 }
126 if value.len() > MAX_TIMER_IDENTITY_COMPONENT_BYTES {
127 return Err(TimerIdentityError::TooLong {
128 field,
129 actual_bytes: value.len(),
130 max_bytes: MAX_TIMER_IDENTITY_COMPONENT_BYTES,
131 });
132 }
133 if value.trim() != value {
134 return Err(TimerIdentityError::SurroundingWhitespace { field });
135 }
136 if let Some((byte_index, character)) = value.char_indices().find(|(_, ch)| ch.is_control()) {
137 return Err(TimerIdentityError::ControlCharacter {
138 field,
139 byte_index,
140 character,
141 });
142 }
143
144 Ok(value.into())
145}