diff --git a/minisql/src/type_system.rs b/minisql/src/type_system.rs index abae701..013a48d 100644 --- a/minisql/src/type_system.rs +++ b/minisql/src/type_system.rs @@ -17,6 +17,7 @@ pub type Uuid = u64; // I would rather have non-nullable values by default, // and something like an explicit Option type for nulls. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(try_from = "String", into = "String")] pub enum Value { Number(f64), // TODO: Can't put floats as keys in maps, since they don't implement Eq. What to // do? @@ -24,6 +25,7 @@ pub enum Value { } #[derive(Debug, Ord, Eq, Clone, PartialOrd, PartialEq, Serialize, Deserialize)] +#[serde(try_from = "String", into = "String")] pub enum IndexableValue { String(String), Int(u64), @@ -113,10 +115,88 @@ impl Value { } } +// Own string serialization so enums can be used as keys in maps +impl From for String { + fn from(value: IndexableValue) -> Self { + match value { + IndexableValue::String(s) => format!("String({s})"), + IndexableValue::Int(i) => format!("Int({i})"), + IndexableValue::Uuid(u) => format!("Uuid({u})"), + } + } +} + +impl TryFrom for IndexableValue { + type Error = String; + fn try_from(value: String) -> Result { + if !value.ends_with(')') { + return Err(format!("Invalid IndexableValue: {}", value)); + } + + if value.starts_with("String(") { + let s = value[7..value.len() - 1].to_string(); + return Ok(Self::String(s)); + } + + if value.starts_with("Int(") { + let s = value[4..value.len() - 1].to_string(); + let i = s + .parse::() + .map_err(|e| format!("Invalid Int: {}", e))?; + return Ok(Self::Int(i)); + } + + if value.starts_with("Uuid(") { + let s = value[5..value.len() - 1].to_string(); + let u = s + .parse::() + .map_err(|e| format!("Invalid UUID: {}", e))?; + return Ok(Self::Uuid(u)); + } + + Err(format!("Invalid IndexableValue: {}", value)) + } +} + +impl From for String { + fn from(value: Value) -> Self { + match value { + Value::Number(n) => format!("Number({n})"), + Value::Indexable(i) => format!("Indexable({})", String::from(i)), + } + } +} + +impl TryFrom for Value { + type Error = String; + fn try_from(value: String) -> Result { + if !value.ends_with(')') { + return Err(format!("Invalid Value: {}", value)); + } + + if value.starts_with("Number(") { + let s = value[7..value.len() - 1].to_string(); + let n = s + .parse::() + .map_err(|e| format!("Invalid Number: {}", e))?; + return Ok(Self::Number(n)); + } + + if value.starts_with("Indexable(") { + let s = value[10..value.len() - 1].to_string(); + let i = IndexableValue::try_from(s)?; + return Ok(Self::Indexable(i)); + } + + Err(format!("Invalid Value: {}", value)) + } +} + #[cfg(test)] mod tests { use super::{IndexableValue, Value}; use crate::error::TypeConversionError::UnknownType; + use crate::type_system::Value::{Indexable, Number}; #[test] fn test_encode_number() { @@ -213,4 +293,25 @@ mod tests { Err(UnknownType { oid: 2950, size: 8 }) )) } + + #[test] + fn test_value_stringification() { + let pairs = vec![ + (Number(1.0), "Number(1)"), + ( + Indexable(IndexableValue::String("hello".to_string())), + "Indexable(String(hello))", + ), + (Indexable(IndexableValue::Int(123)), "Indexable(Int(123))"), + (Indexable(IndexableValue::Uuid(123)), "Indexable(Uuid(123))"), + ]; + + for (value, string) in pairs { + let serialized = String::from(value.clone()); + assert_eq!(serialized, string); + + let deserialized = Value::try_from(serialized); + assert_eq!(deserialized, Ok(value)); + } + } }