use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::types::{
CommandKnowledge, CommandProperties, Effect, EnvGate, FlagSchema, KnowledgeBase, PathSpec,
SubcommandMap, WrapperKnowledge,
};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct WrapperOverlay {
#[serde(default)]
pub floor_effect: Option<Effect>,
#[serde(default)]
pub clears_env: Option<bool>,
#[serde(default)]
pub escalates_privilege: Option<bool>,
}
impl WrapperOverlay {
pub(crate) fn apply_to(self, base: &mut WrapperKnowledge) {
if let Some(floor_effect) = self.floor_effect {
base.floor_effect = floor_effect;
}
if let Some(clears_env) = self.clears_env {
base.clears_env = clears_env;
}
if let Some(escalates_privilege) = self.escalates_privilege {
base.escalates_privilege = escalates_privilege;
}
}
pub(crate) fn into_knowledge(self, key: String) -> WrapperKnowledge {
WrapperKnowledge {
name: key,
floor_effect: self.floor_effect.unwrap_or(Effect::Unknown),
clears_env: self.clears_env.unwrap_or(false),
escalates_privilege: self.escalates_privilege.unwrap_or(false),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct KnowledgeOverlay {
#[serde(default)]
pub commands: HashMap<String, CommandOverlay>,
#[serde(default)]
pub wrappers: HashMap<String, WrapperOverlay>,
#[serde(default)]
pub remove_commands: Vec<String>,
#[serde(default)]
pub remove_wrappers: Vec<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CommandOverlay {
#[serde(default)]
pub effect: Option<Effect>,
#[serde(default)]
pub subcommands: SubcommandMap,
#[serde(default)]
pub flags: FlagSchema,
#[serde(default)]
pub env_gates: Vec<EnvGate>,
#[serde(default)]
pub paths: Option<PathSpec>,
#[serde(default)]
pub properties: Option<CommandProperties>,
#[serde(default)]
pub remove_subcommands: Vec<String>,
}
impl CommandOverlay {
#[cfg(test)]
pub fn with_effect(effect: Effect) -> Self {
Self {
effect: Some(effect),
..Default::default()
}
}
pub(crate) fn apply_to(self, base: &mut CommandKnowledge) {
if let Some(effect) = self.effect {
base.effect = effect;
}
for pattern in &self.remove_subcommands {
base.subcommands.remove(pattern);
}
base.subcommands.extend(self.subcommands);
base.flags.extend(self.flags);
base.env_gates.extend(self.env_gates);
if let Some(paths) = self.paths {
base.paths = paths;
}
if let Some(properties) = self.properties {
base.properties = properties;
}
}
pub(crate) fn into_knowledge(self, key: String) -> CommandKnowledge {
CommandKnowledge {
name: key,
effect: self.effect.unwrap_or(Effect::Unknown),
subcommands: self.subcommands,
flags: self.flags,
env_gates: self.env_gates,
paths: self.paths.unwrap_or_default(),
properties: self.properties.unwrap_or_default(),
}
}
}
impl KnowledgeBase {
pub fn merge(&mut self, overlay: KnowledgeOverlay) {
for key in &overlay.remove_commands {
self.commands.remove(key);
}
for key in &overlay.remove_wrappers {
self.wrappers.remove(key);
}
for (key, cmd_overlay) in overlay.commands {
if let Some(base) = self.commands.get_mut(&key) {
cmd_overlay.apply_to(base);
} else {
self.commands
.insert(key.clone(), cmd_overlay.into_knowledge(key));
}
}
for (key, wrapper_overlay) in overlay.wrappers {
if let Some(base) = self.wrappers.get_mut(&key) {
wrapper_overlay.apply_to(base);
} else {
self.wrappers
.insert(key.clone(), wrapper_overlay.into_knowledge(key));
}
}
}
}
#[cfg(test)]
#[path = "merge_tests.rs"]
mod merge_tests;
#[cfg(test)]
#[path = "merge_proptest.rs"]
mod merge_proptest;