use crate::BaseNamespace;
use crate::decision_point::DecisionPoint;
use rust_embed::RustEmbed;
use std::collections::{HashMap, HashSet};
use std::ops::Deref;
use std::sync::LazyLock;
pub static SELECTION_LIST_SCHEMA: &str = include_str!("../assets/SelectionList_2_0_0.schema.json");
pub static DECISION_POINT_SCHEMA: &str = include_str!("../assets/DecisionPoint_2_0_0.schema.json");
#[derive(RustEmbed)]
#[folder = "assets/ssvc_decision_points/"]
#[include = "*.json"]
struct SsvcDecisionPointJsonFiles;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct DecisionPointId {
pub namespace: BaseNamespace,
pub key: String,
pub version: String,
}
type SsvcDecisionPointsMap = HashMap<DecisionPointId, DecisionPoint>;
pub static DECISION_POINTS: LazyLock<SsvcDecisionPointsMap> = LazyLock::new(|| {
let mut decision_points = HashMap::new();
for filename in SsvcDecisionPointJsonFiles::iter() {
if let Some(file) = SsvcDecisionPointJsonFiles::get(&filename) {
let content = std::str::from_utf8(&file.data).unwrap();
match serde_json::from_str::<DecisionPoint>(content) {
Ok(dp) => {
let namespace = match BaseNamespace::parse_base(dp.namespace.deref(), true) {
Ok(ns) => ns,
Err(err) => {
eprintln!(
"Warning: Failed to parse namespace for decision point from file {filename}: {err}"
);
continue;
}
};
let key = DecisionPointId {
namespace,
key: dp.key.deref().to_owned(),
version: dp.version.deref().to_owned(),
};
decision_points.insert(key, dp);
}
Err(err) => {
eprintln!("Warning: Failed to parse decision point from file {filename}: {err}")
}
}
}
}
decision_points
});
type SsvcDecisionPointsLookupMap = HashMap<DecisionPointId, HashMap<String, i32>>;
pub static DP_VALUE_KEY_ORDER: LazyLock<SsvcDecisionPointsLookupMap> = LazyLock::new(|| {
let mut lookups = HashMap::new();
for (decision_point_id, dp) in DECISION_POINTS.iter() {
let mut lookup_map = HashMap::new();
for (i, v) in dp.values.iter().enumerate() {
lookup_map.insert(v.key.deref().to_owned(), i as i32);
}
lookups.insert(decision_point_id.clone(), lookup_map);
}
lookups
});
pub static AVAILABLE_REGISTERED_NAMESPACES: LazyLock<HashSet<BaseNamespace>> =
LazyLock::new(|| {
let mut namespaces = HashSet::new();
for decision_point_id in DECISION_POINTS.keys().filter(|decision_point_id| {
matches!(
decision_point_id.namespace,
BaseNamespace::Registered { .. }
)
}) {
namespaces.insert(decision_point_id.namespace.to_owned());
}
namespaces
});
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[test]
fn test_ssvc_exploitation_1_0_0_lookup() {
let key = DecisionPointId {
namespace: BaseNamespace::parse_base("ssvc", false).unwrap(),
key: "E".to_string(),
version: "1.0.0".to_string(),
};
let decision_point = DECISION_POINTS.get(&key);
assert!(
decision_point.is_some(),
"SSVC Exploitation 1.0.0 should be found in DECISION_POINTS lookup"
);
let dp = decision_point.unwrap();
assert_eq!(dp.namespace.deref(), "ssvc");
assert_eq!(dp.key.deref(), "E");
assert_eq!(dp.version.deref(), "1.0.0");
assert_eq!(dp.name.deref(), "Exploitation");
assert_eq!(dp.values.len(), 3);
}
#[test]
fn test_ssvc_exploitation_1_0_0_value_keys_order() {
let key = DecisionPointId {
namespace: BaseNamespace::parse_base("ssvc", false).unwrap(),
key: "E".to_string(),
version: "1.0.0".to_string(),
};
let value_order = DP_VALUE_KEY_ORDER.get(&key);
assert!(
value_order.is_some(),
"SSVC Exploitation 1.0.0 should be found in DP_VALUE_KEY_ORDER lookup"
);
let order_map = value_order.unwrap();
assert_eq!(
order_map.get("N"),
Some(&0),
"Value with key 'N' should be at position 0"
);
assert_eq!(
order_map.get("P"),
Some(&1),
"Value with key 'P' should be at position 1"
);
assert_eq!(
order_map.get("A"),
Some(&2),
"Value with key 'A' should be at position 2"
);
}
#[rstest]
#[case::ssvc_is_available("ssvc", true)]
#[case::example_is_not_available("example", false)]
fn test_available_registered_namespaces_lookup(
#[case] namespace: &str,
#[case] expected: bool,
) {
let parsed_namespaces = BaseNamespace::parse_base(namespace, false).unwrap();
assert_eq!(
AVAILABLE_REGISTERED_NAMESPACES.contains(&parsed_namespaces),
expected
);
}
}