#[derive(Debug, Default, Clone)]
pub struct HookFilterPlan {
pub present: bool,
pub unfiltered: bool,
pub plugins: Vec<PluginFilter>,
}
#[derive(Debug, Clone)]
pub struct PluginFilter {
pub id: Vec<regex::Regex>,
pub code: Vec<regex::Regex>,
}
#[derive(Debug, Default, Clone)]
pub struct BuildHookPlan {
pub transform: HookFilterPlan,
pub load: HookFilterPlan,
pub resolve_id: HookFilterPlan,
}
impl HookFilterPlan {
fn fail_open() -> Self {
Self {
present: true,
unfiltered: true,
plugins: Vec::new(),
}
}
pub(crate) fn from_json(v: Option<&serde_json::Value>) -> Self {
let Some(v) = v else {
return Self::fail_open();
};
let present = v.get("present").and_then(|b| b.as_bool()).unwrap_or(true);
let mut unfiltered = v
.get("unfiltered")
.and_then(|b| b.as_bool())
.unwrap_or(true);
let mut plugins = Vec::new();
for entry in v
.get("plugins")
.and_then(|p| p.as_array())
.map(|a| a.as_slice())
.unwrap_or(&[])
{
let compile = |key: &str| -> Option<Vec<regex::Regex>> {
let mut out = Vec::new();
for s in entry.get(key).and_then(|x| x.as_array())? {
out.push(regex::Regex::new(s.as_str()?).ok()?);
}
Some(out)
};
match (compile("id"), compile("code")) {
(Some(id), Some(code)) if !id.is_empty() || !code.is_empty() => {
plugins.push(PluginFilter { id, code });
}
_ => unfiltered = true,
}
}
Self {
present,
unfiltered,
plugins,
}
}
pub fn wants(&self, id: &str, code: Option<&str>) -> bool {
if !self.present {
return false;
}
if self.unfiltered {
return true;
}
let id = if id.contains('\\') {
std::borrow::Cow::Owned(id.replace('\\', "/"))
} else {
std::borrow::Cow::Borrowed(id)
};
let id = id.as_ref();
self.plugins.iter().any(|p| {
let id_ok = p.id.is_empty() || p.id.iter().any(|re| re.is_match(id));
let code_ok = p.code.is_empty()
|| match code {
Some(c) => p.code.iter().any(|re| re.is_match(c)),
None => true,
};
id_ok && code_ok
})
}
}
pub fn hook_gate_debug() -> bool {
oj_env::get().knobs.debug_hook_gate
}
impl BuildHookPlan {
pub fn fail_open() -> Self {
Self {
transform: HookFilterPlan::fail_open(),
load: HookFilterPlan::fail_open(),
resolve_id: HookFilterPlan::fail_open(),
}
}
}