1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
7use std::error::Error;
8
9#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
11pub struct RobotId(String);
12
13impl RobotId {
14 pub fn new(value: impl AsRef<str>) -> Result<Self, RobotIdError> {
20 non_empty_identifier(value).map(Self)
21 }
22
23 #[must_use]
25 pub fn as_str(&self) -> &str {
26 &self.0
27 }
28
29 #[must_use]
31 pub fn into_string(self) -> String {
32 self.0
33 }
34}
35
36impl AsRef<str> for RobotId {
37 fn as_ref(&self) -> &str {
38 self.as_str()
39 }
40}
41
42impl fmt::Display for RobotId {
43 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
44 formatter.write_str(self.as_str())
45 }
46}
47
48impl FromStr for RobotId {
49 type Err = RobotIdError;
50
51 fn from_str(value: &str) -> Result<Self, Self::Err> {
52 Self::new(value)
53 }
54}
55
56#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
58pub struct RobotSerialNumber(String);
59
60impl RobotSerialNumber {
61 pub fn new(value: impl AsRef<str>) -> Result<Self, RobotIdError> {
67 non_empty_identifier(value).map(Self)
68 }
69
70 #[must_use]
72 pub fn as_str(&self) -> &str {
73 &self.0
74 }
75
76 #[must_use]
78 pub fn into_string(self) -> String {
79 self.0
80 }
81}
82
83impl AsRef<str> for RobotSerialNumber {
84 fn as_ref(&self) -> &str {
85 self.as_str()
86 }
87}
88
89impl fmt::Display for RobotSerialNumber {
90 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
91 formatter.write_str(self.as_str())
92 }
93}
94
95impl FromStr for RobotSerialNumber {
96 type Err = RobotIdError;
97
98 fn from_str(value: &str) -> Result<Self, Self::Err> {
99 Self::new(value)
100 }
101}
102
103#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
105pub struct RobotInstanceId(String);
106
107impl RobotInstanceId {
108 pub fn new(value: impl AsRef<str>) -> Result<Self, RobotIdError> {
114 non_empty_identifier(value).map(Self)
115 }
116
117 #[must_use]
119 pub fn as_str(&self) -> &str {
120 &self.0
121 }
122
123 #[must_use]
125 pub fn into_string(self) -> String {
126 self.0
127 }
128}
129
130impl AsRef<str> for RobotInstanceId {
131 fn as_ref(&self) -> &str {
132 self.as_str()
133 }
134}
135
136impl fmt::Display for RobotInstanceId {
137 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
138 formatter.write_str(self.as_str())
139 }
140}
141
142impl FromStr for RobotInstanceId {
143 type Err = RobotIdError;
144
145 fn from_str(value: &str) -> Result<Self, Self::Err> {
146 Self::new(value)
147 }
148}
149
150#[derive(Clone, Copy, Debug, Eq, PartialEq)]
152pub enum RobotIdError {
153 Empty,
155}
156
157impl fmt::Display for RobotIdError {
158 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
159 match self {
160 Self::Empty => formatter.write_str("robot identifier cannot be empty"),
161 }
162 }
163}
164
165impl Error for RobotIdError {}
166
167fn non_empty_identifier(value: impl AsRef<str>) -> Result<String, RobotIdError> {
168 let trimmed = value.as_ref().trim();
169
170 if trimmed.is_empty() {
171 Err(RobotIdError::Empty)
172 } else {
173 Ok(trimmed.to_string())
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::{RobotId, RobotIdError, RobotInstanceId, RobotSerialNumber};
180
181 #[test]
182 fn constructs_valid_robot_id() -> Result<(), RobotIdError> {
183 let id = RobotId::new("robot:A-17")?;
184
185 assert_eq!(id.as_str(), "robot:A-17");
186 Ok(())
187 }
188
189 #[test]
190 fn rejects_empty_robot_id() {
191 assert_eq!(RobotId::new(" "), Err(RobotIdError::Empty));
192 }
193
194 #[test]
195 fn constructs_serial_number() -> Result<(), RobotIdError> {
196 let serial = RobotSerialNumber::new("SN-2026-A")?;
197
198 assert_eq!(serial.as_str(), "SN-2026-A");
199 Ok(())
200 }
201
202 #[test]
203 fn constructs_instance_id() -> Result<(), RobotIdError> {
204 let instance = RobotInstanceId::new("cell-4/arm-1")?;
205
206 assert_eq!(instance.as_str(), "cell-4/arm-1");
207 Ok(())
208 }
209
210 #[test]
211 fn displays_identifiers() -> Result<(), RobotIdError> {
212 let id = RobotId::new("Robot.ID:42")?;
213
214 assert_eq!(id.to_string(), "Robot.ID:42");
215 Ok(())
216 }
217}