use crate::errors::MontycatClientError;
use crate::request::utis::functions::is_custom_type;
use crate::traits::RuntimeSchema;
use rayon::prelude::*;
use serde::Serialize;
use serde_json::{Map, Value};
use std::any::type_name;
use std::collections::HashSet;
pub(crate) fn process_json_value<T>(value: &T) -> Result<String, MontycatClientError>
where
T: Serialize,
{
let value_to_send: String = simd_json::to_string(value)
.map_err(|e| MontycatClientError::ClientValueParsingError(e.to_string()))?;
Ok(value_to_send)
}
pub(crate) fn process_value<T>(value: T) -> Result<String, MontycatClientError>
where
T: Serialize + RuntimeSchema,
{
let pointer_and_timestamp_fields: Vec<(&'static str, &'static str)> =
value.pointer_and_timestamp_fields();
let mut val_as_map: Map<String, Value> = Map::new();
if !pointer_and_timestamp_fields.is_empty() {
let mut pointers: Map<String, Value> = Map::new();
let mut timestamps: Map<String, Value> = Map::new();
let val_as_serde = serde_json::to_value(&value)
.map_err(|e| MontycatClientError::ClientValueParsingError(e.to_string()))?;
if let Some(obj) = val_as_serde.as_object() {
val_as_map = obj.to_owned();
}
let mut removal: Vec<&str> = Vec::new();
for (field_name, field_type) in pointer_and_timestamp_fields {
if let Some(field_value) = val_as_map.get(field_name) {
if field_type == "Pointer" {
let pointing_key = field_value.get("key");
let pointing_keyspace = field_value.get("keyspace");
match (pointing_key, pointing_keyspace) {
(Some(key), Some(keyspace)) => {
let content: Value = serde_json::json!([keyspace, key]);
pointers.insert(field_name.to_string(), content);
}
_ => {
return Err(MontycatClientError::ClientNoValidInputProvided);
}
}
removal.push(field_name);
} else if field_type == "Timestamp" {
let timestamp_value: &Value = field_value
.get("timestamp")
.ok_or(MontycatClientError::ClientNoValidInputProvided)?;
timestamps.insert(field_name.to_string(), timestamp_value.clone());
removal.push(field_name);
}
}
}
for field in removal {
val_as_map.remove(field);
}
if !pointers.is_empty() {
val_as_map.insert("pointers".into(), pointers.into());
}
if !timestamps.is_empty() {
val_as_map.insert("timestamps".into(), timestamps.into());
}
}
let value_to_send: String = {
if val_as_map.is_empty() {
simd_json::to_string(&value)
.map_err(|e| MontycatClientError::ClientValueParsingError(e.to_string()))?
} else {
simd_json::to_string(&val_as_map)
.map_err(|e| MontycatClientError::ClientValueParsingError(e.to_string()))?
}
};
Ok(value_to_send)
}
pub(crate) fn define_type(field_type: &str) -> Result<(&'static str, bool), MontycatClientError> {
let cleaned = field_type.replace(' ', "");
let mut nullable = false;
if let Some(inner) = cleaned
.strip_prefix("Option<")
.and_then(|s| s.strip_suffix('>'))
{
let inner_type = define_type(inner)?;
nullable = true;
return Ok((inner_type.0, nullable));
}
let ty = match cleaned.as_str() {
"String" | "&str" | "char" => "String",
"i8" | "i16" | "i32" | "i64" | "i128" | "u8" | "u16" | "u32" | "u64" | "u128" | "isize"
| "usize" | "f32" | "f64" => "Number",
"bool" => "Boolean",
s if s.starts_with("Vec<") => "Array",
s if s.starts_with("HashSet<") => "Array",
s if s.starts_with("BTreeSet<") => "Array",
s if s.starts_with("HashMap<") => "Object",
s if s.starts_with("BTreeMap<") => "Object",
"Pointer" => "Pointer",
"Timestamp" => "Timestamp",
_ => {
return Err(MontycatClientError::ClientUnsupportedFieldType(
field_type.to_owned(),
));
}
};
Ok((ty, nullable))
}
pub(crate) async fn process_bulk_values<T>(
values: Vec<T>,
) -> Result<(String, Option<String>), MontycatClientError>
where
T: Serialize + RuntimeSchema + Send + 'static,
{
let res: (String, Option<String>) = tokio::task::spawn_blocking(move || {
let serialized_and_schemas: Result<Vec<(String, Option<String>)>, MontycatClientError> =
values
.into_par_iter()
.map(|val| {
let serialized = process_value(val)?;
let type_name_retrieved: &str = type_name::<T>();
let schema = is_custom_type(type_name_retrieved).map(|s| s.to_string());
Ok((serialized, schema))
})
.collect();
let serialized_and_schemas = serialized_and_schemas?;
let serialized_values: Vec<String> = serialized_and_schemas
.iter()
.map(|(s, _)| s.clone())
.collect();
let schemas: HashSet<String> = serialized_and_schemas
.iter()
.filter_map(|(_, s)| s.clone())
.collect();
let schema = match schemas.len() {
0 => None,
1 => Some(schemas.into_iter().next().unwrap()),
_ => return Err(MontycatClientError::ClientMultipleSchemasFound),
};
let value_to_send: String = process_json_value(&serialized_values)?;
Ok((value_to_send, schema))
})
.await
.map_err(|e| MontycatClientError::ClientAsyncRuntimeError(e.to_string()))??;
Ok(res)
}