gun/
valid.rs

1//! Data validation and soul reference detection
2//!
3//! This module validates data values according to Gun's rules and detects soul references.
4//! Based on Gun.js `valid.js` - matches the exact validation logic.
5//!
6//! ## Valid Data Types
7//!
8//! - `null` - Valid (represents deletion)
9//! - `string` - Always valid
10//! - `boolean` - Always valid
11//! - `number` - Valid if not `Infinity` or `NaN`
12//! - Soul reference - Object with only `{"#": "soul_id"}` key
13//! - Objects - Not directly valid (must be stored as nodes)
14//! - Arrays - Not directly supported
15
16use serde_json::Value;
17
18/// Validate a value according to Gun's rules and detect soul references
19///
20/// This is the main validation function matching Gun.js `valid()`. It checks if
21/// a value is valid for storage in Gun and detects if it's a soul reference.
22///
23/// # Arguments
24/// * `value` - The JSON value to validate
25///
26/// # Returns
27///
28/// - `Ok(true)` - Valid simple value (null, string, boolean, or valid number)
29/// - `Err(Some(soul))` - It's a soul reference object `{"#": soul}`
30/// - `Ok(false)` - Invalid value (Infinity, NaN, array, or complex object)
31///
32/// # Example
33///
34/// ```rust,no_run
35/// use gun::valid::valid;
36/// use serde_json::json;
37///
38/// // Valid simple value
39/// assert_eq!(valid(&json!("hello")), Ok(true));
40///
41/// // Valid number
42/// assert_eq!(valid(&json!(42)), Ok(true));
43///
44/// // Invalid number
45/// assert_eq!(valid(&json!(f64::INFINITY)), Ok(false));
46///
47/// // Soul reference
48/// match valid(&json!({"#": "user_123"})) {
49///     Err(Some(soul)) => println!("Found soul reference: {}", soul),
50///     _ => {}
51/// }
52/// ```
53pub fn valid(value: &Value) -> Result<bool, Option<String>> {
54    match value {
55        Value::Null => Ok(true), // null is valid (deletes)
56
57        Value::String(_) => Ok(true), // strings are always valid
58
59        Value::Bool(_) => Ok(true), // booleans are valid
60
61        Value::Number(n) => {
62            // Numbers are valid if not Infinity and not NaN
63            // v === v checks for NaN (NaN !== NaN)
64            if let Some(f) = n.as_f64() {
65                if f.is_finite() && !f.is_nan() {
66                    Ok(true)
67                } else {
68                    Ok(false) // Infinity or NaN
69                }
70            } else {
71                Ok(true) // Integer is always valid
72            }
73        }
74
75        Value::Object(obj) => {
76            // Check if it's a soul reference: object with only "#" key
77            if obj.len() == 1 {
78                if let Some(soul_val) = obj.get("#") {
79                    if let Some(soul) = soul_val.as_str() {
80                        // It's a soul reference
81                        return Err(Some(soul.to_string()));
82                    }
83                }
84            }
85            // Regular objects are not directly valid (must be nodes with soul)
86            // But we allow them for put() to handle
87            Ok(false)
88        }
89
90        Value::Array(_) => {
91            // Arrays are not directly supported in Gun.js
92            // They need special algorithms for concurrency
93            Ok(false)
94        }
95    }
96}
97
98/// Check if a value is a valid soul reference
99///
100/// This checks if a value represents a soul reference, either as:
101/// - A soul reference object: `{"#": "soul_id"}`
102/// - A string that could be a soul ID
103///
104/// # Arguments
105/// * `value` - The JSON value to check
106///
107/// # Returns
108/// `Some(soul_string)` if it's a soul reference, `None` otherwise.
109///
110/// # Example
111///
112/// ```rust,no_run
113/// use gun::valid::valid_soul;
114/// use serde_json::json;
115///
116/// // Soul reference object
117/// assert_eq!(valid_soul(&json!({"#": "user_123"})), Some("user_123".to_string()));
118///
119/// // Soul string
120/// assert_eq!(valid_soul(&json!("user_123")), Some("user_123".to_string()));
121///
122/// // Not a soul
123/// assert_eq!(valid_soul(&json!("hello")), Some("hello".to_string())); // Strings are treated as potential souls
124/// ```
125pub fn valid_soul(value: &Value) -> Option<String> {
126    match valid(value) {
127        Err(Some(soul)) => Some(soul), // It's a soul reference object
128        Ok(true) => {
129            // Check if it's a string that could be a soul
130            if let Value::String(s) = value {
131                if !s.is_empty() {
132                    Some(s.clone())
133                } else {
134                    None
135                }
136            } else {
137                None
138            }
139        }
140        _ => None,
141    }
142}
143
144/// Check if data is valid for storage in Gun
145///
146/// This is a convenience function that returns `true` if the value is either:
147/// - A valid simple value (validated by [`valid`](valid))
148/// - A soul reference
149///
150/// # Arguments
151/// * `value` - The JSON value to check
152///
153/// # Returns
154/// `true` if the value is valid for Gun, `false` otherwise.
155///
156/// # Example
157///
158/// ```rust,no_run
159/// use gun::valid::is_valid_data;
160/// use serde_json::json;
161///
162/// assert!(is_valid_data(&json!("hello")));
163/// assert!(is_valid_data(&json!(42)));
164/// assert!(is_valid_data(&json!({"#": "user_123"})));
165/// assert!(!is_valid_data(&json!(f64::INFINITY)));
166/// ```
167pub fn is_valid_data(value: &Value) -> bool {
168    match valid(value) {
169        Ok(true) => true,
170        Err(Some(_)) => true, // Soul reference is valid
171        _ => false,
172    }
173}