use eyre::{Result, WrapErr};
use serde::{Deserialize, Serialize};
#[cfg(feature = "cache")]
use crate::cache::METADATA_CACHE;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct SidcPart {
pub code: String,
pub label: String,
#[serde(default)]
pub children: Vec<SidcPart>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct SidcEntitiesAndModifiers {
pub entities: Vec<SidcPart>,
pub modifiers1: Vec<SidcPart>,
pub modifiers2: Vec<SidcPart>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct SidcMetadata {
pub contexts: Vec<SidcPart>,
pub identities: Vec<SidcPart>,
pub statuses: Vec<SidcPart>,
pub hqtffd: Vec<SidcPart>,
pub echelons: Vec<SidcPart>,
pub mobilities: Vec<SidcPart>,
pub symbol_sets: Vec<SidcPart>,
}
pub fn get_sidc_metadata() -> Result<SidcMetadata> {
#[cfg(feature = "cache")]
if let Some(metadata) = METADATA_CACHE.get() {
return Ok(metadata.clone());
}
let json_data = r#"{
"contexts": [
{ "code": "0", "label": "Reality" },
{ "code": "1", "label": "Exercise" },
{ "code": "2", "label": "Simulation" }
],
"identities": [
{ "code": "0", "label": "Pending" },
{ "code": "1", "label": "Unknown" },
{ "code": "2", "label": "Assumed Friend" },
{ "code": "3", "label": "Friend" },
{ "code": "4", "label": "Neutral" },
{ "code": "5", "label": "Suspect/Joker" },
{ "code": "6", "label": "Hostile/Faker" }
],
"statuses": [
{ "code": "0", "label": "Present" },
{ "code": "1", "label": "Planned/Anticipated" },
{ "code": "2", "label": "Present/Fully Capable" },
{ "code": "3", "label": "Present/Damaged" },
{ "code": "4", "label": "Present/Destroyed" },
{ "code": "5", "label": "Present/Full to Capacity" }
],
"hqtffd": [
{ "code": "0", "label": "None" },
{ "code": "1", "label": "Feint/Dummy" },
{ "code": "2", "label": "Headquarters" },
{ "code": "3", "label": "Feint/Dummy Headquarters" },
{ "code": "4", "label": "Task Force" },
{ "code": "5", "label": "Feint/Dummy Task Force" },
{ "code": "6", "label": "Task Force Headquarters" },
{ "code": "7", "label": "Feint/Dummy Task Force Headquarters" }
],
"echelons": [
{ "code": "00", "label": "None" },
{ "code": "11", "label": "Team/Crew" },
{ "code": "12", "label": "Squad" },
{ "code": "13", "label": "Section" },
{ "code": "14", "label": "Platoon/Detachment" },
{ "code": "15", "label": "Company/Battery/Troop" },
{ "code": "16", "label": "Battalion/Squadron" },
{ "code": "17", "label": "Regiment/Group" },
{ "code": "18", "label": "Brigade" },
{ "code": "21", "label": "Division" },
{ "code": "22", "label": "Corps/MEF" },
{ "code": "23", "label": "Army" },
{ "code": "24", "label": "Army Group/Front" },
{ "code": "25", "label": "Region/Theater" },
{ "code": "26", "label": "Command" }
],
"mobilities": [
{ "code": "00", "label": "None" },
{ "code": "31", "label": "Wheeled limited cross country" },
{ "code": "32", "label": "Wheeled cross country" },
{ "code": "33", "label": "Tracked" },
{ "code": "34", "label": "Wheeled and tracked combination" },
{ "code": "35", "label": "Towed" },
{ "code": "36", "label": "Rail" },
{ "code": "37", "label": "Pack animals" },
{ "code": "41", "label": "Over snow (prime mover)" },
{ "code": "42", "label": "Sled" },
{ "code": "51", "label": "Barge" },
{ "code": "52", "label": "Amphibious" },
{ "code": "61", "label": "Short towed array" },
{ "code": "62", "label": "Long towed Array" }
],
"symbol_sets": [
{ "code": "01", "label": "Air" },
{ "code": "02", "label": "Air Missile" },
{ "code": "05", "label": "Space" },
{ "code": "06", "label": "Space Missile" },
{ "code": "10", "label": "Land Unit" },
{ "code": "11", "label": "Land Civilian Unit" },
{ "code": "15", "label": "Land Equipment" },
{ "code": "20", "label": "Land Installation" },
{ "code": "25", "label": "Control Measure" },
{ "code": "27", "label": "Dismounted Individual" },
{ "code": "30", "label": "Sea Surface" },
{ "code": "35", "label": "Sea Subsurface" },
{ "code": "36", "label": "Mine Warfare" },
{ "code": "40", "label": "Activities" },
{ "code": "50", "label": "Signals Intelligence (Airborne)" },
{ "code": "51", "label": "Signals Intelligence (Unmanned Airborne)" },
{ "code": "52", "label": "Signals Intelligence (Ground)" },
{ "code": "53", "label": "Signals Intelligence (Sea Surface)" },
{ "code": "54", "label": "Signals Intelligence (Subsurface)" },
{ "code": "60", "label": "Cyberspace" }
]
}"#;
let metadata: SidcMetadata =
serde_json::from_str(json_data).wrap_err("Failed to parse SIDC metadata")?;
#[cfg(feature = "cache")]
let _ = METADATA_CACHE.set(metadata.clone());
Ok(metadata)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::milsymbol::MilsymbolBuilder;
#[test]
fn test_get_sidc_metadata() -> Result<()> {
let metadata = get_sidc_metadata()?;
assert!(!metadata.contexts.is_empty());
assert!(!metadata.identities.is_empty());
assert!(
metadata
.identities
.iter()
.any(|p| p.code == "3" && p.label == "Friend")
);
Ok(())
}
#[test]
fn test_get_sidc_entities_and_modifiers() -> Result<()> {
let mut ms = MilsymbolBuilder::new().build().unwrap();
let data = ms.get_sidc_entities_and_modifiers("10")?; assert!(!data.entities.is_empty());
assert!(!data.modifiers1.is_empty());
assert!(!data.modifiers2.is_empty());
fn find_recursive(entities: &[SidcPart], code: &str) -> bool {
for e in entities {
if e.code == code {
return true;
}
if find_recursive(&e.children, code) {
return true;
}
}
false
}
assert!(find_recursive(&data.entities, "121100")); Ok(())
}
#[test]
fn test_get_sidc_entities_convenience() -> Result<()> {
let mut ms = MilsymbolBuilder::new().build().unwrap();
let entities = ms.get_sidc_entities("10")?;
assert!(!entities.is_empty());
Ok(())
}
#[test]
fn test_invalid_symbol_set() -> Result<()> {
let mut ms = MilsymbolBuilder::new().build().unwrap();
let data = ms.get_sidc_entities_and_modifiers("99")?;
assert!(
data.entities.is_empty(),
"Invalid symbol set should return no entities"
);
Ok(())
}
}