use qubit_datatype::{
DataConversionError,
DataConversionOptions,
DataConverter,
DataListConversionError,
DataType,
InvalidValueReason,
};
use serde_json::{
Number,
Value as JsonValue,
};
use std::str::FromStr;
use crate::multi_values::MultiValuesRepr;
use crate::value::ValueRepr;
use crate::{
MultiValues,
Value,
ValueContainer,
ValueError,
ValueResult,
};
fn finite_float64(
value: f64,
from: DataType,
) -> Result<JsonValue, DataConversionError> {
Number::from_f64(value).map(JsonValue::Number).ok_or(
DataConversionError::invalid(
from,
DataType::Json,
InvalidValueReason::NonFinite,
),
)
}
fn finite_float32(
value: f32,
from: DataType,
) -> Result<JsonValue, DataConversionError> {
Number::from_str(&value.to_string())
.map(JsonValue::Number)
.map_err(|_| {
DataConversionError::invalid(
from,
DataType::Json,
InvalidValueReason::NonFinite,
)
})
}
macro_rules! scalar_to_json {
(json_bool, $value:expr, $from:expr, $options:expr) => {
Ok(JsonValue::Bool(*$value))
};
(json_number, $value:expr, $from:expr, $options:expr) => {
Ok(JsonValue::from(*$value))
};
(json_float32, $value:expr, $from:expr, $options:expr) => {
finite_float32(*$value, $from)
};
(json_float64, $value:expr, $from:expr, $options:expr) => {
finite_float64(*$value as f64, $from)
};
(json_string, $value:expr, $from:expr, $options:expr) => {
Ok(JsonValue::String($value.to_string()))
};
(json_duration, $value:expr, $from:expr, $options:expr) => {
DataConverter::from(*$value)
.to_with::<String>($options)
.map(JsonValue::String)
};
(json_object, $value:expr, $from:expr, $options:expr) => {{
let mut entries: Vec<_> = $value.iter().collect();
entries.sort_unstable_by(|(left, _), (right, _)| left.cmp(right));
let mut object = serde_json::Map::with_capacity(entries.len());
for (key, value) in entries {
object.insert(key.clone(), JsonValue::String(value.clone()));
}
Ok(JsonValue::Object(object))
}};
(json_identity, $value:expr, $from:expr, $options:expr) => {
Ok(crate::wire::json::canonicalize_json_value($value))
};
}
macro_rules! value_to_json_match {
($value:expr, $options:expr; $(([$($cfg:meta),*], $variant:ident, $type:ty, $data_type:expr, $materialization:ident, $json_class:ident, $number_projection:ident, $value_doc:literal, $multi_doc:literal)),+ $(,)?) => {{
let result: Result<JsonValue, DataConversionError> = match &$value.repr {
ValueRepr::Unset(_) => Ok(JsonValue::Null),
$($(#[$cfg])* ValueRepr::$variant(value) => {
scalar_to_json!($json_class, value, $data_type, $options)
},)+
};
result.map_err(ValueError::from)
}};
}
fn collection_to_json<T, F>(
values: &[T],
mut project: F,
) -> ValueResult<JsonValue>
where
F: FnMut(&T) -> Result<JsonValue, DataConversionError>,
{
let mut projected = Vec::with_capacity(values.len());
for (source_index, value) in values.iter().enumerate() {
match project(value) {
Ok(value) => projected.push(value),
Err(source) => {
return Err(
DataListConversionError::new(source_index, source).into()
);
}
}
}
Ok(JsonValue::Array(projected))
}
macro_rules! multi_values_to_json_match {
($value:expr, $options:expr; $(([$($cfg:meta),*], $variant:ident, $type:ty, $data_type:expr, $materialization:ident, $json_class:ident, $number_projection:ident, $value_doc:literal, $multi_doc:literal)),+ $(,)?) => {
match &$value.repr {
MultiValuesRepr::Unset(_) => Ok(JsonValue::Null),
$($(#[$cfg])* MultiValuesRepr::$variant(values) => {
collection_to_json(values, |value| {
scalar_to_json!($json_class, value, $data_type, $options)
})
},)+
}
};
}
impl Value {
#[inline(always)]
pub fn to_json_value(&self) -> ValueResult<JsonValue> {
self.to_json_value_with(DataConversionOptions::default_ref())
}
pub fn to_json_value_with(
&self,
options: &DataConversionOptions,
) -> ValueResult<JsonValue> {
for_each_value_type!(value_to_json_match, self, options)
}
}
impl MultiValues {
#[inline(always)]
pub fn to_json_value(&self) -> ValueResult<JsonValue> {
self.to_json_value_with(DataConversionOptions::default_ref())
}
pub fn to_json_value_with(
&self,
options: &DataConversionOptions,
) -> ValueResult<JsonValue> {
for_each_value_type!(multi_values_to_json_match, self, options)
}
}
impl ValueContainer {
#[inline(always)]
pub fn to_json_value(&self) -> ValueResult<JsonValue> {
self.to_json_value_with(DataConversionOptions::default_ref())
}
#[inline(always)]
pub fn to_json_value_with(
&self,
options: &DataConversionOptions,
) -> ValueResult<JsonValue> {
match self {
Self::Scalar(value) => value.to_json_value_with(options),
Self::Collection(values) => values.to_json_value_with(options),
}
}
}