fix: Return explicit error when dependent rule is not found during deserialization#103
fix: Return explicit error when dependent rule is not found during deserialization#103bashandbone wants to merge 6 commits intomainfrom
Conversation
During RuleConfig parsing, rules that depend on un-registered utilities, rewriters, or global rules were ignored, bypassing cycle checks and generating invalid tree-sitter AST nodes that lacked functionality instead of halting deserialization correctly. This commit injects a context map to resolve rule presence during AST engine topological sorting, and propagates standard parsing errors (UndefinedUtil, DuplicateRule, CyclicRule) robustly. Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com>
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
Reviewer's GuideAdds explicit dependency validation during rule environment deserialization so missing referenced rules now surface as structured errors instead of being silently ignored, by threading RuleRegistration into the topological sort, tightening result types, and updating error mapping and tests. Sequence diagram for rule deserialization with explicit missing dependency errorssequenceDiagram
participant Caller
participant DeserializeEnv
participant TopologicalSort
participant RuleRegistration
Caller->>DeserializeEnv: with_utils(utils)
DeserializeEnv->>TopologicalSort: get_order(utils, env)
loop for each rule id
TopologicalSort->>TopologicalSort: visit(id)
alt rule id found in maps
TopologicalSort->>TopologicalSort: DFS visit dependencies
TopologicalSort-->>DeserializeEnv: push id to order
else rule id not in maps
alt env is Some
TopologicalSort->>RuleRegistration: contains_rule(id)
alt contains_rule == true
TopologicalSort-->>DeserializeEnv: Ok(())
else contains_rule == false
TopologicalSort-->>DeserializeEnv: Err(ReferentRuleError::UndefinedUtil)
end
else env is None
TopologicalSort-->>DeserializeEnv: Ok(())
end
end
end
alt success
DeserializeEnv-->>Caller: Ok(env with ordered utils)
else error
DeserializeEnv-->>Caller: Err(RuleSerializeError::MatchesReference(ReferentRuleError))
end
Sequence diagram for transform order resolution with structured errorssequenceDiagram
participant Caller
participant Transform
participant DeserializeEnv
participant TopologicalSort
Caller->>Transform: from_serializable_transform(env, serializable)
Transform->>DeserializeEnv: get_transform_order(trans_map)
DeserializeEnv->>TopologicalSort: get_order(trans_map, None)
loop for each transform id
TopologicalSort->>TopologicalSort: visit(id)
alt cyclic dependency
TopologicalSort-->>DeserializeEnv: Err(ReferentRuleError::CyclicRule)
else other error (e.g. UndefinedUtil)
TopologicalSort-->>DeserializeEnv: Err(ReferentRuleError::Other)
else success
TopologicalSort-->>DeserializeEnv: Ok(order)
end
end
alt Ok(order)
DeserializeEnv-->>Transform: Ok(order)
Transform-->>Caller: Ok(Transform)
else Err(ReferentRuleError::CyclicRule(s))
DeserializeEnv-->>Transform: Err(ReferentRuleError::CyclicRule(s))
Transform-->>Caller: Err(TransformError::Cyclic(s))
else Err(ReferentRuleError::Other(e))
DeserializeEnv-->>Transform: Err(ReferentRuleError::Other(e))
Transform-->>Caller: Err(TransformError::Cyclic(e.to_string()))
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- In
transform::mod,get_transform_orderis always called withenv: None, so it can only produceCyclicRuletoday; consider either simplifying themap_err(dropping the unreachable branch) or, if you expect more variants in future, mapping them to distinctTransformErrorvariants instead of collapsing everything toCyclic. - The new
contains_key/contains_rulehelpers introduce two subtly different names for similar concepts; consider unifying the naming (e.g., consistently usingcontains_rule) to make intent clearer and reduce cognitive overhead when reading call sites.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `transform::mod`, `get_transform_order` is always called with `env: None`, so it can only produce `CyclicRule` today; consider either simplifying the `map_err` (dropping the unreachable branch) or, if you expect more variants in future, mapping them to distinct `TransformError` variants instead of collapsing everything to `Cyclic`.
- The new `contains_key`/`contains_rule` helpers introduce two subtly different names for similar concepts; consider unifying the naming (e.g., consistently using `contains_rule`) to make intent clearer and reduce cognitive overhead when reading call sites.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Pull request overview
This PR aims to make rule-environment deserialization fail fast when a matches reference points to a missing dependency, instead of silently treating the dependency as external/optional. It updates the dependency topological sort to return structured ReferentRuleErrors and adjusts unit tests to ensure required globals are registered.
Changes:
- Switch
TopologicalSorterror type fromStringtoReferentRuleError, and optionally validate missing dependencies against aRuleRegistration. - Add
contains_key/contains_rulehelpers on registrations to support the new dependency validation. - Update transform deserialization to adapt to the new
ReferentRuleErrorerror type, and update tests to register mocked global dependencies.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| crates/rule-engine/src/transform/mod.rs | Adapts transform ordering error handling to the new ReferentRuleError return type. |
| crates/rule-engine/src/rule/referent_rule.rs | Adds registration presence helpers (contains_key, contains_rule) used by deserialization dependency checks. |
| crates/rule-engine/src/rule/deserialize_env.rs | Enhances topological sorting to optionally validate missing dependencies against the rule registration; updates tests accordingly. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| pub(crate) fn contains_rule(&self, id: &str) -> bool { | ||
| self.local.contains_key(id) | ||
| || self.global.contains_key(id) | ||
| || self.rewriters.contains_key(id) | ||
| } |
There was a problem hiding this comment.
RuleRegistration::contains_rule treats rewriter IDs as valid dependencies. However, matches references are resolved only against local + global rules (ReferentRule never consults rewriters), so this can incorrectly allow a local util rule to depend on a rewriter ID and still be treated as “present”, reintroducing silent failures at runtime. Consider either removing rewriters from contains_rule, or splitting this into two APIs (e.g. contains_match_rule vs contains_rewriter) and using the match-specific one for deserialization dependency checks.
| pub fn parse_global_utils( | ||
| utils: Vec<SerializableGlobalRule<L>>, | ||
| ) -> Result<GlobalRules, RuleCoreError> { | ||
| let registration = GlobalRules::default(); | ||
| let utils = into_map(utils); | ||
| let order = TopologicalSort::get_order(&utils) | ||
| .map_err(ReferentRuleError::CyclicRule) | ||
| let order = TopologicalSort::get_order(&utils, None) | ||
| .map_err(RuleSerializeError::from)?; |
There was a problem hiding this comment.
parse_global_utils calls TopologicalSort::get_order(&utils, None), which means a matches: some-missing-global inside a global util rule will still be treated as an external dependency and won’t error during ordering. Because global rules are parsed with CheckHint::Global (which skips verify_util), this can leave undefined global dependencies undetected and cause silent non-matches at runtime. Passing an env (e.g. a temporary RuleRegistration::from_globals(®istration)) into get_order or otherwise making missing dependencies error for global utils would align with the PR’s goal of failing on missing referenced rules.
| let order = env | ||
| .get_transform_order(&map) | ||
| .map_err(TransformError::Cyclic)?; | ||
| .map_err(|e| match e { ReferentRuleError::CyclicRule(s) => TransformError::Cyclic(s), _ => TransformError::Cyclic(e.to_string()) })?; |
There was a problem hiding this comment.
Transform::deserialize maps any non-CyclicRule ReferentRuleError into TransformError::Cyclic(...). Today get_transform_order(..., None) can only emit CyclicRule, but this mapping will produce a misleading “cyclic dependency” error message if additional ReferentRuleError variants ever become possible here. Consider matching only CyclicRule (and treating other variants as unreachable) or introducing a dedicated TransformError variant for non-cyclic dependency errors.
During rule configuration parsing, cyclic utility checks assumed missing rules (e.g. globals or locals) were valid, resolving them to Ok(()) silently. As a result, tests referring to strictly undefined rules were implicitly permitted instead of correctly halted. This patch injects `RuleRegistration` down into the TopologicalSorter and implements a direct `contains_rule` validation, accurately mapping missing components to the newly enforced `ReferentRuleError::UndefinedUtil` mapping. Wait... tests correctly passing explicitly! Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com>
|
@jules please address the suggestions in #103 (review) and #103 (review) |
I've addressed the feedback from the PR review. I've updated |
Resolved CR comments: 1. Re-mapped `contains_rule` into `contains_match_rule` so dependencies in Match blocks properly disregard matching onto arbitrary rewriters. 2. In `Transform::deserialize`, narrowed the catchall mapping so anything besides `CyclicRule` triggers an `unreachable!()` as get_transform_order defaults to None environments. 3. Removed arbitrary workspace script files used during refactoring. Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com>
…379115 Signed-off-by: Adam Poulemanos <89049923+bashandbone@users.noreply.github.com>
|
👋 Hey @bashandbone, Thanks for your contribution to thread! 🧵You need to agree to the CLA first... 🖊️Before we can accept your contribution, you need to agree to our Contributor License Agreement (CLA). To agree to the CLA, please comment:
Those exact words are important1, so please don't change them. 😉 You can read the full CLA here: Contributor License Agreement ✅ @bashandbone has signed the CLA. You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot. Footnotes
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
crates/rule-engine/src/rule/deserialize_env.rs:184
- New behavior returns
MatchesReference(UndefinedUtil(..))when a util dependency isn’t found in either the current map or the rule registration. There isn’t a unit test asserting this new error path (only the global-rule registration was adjusted to avoid failure). Add a test that references an undefined global/local util and asserts the exact error variant.
let order = TopologicalSort::get_order(utils, Some(&self.registration))
.map_err(RuleSerializeError::MatchesReference)?;
for id in order {
let rule = utils.get(id).expect("must exist");
let rule = self.deserialize_rule(rule.clone())?;
self.registration.insert_local(id, rule)?;
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
crates/services/src/lib.rs
Outdated
| <<<<<<< fix-rule-not-found-deserialize-9177764991747379115 | ||
| ======= | ||
| #![allow(unexpected_cfgs)] | ||
| >>>>>>> main |
There was a problem hiding this comment.
Unresolved git merge conflict markers (<<<<<<<, =======, >>>>>>>) are present at the top of this file, which will break compilation and documentation rendering. Resolve the conflict and keep only the intended crate attributes.
| <<<<<<< fix-rule-not-found-deserialize-9177764991747379115 | |
| ======= | |
| #![allow(unexpected_cfgs)] | |
| >>>>>>> main | |
| #![allow(unexpected_cfgs)] |
| let order = env.get_transform_order(&map).map_err(|e| match e { | ||
| ReferentRuleError::CyclicRule(s) => TransformError::Cyclic(s), | ||
| _ => unreachable!( | ||
| "get_transform_order uses None for env, so only CyclicRule is possible" | ||
| ), | ||
| })?; |
There was a problem hiding this comment.
This error mapping uses unreachable! for non-cyclic ReferentRuleError variants. Even if they are currently impossible, this will panic if get_transform_order ever starts returning UndefinedUtil (or other variants) and makes the call site fragile. Prefer handling all variants (e.g., add a TransformError variant that wraps ReferentRuleError, or keep get_transform_order’s error type limited to cyclic-only).
| if let Some(env) = self.env { | ||
| // Note: We only check if the key is completely missing | ||
| if !env.contains_match_rule(key) { | ||
| return Err(ReferentRuleError::UndefinedUtil(key.to_string())); | ||
| } |
There was a problem hiding this comment.
The new presence check only consults contains_match_rule (local/global match rules). If the intent is to also error on missing rewriter references during deserialization (as described in the PR), this logic doesn’t cover rewriters, and missing rewriters can still be silently ignored elsewhere. Consider extending the validation to cover the rewriter scope as well (or clarifying the PR scope if rewriters are intentionally out of band).
Resolved CR comments: 1. Re-mapped `contains_rule` into `contains_match_rule` so dependencies in Match blocks properly disregard matching onto arbitrary rewriters. 2. In `Transform::deserialize`, narrowed the catchall mapping so anything besides `CyclicRule` triggers an `unreachable!()` as get_transform_order defaults to None environments. 3. Removed arbitrary workspace script files used during refactoring. Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com>
Resolved CR comments: 1. Re-mapped `contains_rule` into `contains_match_rule` so dependencies in Match blocks properly disregard matching onto arbitrary rewriters. 2. In `Transform::deserialize`, narrowed the catchall mapping so anything besides `CyclicRule` triggers an `unreachable!()` as get_transform_order defaults to None environments. 3. Cleaned up stray files and resolved CI compilation differences. Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com>
Added an explicit check during rule environment deserialization to return an error when a referenced global, local, or rewriting rule cannot be found rather than silently ignoring the missing dependency. Updated
TopologicalSortinsidedeserialize_env.rsto leverageRuleRegistrationto verify the rule's presence, mapping unhandled variants directly to standard TransformErrors. Unit tests correctly mock global dependencies where required.PR created automatically by Jules for task 9177764991747379115 started by @bashandbone
Summary by Sourcery
Validate dependent rules during environment deserialization and propagate explicit reference errors instead of silently ignoring missing dependencies.
Bug Fixes:
Enhancements:
Tests: