-
Notifications
You must be signed in to change notification settings - Fork 1
Open
Description
Overview
The Go implementation uses the validator/v10 library for comprehensive struct validation. This provides declarative validation rules, custom validators, and automatic validation at critical points. The Rust implementation needs a similar validation framework for data integrity and security.
Background
Reference: Go implementation uses github.com/go-playground/validator/v10
Validation is critical for:
- Input sanitization
- Data integrity
- Security (preventing malformed data attacks)
- Early error detection
Requirements
1. Define Validation Traits
use thiserror::Error;
/// Trait for validatable types
pub trait Validate {
/// Validate the struct according to its rules
fn validate(&self) -> Result<(), ValidationError>;
}
/// Validation error types
#[derive(Error, Debug)]
pub enum ValidationError {
#[error("Field {field} failed validation: {message}")]
FieldError { field: String, message: String },
#[error("Multiple validation errors: {0:?}")]
MultipleErrors(Vec<ValidationError>),
#[error("Custom validation failed: {0}")]
Custom(String),
}
/// Builder pattern for validation errors
pub struct ValidationErrors {
errors: Vec<ValidationError>,
}
impl ValidationErrors {
pub fn new() -> Self {
Self { errors: Vec::new() }
}
pub fn add_field_error(&mut self, field: &str, message: &str) {
self.errors.push(ValidationError::FieldError {
field: field.to_string(),
message: message.to_string(),
});
}
pub fn is_empty(&self) -> bool {
self.errors.is_empty()
}
pub fn into_result(self) -> Result<(), ValidationError> {
if self.errors.is_empty() {
Ok(())
} else if self.errors.len() == 1 {
Err(self.errors.into_iter().next().unwrap())
} else {
Err(ValidationError::MultipleErrors(self.errors))
}
}
}2. Implement Derive Macro
Using validator crate or custom proc macro:
use validator::{Validate, ValidationError};
#[derive(Debug, Clone, Validate)]
pub struct Identifier {
#[validate(length(equal = 32))]
bytes: Vec<u8>,
}
#[derive(Debug, Clone, Validate)]
pub struct NetworkConfig {
#[validate(range(min = 1024, max = 65535))]
pub port: u16,
#[validate(length(min = 1, max = 255))]
pub node_name: String,
#[validate(url)]
pub bootstrap_url: Option<String>,
#[validate(custom = "validate_address")]
pub bind_address: String,
#[validate(range(min = 1, max = 1000))]
pub max_connections: usize,
}
fn validate_address(address: &str) -> Result<(), ValidationError> {
use std::net::SocketAddr;
address.parse::<SocketAddr>()
.map(|_| ())
.map_err(|_| ValidationError::new("invalid socket address"))
}3. Custom Validators
pub mod validators {
use super::*;
/// Validate that bytes are exactly 32 bytes (for identifiers)
pub fn validate_identifier_bytes(bytes: &[u8]) -> Result<(), ValidationError> {
if bytes.len() != 32 {
return Err(ValidationError::Custom(
format!("Identifier must be exactly 32 bytes, got {}", bytes.len())
));
}
Ok(())
}
/// Validate membership vector format
pub fn validate_membership_vector(vec: &[u8]) -> Result<(), ValidationError> {
if vec.is_empty() {
return Err(ValidationError::Custom(
"Membership vector cannot be empty".to_string()
));
}
for (i, &byte) in vec.iter().enumerate() {
if byte != 0 && byte != 1 {
return Err(ValidationError::Custom(
format!("Membership vector byte {} must be 0 or 1, got {}", i, byte)
));
}
}
Ok(())
}
/// Validate skip graph level
pub fn validate_level(level: u32, max_level: u32) -> Result<(), ValidationError> {
if level > max_level {
return Err(ValidationError::Custom(
format!("Level {} exceeds maximum {}", level, max_level)
));
}
Ok(())
}
}4. Manual Validation Implementation
For types that need complex validation:
impl Validate for SearchRequest {
fn validate(&self) -> Result<(), ValidationError> {
let mut errors = ValidationErrors::new();
// Validate target ID
if let Err(e) = validators::validate_identifier_bytes(&self.target_id) {
errors.add_field_error("target_id", &e.to_string());
}
// Validate level
if let Err(e) = validators::validate_level(self.level, MAX_LEVEL) {
errors.add_field_error("level", &e.to_string());
}
// Validate request ID format
if self.request_id.is_empty() {
errors.add_field_error("request_id", "Request ID cannot be empty");
}
errors.into_result()
}
}5. Integration Points
/// Network layer integration
impl NetworkImpl {
pub async fn send_message(&self, msg: impl Validate + ProtoMessage) -> Result<(), NetworkError> {
// Validate before sending
msg.validate()
.map_err(|e| NetworkError::ValidationFailed(e))?;
// Continue with sending...
Ok(())
}
}
/// API layer integration
pub async fn handle_join_request(req: JoinRequest) -> Result<JoinResponse, ApiError> {
// Validate request
req.validate()?;
// Process valid request
Ok(process_join(req).await?)
}
/// Deserialization integration
impl ProtoMessage for JoinRequest {
fn from_proto(proto: proto::JoinRequest) -> Result<Self, DecodeError> {
let req = JoinRequest {
node_id: Identifier::from_bytes(proto.node_id)?,
membership_vector: proto.membership_vector,
address: proto.address,
};
// Validate after deserialization
req.validate()
.map_err(|e| DecodeError::ValidationFailed(e))?;
Ok(req)
}
}6. Validation Middleware
/// Middleware for automatic validation
pub struct ValidationMiddleware<T> {
inner: T,
}
impl<T: MessageProcessor> MessageProcessor for ValidationMiddleware<T> {
async fn process_incoming_message(
&self,
channel: Channel,
origin_id: Identifier,
message: Box<dyn Message>,
) {
// Validate origin ID
if let Err(e) = origin_id.validate() {
log::warn!("Invalid origin ID from {}: {}", origin_id, e);
return;
}
// Forward to inner processor
self.inner.process_incoming_message(channel, origin_id, message).await;
}
}Validation Rules to Implement
- Identifiers: Exactly 32 bytes
- Membership Vectors: Binary string (0s and 1s only)
- Network Addresses: Valid socket addresses
- Port Numbers: Valid range (1024-65535 for user ports)
- Message Sizes: Within acceptable limits
- Timestamps: Not too far in future/past
- Channel Names: From allowed set
- Node Names: Length and character restrictions
Benefits
- Security: Prevent malformed data attacks
- Early Detection: Catch errors at boundaries
- Declarative: Validation rules in one place
- Reusable: Common validators across codebase
- Type Safety: Compile-time validation where possible
Testing Requirements
- Test all validators with valid/invalid inputs
- Test validation error messages
- Test composite validations
- Test custom validators
- Performance benchmarks for validation
Dependencies
- validator (declarative validation)
- validator_derive (proc macros)
- thiserror (error handling)
Priority
Medium - Important for security and robustness
Related Issues
- Enhances: All network and message handling components
Metadata
Metadata
Assignees
Labels
No labels