use std::{fmt, ops::Deref};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct KalamCellValue(pub JsonValue);
impl KalamCellValue {
#[inline]
pub fn null() -> Self {
Self(JsonValue::Null)
}
#[inline]
pub fn text(s: impl Into<String>) -> Self {
Self(JsonValue::String(s.into()))
}
#[inline]
pub fn boolean(b: bool) -> Self {
Self(JsonValue::Bool(b))
}
#[inline]
pub fn int(i: i64) -> Self {
Self(JsonValue::Number(i.into()))
}
#[inline]
pub fn float(f: f64) -> Option<Self> {
serde_json::Number::from_f64(f).map(|n| Self(JsonValue::Number(n)))
}
#[inline]
pub fn from_json(value: JsonValue) -> Self {
Self(value)
}
}
impl KalamCellValue {
#[inline]
pub fn inner(&self) -> &JsonValue {
&self.0
}
#[inline]
pub fn into_inner(self) -> JsonValue {
self.0
}
}
impl KalamCellValue {
#[inline]
pub fn as_text(&self) -> Option<&str> {
self.0.as_str()
}
pub fn as_small_int(&self) -> Option<i16> {
match &self.0 {
JsonValue::Number(n) => n.as_i64().map(|v| v as i16),
JsonValue::String(s) => s.parse().ok(),
_ => None,
}
}
pub fn as_int(&self) -> Option<i32> {
match &self.0 {
JsonValue::Number(n) => n.as_i64().map(|v| v as i32),
JsonValue::String(s) => s.parse().ok(),
_ => None,
}
}
pub fn as_big_int(&self) -> Option<i64> {
match &self.0 {
JsonValue::Number(n) => n.as_i64(),
JsonValue::String(s) => s.parse().ok(),
_ => None,
}
}
pub fn as_float(&self) -> Option<f32> {
match &self.0 {
JsonValue::Number(n) => n.as_f64().map(|v| v as f32),
JsonValue::String(s) => s.parse().ok(),
_ => None,
}
}
pub fn as_double(&self) -> Option<f64> {
match &self.0 {
JsonValue::Number(n) => n.as_f64(),
JsonValue::String(s) => s.parse().ok(),
_ => None,
}
}
pub fn as_decimal(&self) -> Option<f64> {
match &self.0 {
JsonValue::Number(n) => n.as_f64(),
JsonValue::String(s) => s.parse().ok(),
_ => None,
}
}
#[inline]
pub fn as_boolean(&self) -> Option<bool> {
self.0.as_bool()
}
pub fn as_timestamp(&self) -> Option<i64> {
match &self.0 {
JsonValue::Number(n) => n.as_i64(),
JsonValue::String(s) => s.parse().ok(),
_ => None,
}
}
pub fn as_date(&self) -> Option<i32> {
match &self.0 {
JsonValue::Number(n) => n.as_i64().map(|v| v as i32),
JsonValue::String(s) => s.parse().ok(),
_ => None,
}
}
pub fn as_datetime(&self) -> Option<i64> {
match &self.0 {
JsonValue::Number(n) => n.as_i64(),
JsonValue::String(s) => s.parse().ok(),
_ => None,
}
}
pub fn as_time(&self) -> Option<i64> {
match &self.0 {
JsonValue::Number(n) => n.as_i64(),
JsonValue::String(s) => s.parse().ok(),
_ => None,
}
}
#[inline]
pub fn as_uuid(&self) -> Option<&str> {
self.0.as_str()
}
pub fn as_json(&self) -> Option<&JsonValue> {
match &self.0 {
JsonValue::Object(_) | JsonValue::Array(_) => Some(&self.0),
_ => None,
}
}
pub fn as_bytes(&self) -> Option<Vec<u8>> {
use base64::{engine::general_purpose::STANDARD, Engine as _};
self.0.as_str().and_then(|s| STANDARD.decode(s).ok())
}
pub fn as_embedding(&self) -> Option<Vec<f32>> {
self.0.as_array().and_then(|arr| {
arr.iter().map(|v| v.as_f64().map(|f| f as f32)).collect::<Option<Vec<_>>>()
})
}
pub fn as_file(&self) -> Option<super::file_ref::FileRef> {
super::file_ref::FileRef::from_json_value(&self.0)
}
}
impl Deref for KalamCellValue {
type Target = JsonValue;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<JsonValue> for KalamCellValue {
#[inline]
fn from(v: JsonValue) -> Self {
Self(v)
}
}
impl From<KalamCellValue> for JsonValue {
#[inline]
fn from(cell: KalamCellValue) -> Self {
cell.0
}
}
impl fmt::Display for KalamCellValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
JsonValue::Null => write!(f, "NULL"),
JsonValue::String(s) => write!(f, "{s}"),
JsonValue::Number(n) => write!(f, "{n}"),
JsonValue::Bool(b) => write!(f, "{b}"),
other => write!(f, "{other}"),
}
}
}
impl Default for KalamCellValue {
#[inline]
fn default() -> Self {
Self::null()
}
}
pub type RowData = std::collections::HashMap<String, KalamCellValue>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn transparent_roundtrip() {
let cell = KalamCellValue::text("hello");
let json = serde_json::to_string(&cell).unwrap();
assert_eq!(json, r#""hello""#);
let back: KalamCellValue = serde_json::from_str(&json).unwrap();
assert_eq!(back, cell);
}
#[test]
fn null_cell() {
let cell = KalamCellValue::null();
assert!(cell.is_null());
assert_eq!(cell.to_string(), "NULL");
}
#[test]
fn int_cell() {
let cell = KalamCellValue::int(42);
assert_eq!(cell.as_i64(), Some(42));
}
#[test]
fn deref_access() {
let cell = KalamCellValue::text("world");
assert_eq!(cell.as_str(), Some("world"));
}
#[test]
fn from_json_value() {
let jv = serde_json::json!({"key": "val"});
let cell = KalamCellValue::from(jv.clone());
assert_eq!(*cell, jv);
}
#[test]
fn into_json_value() {
let cell = KalamCellValue::boolean(true);
let jv: JsonValue = cell.into();
assert_eq!(jv, JsonValue::Bool(true));
}
}