Skip to main content

greentic_interfaces/
validate.rs

1//! Validation routines for WIT-derived metadata.
2
3use greentic_types::error::{ErrorCode, GResult, GreenticError};
4use semver::Version;
5
6fn invalid_input(message: impl Into<String>) -> GreenticError {
7    GreenticError::new(ErrorCode::InvalidInput, message)
8}
9
10/// Validates provider metadata generated from WIT bindings.
11pub fn validate_provider_meta(meta: crate::abi::v0_6_0::provider::ProviderMeta) -> GResult<()> {
12    if meta.name.trim().is_empty() {
13        return Err(invalid_input("provider name must not be empty"));
14    }
15
16    Version::parse(&meta.version)
17        .map_err(|err| invalid_input(format!("invalid semantic version: {err}")))?;
18
19    for domain in &meta.allow_list.domains {
20        if domain.trim().is_empty() {
21            return Err(invalid_input(
22                "allow-list domains must not contain empty entries",
23            ));
24        }
25    }
26
27    for port in &meta.allow_list.ports {
28        if *port == 0 {
29            return Err(invalid_input("allow-list ports must be greater than zero"));
30        }
31    }
32
33    for protocol in &meta.allow_list.protocols {
34        if let crate::canonical::types::Protocol::Custom(value) = protocol
35            && value.trim().is_empty()
36        {
37            return Err(invalid_input(
38                "custom protocol identifiers must not be empty",
39            ));
40        }
41    }
42
43    if meta.network_policy.deny_on_miss {
44        // strict policies must include at least one allowed entry to avoid total lockout
45        let allow = &meta.network_policy.egress;
46        if allow.domains.is_empty() && allow.ports.is_empty() && allow.protocols.is_empty() {
47            return Err(invalid_input(
48                "network policy denying misses requires explicit allow rules",
49            ));
50        }
51    }
52
53    Ok(())
54}