use std::fmt;
#[derive(Debug, Clone)]
pub struct WireValidationError {
pub field: &'static str,
pub message: String,
}
impl fmt::Display for WireValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "wire validation error: {}: {}", self.field, self.message)
}
}
impl std::error::Error for WireValidationError {}
pub trait WireValidate {
fn wire_validate(&self) -> Result<(), WireValidationError>;
}
pub const MAX_REPORT_HOSTS: usize = 500;
pub const MAX_VERSION_CHECK_ASSIGNMENTS: usize = 2_000;
pub const MAX_VERSION_CHECK_RESULTS: usize = 2_000;
pub const MAX_UPDATE_HOOKS: usize = 50;
pub const MAX_BATCH_UPDATES: usize = 500;
pub const MAX_BATCH_UPDATE_RESULTS: usize = 500;
pub const MAX_DISCOVERY_PLUGINS: usize = 50;
pub const MAX_DISCOVERY_PLUGIN_RESULTS: usize = 50;
pub const MAX_DISCOVERIES_PER_PLUGIN: usize = 1_000;
pub const MAX_DISCOVERED_QUALIFIER_LEN: usize = 256;
pub const MAX_CAPABILITIES: usize = 50;
pub const MAX_MQTT_TENANTS: usize = 500;
pub const MAX_SOFTWARE_STATE_ITEMS: usize = 2_000;
pub const MAX_SOFTWARE_STATE_HOSTS: usize = 500;
pub const MAX_HOST_PACKAGE_HOST_STATES: usize = 2_000;
pub const MAX_MQTT_HOSTS: usize = 2_000;
pub const MAX_HOST_TAGS: usize = 100;
pub const MAX_CONNECTIVITY_UPDATES: usize = 500;
pub const MAX_ACTIVE_MQTT_CLIENTS: usize = 50_000;
pub const MAX_CAPABILITIES_PER_SERVICE: usize = 64;
pub const MAX_SURFACE_MANIFESTS: usize = 50;
pub const MAX_SURFACE_COLUMNS: usize = 50;
pub const MAX_SURFACE_ACTION_REFS: usize = 50;
pub const MAX_SURFACE_ACTIONS: usize = 200;
pub const MAX_SURFACE_FIELDS: usize = 100;
pub const MAX_SURFACE_WIZARD_STEPS: usize = 20;
pub const MAX_SURFACE_SELECT_OPTIONS: usize = 200;
pub const MAX_SURFACE_PARAMS_LEN: usize = 65_536;
pub const MAX_SURFACE_RESPONSE_LEN: usize = 1_048_576;
pub const MAX_SURFACE_JSON_DEPTH: usize = 32;
pub const MAX_SURFACE_JSON_NODES: usize = 20_000;
pub const MAX_PLUGIN_CONFIG_JSON_LEN: usize = 65_536;
pub const MAX_STDIN_DATA_LEN: usize = 65_536;
pub const MAX_ICON_URL_LEN: usize = 2_048;
pub const MAX_REPORT_PAGES: u32 = 50;
pub const STATES_HOST_PAGE_SIZE: u64 = 100;
pub const MAX_PENDING_REPORTS_PER_CONNECTION: usize = 10;
pub const REPORT_TOTAL_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(300);
pub const REPORT_IDLE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(15);
pub const PAGINATION_SIZE_THRESHOLD: usize = 786_432;
pub const MAX_TRACE_ID_LEN: usize = 32;
pub const MAX_SPAN_ID_LEN: usize = 16;
pub const MAX_SHORT_STRING_LEN: usize = 1_024;
pub const MAX_MEDIUM_STRING_LEN: usize = 4_096;
pub const MAX_LONG_STRING_LEN: usize = 65_536;
pub const MAX_OUTPUT_STRING_LEN: usize = 1_048_576;
pub const MAX_CONFIG_TEST_OUTPUT_LEN: usize = 65_536;
pub const MAX_RELEASE_ASSETS: usize = 500;
pub const MAX_SERVICE_CONFIG_ENTRIES: usize = 1_000_000;
pub const MAX_SERVICE_CONFIG_VALUE_LEN: usize = 65_536;
pub const MAX_WORKLOAD_CLAIM_KEYS: usize = 100_000;
pub const SHA256_DIGEST_LEN: usize = 64;
pub fn check_vec_len<T>(
items: &[T],
max: usize,
field: &'static str,
) -> Result<(), WireValidationError> {
if items.len() > max {
return Err(WireValidationError {
field,
message: format!("collection has {} items, max {max}", items.len()),
});
}
Ok(())
}
pub fn check_string_len(
s: &str,
max: usize,
field: &'static str,
) -> Result<(), WireValidationError> {
if s.len() > max {
return Err(WireValidationError {
field,
message: format!("string is {} bytes, max {max}", s.len()),
});
}
Ok(())
}
pub fn check_map_len<K, V>(
items: &std::collections::BTreeMap<K, V>,
max: usize,
field: &'static str,
) -> Result<(), WireValidationError> {
if items.len() > max {
return Err(WireValidationError {
field,
message: format!("map has {} entries, max {max}", items.len()),
});
}
Ok(())
}
pub fn check_set_len<T>(
items: &std::collections::BTreeSet<T>,
max: usize,
field: &'static str,
) -> Result<(), WireValidationError> {
if items.len() > max {
return Err(WireValidationError {
field,
message: format!("set has {} items, max {max}", items.len()),
});
}
Ok(())
}
pub fn check_opt_string_len(
s: &Option<String>,
max: usize,
field: &'static str,
) -> Result<(), WireValidationError> {
if let Some(s) = s {
check_string_len(s, max, field)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
#![expect(
clippy::assertions_on_result_states,
reason = "test assertions — is_ok/is_err provides readable failure messages"
)]
use super::*;
#[test]
fn check_vec_len_at_limit() {
let items = vec![0u8; MAX_REPORT_HOSTS];
assert!(check_vec_len(&items, MAX_REPORT_HOSTS, "test").is_ok());
}
#[test]
fn check_vec_len_over_limit() {
let items = vec![0u8; MAX_REPORT_HOSTS + 1];
let err = check_vec_len(&items, MAX_REPORT_HOSTS, "test").unwrap_err();
assert_eq!(err.field, "test");
assert!(err.message.contains("501"));
}
#[test]
fn check_vec_len_empty() {
let items: Vec<u8> = vec![];
assert!(check_vec_len(&items, MAX_REPORT_HOSTS, "test").is_ok());
}
#[test]
fn check_string_len_at_limit() {
let s = "a".repeat(MAX_SHORT_STRING_LEN);
assert!(check_string_len(&s, MAX_SHORT_STRING_LEN, "test").is_ok());
}
#[test]
fn check_string_len_over_limit() {
let s = "a".repeat(MAX_SHORT_STRING_LEN + 1);
let err = check_string_len(&s, MAX_SHORT_STRING_LEN, "test").unwrap_err();
assert_eq!(err.field, "test");
assert!(err.message.contains("1025"));
}
#[test]
fn check_opt_string_len_none() {
assert!(check_opt_string_len(&None, MAX_SHORT_STRING_LEN, "test").is_ok());
}
#[test]
fn check_opt_string_len_some_over() {
let s = Some("a".repeat(MAX_SHORT_STRING_LEN + 1));
assert!(check_opt_string_len(&s, MAX_SHORT_STRING_LEN, "test").is_err());
}
#[test]
fn wire_validation_error_display() {
let err = WireValidationError {
field: "hosts",
message: "too many".to_string(),
};
let display = err.to_string();
assert!(display.contains("hosts"));
assert!(display.contains("too many"));
}
}