use crate::error::{Result, TqlError};
use serde_json::Value as JsonValue;
pub fn get_field<'a>(record: &'a JsonValue, field_path: &str) -> Result<Option<&'a JsonValue>> {
let parts: Vec<&str> = field_path.split('.').collect();
let mut current = record;
for part in parts {
match current {
JsonValue::Object(map) => {
match map.get(part) {
Some(value) => current = value,
None => return Ok(None), }
}
JsonValue::Array(arr) => {
if let Ok(index) = part.parse::<usize>() {
match arr.get(index) {
Some(value) => current = value,
None => return Ok(None), }
} else {
return Ok(None);
}
}
_ => {
return Ok(None);
}
}
}
Ok(Some(current))
}
pub fn field_exists(record: &JsonValue, field_path: &str) -> Result<bool> {
Ok(get_field(record, field_path)?.is_some())
}
pub fn get_field_as_string(record: &JsonValue, field_path: &str) -> Result<Option<String>> {
match get_field(record, field_path)? {
Some(JsonValue::String(s)) => Ok(Some(s.clone())),
Some(JsonValue::Number(n)) => Ok(Some(n.to_string())),
Some(JsonValue::Bool(b)) => Ok(Some(b.to_string())),
Some(JsonValue::Null) => Ok(Some("null".to_string())),
Some(_) => Ok(None), None => Ok(None),
}
}
pub fn get_field_as_i64(record: &JsonValue, field_path: &str) -> Result<Option<i64>> {
match get_field(record, field_path)? {
Some(JsonValue::Number(n)) => Ok(n.as_i64()),
Some(JsonValue::String(s)) => Ok(s.parse::<i64>().ok()),
Some(_) => Ok(None),
None => Ok(None),
}
}
pub fn get_field_as_f64(record: &JsonValue, field_path: &str) -> Result<Option<f64>> {
match get_field(record, field_path)? {
Some(JsonValue::Number(n)) => Ok(n.as_f64()),
Some(JsonValue::String(s)) => Ok(s.parse::<f64>().ok()),
Some(_) => Ok(None),
None => Ok(None),
}
}
pub fn get_field_as_bool(record: &JsonValue, field_path: &str) -> Result<Option<bool>> {
match get_field(record, field_path)? {
Some(JsonValue::Bool(b)) => Ok(Some(*b)),
Some(JsonValue::String(s)) => {
let lower = s.to_lowercase();
match lower.as_str() {
"true" | "yes" | "1" => Ok(Some(true)),
"false" | "no" | "0" => Ok(Some(false)),
_ => Ok(None),
}
}
Some(JsonValue::Number(n)) => {
if let Some(i) = n.as_i64() {
Ok(Some(i != 0))
} else {
Ok(None)
}
}
Some(_) => Ok(None),
None => Ok(None),
}
}
pub fn get_field_as_array<'a>(
record: &'a JsonValue,
field_path: &str,
) -> Result<Option<&'a Vec<JsonValue>>> {
match get_field(record, field_path)? {
Some(JsonValue::Array(arr)) => Ok(Some(arr)),
Some(_) => Ok(None),
None => Ok(None),
}
}
pub fn set_field(record: &mut JsonValue, field_path: &str, value: JsonValue) -> Result<()> {
let parts: Vec<&str> = field_path.split('.').collect();
if parts.is_empty() {
return Err(TqlError::FieldError(format!(
"Empty field path: {}",
field_path
)));
}
let mut current = record;
for (i, part) in parts.iter().enumerate() {
if i == parts.len() - 1 {
match current {
JsonValue::Object(map) => {
map.insert(part.to_string(), value);
return Ok(());
}
_ => {
return Err(TqlError::FieldError(format!(
"Cannot set field '{}' on non-object",
field_path
)));
}
}
} else {
match current {
JsonValue::Object(map) => {
current = map
.entry(part.to_string())
.or_insert_with(|| JsonValue::Object(serde_json::Map::new()));
}
_ => {
return Err(TqlError::FieldError(format!(
"Cannot navigate through non-object at '{}' in path '{}'",
part, field_path
)));
}
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_get_field_simple() {
let record = json!({
"name": "John",
"age": 30
});
let name = get_field(&record, "name").unwrap();
assert_eq!(name, Some(&json!("John")));
let age = get_field(&record, "age").unwrap();
assert_eq!(age, Some(&json!(30)));
}
#[test]
fn test_get_field_nested() {
let record = json!({
"user": {
"profile": {
"name": "John",
"age": 30
}
}
});
let name = get_field(&record, "user.profile.name").unwrap();
assert_eq!(name, Some(&json!("John")));
}
#[test]
fn test_get_field_nonexistent() {
let record = json!({
"name": "John"
});
let result = get_field(&record, "nonexistent").unwrap();
assert_eq!(result, None);
let result = get_field(&record, "user.profile.name").unwrap();
assert_eq!(result, None);
}
#[test]
fn test_get_field_array_index() {
let record = json!({
"tags": ["rust", "tql", "parser"]
});
let tag = get_field(&record, "tags.1").unwrap();
assert_eq!(tag, Some(&json!("tql")));
}
#[test]
fn test_field_exists() {
let record = json!({
"user": {
"name": "John"
}
});
assert!(field_exists(&record, "user.name").unwrap());
assert!(!field_exists(&record, "user.age").unwrap());
}
#[test]
fn test_get_field_as_string() {
let record = json!({
"name": "John",
"age": 30,
"active": true
});
assert_eq!(
get_field_as_string(&record, "name").unwrap(),
Some("John".to_string())
);
assert_eq!(
get_field_as_string(&record, "age").unwrap(),
Some("30".to_string())
);
assert_eq!(
get_field_as_string(&record, "active").unwrap(),
Some("true".to_string())
);
}
#[test]
fn test_get_field_as_i64() {
let record = json!({
"age": 30,
"count": "42"
});
assert_eq!(get_field_as_i64(&record, "age").unwrap(), Some(30));
assert_eq!(get_field_as_i64(&record, "count").unwrap(), Some(42));
}
#[test]
fn test_get_field_as_bool() {
let record = json!({
"active": true,
"enabled": "yes",
"disabled": "no"
});
assert_eq!(get_field_as_bool(&record, "active").unwrap(), Some(true));
assert_eq!(get_field_as_bool(&record, "enabled").unwrap(), Some(true));
assert_eq!(get_field_as_bool(&record, "disabled").unwrap(), Some(false));
}
#[test]
fn test_set_field() {
let mut record = json!({});
set_field(&mut record, "name", json!("John")).unwrap();
assert_eq!(get_field(&record, "name").unwrap(), Some(&json!("John")));
set_field(&mut record, "user.profile.age", json!(30)).unwrap();
assert_eq!(
get_field(&record, "user.profile.age").unwrap(),
Some(&json!(30))
);
}
}