#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
use core::{fmt, str::FromStr};
use std::error::Error;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct RobotId(String);
impl RobotId {
pub fn new(value: impl AsRef<str>) -> Result<Self, RobotIdError> {
non_empty_identifier(value).map(Self)
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn into_string(self) -> String {
self.0
}
}
impl AsRef<str> for RobotId {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for RobotId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for RobotId {
type Err = RobotIdError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::new(value)
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct RobotSerialNumber(String);
impl RobotSerialNumber {
pub fn new(value: impl AsRef<str>) -> Result<Self, RobotIdError> {
non_empty_identifier(value).map(Self)
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn into_string(self) -> String {
self.0
}
}
impl AsRef<str> for RobotSerialNumber {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for RobotSerialNumber {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for RobotSerialNumber {
type Err = RobotIdError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::new(value)
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct RobotInstanceId(String);
impl RobotInstanceId {
pub fn new(value: impl AsRef<str>) -> Result<Self, RobotIdError> {
non_empty_identifier(value).map(Self)
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn into_string(self) -> String {
self.0
}
}
impl AsRef<str> for RobotInstanceId {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for RobotInstanceId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for RobotInstanceId {
type Err = RobotIdError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::new(value)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RobotIdError {
Empty,
}
impl fmt::Display for RobotIdError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("robot identifier cannot be empty"),
}
}
}
impl Error for RobotIdError {}
fn non_empty_identifier(value: impl AsRef<str>) -> Result<String, RobotIdError> {
let trimmed = value.as_ref().trim();
if trimmed.is_empty() {
Err(RobotIdError::Empty)
} else {
Ok(trimmed.to_string())
}
}
#[cfg(test)]
mod tests {
use super::{RobotId, RobotIdError, RobotInstanceId, RobotSerialNumber};
#[test]
fn constructs_valid_robot_id() -> Result<(), RobotIdError> {
let id = RobotId::new("robot:A-17")?;
assert_eq!(id.as_str(), "robot:A-17");
Ok(())
}
#[test]
fn rejects_empty_robot_id() {
assert_eq!(RobotId::new(" "), Err(RobotIdError::Empty));
}
#[test]
fn constructs_serial_number() -> Result<(), RobotIdError> {
let serial = RobotSerialNumber::new("SN-2026-A")?;
assert_eq!(serial.as_str(), "SN-2026-A");
Ok(())
}
#[test]
fn constructs_instance_id() -> Result<(), RobotIdError> {
let instance = RobotInstanceId::new("cell-4/arm-1")?;
assert_eq!(instance.as_str(), "cell-4/arm-1");
Ok(())
}
#[test]
fn displays_identifiers() -> Result<(), RobotIdError> {
let id = RobotId::new("Robot.ID:42")?;
assert_eq!(id.to_string(), "Robot.ID:42");
Ok(())
}
}