use crate::error::{Error, Result};
use crate::field_type::BpsvFieldType;
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum BpsvValue {
String(String),
Hex(String),
Decimal(i64),
Empty,
}
impl BpsvValue {
pub fn parse(value: &str, field_type: &BpsvFieldType) -> Result<Self> {
if value.is_empty() {
return Ok(Self::Empty);
}
match field_type {
BpsvFieldType::String(_) => {
if !field_type.is_valid_value(value) {
return Err(Error::InvalidValue {
field: "unknown".to_string(),
field_type: field_type.to_string(),
value: value.to_string(),
});
}
Ok(Self::String(value.to_string()))
}
BpsvFieldType::Hex(_) => {
if value.is_empty() {
return Ok(Self::Empty);
}
if !field_type.is_valid_value(value) {
return Err(Error::InvalidHex {
value: value.to_string(),
});
}
Ok(Self::Hex(value.to_lowercase()))
}
BpsvFieldType::Decimal(_) => {
if value.is_empty() {
return Ok(Self::Empty);
}
let num = value.parse::<i64>().map_err(|_| Error::InvalidNumber {
value: value.to_string(),
})?;
Ok(Self::Decimal(num))
}
}
}
pub fn to_bpsv_string(&self) -> String {
match self {
Self::String(s) => s.clone(),
Self::Hex(h) => h.clone(),
Self::Decimal(d) => d.to_string(),
Self::Empty => String::new(),
}
}
pub fn is_empty(&self) -> bool {
matches!(self, Self::Empty)
}
pub fn as_string(&self) -> Option<&str> {
match self {
Self::String(s) => Some(s),
_ => None,
}
}
pub fn as_hex(&self) -> Option<&str> {
match self {
Self::Hex(h) => Some(h),
_ => None,
}
}
pub fn as_decimal(&self) -> Option<i64> {
match self {
Self::Decimal(d) => Some(*d),
_ => None,
}
}
pub fn into_string(self) -> Option<String> {
match self {
Self::String(s) => Some(s),
_ => None,
}
}
pub fn into_hex(self) -> Option<String> {
match self {
Self::Hex(h) => Some(h),
_ => None,
}
}
pub fn into_decimal(self) -> Option<i64> {
match self {
Self::Decimal(d) => Some(d),
_ => None,
}
}
pub fn value_type(&self) -> &'static str {
match self {
Self::String(_) => "String",
Self::Hex(_) => "Hex",
Self::Decimal(_) => "Decimal",
Self::Empty => "Empty",
}
}
pub fn is_compatible_with(&self, field_type: &BpsvFieldType) -> bool {
match (self, field_type) {
(Self::String(_), BpsvFieldType::String(_)) => true,
(Self::Hex(_), BpsvFieldType::Hex(_)) => true,
(Self::Decimal(_), BpsvFieldType::Decimal(_)) => true,
(Self::Empty, _) => true, _ => false,
}
}
}
impl fmt::Display for BpsvValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_bpsv_string())
}
}
impl From<String> for BpsvValue {
fn from(s: String) -> Self {
if s.is_empty() {
Self::Empty
} else {
Self::String(s)
}
}
}
impl From<&str> for BpsvValue {
fn from(s: &str) -> Self {
if s.is_empty() {
Self::Empty
} else {
Self::String(s.to_string())
}
}
}
impl From<i64> for BpsvValue {
fn from(i: i64) -> Self {
Self::Decimal(i)
}
}
impl From<i32> for BpsvValue {
fn from(i: i32) -> Self {
Self::Decimal(i64::from(i))
}
}
impl From<u32> for BpsvValue {
fn from(i: u32) -> Self {
Self::Decimal(i64::from(i))
}
}
impl From<u64> for BpsvValue {
fn from(i: u64) -> Self {
#[allow(clippy::cast_possible_wrap)]
Self::Decimal(i as i64)
}
}
impl TryFrom<BpsvValue> for String {
type Error = Error;
fn try_from(value: BpsvValue) -> Result<Self> {
match value {
BpsvValue::String(s) => Ok(s),
BpsvValue::Empty => Ok(String::new()),
_ => Err(Error::InvalidValue {
field: "unknown".to_string(),
field_type: "String".to_string(),
value: value.to_bpsv_string(),
}),
}
}
}
impl TryFrom<BpsvValue> for i64 {
type Error = Error;
fn try_from(value: BpsvValue) -> Result<Self> {
match value {
BpsvValue::Decimal(d) => Ok(d),
_ => Err(Error::InvalidValue {
field: "unknown".to_string(),
field_type: "Decimal".to_string(),
value: value.to_bpsv_string(),
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_values() {
let string_type = BpsvFieldType::String(0);
assert_eq!(
BpsvValue::parse("hello", &string_type).unwrap(),
BpsvValue::String("hello".to_string())
);
let hex_type = BpsvFieldType::Hex(4); assert_eq!(
BpsvValue::parse("ABCD1234", &hex_type).unwrap(),
BpsvValue::Hex("abcd1234".to_string())
);
let dec_type = BpsvFieldType::Decimal(4);
assert_eq!(
BpsvValue::parse("1234", &dec_type).unwrap(),
BpsvValue::Decimal(1234)
);
assert_eq!(
BpsvValue::parse("", &string_type).unwrap(),
BpsvValue::Empty
);
}
#[test]
fn test_invalid_values() {
let hex_type = BpsvFieldType::Hex(4);
assert!(BpsvValue::parse("xyz", &hex_type).is_err());
let dec_type = BpsvFieldType::Decimal(4);
assert!(BpsvValue::parse("abc", &dec_type).is_err());
}
#[test]
fn test_conversions() {
let string_val: BpsvValue = "hello".into();
assert_eq!(string_val, BpsvValue::String("hello".to_string()));
let num_val: BpsvValue = 1234i64.into();
assert_eq!(num_val, BpsvValue::Decimal(1234));
let empty_val: BpsvValue = "".into();
assert_eq!(empty_val, BpsvValue::Empty);
}
#[test]
fn test_accessors() {
let string_val = BpsvValue::String("hello".to_string());
assert_eq!(string_val.as_string(), Some("hello"));
assert_eq!(string_val.as_hex(), None);
assert_eq!(string_val.as_decimal(), None);
let hex_val = BpsvValue::Hex("abcd".to_string());
assert_eq!(hex_val.as_hex(), Some("abcd"));
assert_eq!(hex_val.as_string(), None);
let dec_val = BpsvValue::Decimal(1234);
assert_eq!(dec_val.as_decimal(), Some(1234));
assert_eq!(dec_val.as_string(), None);
}
#[test]
fn test_compatibility() {
let string_val = BpsvValue::String("hello".to_string());
let string_type = BpsvFieldType::String(0);
let hex_type = BpsvFieldType::Hex(4);
assert!(string_val.is_compatible_with(&string_type));
assert!(!string_val.is_compatible_with(&hex_type));
let empty_val = BpsvValue::Empty;
assert!(empty_val.is_compatible_with(&string_type));
assert!(empty_val.is_compatible_with(&hex_type));
}
#[test]
fn test_to_bpsv_string() {
assert_eq!(
BpsvValue::String("hello".to_string()).to_bpsv_string(),
"hello"
);
assert_eq!(BpsvValue::Hex("abcd".to_string()).to_bpsv_string(), "abcd");
assert_eq!(BpsvValue::Decimal(1234).to_bpsv_string(), "1234");
assert_eq!(BpsvValue::Empty.to_bpsv_string(), "");
}
}