Skip to content

Validate df.break() is inside a loop at graph construction time#59

Open
pinodeca wants to merge 1 commit into
mainfrom
pinodeca/break-loop
Open

Validate df.break() is inside a loop at graph construction time#59
pinodeca wants to merge 1 commit into
mainfrom
pinodeca/break-loop

Conversation

@pinodeca
Copy link
Copy Markdown
Contributor

Problem

df.break() used outside a df.loop() body silently produced a corrupted result — the raw internal {"__break__": true, "value": ...} sentinel was written as the instance output, and the instance was marked completed. No error was raised.

Fix

Reject such graphs immediately at df.start() time, before any nodes are inserted into the database.

Changes

  • src/types.rs: Add Durofut::validate_structural(in_loop: bool) which walks the graph, tracking whether execution is inside a loop body. A BREAK node encountered with in_loop = false returns a clear error. LOOP nodes recurse into their body with in_loop = true and into their while-condition with in_loop = false.

  • src/dsl.rs: Call validate_structural(false) in df.start() after the existing validate_recursive() check, before any nodes are inserted.

  • tests/e2e/sql/39_break_outside_loop.sql: E2E test covering four cases:

    1. Bare df.break() at top level → must error
    2. df.break() inside ~> sequence without loop → must error
    3. df.break() in conditional branch outside loop → must error
    4. df.break() inside df.loop() body → must complete successfully

Behavior table

Pattern Before After
df.start(df.break()) Completed with sentinel result Error at df.start()
df.start('step1' ~> df.break()) Completed with sentinel result Error at df.start()
df.start(df.loop('step' ~> df.break())) Completed correctly Completed correctly

@pinodeca pinodeca force-pushed the pinodeca/break-loop branch 2 times, most recently from 68010ae to a613bc2 Compare March 14, 2026 13:25
df.break() used outside a df.loop() body previously silently produced a
corrupted result (the raw __break__ sentinel) and marked the instance as
completed. Now df.start() rejects such graphs immediately with a clear error.

Changes:
- src/types.rs: add Durofut::validate_structural(in_loop) which walks the
  graph tracking loop context and returns an error if a BREAK node is
  encountered outside a LOOP body
- src/dsl.rs: call validate_structural(false) in df.start() after the
  existing validate_recursive() check, before any nodes are inserted
- tests/e2e/sql/39_break_outside_loop.sql: E2E test covering bare break,
  break in sequence, break in conditional (all must error), and valid
  break inside loop (must complete)
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds graph-construction-time validation to prevent df.break() from being used outside a df.loop() body, turning a previously silent “completed with sentinel output” failure mode into an immediate df.start() error.

Changes:

  • Add Durofut::validate_structural(in_loop: bool) to enforce loop-context structural rules.
  • Invoke structural validation in df.start() before inserting any nodes.
  • Add an E2E test covering invalid df.break() placements and a valid in-loop usage.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/types.rs Adds structural graph validation that tracks loop context and rejects invalid BREAK placement.
src/dsl.rs Calls the new structural validator from df.start() before DB writes.
tests/e2e/sql/39_break_outside_loop.sql Adds E2E coverage for df.break() outside vs. inside df.loop().

You can also share your feedback on Copilot code review. Take the survey.

Comment thread src/types.rs
Comment on lines +566 to +574
// For all other nodes propagate the current loop context.
if let Some(ref left) = self.left_node {
left.validate_structural(in_loop)?;
}
if let Some(ref right) = self.right_node {
right.validate_structural(in_loop)?;
}
self.for_each_config_child(|child| child.validate_structural(in_loop))?;
Ok(())
Comment on lines +7 to +12
BEGIN
PERFORM df.start(df.break());
RAISE EXCEPTION 'TEST FAILED: bare df.break() at top level should have been rejected';
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'Case 1 OK - caught expected error: %', SQLERRM;
END;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants