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}