use serde_json::Value;
use std::collections::HashMap;
pub fn get_value(field: &Value) -> Value {
if let Value::Object(map) = field {
if let Some(value) = map.get("value") {
return value.clone();
}
}
field.clone()
}
pub fn get_string_value(field: &Value) -> Option<String> {
get_value(field).as_str().map(|s| s.to_string())
}
pub fn get_int_value(field: &Value) -> Option<i64> {
get_value(field).as_i64()
}
pub fn get_float_value(field: &Value) -> Option<f64> {
get_value(field).as_f64()
}
pub fn get_bool_value(field: &Value) -> Option<bool> {
get_value(field).as_bool()
}
pub fn get_datetime_value(field: &Value) -> Option<String> {
get_string_value(field)
}
pub fn get_uuid_value(field: &Value) -> Option<String> {
get_string_value(field)
}
pub fn get_decimal_value(field: &Value) -> Option<f64> {
get_float_value(field)
}
pub fn get_duration_value(field: &Value) -> Option<i64> {
let val = get_value(field);
if let Some(i) = val.as_i64() {
return Some(i);
}
if let Some(obj) = val.as_object() {
let secs = obj.get("secs")?.as_i64().unwrap_or(0);
let nanos = obj.get("nanos")?.as_i64().unwrap_or(0);
return Some(secs * 1000 + nanos / 1_000_000);
}
None
}
pub fn get_bytes_value(field: &Value) -> Option<Vec<u8>> {
let val = get_value(field);
if let Some(arr) = val.as_array() {
return Some(
arr.iter()
.filter_map(|v| v.as_u64().map(|n| n as u8))
.collect(),
);
}
if let Some(s) = val.as_str() {
return base64::Engine::decode(&base64::engine::general_purpose::STANDARD, s).ok();
}
None
}
pub fn get_binary_value(field: &Value) -> Option<Vec<u8>> {
get_bytes_value(field)
}
pub fn get_array_value(field: &Value) -> Option<Vec<Value>> {
let val = get_value(field);
val.as_array().cloned()
}
pub fn get_set_value(field: &Value) -> Option<Vec<Value>> {
let val = get_value(field);
val.as_array().cloned()
}
pub fn get_vector_value(field: &Value) -> Option<Vec<f64>> {
let val = get_value(field);
if let Some(arr) = val.as_array() {
return Some(arr.iter().filter_map(|v| v.as_f64()).collect());
}
None
}
pub fn get_object_value(field: &Value) -> Option<serde_json::Map<String, Value>> {
let val = get_value(field);
val.as_object().cloned()
}
pub fn get_values(record: &Value, fields: &[String]) -> HashMap<String, Value> {
let mut result = HashMap::new();
if let Value::Object(map) = record {
for field in fields {
if let Some(value) = map.get(field) {
result.insert(field.clone(), get_value(value));
}
}
}
result
}
const ID_CANDIDATES: &[&str] = &["id", "_id"];
pub fn extract_record_id(record: &Value, extra_candidates: &[&str]) -> Option<String> {
if let Value::Object(map) = record {
for key in extra_candidates {
if let Some(val) = map.get(*key) {
if let Some(s) = get_string_value(val) {
return Some(s);
}
}
}
for key in ID_CANDIDATES {
if let Some(val) = map.get(*key) {
if let Some(s) = get_string_value(val) {
return Some(s);
}
}
}
}
None
}
pub fn extract_record(record: &Value) -> Value {
if let Value::Object(map) = record {
let mut result = serde_json::Map::new();
for (key, value) in map {
if ID_CANDIDATES.contains(&key.as_str()) {
result.insert(key.clone(), value.clone());
} else {
result.insert(key.clone(), get_value(value));
}
}
Value::Object(result)
} else {
record.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_get_value_from_field_object() {
let field = json!({"type": "String", "value": "test@example.com"});
let result = get_value(&field);
assert_eq!(result, json!("test@example.com"));
}
#[test]
fn test_get_value_from_plain_value() {
let field = json!("direct_value");
let result = get_value(&field);
assert_eq!(result, json!("direct_value"));
}
#[test]
fn test_get_string_value() {
let field = json!({"type": "String", "value": "hello"});
let result = get_string_value(&field);
assert_eq!(result, Some("hello".to_string()));
}
#[test]
fn test_get_int_value() {
let field = json!({"type": "Integer", "value": 42});
let result = get_int_value(&field);
assert_eq!(result, Some(42));
}
#[test]
fn test_extract_record() {
let record = json!({
"id": "user123",
"email": {"type": "String", "value": "user@example.com"},
"age": {"type": "Integer", "value": 30}
});
let result = extract_record(&record);
assert_eq!(result["id"], "user123");
assert_eq!(result["email"], "user@example.com");
assert_eq!(result["age"], 30);
}
#[test]
fn test_extract_record_id_default() {
let record = json!({"id": "abc123", "name": "Alice"});
assert_eq!(extract_record_id(&record, &[]), Some("abc123".to_string()));
}
#[test]
fn test_extract_record_id_underscore() {
let record = json!({"_id": "def456", "name": "Bob"});
assert_eq!(extract_record_id(&record, &[]), Some("def456".to_string()));
}
#[test]
fn test_extract_record_id_custom_alias() {
let record = json!({"user_id": "ghi789", "name": "Charlie"});
assert_eq!(
extract_record_id(&record, &["user_id"]),
Some("ghi789".to_string())
);
}
#[test]
fn test_extract_record_id_custom_takes_priority() {
let record = json!({"id": "default", "user_id": "custom", "name": "Dave"});
assert_eq!(
extract_record_id(&record, &["user_id"]),
Some("custom".to_string())
);
}
#[test]
fn test_extract_record_id_typed_wrapper() {
let record = json!({"id": {"type": "String", "value": "wrapped123"}, "name": "Eve"});
assert_eq!(
extract_record_id(&record, &[]),
Some("wrapped123".to_string())
);
}
#[test]
fn test_extract_record_id_missing() {
let record = json!({"name": "NoId"});
assert_eq!(extract_record_id(&record, &[]), None);
}
#[test]
fn test_extract_record_id_not_object() {
let record = json!("just a string");
assert_eq!(extract_record_id(&record, &[]), None);
}
}