use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RegisterDataType {
Bool,
Int16,
UInt16,
Int32,
UInt32,
Float32,
Int64,
UInt64,
Float64,
#[serde(rename = "string")]
String(u16),
#[serde(rename = "bytes")]
Bytes(u16),
}
impl Default for RegisterDataType {
fn default() -> Self {
Self::UInt16
}
}
impl RegisterDataType {
pub fn register_count(&self) -> u16 {
match self {
Self::Bool | Self::Int16 | Self::UInt16 => 1,
Self::Int32 | Self::UInt32 | Self::Float32 => 2,
Self::Int64 | Self::UInt64 | Self::Float64 => 4,
Self::String(n) | Self::Bytes(n) => *n,
}
}
pub fn byte_count(&self) -> usize {
(self.register_count() * 2) as usize
}
pub fn is_single_register(&self) -> bool {
self.register_count() == 1
}
pub fn is_multi_register(&self) -> bool {
self.register_count() > 1
}
pub fn is_numeric(&self) -> bool {
!matches!(self, Self::String(_) | Self::Bytes(_) | Self::Bool)
}
pub fn is_integer(&self) -> bool {
matches!(
self,
Self::Int16 | Self::UInt16 | Self::Int32 | Self::UInt32 | Self::Int64 | Self::UInt64
)
}
pub fn is_float(&self) -> bool {
matches!(self, Self::Float32 | Self::Float64)
}
pub fn is_signed(&self) -> bool {
matches!(
self,
Self::Int16 | Self::Int32 | Self::Int64 | Self::Float32 | Self::Float64
)
}
pub fn min_value(&self) -> Option<i128> {
match self {
Self::Bool => Some(0),
Self::Int16 => Some(i16::MIN as i128),
Self::UInt16 => Some(0),
Self::Int32 => Some(i32::MIN as i128),
Self::UInt32 => Some(0),
Self::Int64 => Some(i64::MIN as i128),
Self::UInt64 => Some(0),
_ => None,
}
}
pub fn max_value(&self) -> Option<i128> {
match self {
Self::Bool => Some(1),
Self::Int16 => Some(i16::MAX as i128),
Self::UInt16 => Some(u16::MAX as i128),
Self::Int32 => Some(i32::MAX as i128),
Self::UInt32 => Some(u32::MAX as i128),
Self::Int64 => Some(i64::MAX as i128),
Self::UInt64 => Some(u64::MAX as i128),
_ => None,
}
}
pub fn string_with_chars(chars: u16) -> Self {
Self::String((chars + 1) / 2) }
pub fn bytes_with_capacity(bytes: u16) -> Self {
Self::Bytes((bytes + 1) / 2)
}
pub fn name(&self) -> &'static str {
match self {
Self::Bool => "Bool",
Self::Int16 => "Int16",
Self::UInt16 => "UInt16",
Self::Int32 => "Int32",
Self::UInt32 => "UInt32",
Self::Float32 => "Float32",
Self::Int64 => "Int64",
Self::UInt64 => "UInt64",
Self::Float64 => "Float64",
Self::String(_) => "String",
Self::Bytes(_) => "Bytes",
}
}
pub fn basic_types() -> &'static [RegisterDataType] {
&[
Self::Bool,
Self::Int16,
Self::UInt16,
Self::Int32,
Self::UInt32,
Self::Float32,
Self::Int64,
Self::UInt64,
Self::Float64,
]
}
}
impl fmt::Display for RegisterDataType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Bool => write!(f, "Bool"),
Self::Int16 => write!(f, "Int16"),
Self::UInt16 => write!(f, "UInt16"),
Self::Int32 => write!(f, "Int32"),
Self::UInt32 => write!(f, "UInt32"),
Self::Float32 => write!(f, "Float32"),
Self::Int64 => write!(f, "Int64"),
Self::UInt64 => write!(f, "UInt64"),
Self::Float64 => write!(f, "Float64"),
Self::String(n) => write!(f, "String[{}]", n * 2),
Self::Bytes(n) => write!(f, "Bytes[{}]", n * 2),
}
}
}
impl std::str::FromStr for RegisterDataType {
type Err = DataTypeParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let normalized = s.to_lowercase();
if normalized.starts_with("string[") && normalized.ends_with(']') {
let len_str = &normalized[7..normalized.len() - 1];
let bytes: u16 = len_str
.parse()
.map_err(|_| DataTypeParseError(s.to_string()))?;
return Ok(Self::String((bytes + 1) / 2));
}
if normalized.starts_with("bytes[") && normalized.ends_with(']') {
let len_str = &normalized[6..normalized.len() - 1];
let bytes: u16 = len_str
.parse()
.map_err(|_| DataTypeParseError(s.to_string()))?;
return Ok(Self::Bytes((bytes + 1) / 2));
}
match normalized.as_str() {
"bool" | "boolean" => Ok(Self::Bool),
"int16" | "i16" | "short" => Ok(Self::Int16),
"uint16" | "u16" | "word" | "ushort" => Ok(Self::UInt16),
"int32" | "i32" | "int" | "dint" => Ok(Self::Int32),
"uint32" | "u32" | "dword" | "uint" => Ok(Self::UInt32),
"float32" | "f32" | "float" | "real" | "single" => Ok(Self::Float32),
"int64" | "i64" | "long" | "lint" => Ok(Self::Int64),
"uint64" | "u64" | "ulong" | "ulint" => Ok(Self::UInt64),
"float64" | "f64" | "double" | "lreal" => Ok(Self::Float64),
"string" => Ok(Self::String(16)), "bytes" => Ok(Self::Bytes(8)), _ => Err(DataTypeParseError(s.to_string())),
}
}
}
#[derive(Debug, Clone)]
pub struct DataTypeParseError(String);
impl fmt::Display for DataTypeParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Invalid data type '{}'. Valid options: bool, int16, uint16, int32, uint32, float32, int64, uint64, float64, string[n], bytes[n]",
self.0
)
}
}
impl std::error::Error for DataTypeParseError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_register_count() {
assert_eq!(RegisterDataType::Bool.register_count(), 1);
assert_eq!(RegisterDataType::Int16.register_count(), 1);
assert_eq!(RegisterDataType::UInt16.register_count(), 1);
assert_eq!(RegisterDataType::Int32.register_count(), 2);
assert_eq!(RegisterDataType::UInt32.register_count(), 2);
assert_eq!(RegisterDataType::Float32.register_count(), 2);
assert_eq!(RegisterDataType::Int64.register_count(), 4);
assert_eq!(RegisterDataType::UInt64.register_count(), 4);
assert_eq!(RegisterDataType::Float64.register_count(), 4);
assert_eq!(RegisterDataType::String(10).register_count(), 10);
assert_eq!(RegisterDataType::Bytes(5).register_count(), 5);
}
#[test]
fn test_is_flags() {
assert!(!RegisterDataType::Bool.is_numeric());
assert!(RegisterDataType::Int16.is_integer());
assert!(RegisterDataType::Float32.is_float());
assert!(RegisterDataType::Int32.is_signed());
assert!(!RegisterDataType::UInt32.is_signed());
}
#[test]
fn test_string_with_chars() {
assert_eq!(RegisterDataType::string_with_chars(20).register_count(), 10);
assert_eq!(RegisterDataType::string_with_chars(21).register_count(), 11);
}
#[test]
fn test_parse() {
assert_eq!(
"bool".parse::<RegisterDataType>().unwrap(),
RegisterDataType::Bool
);
assert_eq!(
"float32".parse::<RegisterDataType>().unwrap(),
RegisterDataType::Float32
);
assert_eq!(
"real".parse::<RegisterDataType>().unwrap(),
RegisterDataType::Float32
);
assert_eq!(
"i32".parse::<RegisterDataType>().unwrap(),
RegisterDataType::Int32
);
assert_eq!(
"string[20]".parse::<RegisterDataType>().unwrap(),
RegisterDataType::String(10)
);
}
#[test]
fn test_display() {
assert_eq!(format!("{}", RegisterDataType::Float32), "Float32");
assert_eq!(format!("{}", RegisterDataType::String(10)), "String[20]");
}
#[test]
fn test_serde() {
let dt = RegisterDataType::Float32;
let json = serde_json::to_string(&dt).unwrap();
assert_eq!(json, "\"float32\"");
let parsed: RegisterDataType = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, dt);
}
#[test]
fn test_min_max_values() {
assert_eq!(RegisterDataType::Int16.min_value(), Some(i16::MIN as i128));
assert_eq!(RegisterDataType::Int16.max_value(), Some(i16::MAX as i128));
assert_eq!(RegisterDataType::UInt16.min_value(), Some(0));
assert_eq!(RegisterDataType::UInt16.max_value(), Some(u16::MAX as i128));
assert!(RegisterDataType::Float32.min_value().is_none());
}
}