use super::{ValidationError, ValidationResult};
use crate::command::v1::HierarchicalCommand;
pub fn validate_hierarchical_command(cmd: &HierarchicalCommand) -> ValidationResult<()> {
if cmd.command_id.is_empty() {
return Err(ValidationError::MissingField("command_id".to_string()));
}
if cmd.originator_id.is_empty() {
return Err(ValidationError::MissingField("originator_id".to_string()));
}
let target = cmd
.target
.as_ref()
.ok_or_else(|| ValidationError::MissingField("target".to_string()))?;
if target.target_ids.is_empty() && target.scope != 4 {
return Err(ValidationError::MissingField(
"target.target_ids (required for non-broadcast commands)".to_string(),
));
}
if cmd.issued_at.is_none() {
return Err(ValidationError::MissingField("issued_at".to_string()));
}
if let (Some(issued), Some(expires)) = (&cmd.issued_at, &cmd.expires_at) {
if expires.seconds < issued.seconds
|| (expires.seconds == issued.seconds && expires.nanos < issued.nanos)
{
return Err(ValidationError::ConstraintViolation(
"expires_at must be after issued_at".to_string(),
));
}
}
if cmd.command_type.is_none() {
return Err(ValidationError::MissingField("command_type".to_string()));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::command::v1::{
command_target::Scope, mission_order::MissionType, CommandTarget, MissionOrder,
};
use crate::common::v1::Timestamp;
fn valid_command() -> HierarchicalCommand {
HierarchicalCommand {
command_id: "CMD-001".to_string(),
originator_id: "HQ-1".to_string(),
target: Some(CommandTarget {
scope: Scope::Squad as i32,
target_ids: vec!["Alpha".to_string()],
}),
command_type: Some(
crate::command::v1::hierarchical_command::CommandType::MissionOrder(MissionOrder {
mission_type: MissionType::Isr as i32,
mission_id: "ISR-001".to_string(),
description: "Conduct ISR in sector Alpha".to_string(),
objective_location: None,
start_time: None,
end_time: None,
roe: None,
}),
),
priority: 1,
buffer_policy: 1,
conflict_policy: 1,
acknowledgment_policy: 2,
leader_change_policy: 1,
issued_at: Some(Timestamp {
seconds: 1702000000,
nanos: 0,
}),
expires_at: None,
version: 1,
}
}
#[test]
fn test_valid_command() {
let cmd = valid_command();
assert!(validate_hierarchical_command(&cmd).is_ok());
}
#[test]
fn test_missing_command_id() {
let mut cmd = valid_command();
cmd.command_id = String::new();
let err = validate_hierarchical_command(&cmd).unwrap_err();
assert!(matches!(err, ValidationError::MissingField(f) if f == "command_id"));
}
#[test]
fn test_missing_originator_id() {
let mut cmd = valid_command();
cmd.originator_id = String::new();
let err = validate_hierarchical_command(&cmd).unwrap_err();
assert!(matches!(err, ValidationError::MissingField(f) if f == "originator_id"));
}
#[test]
fn test_missing_target() {
let mut cmd = valid_command();
cmd.target = None;
let err = validate_hierarchical_command(&cmd).unwrap_err();
assert!(matches!(err, ValidationError::MissingField(f) if f == "target"));
}
#[test]
fn test_missing_issued_at() {
let mut cmd = valid_command();
cmd.issued_at = None;
let err = validate_hierarchical_command(&cmd).unwrap_err();
assert!(matches!(err, ValidationError::MissingField(f) if f == "issued_at"));
}
#[test]
fn test_expires_before_issued() {
let mut cmd = valid_command();
cmd.issued_at = Some(Timestamp {
seconds: 1702000000,
nanos: 0,
});
cmd.expires_at = Some(Timestamp {
seconds: 1701000000, nanos: 0,
});
let err = validate_hierarchical_command(&cmd).unwrap_err();
assert!(matches!(err, ValidationError::ConstraintViolation(_)));
}
#[test]
fn test_missing_command_type() {
let mut cmd = valid_command();
cmd.command_type = None;
let err = validate_hierarchical_command(&cmd).unwrap_err();
assert!(matches!(err, ValidationError::MissingField(f) if f == "command_type"));
}
}