greentic_interfaces/
validate.rs

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