Skip to main content

use_thread_id/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7/// A stable label or numeric thread-like identifier.
8#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub enum ThreadIdLabel {
10    /// A caller-provided stable thread label.
11    Label(String),
12    /// A caller-provided numeric thread-like identifier.
13    Number(u64),
14}
15
16impl ThreadIdLabel {
17    /// Creates a thread ID label from non-empty text.
18    ///
19    /// # Errors
20    ///
21    /// Returns [`ThreadIdLabelError::Empty`] when the trimmed input is empty.
22    pub fn label(value: impl AsRef<str>) -> Result<Self, ThreadIdLabelError> {
23        let trimmed = value.as_ref().trim();
24
25        if trimmed.is_empty() {
26            return Err(ThreadIdLabelError::Empty);
27        }
28
29        Ok(Self::Label(trimmed.to_string()))
30    }
31
32    /// Creates a numeric thread-like identifier.
33    #[must_use]
34    pub const fn number(value: u64) -> Self {
35        Self::Number(value)
36    }
37
38    /// Returns the label text when this value is label-backed.
39    #[must_use]
40    pub fn as_label(&self) -> Option<&str> {
41        match self {
42            Self::Label(value) => Some(value),
43            Self::Number(_) => None,
44        }
45    }
46
47    /// Returns the numeric identifier when this value is number-backed.
48    #[must_use]
49    pub const fn as_number(&self) -> Option<u64> {
50        match self {
51            Self::Label(_) => None,
52            Self::Number(value) => Some(*value),
53        }
54    }
55}
56
57impl fmt::Display for ThreadIdLabel {
58    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
59        match self {
60            Self::Label(value) => formatter.write_str(value),
61            Self::Number(value) => value.fmt(formatter),
62        }
63    }
64}
65
66impl FromStr for ThreadIdLabel {
67    type Err = ThreadIdLabelError;
68
69    fn from_str(value: &str) -> Result<Self, Self::Err> {
70        Self::label(value)
71    }
72}
73
74/// Errors returned while constructing thread ID labels.
75#[derive(Clone, Copy, Debug, Eq, PartialEq)]
76pub enum ThreadIdLabelError {
77    /// The label was empty after trimming whitespace.
78    Empty,
79}
80
81impl fmt::Display for ThreadIdLabelError {
82    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
83        match self {
84            Self::Empty => formatter.write_str("thread ID label cannot be empty"),
85        }
86    }
87}
88
89impl Error for ThreadIdLabelError {}
90
91#[cfg(test)]
92mod tests {
93    use super::{ThreadIdLabel, ThreadIdLabelError};
94
95    #[test]
96    fn accepts_non_empty_labels() {
97        let label = ThreadIdLabel::label(" worker-1 ").unwrap();
98
99        assert_eq!(label.as_label(), Some("worker-1"));
100        assert_eq!(label.to_string(), "worker-1");
101    }
102
103    #[test]
104    fn rejects_empty_labels() {
105        assert_eq!(ThreadIdLabel::label("  "), Err(ThreadIdLabelError::Empty));
106    }
107
108    #[test]
109    fn stores_numeric_identifiers() {
110        let label = ThreadIdLabel::number(12);
111
112        assert_eq!(label.as_number(), Some(12));
113        assert_eq!(label.to_string(), "12");
114    }
115}