use std::fmt;
use thiserror::Error;
pub const MAX_TIMER_IDENTITY_COMPONENT_BYTES: usize = 64;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum TimerIdentityField {
Owner,
Subsystem,
Name,
}
impl fmt::Display for TimerIdentityField {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::Owner => "owner",
Self::Subsystem => "subsystem",
Self::Name => "name",
})
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Eq, Error, PartialEq)]
pub enum TimerIdentityError {
#[error("invalid timer {field}: identity component must not be empty")]
Empty {
field: TimerIdentityField,
},
#[error(
"invalid timer {field}: identity component is {actual_bytes} bytes; maximum is {max_bytes}"
)]
TooLong {
field: TimerIdentityField,
actual_bytes: usize,
max_bytes: usize,
},
#[error("invalid timer {field}: identity component has leading or trailing whitespace")]
SurroundingWhitespace {
field: TimerIdentityField,
},
#[error(
"invalid timer {field}: identity component contains control character {character:?} at byte {byte_index}"
)]
ControlCharacter {
field: TimerIdentityField,
byte_index: usize,
character: char,
},
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct TimerIdentity {
owner: Box<str>,
subsystem: Box<str>,
name: Box<str>,
}
impl TimerIdentity {
pub fn try_new(
owner: impl AsRef<str>,
subsystem: impl AsRef<str>,
name: impl AsRef<str>,
) -> Result<Self, TimerIdentityError> {
Ok(Self {
owner: validate_component(TimerIdentityField::Owner, owner)?,
subsystem: validate_component(TimerIdentityField::Subsystem, subsystem)?,
name: validate_component(TimerIdentityField::Name, name)?,
})
}
#[must_use]
pub fn owner(&self) -> &str {
&self.owner
}
#[must_use]
pub fn subsystem(&self) -> &str {
&self.subsystem
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
}
fn validate_component(
field: TimerIdentityField,
value: impl AsRef<str>,
) -> Result<Box<str>, TimerIdentityError> {
let value = value.as_ref();
if value.is_empty() {
return Err(TimerIdentityError::Empty { field });
}
if value.len() > MAX_TIMER_IDENTITY_COMPONENT_BYTES {
return Err(TimerIdentityError::TooLong {
field,
actual_bytes: value.len(),
max_bytes: MAX_TIMER_IDENTITY_COMPONENT_BYTES,
});
}
if value.trim() != value {
return Err(TimerIdentityError::SurroundingWhitespace { field });
}
if let Some((byte_index, character)) = value.char_indices().find(|(_, ch)| ch.is_control()) {
return Err(TimerIdentityError::ControlCharacter {
field,
byte_index,
character,
});
}
Ok(value.into())
}