use super::{ValidationError, ValidationResult};
use crate::capability::v1::Capability;
use crate::cell::v1::{CellConfig, CellState};
use crate::node::v1::{NodeConfig, NodeState};
pub fn validate_capability(cap: &Capability) -> ValidationResult<()> {
if cap.confidence < 0.0 || cap.confidence > 1.0 {
return Err(ValidationError::InvalidConfidence(cap.confidence));
}
if cap.id.is_empty() {
return Err(ValidationError::MissingField("id".to_string()));
}
if cap.name.is_empty() {
return Err(ValidationError::MissingField("name".to_string()));
}
Ok(())
}
pub fn validate_node_config(config: &NodeConfig) -> ValidationResult<()> {
if config.id.is_empty() {
return Err(ValidationError::MissingField("id".to_string()));
}
if config.platform_type.is_empty() {
return Err(ValidationError::MissingField("platform_type".to_string()));
}
for cap in &config.capabilities {
validate_capability(cap)?;
}
if config.comm_range_m <= 0.0 {
return Err(ValidationError::InvalidValue(
"comm_range_m must be positive".to_string(),
));
}
if config.max_speed_mps <= 0.0 {
return Err(ValidationError::InvalidValue(
"max_speed_mps must be positive".to_string(),
));
}
Ok(())
}
pub fn validate_node_state(state: &NodeState) -> ValidationResult<()> {
if let Some(pos) = &state.position {
if pos.latitude < -90.0 || pos.latitude > 90.0 {
return Err(ValidationError::InvalidValue(
"latitude must be between -90 and 90".to_string(),
));
}
if pos.longitude < -180.0 || pos.longitude > 180.0 {
return Err(ValidationError::InvalidValue(
"longitude must be between -180 and 180".to_string(),
));
}
}
Ok(())
}
pub fn validate_cell_config(config: &CellConfig) -> ValidationResult<()> {
if config.id.is_empty() {
return Err(ValidationError::MissingField("id".to_string()));
}
if config.max_size < config.min_size {
return Err(ValidationError::ConstraintViolation(
"max_size must be >= min_size".to_string(),
));
}
if config.min_size < 2 {
return Err(ValidationError::ConstraintViolation(
"min_size must be at least 2".to_string(),
));
}
Ok(())
}
pub fn validate_cell_state(state: &CellState) -> ValidationResult<()> {
if let Some(config) = &state.config {
validate_cell_config(config)?;
let member_count = state.members.len();
if member_count > config.max_size as usize {
return Err(ValidationError::ConstraintViolation(format!(
"member count ({}) exceeds max_size ({})",
member_count, config.max_size
)));
}
}
for cap in &state.capabilities {
validate_capability(cap)?;
}
if let Some(leader_id) = &state.leader_id {
if !state.members.contains(leader_id) {
return Err(ValidationError::ConstraintViolation(
"leader_id must be in members list".to_string(),
));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::capability::v1::CapabilityType;
#[test]
fn test_validate_capability_success() {
let cap = Capability {
id: "cap-1".to_string(),
name: "Camera".to_string(),
capability_type: CapabilityType::Sensor as i32,
confidence: 0.9,
metadata_json: String::new(),
registered_at: None,
};
assert!(validate_capability(&cap).is_ok());
}
#[test]
fn test_validate_capability_invalid_confidence() {
let cap = Capability {
id: "cap-1".to_string(),
name: "Camera".to_string(),
capability_type: CapabilityType::Sensor as i32,
confidence: 1.5, metadata_json: String::new(),
registered_at: None,
};
assert!(validate_capability(&cap).is_err());
}
#[test]
fn test_validate_capability_missing_id() {
let cap = Capability {
id: String::new(), name: "Camera".to_string(),
capability_type: CapabilityType::Sensor as i32,
confidence: 0.9,
metadata_json: String::new(),
registered_at: None,
};
assert!(validate_capability(&cap).is_err());
}
#[test]
fn test_validate_cell_config_invalid_sizes() {
let config = CellConfig {
id: "cell-1".to_string(),
max_size: 2,
min_size: 5, created_at: None,
};
assert!(validate_cell_config(&config).is_err());
}
}