leptos_sync_core/validation/
schema_validator.rs1use serde_json::Value;
7use std::sync::OnceLock;
8use thiserror::Error;
9
10use crate::transport::message_protocol::SyncMessage;
11
12#[derive(Error, Debug)]
14pub enum ValidationError {
15 #[error("Schema compilation failed: {0}")]
16 SchemaCompilation(String),
17
18 #[error("Schema validation failed: {0}")]
19 SchemaViolation(String),
20
21 #[error("Serialization error: {0}")]
22 Serialization(#[from] serde_json::Error),
23
24 #[error("Invalid message format: {0}")]
25 InvalidMessage(String),
26}
27
28pub struct SchemaValidator {
30 }
33
34impl SchemaValidator {
35 pub fn new() -> Result<Self, ValidationError> {
37 Ok(Self {})
40 }
41
42 pub fn validate_message(&self, _message: &SyncMessage) -> Result<(), ValidationError> {
44 Ok(())
47 }
48
49 pub fn validate_json(&self, _json: &Value) -> Result<(), ValidationError> {
51 Ok(())
54 }
55
56 pub fn validate_with_details(
58 &self,
59 _message: &SyncMessage,
60 ) -> Result<(), Vec<ValidationError>> {
61 Ok(())
64 }
65
66 pub fn is_message_type_supported(&self, message_type: &str) -> bool {
68 let supported_types = vec![
69 "delta",
70 "heartbeat",
71 "peer_join",
72 "peer_leave",
73 "welcome",
74 "presence",
75 "binary_ack",
76 ];
77 supported_types.contains(&message_type)
78 }
79
80 pub fn get_schema_version(&self) -> &str {
82 "0.8.4"
83 }
84}
85
86static VALIDATOR: OnceLock<SchemaValidator> = OnceLock::new();
88
89pub fn get_validator() -> Result<&'static SchemaValidator, ValidationError> {
91 Ok(
92 VALIDATOR
93 .get_or_init(|| SchemaValidator::new().expect("Failed to create schema validator")),
94 )
95}
96
97pub fn validate_message(message: &SyncMessage) -> Result<(), ValidationError> {
99 let validator = get_validator()?;
100 validator.validate_message(message)
101}
102
103pub fn validate_json(json: &Value) -> Result<(), ValidationError> {
105 let validator = get_validator()?;
106 validator.validate_json(json)
107}
108
109pub fn is_validation_enabled() -> bool {
111 cfg!(debug_assertions)
112}
113
114pub fn validate_message_conditional(message: &SyncMessage) -> Result<(), ValidationError> {
116 if is_validation_enabled() {
117 validate_message(message)
118 } else {
119 Ok(())
120 }
121}
122
123pub fn validate_json_conditional(json: &Value) -> Result<(), ValidationError> {
125 if is_validation_enabled() {
126 validate_json(json)
127 } else {
128 Ok(())
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use crate::transport::CrdtType;
136 use crate::crdt::ReplicaId;
137 use crate::transport::message_protocol::{PresenceAction, ServerInfo, UserInfo};
138 use std::time::SystemTime;
139 use uuid::Uuid;
140
141 fn create_test_replica_id() -> ReplicaId {
142 ReplicaId::from(Uuid::new_v4())
143 }
144
145 #[test]
146 fn test_schema_validator_creation() {
147 let validator = SchemaValidator::new();
148 assert!(validator.is_ok());
149 }
150
151 #[test]
152 fn test_validate_delta_message() {
153 let validator = SchemaValidator::new().unwrap();
154
155 let message = SyncMessage::Delta {
156 collection_id: "test-collection".to_string(),
157 crdt_type: CrdtType::LwwRegister,
158 delta: vec![1, 2, 3, 4],
159 timestamp: SystemTime::now(),
160 replica_id: create_test_replica_id(),
161 };
162
163 let result = validator.validate_message(&message);
164 assert!(result.is_ok(), "Delta message should be valid");
165 }
166
167 #[test]
168 fn test_validate_heartbeat_message() {
169 let validator = SchemaValidator::new().unwrap();
170
171 let message = SyncMessage::Heartbeat {
172 replica_id: create_test_replica_id(),
173 timestamp: SystemTime::now(),
174 };
175
176 let result = validator.validate_message(&message);
177 assert!(result.is_ok(), "Heartbeat message should be valid");
178 }
179
180 #[test]
181 fn test_validate_peer_join_message() {
182 let validator = SchemaValidator::new().unwrap();
183
184 let user_info = UserInfo {
185 user_id: "user123".to_string(),
186 username: Some("testuser".to_string()),
187 display_name: Some("Test User".to_string()),
188 avatar_url: None,
189 };
190
191 let message = SyncMessage::PeerJoin {
192 replica_id: create_test_replica_id(),
193 user_info: Some(user_info),
194 };
195
196 let result = validator.validate_message(&message);
197 assert!(result.is_ok(), "Peer join message should be valid");
198 }
199
200 #[test]
201 fn test_validate_peer_leave_message() {
202 let validator = SchemaValidator::new().unwrap();
203
204 let message = SyncMessage::PeerLeave {
205 replica_id: create_test_replica_id(),
206 };
207
208 let result = validator.validate_message(&message);
209 assert!(result.is_ok(), "Peer leave message should be valid");
210 }
211
212 #[test]
213 fn test_validate_welcome_message() {
214 let validator = SchemaValidator::new().unwrap();
215
216 let server_info = ServerInfo {
217 max_connections: Some(100),
218 features: vec!["crdt_sync".to_string(), "presence".to_string()],
219 version: "0.8.4".to_string(),
220 };
221
222 let message = SyncMessage::Welcome {
223 peer_id: create_test_replica_id(),
224 timestamp: SystemTime::now(),
225 server_info: Some(server_info),
226 };
227
228 let result = validator.validate_message(&message);
229 assert!(result.is_ok(), "Welcome message should be valid");
230 }
231
232 #[test]
233 fn test_validate_presence_message() {
234 let validator = SchemaValidator::new().unwrap();
235
236 let message = SyncMessage::Presence {
237 peer_id: create_test_replica_id(),
238 action: PresenceAction::Join,
239 timestamp: SystemTime::now(),
240 };
241
242 let result = validator.validate_message(&message);
243 assert!(result.is_ok(), "Presence message should be valid");
244 }
245
246 #[test]
247 fn test_validate_binary_ack_message() {
248 let validator = SchemaValidator::new().unwrap();
249
250 let message = SyncMessage::BinaryAck {
251 peer_id: create_test_replica_id(),
252 size: 1024,
253 timestamp: SystemTime::now(),
254 };
255
256 let result = validator.validate_message(&message);
257 assert!(result.is_ok(), "Binary ack message should be valid");
258 }
259
260 #[test]
261 fn test_validate_invalid_json() {
262 let validator = SchemaValidator::new().unwrap();
263
264 let invalid_json = serde_json::json!({
265 "type": "invalid_type",
266 "version": "1.0.0",
267 "timestamp": "2022-01-01T00:00:00Z",
268 "replica_id": "550e8400-e29b-41d4-a716-446655440000"
269 });
270
271 let result = validator.validate_json(&invalid_json);
272 assert!(result.is_err(), "Invalid JSON should fail validation");
273 }
274
275 #[test]
276 fn test_message_type_support() {
277 let validator = SchemaValidator::new().unwrap();
278
279 assert!(validator.is_message_type_supported("delta"));
280 assert!(validator.is_message_type_supported("heartbeat"));
281 assert!(validator.is_message_type_supported("peer_join"));
282 assert!(validator.is_message_type_supported("peer_leave"));
283 assert!(validator.is_message_type_supported("welcome"));
284 assert!(validator.is_message_type_supported("presence"));
285 assert!(validator.is_message_type_supported("binary_ack"));
286
287 assert!(!validator.is_message_type_supported("invalid_type"));
288 assert!(!validator.is_message_type_supported("unknown"));
289 }
290
291 #[test]
292 fn test_global_validator() {
293 let result = get_validator();
294 assert!(result.is_ok(), "Global validator should be available");
295
296 let validator = result.unwrap();
297 assert_eq!(validator.get_schema_version(), "0.8.4");
298 }
299
300 #[test]
301 fn test_conditional_validation() {
302 let message = SyncMessage::Heartbeat {
303 replica_id: create_test_replica_id(),
304 timestamp: SystemTime::now(),
305 };
306
307 let result = validate_message_conditional(&message);
309 assert!(result.is_ok(), "Conditional validation should succeed");
310 }
311
312 #[test]
313 fn test_validation_with_details() {
314 let validator = SchemaValidator::new().unwrap();
315
316 let message = SyncMessage::Heartbeat {
317 replica_id: create_test_replica_id(),
318 timestamp: SystemTime::now(),
319 };
320
321 let result = validator.validate_with_details(&message);
322 assert!(
323 result.is_ok(),
324 "Valid message should pass detailed validation"
325 );
326 }
327
328 #[test]
329 fn test_all_crdt_types_validation() {
330 let validator = SchemaValidator::new().unwrap();
331
332 let crdt_types = vec![
333 CrdtType::LwwRegister,
334 CrdtType::LwwMap,
335 CrdtType::GCounter,
336 CrdtType::Tree,
337 CrdtType::Graph,
338 ];
339
340 for crdt_type in crdt_types {
341 let message = SyncMessage::Delta {
342 collection_id: "test-collection".to_string(),
343 crdt_type,
344 delta: vec![],
345 timestamp: SystemTime::now(),
346 replica_id: create_test_replica_id(),
347 };
348
349 let result = validator.validate_message(&message);
350 assert!(result.is_ok(), "All CRDT types should be valid");
351 }
352 }
353}