use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum DurationUnit {
Nanoseconds,
Microseconds,
Milliseconds,
Seconds,
Minutes,
Hours,
Days,
Weeks,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum WireValue {
Null,
Bool(bool),
Number(f64),
Integer(i64),
I8(i8),
U8(u8),
I16(i16),
U16(u16),
I32(i32),
U32(u32),
I64(i64),
U64(u64),
Isize(i64),
Usize(u64),
Ptr(u64),
F32(f32),
String(String),
Timestamp(i64),
Duration { value: f64, unit: DurationUnit },
Array(Vec<WireValue>),
Object(BTreeMap<String, WireValue>),
Table(WireTable),
Result { ok: bool, value: Box<WireValue> },
Range {
start: Option<Box<WireValue>>,
end: Option<Box<WireValue>>,
inclusive: bool,
},
FunctionRef { name: String },
PrintResult(crate::print_result::WirePrintResult),
Content(shape_value::content::ContentNode),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WireTable {
pub ipc_bytes: Vec<u8>,
pub type_name: Option<String>,
pub schema_id: Option<u32>,
pub row_count: usize,
pub column_count: usize,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum WireColumn {
Numbers(Vec<f64>),
Integers(Vec<i64>),
Booleans(Vec<bool>),
Strings(Vec<String>),
Objects(Vec<WireValue>),
}
impl WireValue {
pub fn null() -> Self {
WireValue::Null
}
pub fn is_null(&self) -> bool {
matches!(self, WireValue::Null)
}
pub fn as_number(&self) -> Option<f64> {
match self {
WireValue::Number(n) => Some(*n),
WireValue::Integer(i) => Some(*i as f64),
WireValue::I8(v) => Some(*v as f64),
WireValue::U8(v) => Some(*v as f64),
WireValue::I16(v) => Some(*v as f64),
WireValue::U16(v) => Some(*v as f64),
WireValue::I32(v) => Some(*v as f64),
WireValue::U32(v) => Some(*v as f64),
WireValue::I64(_)
| WireValue::U64(_)
| WireValue::Isize(_)
| WireValue::Usize(_)
| WireValue::Ptr(_) => None,
WireValue::F32(v) => Some(*v as f64),
_ => None,
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
WireValue::String(s) => Some(s),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
WireValue::Bool(b) => Some(*b),
_ => None,
}
}
pub fn type_name(&self) -> &'static str {
match self {
WireValue::Null => "Null",
WireValue::Bool(_) => "Bool",
WireValue::Number(_) => "Number",
WireValue::Integer(_) => "Integer",
WireValue::I8(_) => "i8",
WireValue::U8(_) => "u8",
WireValue::I16(_) => "i16",
WireValue::U16(_) => "u16",
WireValue::I32(_) => "i32",
WireValue::U32(_) => "u32",
WireValue::I64(_) => "i64",
WireValue::U64(_) => "u64",
WireValue::Isize(_) => "isize",
WireValue::Usize(_) => "usize",
WireValue::Ptr(_) => "ptr",
WireValue::F32(_) => "f32",
WireValue::String(_) => "String",
WireValue::Timestamp(_) => "Timestamp",
WireValue::Duration { .. } => "Duration",
WireValue::Array(_) => "Array",
WireValue::Object(_) => "Object",
WireValue::Table(_) => "Table",
WireValue::Result { .. } => "Result",
WireValue::Range { .. } => "Range",
WireValue::FunctionRef { .. } => "Function",
WireValue::PrintResult(_) => "PrintResult",
WireValue::Content(_) => "Content",
}
}
}
impl WireTable {
pub fn empty() -> Self {
WireTable {
ipc_bytes: Vec::new(),
type_name: None,
schema_id: None,
row_count: 0,
column_count: 0,
}
}
}
impl WireColumn {
pub fn len(&self) -> usize {
match self {
WireColumn::Numbers(v) => v.len(),
WireColumn::Integers(v) => v.len(),
WireColumn::Booleans(v) => v.len(),
WireColumn::Strings(v) => v.len(),
WireColumn::Objects(v) => v.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn element_type(&self) -> &'static str {
match self {
WireColumn::Numbers(_) => "Number",
WireColumn::Integers(_) => "Integer",
WireColumn::Booleans(_) => "Bool",
WireColumn::Strings(_) => "String",
WireColumn::Objects(_) => "Object",
}
}
}
impl From<bool> for WireValue {
fn from(b: bool) -> Self {
WireValue::Bool(b)
}
}
impl From<f64> for WireValue {
fn from(n: f64) -> Self {
WireValue::Number(n)
}
}
impl From<i64> for WireValue {
fn from(n: i64) -> Self {
WireValue::Integer(n)
}
}
impl From<u64> for WireValue {
fn from(n: u64) -> Self {
WireValue::U64(n)
}
}
impl From<i32> for WireValue {
fn from(n: i32) -> Self {
WireValue::I32(n)
}
}
impl From<u32> for WireValue {
fn from(n: u32) -> Self {
WireValue::U32(n)
}
}
impl From<i16> for WireValue {
fn from(n: i16) -> Self {
WireValue::I16(n)
}
}
impl From<u16> for WireValue {
fn from(n: u16) -> Self {
WireValue::U16(n)
}
}
impl From<i8> for WireValue {
fn from(n: i8) -> Self {
WireValue::I8(n)
}
}
impl From<u8> for WireValue {
fn from(n: u8) -> Self {
WireValue::U8(n)
}
}
impl From<f32> for WireValue {
fn from(n: f32) -> Self {
WireValue::F32(n)
}
}
impl From<String> for WireValue {
fn from(s: String) -> Self {
WireValue::String(s)
}
}
impl From<&str> for WireValue {
fn from(s: &str) -> Self {
WireValue::String(s.to_string())
}
}
impl<T: Into<WireValue>> From<Vec<T>> for WireValue {
fn from(v: Vec<T>) -> Self {
WireValue::Array(v.into_iter().map(Into::into).collect())
}
}
impl<T: Into<WireValue>> From<Option<T>> for WireValue {
fn from(opt: Option<T>) -> Self {
match opt {
Some(v) => v.into(),
None => WireValue::Null,
}
}
}
impl From<serde_json::Value> for WireValue {
fn from(json: serde_json::Value) -> Self {
match json {
serde_json::Value::Null => WireValue::Null,
serde_json::Value::Bool(b) => WireValue::Bool(b),
serde_json::Value::Number(n) => {
if let Some(i) = n.as_i64() {
WireValue::Integer(i)
} else if let Some(u) = n.as_u64() {
WireValue::U64(u)
} else if let Some(f) = n.as_f64() {
WireValue::Number(f)
} else {
WireValue::Null
}
}
serde_json::Value::String(s) => WireValue::String(s),
serde_json::Value::Array(arr) => {
WireValue::Array(arr.into_iter().map(WireValue::from).collect())
}
serde_json::Value::Object(obj) => {
let map: BTreeMap<String, WireValue> = obj
.into_iter()
.map(|(k, v)| (k, WireValue::from(v)))
.collect();
WireValue::Object(map)
}
}
}
}
impl From<&serde_json::Value> for WireValue {
fn from(json: &serde_json::Value) -> Self {
WireValue::from(json.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wire_value_type_names() {
assert_eq!(WireValue::Null.type_name(), "Null");
assert_eq!(WireValue::Bool(true).type_name(), "Bool");
assert_eq!(WireValue::Number(1.0).type_name(), "Number");
assert_eq!(WireValue::String("test".into()).type_name(), "String");
}
#[test]
fn test_wire_value_conversions() {
let v: WireValue = 42.0.into();
assert_eq!(v.as_number(), Some(42.0));
let v = WireValue::I64(42);
assert_eq!(v.as_number(), None, "i64 should not coerce to number");
let v: WireValue = "hello".into();
assert_eq!(v.as_str(), Some("hello"));
let v: WireValue = true.into();
assert_eq!(v.as_bool(), Some(true));
}
#[test]
fn test_wire_series_empty() {
let series = WireTable::empty();
assert_eq!(series.row_count, 0);
assert_eq!(series.column_count, 0);
assert!(series.ipc_bytes.is_empty());
}
#[test]
fn test_json_to_wire_conversion() {
let json = serde_json::json!(null);
let wire = WireValue::from(json);
assert!(wire.is_null());
let json = serde_json::json!(true);
let wire = WireValue::from(json);
assert_eq!(wire.as_bool(), Some(true));
let json = serde_json::json!(42);
let wire = WireValue::from(json);
assert!(matches!(wire, WireValue::Integer(42)));
let json = serde_json::json!(3.14);
let wire = WireValue::from(json);
assert!(matches!(wire, WireValue::Number(n) if (n - 3.14).abs() < 0.001));
let json = serde_json::json!("hello");
let wire = WireValue::from(json);
assert_eq!(wire.as_str(), Some("hello"));
let json = serde_json::json!([1, 2, 3]);
let wire = WireValue::from(json);
assert!(matches!(wire, WireValue::Array(arr) if arr.len() == 3));
let json = serde_json::json!({"x": 10, "y": 20});
let wire = WireValue::from(json);
if let WireValue::Object(map) = wire {
assert_eq!(map.len(), 2);
} else {
panic!("Expected Object");
}
}
}