Generic condition-DSL rule evaluator: JSON-record match/action model, an in-memory evaluator, and a field-mapped SQL WHERE-clause compiler
| Tier | infrastructure |
| Role | unclassified (baselined) |
| Path | crates/infrastructure/rules-engine |
| Edition | 2021 |
| Targets | infrastructure_rules_engine |
| Public items | 16 across 3 modules |
| Tests | 33 |
What it is for
# infrastructure-rules-engine
A generic condition-DSL rule evaluator: a JSON-serializable match/action model (dsl), an in-memory evaluator over arbitrary serde_json::Value records (engine), and a field-mapped SQL WHERE-clause compiler for pushing the same conditions into Postgres (sql).
Sprint 3.45 crate 8 (docs/userstories/sprint-3.45-mail-manager-harvest.md). Source: rust-gmail-manager/src/rules_engine/{engine,dsl,sql_runner}.rs (Gate 4.5 provenance — see CHANGELOG.md for the per-module deviation notes, including why Eq's SQL compilation was corrected rather than carried forward as-is).
Deliberately generic — no mail-specific assumption survives
The source's EmailRow/gmail_labels/from.domain field-name allowlist is gone. This crate knows nothing about mail: Condition::field is an opaque dot-path into whatever JSON record the caller evaluates against, and Action::action is an opaque verb the caller defines and interprets. A tools-mail-manager consumer supplies its own field names ("from.domain", "subject", ...) and its own action vocabulary ("tag", "archive", ...) as plain strings/JSON — this crate imposes neither.
What was read but deliberately not ported: rules.rs
The sprint doc's source list for this crate also names rust-gmail-manager/src/rules.rs. That file's plan_actions/ execute_actions/execute_single/ActionType (Label/Archive/ Reply/Calendar/Flag/Trash) plan and execute actions by calling infrastructure_adapters_google_gmail::GmailClient::modify_labels/trash_message directly against a Gmail-shaped crate::mime::Email and an AI Classification. That is Gmail-adapter action execution, not condition-DSL evaluation — the two are different concerns bundled together in the source under one module path. Porting it here would mean either (a) inventing a generic ActionExecutor trait with no second consumer to ratify its shape (against Gate 4.5's first-consumer rule, and the sprint doc's own repeated non-goal philosophy — workflows/*, ml/*, web/handlers.rs are all deferred for exactly this reason), or (b) pulling a live infrastructure_adapters_google_gmail::GmailClient dependency into an infrastructure-layer crate that is otherwise dependency-free (violates AC-generic and this crate's whole reason for existing). Left for the tools-mail-manager tool (crate 11) to compose directly: read this crate's dsl::Action list off a matched rule, execute it however the target backend requires.
Capabilities
Action
The rule DSL's wire model: match conditions plus the actions a matching
| Item |
|---|
pub struct Action |
Condition
The rule DSL's wire model: match conditions plus the actions a matching
| Item |
|---|
pub struct Condition |
MatchBlock
The rule DSL's wire model: match conditions plus the actions a matching
| Item |
|---|
pub struct MatchBlock |
MatchLogic
The rule DSL's wire model: match conditions plus the actions a matching
| Item |
|---|
pub enum MatchLogic |
Operator
The rule DSL's wire model: match conditions plus the actions a matching
| Item |
|---|
pub enum Operator |
RuleDsl
The rule DSL's wire model: match conditions plus the actions a matching
| Item |
|---|
pub struct RuleDsl |
RuleDsl :: fn validate(& self) -> Result <(), String> |
ConditionResult
In-memory rule evaluation over an arbitrary JSON record.
| Item |
|---|
pub struct ConditionResult |
fn evaluate_rule(dsl : & RuleDsl, record : & serde_json::Value) ->(bool, Vec <ConditionResult>) |
sql (other)
Compile a rule's conditions into a parameterized SQL WHERE-clause
| Item |
|---|
fn compile_where(dsl : & RuleDsl, columns : & ColumnMap) -> Option <CompiledWhere> |
fn compile_where_from(dsl : & RuleDsl, columns : & ColumnMap, start : usize,) -> Option <CompiledWhere> |
ColumnMap
Compile a rule's conditions into a parameterized SQL WHERE-clause
| Item |
|---|
pub struct ColumnMap |
ColumnMap :: fn new() -> Self |
ColumnMap :: fn with(mut self, field : impl Into <String>, column : impl Into <String>) -> Self |
ColumnMap :: fn column_for(& self, field : & str) -> Option <& str> |
CompiledWhere
Compile a rule's conditions into a parameterized SQL WHERE-clause
| Item |
|---|
pub struct CompiledWhere |
How to use it
No examples/ target and no doctest in this crate's rustdoc. The tests listed under Verification are the closest executable usage.
Module structure
infrastructure_rules_engine
dslenginesql
flowchart TD n_infrastructure_rules_engine["infrastructure_rules_engine"] n_infrastructure_rules_engine --> n_dsl["dsl"] n_infrastructure_rules_engine --> n_engine["engine"] n_infrastructure_rules_engine --> n_sql["sql"]
Public surface
`dsl`
| Item | What it is |
|---|---|
pub struct RuleDsl | One rule: a match expression plus the actions to fire when it matches. |
pub struct MatchBlock | A set of conditions joined by MatchLogic. |
pub enum MatchLogic | How a MatchBlock's conditions combine. |
pub struct Condition | One condition: a field path, an operator, and (except for Operator::Exists) the value to compare against. |
pub enum Operator | A condition operator |
pub struct Action | One action a matching rule fires |
RuleDsl :: fn validate(& self) -> Result <(), String> | Structural validation only: at least one condition, at least one action, no blank field/action names, and every non-Exists operator carries a value |
`engine`
| Item | What it is |
|---|---|
pub struct ConditionResult | The per-condition outcome from evaluate_rule, useful for explaining why a rule did or didn't match. |
fn evaluate_rule(dsl : & RuleDsl, record : & serde_json::Value) ->(bool, Vec <ConditionResult>) | Evaluate whether record matches dsl's conditions, returning the overall verdict plus a per-condition breakdown. |
`sql`
| Item | What it is |
|---|---|
pub struct ColumnMap | A caller-supplied mapping from DSL field name to a trusted SQL column expression, e.g |
ColumnMap :: fn new() -> Self | — |
ColumnMap :: fn with(mut self, field : impl Into <String>, column : impl Into <String>) -> Self | Register a field -> column mapping, builder-style. |
ColumnMap :: fn column_for(& self, field : & str) -> Option <& str> | — |
pub struct CompiledWhere | A compiled WHERE-clause fragment: sql uses $N placeholders and params holds the bind values in the matching order. |
fn compile_where(dsl : & RuleDsl, columns : & ColumnMap) -> Option <CompiledWhere> | Compile dsl's conditions into a WHERE-clause fragment, placeholders starting at $1 |
fn compile_where_from(dsl : & RuleDsl, columns : & ColumnMap, start : usize,) -> Option <CompiledWhere> | Same as compile_where, but placeholders start at start — for splicing into a query that already bound $1..=$(start - 1) itself (e.g |
Re-exports. Exported here, defined elsewhere.
| Export | Defined in |
|---|---|
{Action,Condition,MatchBlock,MatchLogic,Operator,RuleDsl} | dsl::{Action,Condition,MatchBlock,MatchLogic,Operator,RuleDsl} |
{compile_where,compile_where_from,ColumnMap,CompiledWhere} | sql::{compile_where,compile_where_from,ColumnMap,CompiledWhere} |
{evaluate_rule,ConditionResult} | engine::{evaluate_rule,ConditionResult} |
Boundary
Depends on no other workspace tier.
Shares tier infrastructure with 82 other crates: infrastructure-acquire, infrastructure-adapters-google-calendar, infrastructure-adapters-google-gmail, infrastructure-adapters-google-places, infrastructure-adapters-google-trends, infrastructure-adapters-shodan, infrastructure-adapters-yelp, infrastructure-agent, … (82 total).
_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._
Where it sits
| Tier (ontology) | infrastructure |
| Architectural role (taxonomy) | unclassified (baselined) |
| Location | crates/infrastructure/rules-engine |
| Vocabulary in force (lexicon) | current |
Dependencies
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
regex | ^1 | — | no | always |
serde | ^1 | derive | no | always |
serde_json | ^1 | — | no | always |
Development. None.
Build. None.
Depended on by. Nothing in this workspace.
Feature flags
No Cargo features are defined: every capability is unconditional, so no consumer can receive a half-wired crate.
Targets
| Kind | Name | Source |
|---|---|---|
| lib | infrastructure_rules_engine | `src/lib.rs` |
Error model
No public error type was detected: no public item declares a type named *Error, and no public signature returns one.
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | none detected |
| async runtime | none detected |
| database access | none detected |
| network I/O | none detected |
| unsafe code | none detected |
| environment variables | none detected |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
No environment variable is read with a literal name anywhere in this crate. A variable whose key is computed at run time cannot be listed here, and is not claimed to be absent.
Related capabilities
No workspace crate depends on this one.
Verification
| Kind | Count |
|---|---|
| Unit tests | 33 |
| Integration tests | 0 |
| Examples | 0 |
| Doctests | 0 |
Evidence by module. How often each public module is named by something executable.
| Module | Tests | Examples | Consumers |
|---|---|---|---|
dsl | 6 | 0 | 0 |
engine | 2 | 0 | 0 |
sql | 4 | 0 | 0 |
What the tests establish, by name:
exists_operator_needs_no_value—src/dsl.rsfield_and_action_are_opaque_no_allowlist—src/dsl.rsnon_exists_operator_without_value_is_rejected—src/dsl.rsrejects_blank_action_name—src/dsl.rsrejects_blank_field_name—src/dsl.rsrejects_empty_actions—src/dsl.rsrejects_empty_conditions—src/dsl.rsvalid_dsl_parses_and_validates—src/dsl.rsall_logic_requires_every_condition—src/engine.rsany_logic_needs_only_one—src/engine.rscondition_result_reports_per_condition_field_and_verdict—src/engine.rscontains_matches_substring—src/engine.rseq_matches_case_insensitively_on_nested_field—src/engine.rseq_no_match—src/engine.rsexists_checks_presence—src/engine.rsin_and_not_in_operators—src/engine.rsmissing_nested_path_does_not_panic_and_is_absent—src/engine.rsnon_mail_record_matches_generically—src/engine.rsregex_operator—src/engine.rsall_logic_joins_with_and_and_advances_placeholder_numbers—src/sql.rsany_logic_joins_with_or—src/sql.rscompile_where_from_starts_placeholders_at_the_given_index—src/sql.rscontains_is_parameterized_not_interpolated—src/sql.rsempty_conditions_is_none—src/sql.rseq_compiles_to_exact_match_not_substring—src/sql.rsexists_checks_non_null_non_empty_with_no_params—src/sql.rsin_operator_binds_each_value_as_its_own_placeholder—src/sql.rsin_operator_without_array_value_fails_closed—src/sql.rsneq_and_not_in_are_not_compiled_to_sql—src/sql.rsone_uncompilable_condition_fails_the_whole_rule—src/sql.rs- _… 3 more_
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 14 | 16 |
Public modules with a //! block | 3 | 3 |
pie showData
title Public items with rustdoc
"Documented" : 14
"No rustdoc detected" : 2
Metrics
| Metric | Value |
|---|---|
| Rust source files | 4 |
| Source lines | 949 |
| Code lines | 698 |
| Public API items | 16 |
| Public modules | 3 |
| Tests | 33 |
| Examples | 0 |
| Cargo features | 0 |
| Direct runtime dependencies | 3 |
| Workspace reverse dependencies | 0 |
pie showData
title Public API by kind
"enum" : 2
"function" : 3
"method" : 4
"struct" : 7
pie showData
title Rust source composition
"Code" : 698
"Blank or comment" : 251
Generation
Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.