Skip to main content

camel_api/
value.rs

1/// Value type alias for dynamic header/property values.
2pub type Value = serde_json::Value;
3
4/// Headers type alias.
5pub type Headers = std::collections::HashMap<String, Value>;
6
7/// Total order over two `serde_json::Value`s.
8///
9/// Tier: Null < Bool < Number < String < Array < Object.
10/// NaN sorts AFTER all real numbers (NaN-last), consistent with
11/// `camel_processor::sort::SortKey`.
12///
13/// Shared by batch/stream resequencing policies (I3).
14pub fn cmp_values(a: &Value, b: &Value) -> std::cmp::Ordering {
15    use serde_json::Value as J;
16    match (a, b) {
17        (J::Null, J::Null) => std::cmp::Ordering::Equal,
18        (J::Null, _) => std::cmp::Ordering::Less,
19        (_, J::Null) => std::cmp::Ordering::Greater,
20        (J::Bool(va), J::Bool(vb)) => va.cmp(vb),
21        (J::Bool(_), _) => std::cmp::Ordering::Less,
22        (_, J::Bool(_)) => std::cmp::Ordering::Greater,
23        (J::Number(va), J::Number(vb)) => {
24            let af = va.as_f64().unwrap_or(f64::NAN);
25            let bf = vb.as_f64().unwrap_or(f64::NAN);
26            // NaN-last: NaN > all real numbers; NaN == NaN
27            af.partial_cmp(&bf)
28                .unwrap_or_else(|| af.is_nan().cmp(&bf.is_nan()))
29        }
30        (J::Number(_), _) => std::cmp::Ordering::Less,
31        (_, J::Number(_)) => std::cmp::Ordering::Greater,
32        (J::String(sa), J::String(sb)) => sa.cmp(sb),
33        (J::Array(va), J::Array(vb)) => serde_json::to_string(va)
34            .unwrap_or_default()
35            .cmp(&serde_json::to_string(vb).unwrap_or_default()),
36        (J::Object(va), J::Object(vb)) => serde_json::to_string(va)
37            .unwrap_or_default()
38            .cmp(&serde_json::to_string(vb).unwrap_or_default()),
39        (J::Array(_), _) => std::cmp::Ordering::Less,
40        (_, J::Array(_)) => std::cmp::Ordering::Greater,
41        (J::Object(_), _) => std::cmp::Ordering::Less,
42        (_, J::Object(_)) => std::cmp::Ordering::Greater,
43    }
44}
45
46/// Returns a human-readable name for the JSON value variant.
47///
48/// Used by error messages that report the received value type without
49/// including value content. The catch-all keeps the match robust if
50/// `serde_json` ever grows variants and preserves the `other` output.
51#[allow(unreachable_patterns)]
52pub fn value_type_name(v: &Value) -> &'static str {
53    match v {
54        Value::String(_) => "string",
55        Value::Array(_) => "array",
56        Value::Number(_) => "number",
57        Value::Bool(_) => "boolean",
58        Value::Object(_) => "object",
59        Value::Null => "null",
60        _ => "other",
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    #[test]
69    fn test_value_type_name_variants() {
70        assert_eq!(value_type_name(&Value::String("x".into())), "string");
71        assert_eq!(value_type_name(&Value::Array(Vec::new())), "array");
72        assert_eq!(
73            value_type_name(&Value::Number(serde_json::Number::from(1))),
74            "number"
75        );
76        assert_eq!(value_type_name(&Value::Bool(true)), "boolean");
77        assert_eq!(
78            value_type_name(&Value::Object(serde_json::Map::new())),
79            "object"
80        );
81        assert_eq!(value_type_name(&Value::Null), "null");
82    }
83}