rline_api 1.0.0

rline public API to create, manipulate, and convert rows of data, making it easy to work with datasets
Documentation
//! # value
//!
//! The `value` module defines the `Value` enum, representing any valid row value in the
//! `rline_api` library.
//!
//! ## Enum Variants
//!
//! - [`String`](enum.Value.html#variant.String): Represents a string value.
//!
//! - [`Integer`](enum.Value.html#variant.Integer): Represents an integer value.
//!
//! - [`Float`](enum.Value.html#variant.Float): Represents a floating-point value.
//!
//! - [`VecString`](enum.Value.html#variant.VecString): Represents a vector of strings.
//!
//! - [`VecInteger`](enum.Value.html#variant.VecInteger): Represents a vector of integers.
//!
//! - [`VecFloat`](enum.Value.html#variant.VecFloat): Represents a vector of floating-point numbers.
//!
//! - [`VecU8`](enum.Value.html#variant.VecU8): Represents a vector of unsigned 8-bit integers.
//!
//! ## Examples
//!
//! ```rust
//! use rline_api::value::Value;
//!
//! fn example_function() {
//!     let string_value: Value = "Hello, World!".to_string().into();
//!     let integer_value: Value = 42.into();
//!     let float_value: Value = 3.14.into();
//!     let vec_string_value: Value = vec!["one".to_string(), "two".to_string()].into();
//!     let vec_integer_value: Value = vec![1, 2, 3].into();
//!     let vec_float_value: Value = vec![1.1, 2.2, 3.3].into();
//!     let vec_u8_value: Value = vec![0, 1, 2].into();
//! }
//! ```
//!
//! ## Usage
//!
//! This module is used within the `rline_api` library to represent values associated with row
//! columns. The `Value` enum is versatile and can store various types of data,
//! making it suitable for diverse use cases in data processing.
//!
//! ## License
//!
//! This module is part of the `rline_api` crate, licensed under the Apache-2.0 License.
use std::fmt::{Display, Formatter};

use bincode::{Decode as BincodeDecode, Encode as BincodeEncode};
use serde::{Deserialize as SerdeDeserialize, Serialize as SerdeSerialize};
use serde_json::{json, to_value, Value as JsonValue};

use crate::rline_error::RlineError;
use crate::rline_error::RlineError::{BadTypeError, RuntimeError};

#[derive(
    SerdeSerialize, SerdeDeserialize, BincodeDecode, BincodeEncode, Debug, Clone, PartialEq,
)]
/// Represents any valid Row value.
pub enum Value {
    String(String),
    Integer(isize),
    Float(f64),
    VecString(Vec<String>),
    VecInteger(Vec<isize>),
    VecFloat(Vec<f64>),
    VecU8(Vec<u8>),
}

impl Value {
    /// Try to convert this Value to a string.
    /// Returns a reference to the string if success,
    /// returns a BadTypeError if this Value is not a string.
    pub fn as_string(&self) -> Result<&String, RlineError> {
        if let Self::String(v) = self {
            Ok(v)
        } else {
            Err(BadTypeError(String::from("Not a string")))
        }
    }

    /// Try to convert this Value to a isize.
    /// Returns a reference to the isize if success,
    /// returns a BadTypeError if this Value is not an isize.
    pub fn as_integer(&self) -> Result<&isize, RlineError> {
        if let Self::Integer(v) = self {
            Ok(v)
        } else {
            Err(BadTypeError(String::from("Not an integer")))
        }
    }

    /// Try to convert this Value to a f64.
    /// Returns a reference to the f64 if success,
    /// returns a BadTypeError if this Value is not a f64.
    pub fn as_float(&self) -> Result<&f64, RlineError> {
        if let Self::Float(v) = self {
            Ok(v)
        } else {
            Err(BadTypeError(String::from("Not a float")))
        }
    }

    /// Try to convert this Value to a Vec<String>.
    /// Returns a reference to the Vec<String> if success,
    /// returns a BadTypeError if this Value is not a Vec<String>.
    pub fn as_vec_string(&self) -> Result<&Vec<String>, RlineError> {
        if let Self::VecString(v) = self {
            Ok(v)
        } else {
            Err(BadTypeError(String::from("Not a vector<string>")))
        }
    }

    /// Try to convert this Value to a Vec<isize>.
    /// Returns a reference to the Vec<isize> if success,
    /// returns a BadTypeError if this Value is not a Vec<isize>.
    pub fn as_vec_integer(&self) -> Result<&Vec<isize>, RlineError> {
        if let Self::VecInteger(v) = self {
            Ok(v)
        } else {
            Err(BadTypeError(String::from("Not a vector<integer>")))
        }
    }

    /// Try to convert this Value to a Vec<f64>.
    /// Returns a reference to the Vec<f64> if success,
    /// returns a BadTypeError if this Value is not a Vec<f64>.
    pub fn as_vec_float(&self) -> Result<&Vec<f64>, RlineError> {
        if let Self::VecFloat(v) = self {
            Ok(v)
        } else {
            Err(BadTypeError(String::from("Not a vector<float>")))
        }
    }

    /// Try to convert this Value to a Vec<u8>.
    /// Returns a reference to the Vec<u8> if success,
    /// returns a BadTypeError if this Value is not a Vec<u8>.
    pub fn as_vec_u8(&self) -> Result<&Vec<u8>, RlineError> {
        if let Self::VecU8(v) = self {
            Ok(v)
        } else {
            Err(BadTypeError(String::from("Not a vector<u8>")))
        }
    }

    /// Json value to Self.
    /// Nested json values are kept as string.
    pub fn from_json_value(json_value: JsonValue) -> Result<Self, RlineError> {
        match json_value {
            JsonValue::Null => Err(RuntimeError(format!("Invalid null value {}", json_value))),
            JsonValue::Bool(b) => Ok(Self::Integer(b as isize)),
            JsonValue::Number(number) => {
                if let Some(f) = number.as_f64() {
                    Ok(Self::Float(f))
                } else if let Some(i) = number.as_i64() {
                    Ok(Self::Integer(i as isize))
                } else {
                    return Err(RuntimeError("Invalid number from json value".into()));
                }
            }
            JsonValue::String(s) => Ok(Self::String(s)),
            JsonValue::Array(values) => {
                if let Some(first) = values.get(0) {
                    match first {
                        JsonValue::Null => Err(RuntimeError("Invalid null value".into())),
                        JsonValue::Array(_) | JsonValue::Object(_) => {
                            // Nested json value, keep it as string
                            Ok(Self::String(
                                serde_json::to_string(&values)
                                    .map_err(|e| RlineError::runtime_error(&e))?,
                            ))
                        }
                        JsonValue::String(_) => {
                            let mut res: Vec<String> = Vec::with_capacity(values.len());
                            for v in values {
                                if let JsonValue::String(s) = v {
                                    res.push(s)
                                } else {
                                    return Err(RuntimeError(
                                        "Unsupported heterogenous list".into(),
                                    ));
                                }
                            }
                            Ok(Self::VecString(res))
                        }
                        JsonValue::Bool(_) => {
                            let mut res: Vec<isize> = Vec::with_capacity(values.len());
                            for v in values {
                                if let JsonValue::Bool(b) = v {
                                    res.push(b as isize)
                                } else {
                                    return Err(RuntimeError(
                                        "Unsupported heterogenous list".into(),
                                    ));
                                }
                            }
                            Ok(Self::VecInteger(res))
                        }
                        JsonValue::Number(first_number) => {
                            if first_number.as_f64().is_some() {
                                let mut res: Vec<f64> = Vec::with_capacity(values.len());
                                for v in values {
                                    if let JsonValue::Number(number) = v {
                                        if let Some(f) = number.as_f64() {
                                            res.push(f);
                                            continue;
                                        }
                                    }
                                    return Err(RuntimeError(
                                        "Unsupported heterogenous list".into(),
                                    ));
                                }
                                Ok(Self::VecFloat(res))
                            } else if first_number.as_i64().is_some() {
                                let mut res: Vec<isize> = Vec::with_capacity(values.len());
                                for v in values {
                                    if let JsonValue::Number(number) = v {
                                        if let Some(i) = number.as_i64() {
                                            res.push(i as isize);
                                            continue;
                                        }
                                    }
                                    return Err(RuntimeError(
                                        "Unsupported heterogenous list".into(),
                                    ));
                                }
                                Ok(Self::VecInteger(res))
                            } else {
                                return Err(RuntimeError("Invalid number from json value".into()));
                            }
                        }
                    }
                } else {
                    // Empty list
                    Ok(Self::VecInteger(Vec::new()))
                }
            }
            JsonValue::Object(map) => Ok(Self::String(
                serde_json::to_string(&map).map_err(|e| RlineError::runtime_error(&e))?,
            )),
        }
    }
}

impl Display for Value {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Value::String(v) => f.write_str(v),
            Value::Integer(v) => f.write_str(&v.to_string()),
            Value::Float(v) => f.write_str(&v.to_string()),
            Value::VecString(v) => f.write_str(&format!("[{}]", v.to_vec().join(","))),
            Value::VecInteger(v) => f.write_str(&format!(
                "[{}]",
                v.iter()
                    .map(|i| i.to_string())
                    .collect::<Vec<String>>()
                    .join(",")
            )),
            Value::VecFloat(v) => f.write_str(&format!(
                "[{}]",
                v.iter()
                    .map(|i| i.to_string())
                    .collect::<Vec<String>>()
                    .join(",")
            )),
            Value::VecU8(v) => f.write_str(&format!(
                "[{}]",
                v.iter()
                    .map(|i| i.to_string())
                    .collect::<Vec<String>>()
                    .join(",")
            )),
        }
    }
}

impl From<String> for Value {
    fn from(value: String) -> Self {
        Self::String(value)
    }
}

impl From<isize> for Value {
    fn from(value: isize) -> Self {
        Self::Integer(value)
    }
}

impl From<f64> for Value {
    fn from(value: f64) -> Self {
        Self::Float(value)
    }
}

impl From<Vec<String>> for Value {
    fn from(value: Vec<String>) -> Self {
        Self::VecString(value)
    }
}

impl From<Vec<isize>> for Value {
    fn from(value: Vec<isize>) -> Self {
        Self::VecInteger(value)
    }
}

impl From<Vec<f64>> for Value {
    fn from(value: Vec<f64>) -> Self {
        Self::VecFloat(value)
    }
}

impl From<Vec<u8>> for Value {
    fn from(value: Vec<u8>) -> Self {
        Self::VecU8(value)
    }
}

impl TryFrom<JsonValue> for Value {
    type Error = RlineError;

    fn try_from(value: JsonValue) -> Result<Self, Self::Error> {
        match value {
            JsonValue::String(v) => Ok(Self::String(v)),
            JsonValue::Number(v) => match v.as_f64() {
                Some(w) => Ok(Self::Float(w)),
                None => Ok(Self::Integer(
                    v.as_i64()
                        .ok_or(BadTypeError("Not an integer".to_string()))?
                        as isize,
                )),
            },
            JsonValue::Array(v) => v.try_into(),
            JsonValue::Null => Ok(Self::String("".to_string())),
            JsonValue::Bool(v) => Ok(Self::Integer(v as isize)),
            JsonValue::Object(v) => Ok(Self::String(json!(v).to_string())),
        }
    }
}

impl TryInto<JsonValue> for Value {
    type Error = RlineError;

    fn try_into(self) -> Result<JsonValue, Self::Error> {
        match self {
            Value::String(v) => to_value(v).map_err(|e| RlineError::runtime_error(&e)),
            Value::Integer(v) => to_value(v).map_err(|e| RlineError::runtime_error(&e)),
            Value::Float(v) => to_value(v).map_err(|e| RlineError::runtime_error(&e)),
            Value::VecString(v) => to_value(v).map_err(|e| RlineError::runtime_error(&e)),
            Value::VecInteger(v) => to_value(v).map_err(|e| RlineError::runtime_error(&e)),
            Value::VecFloat(v) => to_value(v).map_err(|e| RlineError::runtime_error(&e)),
            Value::VecU8(v) => to_value(v).map_err(|e| RlineError::runtime_error(&e)),
        }
    }
}

impl TryFrom<Vec<JsonValue>> for Value {
    type Error = RlineError;

    fn try_from(value: Vec<JsonValue>) -> Result<Self, Self::Error> {
        match value.first() {
            Some(first) => match first {
                JsonValue::String(_)
                | JsonValue::Array(_)
                | JsonValue::Object(_)
                | JsonValue::Null => Ok(Self::VecString(
                    value.iter().map(|e| e.to_string()).collect(),
                )),
                JsonValue::Number(_) | JsonValue::Bool(_) => {
                    if value.iter().any(|e| e.is_f64()) {
                        let mut res = Vec::new();
                        for e in value.iter() {
                            match e {
                                JsonValue::Number(v) => {
                                    res.push(v.as_f64().ok_or(BadTypeError("".to_string()))?)
                                }
                                JsonValue::Bool(v) => res.push(*v as i8 as f64),
                                _ => return Err(BadTypeError("".to_string())),
                            }
                        }
                        Ok(Self::VecFloat(res))
                    } else {
                        let mut res = Vec::new();
                        for e in value.iter() {
                            match e {
                                JsonValue::Number(v) => res
                                    .push(v.as_i64().ok_or(BadTypeError("".to_string()))? as isize),
                                JsonValue::Bool(v) => res.push(*v as isize),
                                _ => return Err(BadTypeError("".to_string())),
                            }
                        }
                        Ok(Self::VecInteger(res))
                    }
                }
            },
            None => Ok(Self::VecString(vec![])),
        }
    }
}