//! NxMesh Protocol Buffers //! //! This crate contains the gRPC protocol definitions for master-agent communication. #![forbid(clippy::unwrap_used, clippy::panic, unsafe_code)] #![deny(clippy::expect_used)] pub mod agent { tonic::include_proto!("nxmesh.agent.v1"); } pub use agent::*; pub mod auth; #[allow(ambiguous_glob_reexports)] pub use tonic_async_interceptor::*; #[cfg(test)] mod tests { use prost::Message; use crate::agent::{ AgentMessage, ConfigApplyStatus, ConfigStatus, DeploymentMode, Error, MasterMessage, MetricType, RegistrationRequest, agent_message, master_message, }; #[test] fn agent_message_round_trip_with_registration_payload() { let msg = AgentMessage { agent_id: "agent-1".to_string(), timestamp: 123, payload: Some(agent_message::Payload::Registration(RegistrationRequest { hostname: "node-1".to_string(), ip_address: "127.0.0.1".to_string(), version: "1.0.0".to_string(), capabilities: vec!["reload".to_string(), "metrics".to_string()], labels: std::collections::HashMap::from([ ("region".to_string(), "dev".to_string()), ("tier".to_string(), "edge".to_string()), ]), deployment_mode: DeploymentMode::Standalone as i32, })), }; let encoded = msg.encode_to_vec(); let decoded = AgentMessage::decode(encoded.as_slice()); assert!(decoded.is_ok()); let decoded = decoded.unwrap_or_else(|_| unreachable!()); assert_eq!(decoded.agent_id, "agent-1"); assert_eq!(decoded.timestamp, 123); match decoded.payload { Some(agent_message::Payload::Registration(payload)) => { assert_eq!(payload.hostname, "node-1"); assert_eq!(payload.ip_address, "127.0.0.1"); assert_eq!(payload.version, "1.0.0"); assert_eq!(payload.capabilities.len(), 2); assert_eq!(payload.labels.get("region"), Some(&"dev".to_string())); assert_eq!(payload.deployment_mode, DeploymentMode::Standalone as i32); } _ => unreachable!(), } } #[test] fn master_message_round_trip_with_error_payload() { let msg = MasterMessage { timestamp: 999, payload: Some(master_message::Payload::Error(Error { code: "E_CONFIG_INVALID".to_string(), message: "invalid config".to_string(), details: std::collections::HashMap::from([ ("file".to_string(), "site.conf".to_string()), ("line".to_string(), "42".to_string()), ]), })), }; let encoded = msg.encode_to_vec(); let decoded = MasterMessage::decode(encoded.as_slice()); assert!(decoded.is_ok()); let decoded = decoded.unwrap_or_else(|_| unreachable!()); assert_eq!(decoded.timestamp, 999); match decoded.payload { Some(master_message::Payload::Error(err)) => { assert_eq!(err.code, "E_CONFIG_INVALID"); assert_eq!(err.message, "invalid config"); assert_eq!(err.details.get("line"), Some(&"42".to_string())); } _ => unreachable!(), } } #[test] fn enum_integer_mappings_are_stable() { assert_eq!(DeploymentMode::Unspecified as i32, 0); assert_eq!(DeploymentMode::DockerSidecar as i32, 1); assert_eq!(DeploymentMode::KubernetesSidecar as i32, 2); assert_eq!(DeploymentMode::Standalone as i32, 3); assert_eq!(ConfigApplyStatus::Unspecified as i32, 0); assert_eq!(ConfigApplyStatus::Pending as i32, 1); assert_eq!(ConfigApplyStatus::Validating as i32, 2); assert_eq!(ConfigApplyStatus::Applying as i32, 3); assert_eq!(ConfigApplyStatus::Success as i32, 4); assert_eq!(ConfigApplyStatus::Failed as i32, 5); assert_eq!(ConfigApplyStatus::RolledBack as i32, 6); assert_eq!(MetricType::Unspecified as i32, 0); assert_eq!(MetricType::Gauge as i32, 1); assert_eq!(MetricType::Counter as i32, 2); assert_eq!(MetricType::Histogram as i32, 3); } #[test] fn config_status_defaults_are_proto3_zero_values() { let status = ConfigStatus::default(); assert_eq!(status.config_id, ""); assert_eq!(status.version, 0); assert_eq!(status.status, ConfigApplyStatus::Unspecified as i32); assert_eq!(status.error_message, ""); assert_eq!(status.applied_at, 0); } }