Implement profile inheritance
This commit is contained in:
+50
-16
@@ -289,9 +289,7 @@ pub struct FileConfig {
|
||||
#[serde(flatten)]
|
||||
pub options: Options,
|
||||
#[serde(default)]
|
||||
pub profile: HashMap<String, Options>,
|
||||
#[serde(rename = "default-profile", default)]
|
||||
pub default_profile: Option<String>,
|
||||
pub profiles: HashMap<String, Options>,
|
||||
#[serde(rename = "extra-config", default)]
|
||||
pub extra_config: Option<PathBuf>,
|
||||
// Collects unrecognized keys; deny_unknown_fields is incompatible with flatten.
|
||||
@@ -339,18 +337,17 @@ impl FileConfig {
|
||||
}
|
||||
|
||||
fn merge_with(self, extra: FileConfig) -> FileConfig {
|
||||
let mut profile = self.profile;
|
||||
for (profile_name, profile_options) in extra.profile {
|
||||
let merged = match profile.remove(&profile_name) {
|
||||
let mut profiles = self.profiles;
|
||||
for (profile_name, profile_options) in extra.profiles {
|
||||
let merged = match profiles.remove(&profile_name) {
|
||||
Some(existing) => existing.merge_with(profile_options),
|
||||
None => profile_options,
|
||||
};
|
||||
profile.insert(profile_name, merged);
|
||||
profiles.insert(profile_name, merged);
|
||||
}
|
||||
FileConfig {
|
||||
options: self.options.merge_with(extra.options),
|
||||
profile,
|
||||
default_profile: extra.default_profile.or(self.default_profile),
|
||||
profiles,
|
||||
extra_config: None,
|
||||
_unknown: HashMap::new(),
|
||||
}
|
||||
@@ -367,21 +364,57 @@ impl FileConfig {
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn resolve_profile(&self, name: Option<&str>) -> Result<Options, SandboxError> {
|
||||
match name.or(self.default_profile.as_deref()) {
|
||||
Some(n) => self
|
||||
.profile
|
||||
.get(n)
|
||||
.cloned()
|
||||
.ok_or_else(|| SandboxError::ProfileNotFound(n.to_string())),
|
||||
fn resolve_profile(&self, selected: Option<&str>) -> Result<Options, SandboxError> {
|
||||
match selected.or(self.options.profile.as_deref()) {
|
||||
Some(leaf) => self.resolve_chain(leaf),
|
||||
None => Ok(Options::default()),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_chain(&self, leaf: &str) -> Result<Options, SandboxError> {
|
||||
let chain = self.collect_chain(leaf)?;
|
||||
|
||||
let merged = chain
|
||||
.into_iter()
|
||||
.rev()
|
||||
.fold(Options::default(), |base, child| base.merge_with(child));
|
||||
|
||||
Ok(merged)
|
||||
}
|
||||
|
||||
fn collect_chain(&self, leaf: &str) -> Result<Vec<Options>, SandboxError> {
|
||||
let mut visited: Vec<String> = Vec::new();
|
||||
let mut chain: Vec<Options> = Vec::new();
|
||||
let mut current = leaf.to_string();
|
||||
|
||||
loop {
|
||||
if visited.iter().any(|seen| seen == ¤t) {
|
||||
visited.push(current);
|
||||
return Err(SandboxError::ProfileCycle(visited));
|
||||
}
|
||||
|
||||
let options = self
|
||||
.profiles
|
||||
.get(¤t)
|
||||
.cloned()
|
||||
.ok_or_else(|| SandboxError::ProfileNotFound(current.clone()))?;
|
||||
|
||||
visited.push(current);
|
||||
let parent = options.profile.clone();
|
||||
chain.push(options);
|
||||
|
||||
match parent {
|
||||
Some(p) => current = p,
|
||||
None => return Ok(chain),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Clone)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub struct Options {
|
||||
pub profile: Option<String>,
|
||||
pub blacklist: Option<bool>,
|
||||
pub whitelist: Option<bool>,
|
||||
pub hardened: Option<bool>,
|
||||
@@ -410,6 +443,7 @@ impl Options {
|
||||
fn merge_with(self, extra: Options) -> Options {
|
||||
let (blacklist, whitelist) = pick_mode_flags(&self, &extra);
|
||||
Options {
|
||||
profile: extra.profile.or(self.profile),
|
||||
blacklist,
|
||||
whitelist,
|
||||
hardened: extra.hardened.or(self.hardened),
|
||||
|
||||
Reference in New Issue
Block a user