#![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 EndEffectorName(String);
impl EndEffectorName {
pub fn new(value: impl AsRef<str>) -> Result<Self, EndEffectorTextError> {
non_empty_end_effector_text(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 EndEffectorName {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for EndEffectorName {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for EndEffectorName {
type Err = EndEffectorTextError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::new(value)
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum EndEffectorKind {
Gripper,
VacuumGripper,
Welder,
Cutter,
Drill,
Nozzle,
SuctionCup,
ToolChanger,
Unknown,
Custom(String),
}
impl fmt::Display for EndEffectorKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::Gripper => "gripper",
Self::VacuumGripper => "vacuum-gripper",
Self::Welder => "welder",
Self::Cutter => "cutter",
Self::Drill => "drill",
Self::Nozzle => "nozzle",
Self::SuctionCup => "suction-cup",
Self::ToolChanger => "tool-changer",
Self::Unknown => "unknown",
Self::Custom(value) => value.as_str(),
})
}
}
impl FromStr for EndEffectorKind {
type Err = EndEffectorKindParseError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let trimmed = value.trim();
if trimmed.is_empty() {
return Err(EndEffectorKindParseError::Empty);
}
match normalized_token(trimmed).as_str() {
"gripper" => Ok(Self::Gripper),
"vacuum-gripper" => Ok(Self::VacuumGripper),
"welder" => Ok(Self::Welder),
"cutter" => Ok(Self::Cutter),
"drill" => Ok(Self::Drill),
"nozzle" => Ok(Self::Nozzle),
"suction-cup" => Ok(Self::SuctionCup),
"tool-changer" => Ok(Self::ToolChanger),
"unknown" => Ok(Self::Unknown),
_ => Ok(Self::Custom(trimmed.to_string())),
}
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum GripState {
Open,
Closed,
Holding,
Released,
Unknown,
Custom(String),
}
impl fmt::Display for GripState {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::Open => "open",
Self::Closed => "closed",
Self::Holding => "holding",
Self::Released => "released",
Self::Unknown => "unknown",
Self::Custom(value) => value.as_str(),
})
}
}
impl FromStr for GripState {
type Err = GripStateParseError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let trimmed = value.trim();
if trimmed.is_empty() {
return Err(GripStateParseError::Empty);
}
match normalized_token(trimmed).as_str() {
"open" => Ok(Self::Open),
"closed" => Ok(Self::Closed),
"holding" => Ok(Self::Holding),
"released" | "release" => Ok(Self::Released),
"unknown" => Ok(Self::Unknown),
_ => Ok(Self::Custom(trimmed.to_string())),
}
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ToolMount(String);
impl ToolMount {
pub fn new(value: impl AsRef<str>) -> Result<Self, EndEffectorTextError> {
non_empty_end_effector_text(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 ToolMount {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for ToolMount {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for ToolMount {
type Err = EndEffectorTextError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::new(value)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EndEffectorTextError {
Empty,
}
impl fmt::Display for EndEffectorTextError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("end-effector text cannot be empty"),
}
}
}
impl Error for EndEffectorTextError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EndEffectorKindParseError {
Empty,
}
impl fmt::Display for EndEffectorKindParseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("end-effector kind cannot be empty"),
}
}
}
impl Error for EndEffectorKindParseError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GripStateParseError {
Empty,
}
impl fmt::Display for GripStateParseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("grip state cannot be empty"),
}
}
}
impl Error for GripStateParseError {}
fn non_empty_end_effector_text(value: impl AsRef<str>) -> Result<String, EndEffectorTextError> {
let trimmed = value.as_ref().trim();
if trimmed.is_empty() {
Err(EndEffectorTextError::Empty)
} else {
Ok(trimmed.to_string())
}
}
fn normalized_token(value: &str) -> String {
value
.trim()
.chars()
.map(|character| match character {
'_' | ' ' => '-',
other => other.to_ascii_lowercase(),
})
.collect()
}
#[cfg(test)]
mod tests {
use super::{
EndEffectorKind, EndEffectorKindParseError, EndEffectorName, EndEffectorTextError,
GripState, GripStateParseError,
};
#[test]
fn constructs_valid_end_effector_name() -> Result<(), EndEffectorTextError> {
let name = EndEffectorName::new(" parallel-gripper ")?;
assert_eq!(name.as_str(), "parallel-gripper");
Ok(())
}
#[test]
fn rejects_empty_end_effector_name() {
assert_eq!(EndEffectorName::new(""), Err(EndEffectorTextError::Empty));
}
#[test]
fn displays_and_parses_end_effector_kind() -> Result<(), EndEffectorKindParseError> {
assert_eq!(
"vacuum gripper".parse::<EndEffectorKind>()?,
EndEffectorKind::VacuumGripper
);
assert_eq!(EndEffectorKind::ToolChanger.to_string(), "tool-changer");
Ok(())
}
#[test]
fn displays_and_parses_grip_state() -> Result<(), GripStateParseError> {
assert_eq!("open".parse::<GripState>()?, GripState::Open);
assert_eq!(GripState::Holding.to_string(), "holding");
Ok(())
}
#[test]
fn stores_custom_end_effector_kind() -> Result<(), EndEffectorKindParseError> {
assert_eq!(
"magnetic-gripper".parse::<EndEffectorKind>()?,
EndEffectorKind::Custom("magnetic-gripper".to_string())
);
Ok(())
}
}