Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 56 additions & 12 deletions src/enforcer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use crate::{
Result,
};

use crate::model::DefaultModel;

#[cfg(any(feature = "logging", feature = "watcher"))]
use crate::emitter::notify_logger_and_watcher;

Expand Down Expand Up @@ -149,10 +151,21 @@ impl Enforcer {

let mut eft_stream =
self.eft.new_stream(&e_ast.value, max(policy_len, 1));
let m_ast_compiled = self
.engine
.compile_expression(escape_eval(&m_ast.value))
.map_err(Into::<Box<EvalAltResult>>::into)?;
let m_ast_compiled = if let Some(default_model) =
self.model.as_any().downcast_ref::<DefaultModel>()
{
default_model.get_compiled_matcher("m").ok_or_else(|| {
crate::error::Error::ModelError(crate::error::ModelError::M(
"Matcher 'm' not compiled".to_string(),
))
})?
} else {
// Fallback to original compilation (for other Model implementations)
&self
.engine
.compile_expression(escape_eval(&m_ast.value))
.map_err(Into::<Box<EvalAltResult>>::into)?
};

if policy_len == 0 {
for token in p_ast.tokens.iter() {
Expand All @@ -161,7 +174,7 @@ impl Enforcer {

let eval_result = self
.engine
.eval_ast_with_scope::<bool>(&mut scope, &m_ast_compiled)?;
.eval_ast_with_scope::<bool>(&mut scope, m_ast_compiled)?;
let eft = if eval_result {
EffectKind::Allow
} else {
Expand Down Expand Up @@ -189,7 +202,7 @@ impl Enforcer {

let eval_result = self
.engine
.eval_ast_with_scope::<bool>(&mut scope, &m_ast_compiled)?;
.eval_ast_with_scope::<bool>(&mut scope, m_ast_compiled)?;
let eft = match p_ast.tokens.iter().position(|x| x == "p_eft") {
Some(j) if eval_result => {
let p_eft = &pvals[j];
Expand Down Expand Up @@ -278,10 +291,26 @@ impl Enforcer {

let mut eft_stream =
self.eft.new_stream(&e_ast.value, max(policy_len, 1));
let m_ast_compiled = self
.engine
.compile_expression(escape_eval(&m_ast.value))
.map_err(Into::<Box<EvalAltResult>>::into)?;
let m_ast_compiled = if let Some(default_model) =
self.model.as_any().downcast_ref::<DefaultModel>()
{
default_model.get_compiled_matcher(&ctx.m_type).ok_or_else(
|| {
crate::error::Error::ModelError(
crate::error::ModelError::M(format!(
"Matcher '{}' not compiled",
ctx.m_type
)),
)
},
)?
} else {
// Fallback to original compilation (for other Model implementations)
&self
.engine
.compile_expression(escape_eval(&m_ast.value))
.map_err(Into::<Box<EvalAltResult>>::into)?
};

if policy_len == 0 {
for token in p_ast.tokens.iter() {
Expand All @@ -290,7 +319,7 @@ impl Enforcer {

let eval_result = self
.engine
.eval_ast_with_scope::<bool>(&mut scope, &m_ast_compiled)?;
.eval_ast_with_scope::<bool>(&mut scope, m_ast_compiled)?;
let eft = if eval_result {
EffectKind::Allow
} else {
Expand Down Expand Up @@ -318,7 +347,7 @@ impl Enforcer {

let eval_result = self
.engine
.eval_ast_with_scope::<bool>(&mut scope, &m_ast_compiled)?;
.eval_ast_with_scope::<bool>(&mut scope, m_ast_compiled)?;
let eft = match p_ast.tokens.iter().position(|x| x == "p_eft") {
Some(j) if eval_result => {
let p_eft = &pvals[j];
Expand Down Expand Up @@ -433,6 +462,13 @@ impl CoreApi for Enforcer {

e.register_g_functions()?;

// If using DefaultModel, compile matcher expressions
if let Some(default_model) =
e.model.as_any_mut().downcast_mut::<DefaultModel>()
{
default_model.compile_matchers(&e.engine)?;
}

Ok(e)
}

Expand Down Expand Up @@ -534,6 +570,14 @@ impl CoreApi for Enforcer {

async fn set_model<M: TryIntoModel>(&mut self, m: M) -> Result<()> {
self.model = m.try_into_model().await?;

// If using DefaultModel, recompile matcher expressions
if let Some(default_model) =
self.model.as_any_mut().downcast_mut::<DefaultModel>()
{
default_model.compile_matchers(&self.engine)?;
}

self.load_policy().await?;
Ok(())
}
Expand Down
45 changes: 45 additions & 0 deletions src/model/default_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,51 @@ use async_std::path::Path as ioPath;
#[cfg(feature = "runtime-tokio")]
use std::path::Path as ioPath;

use rhai::{Engine, AST};
use std::{collections::HashMap, sync::Arc};

#[derive(Clone, Default)]
pub struct DefaultModel {
pub(crate) model: HashMap<String, AssertionMap>,
// Precompiled matcher expressions - compiled during Model initialization
// Keys are full matcher names (e.g., "m", "m2", "m3")
compiled_matchers: HashMap<String, AST>,
}

impl DefaultModel {
// Compiles all matcher expressions after Model loading is complete
// Should be called before Enforcer is used
pub fn compile_matchers(&mut self, engine: &Engine) -> Result<()> {
self.compiled_matchers.clear();

// Only compile matchers from the 'm' section
if let Some(assertions) = self.model.get("m") {
for (key, assertion) in assertions {
let compiled = engine
.compile_expression(crate::util::escape_eval(
&assertion.value,
))
.map_err(|e| {
crate::error::Error::ModelError(
crate::error::ModelError::M(format!(
"Failed to compile matcher '{}': {}",
key, e
)),
)
})?;

// Directly use the complete matcher key as HashMap key
self.compiled_matchers.insert(key.clone(), compiled);
}
}
Ok(())
}

// Gets the precompiled matcher - O(1) lookup, simple and direct
#[inline]
pub fn get_compiled_matcher(&self, key: &str) -> Option<&AST> {
self.compiled_matchers.get(key)
}
#[cfg(not(target_arch = "wasm32"))]
pub async fn from_file<P: AsRef<ioPath>>(p: P) -> Result<DefaultModel> {
let cfg = Config::from_file(p).await?;
Expand Down Expand Up @@ -444,6 +481,14 @@ impl Model for DefaultModel {

s
}

fn as_any(&self) -> &dyn std::any::Any {
self
}

fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}

#[cfg(test)]
Expand Down
3 changes: 3 additions & 0 deletions src/model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,7 @@ pub trait Model: Send + Sync {
field_values: Vec<String>,
) -> (bool, Vec<Vec<String>>);
fn to_text(&self) -> String;
// Downcast support for performance
fn as_any(&self) -> &dyn std::any::Any;
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
}
Loading