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