Skip to main content

peat_schema/validation/
capability.rs

1//! CapabilityAdvertisement validators
2//!
3//! Validates capability advertisement messages for Peat Protocol.
4
5use super::{ValidationError, ValidationResult};
6use crate::capability::v1::{CapabilityAdvertisement, OperationalStatus, ResourceStatus};
7use crate::validation::core::validate_capability;
8
9/// Validate a CapabilityAdvertisement message
10///
11/// Validates:
12/// - platform_id is present
13/// - advertised_at timestamp is present
14/// - All capabilities pass validation
15/// - Resource status values are in valid range (0.0 - 1.0)
16/// - Operational status is valid (not unspecified)
17pub fn validate_capability_advertisement(ad: &CapabilityAdvertisement) -> ValidationResult<()> {
18    // Check required fields
19    if ad.platform_id.is_empty() {
20        return Err(ValidationError::MissingField("platform_id".to_string()));
21    }
22
23    if ad.advertised_at.is_none() {
24        return Err(ValidationError::MissingField("advertised_at".to_string()));
25    }
26
27    // Validate all capabilities
28    for cap in &ad.capabilities {
29        validate_capability(cap)?;
30    }
31
32    // Validate resource status if present
33    if let Some(resources) = &ad.resources {
34        validate_resource_status(resources)?;
35    }
36
37    // Check operational status is specified
38    if ad.operational_status == OperationalStatus::Unspecified as i32 {
39        return Err(ValidationError::InvalidValue(
40            "operational_status must be specified".to_string(),
41        ));
42    }
43
44    Ok(())
45}
46
47/// Validate resource status values
48fn validate_resource_status(resources: &ResourceStatus) -> ValidationResult<()> {
49    // All utilization values should be 0.0 - 1.0
50    if resources.compute_utilization < 0.0 || resources.compute_utilization > 1.0 {
51        return Err(ValidationError::InvalidValue(format!(
52            "compute_utilization {} must be between 0.0 and 1.0",
53            resources.compute_utilization
54        )));
55    }
56
57    if resources.memory_utilization < 0.0 || resources.memory_utilization > 1.0 {
58        return Err(ValidationError::InvalidValue(format!(
59            "memory_utilization {} must be between 0.0 and 1.0",
60            resources.memory_utilization
61        )));
62    }
63
64    if resources.power_level < 0.0 || resources.power_level > 1.0 {
65        return Err(ValidationError::InvalidValue(format!(
66            "power_level {} must be between 0.0 and 1.0",
67            resources.power_level
68        )));
69    }
70
71    if resources.storage_utilization < 0.0 || resources.storage_utilization > 1.0 {
72        return Err(ValidationError::InvalidValue(format!(
73            "storage_utilization {} must be between 0.0 and 1.0",
74            resources.storage_utilization
75        )));
76    }
77
78    if resources.bandwidth_utilization < 0.0 || resources.bandwidth_utilization > 1.0 {
79        return Err(ValidationError::InvalidValue(format!(
80            "bandwidth_utilization {} must be between 0.0 and 1.0",
81            resources.bandwidth_utilization
82        )));
83    }
84
85    Ok(())
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use crate::capability::v1::{Capability, CapabilityType};
92    use crate::common::v1::Timestamp;
93
94    fn valid_capability_advertisement() -> CapabilityAdvertisement {
95        CapabilityAdvertisement {
96            platform_id: "Alpha-3".to_string(),
97            advertised_at: Some(Timestamp {
98                seconds: 1702000000,
99                nanos: 0,
100            }),
101            capabilities: vec![Capability {
102                id: "ai-model-1".to_string(),
103                name: "Object Detector".to_string(),
104                capability_type: CapabilityType::Compute as i32,
105                confidence: 0.95,
106                metadata_json: r#"{"model_type": "detector"}"#.to_string(),
107                registered_at: None,
108            }],
109            resources: Some(ResourceStatus {
110                compute_utilization: 0.65,
111                memory_utilization: 0.5,
112                power_level: 0.9,
113                storage_utilization: 0.3,
114                bandwidth_utilization: 0.1,
115                extra_json: String::new(),
116            }),
117            operational_status: OperationalStatus::Ready as i32,
118        }
119    }
120
121    #[test]
122    fn test_valid_capability_advertisement() {
123        let ad = valid_capability_advertisement();
124        assert!(validate_capability_advertisement(&ad).is_ok());
125    }
126
127    #[test]
128    fn test_missing_platform_id() {
129        let mut ad = valid_capability_advertisement();
130        ad.platform_id = String::new();
131        let err = validate_capability_advertisement(&ad).unwrap_err();
132        assert!(matches!(err, ValidationError::MissingField(f) if f == "platform_id"));
133    }
134
135    #[test]
136    fn test_missing_advertised_at() {
137        let mut ad = valid_capability_advertisement();
138        ad.advertised_at = None;
139        let err = validate_capability_advertisement(&ad).unwrap_err();
140        assert!(matches!(err, ValidationError::MissingField(f) if f == "advertised_at"));
141    }
142
143    #[test]
144    fn test_invalid_resource_utilization() {
145        let mut ad = valid_capability_advertisement();
146        ad.resources = Some(ResourceStatus {
147            compute_utilization: 1.5, // Invalid
148            memory_utilization: 0.5,
149            power_level: 0.9,
150            storage_utilization: 0.3,
151            bandwidth_utilization: 0.1,
152            extra_json: String::new(),
153        });
154        let err = validate_capability_advertisement(&ad).unwrap_err();
155        assert!(matches!(err, ValidationError::InvalidValue(_)));
156    }
157
158    #[test]
159    fn test_unspecified_operational_status() {
160        let mut ad = valid_capability_advertisement();
161        ad.operational_status = OperationalStatus::Unspecified as i32;
162        let err = validate_capability_advertisement(&ad).unwrap_err();
163        assert!(matches!(err, ValidationError::InvalidValue(_)));
164    }
165}