Skip to main content

ui_grid_core/
utils.rs

1use serde_json::Value;
2
3use crate::models::{GridColumnDef, GridRecord};
4
5const PROTECTED_PATH_SEGMENTS: &[&str] = &["__proto__", "constructor", "prototype"];
6
7pub fn is_null_or_undefined(value: &Value) -> bool {
8    value.is_null()
9}
10
11pub fn get_path_value(record: &GridRecord, path: &str) -> Option<Value> {
12    let mut current = record;
13
14    for part in path.split('.') {
15        if PROTECTED_PATH_SEGMENTS.contains(&part) {
16            return None;
17        }
18
19        match current {
20            Value::Object(map) => {
21                current = map.get(part)?;
22            }
23            _ => return None,
24        }
25    }
26
27    Some(current.clone())
28}
29
30pub fn get_cell_value(row: &GridRecord, column: &GridColumnDef) -> Value {
31    if let Some(field) = &column.field {
32        return get_path_value(row, field).unwrap_or(Value::Null);
33    }
34
35    match row {
36        Value::Object(map) => map.get(&column.name).cloned().unwrap_or(Value::Null),
37        _ => Value::Null,
38    }
39}
40
41pub fn stringify_cell_value(value: &Value) -> String {
42    match value {
43        Value::Null => String::new(),
44        Value::Bool(value) => value.to_string(),
45        Value::Number(value) => value.to_string(),
46        Value::String(value) => value.clone(),
47        Value::Array(values) => values
48            .iter()
49            .map(stringify_cell_value)
50            .collect::<Vec<_>>()
51            .join(", "),
52        Value::Object(_) => serde_json::to_string(value).unwrap_or_default(),
53    }
54}
55
56pub fn to_csv_value(value: &str) -> String {
57    let mut escaped = value.to_string();
58
59    if matches!(
60        escaped.chars().next(),
61        Some('=' | '+' | '-' | '@' | '\t' | '\r')
62    ) {
63        escaped = format!("'{}", escaped);
64    }
65
66    if escaped.contains([',', '"', '\n']) {
67        return format!("\"{}\"", escaped.replace('"', "\"\""));
68    }
69
70    escaped
71}
72
73pub fn titleize(value: &str) -> String {
74    let mut output = String::with_capacity(value.len());
75    let mut previous_is_lower_or_digit = false;
76
77    for ch in value.chars() {
78        if matches!(ch, '_' | '.' | '-') {
79            if !output.ends_with(' ') {
80                output.push(' ');
81            }
82            previous_is_lower_or_digit = false;
83            continue;
84        }
85
86        if previous_is_lower_or_digit && ch.is_ascii_uppercase() && !output.ends_with(' ') {
87            output.push(' ');
88        }
89
90        output.push(ch);
91        previous_is_lower_or_digit = ch.is_ascii_lowercase() || ch.is_ascii_digit();
92    }
93
94    let compact = output.split_whitespace().collect::<Vec<_>>().join(" ");
95    let mut chars = compact.chars();
96    match chars.next() {
97        Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
98        None => String::new(),
99    }
100}