Skip to main content

LoopConfig

Struct LoopConfig 

Source
#[non_exhaustive]
pub struct LoopConfig { pub counter: Option<String>, pub init: i64, pub increment: i64, pub max: i64, pub setup: Vec<Task>, pub over: Option<Value>, pub item: Option<String>, pub scratch: Option<String>, /* private fields */ }
Expand description

Engine-managed for loop over a workflow’s task list.

A workflow carrying a loop runs its task list repeatedly — one sweep per iteration — rather than once. Per sweep the engine writes the counter into temp_data (when counter names it), checks counter < max, then re-evaluates the workflow condition; the sweep runs only if both hold. Afterwards the counter advances by increment.

The bound is half-open, matching Rollout: init: 0, max: n yields counter values 0..n-1 — exactly array indices.

Reaching max is normal completion, never an error: max is always author-supplied, so hitting it is the stated bound rather than a runaway. To stop mid-body, use a filter task with on_reject: halt — that breaks the whole loop, not just the current sweep.

§Iterating an array

setup, over, item (as) and scratch turn the counter into a batch loop: setup runs once, over is evaluated once after it, and each iteration sees over[counter] in temp_data.<as> and a fresh {} in temp_data.<scratch>. A loop carrying none of them behaves exactly as a plain counter loop always has.

§Memory

Every sweep adds one audit entry per task, and while the message has capture_changes on — the default — each entry holds a deep copy of the old and new value of every write. Nothing is released until process_message returns, so a loop’s memory grows with sweeps × writes × value size: about 65 bytes per number written, so a body writing one 10,000-number array per sweep holds roughly 6.5 GB by sweep 10,000. A host that does not read AuditTrail::changes should build the message with MessageBuilder::capture_changes(false); the entries are still recorded, without values. See the Loops guide, “Memory in long loops”.

§Example

{
    "id": "per_item",
    "condition": {"<": [{"var": "temp_data.i"}, {"var": "temp_data.n"}]},
    "loop": { "counter": "i", "max": 10000 },
    "tasks": [ ... ]
}

#[non_exhaustive]: construct through LoopConfig::bounded and assign the public fields you need. The compiled fields are engine internals a struct literal would otherwise have to name. Not PartialEq: setup holds Tasks, which are not comparable, and neither is the compiled over.

Fields (Non-exhaustive)§

This struct is marked as non-exhaustive
Non-exhaustive structs could have additional fields added in future. Therefore, non-exhaustive structs cannot be constructed in external crates using the traditional Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.
§counter: Option<String>

temp_data field the engine maintains as the induction variable — "i" means temp_data.i, and dot-paths nest ("cursor.index" → temp_data.cursor.index).

None still bounds the loop by max; the count is simply not exposed to conditions or tasks. The engine tracks it either way, so the audit trail carries it regardless.

The engine owns this field: it is rewritten before every sweep, so a body task writing the same path is overwritten at the next increment.

§init: i64

First counter value. Defaults to 0.

§increment: i64

Added to the counter after each sweep. Defaults to 1; must be >= 1, so the counter strictly increases and the loop cannot stall.

§max: i64

Required upper bound — sweeps run while counter < max. There is no default: an unbounded loop is never what the author meant, and the bound is what makes termination structural rather than a property of the condition being written correctly.

§setup: Vec<Task>

Steps run once, before the first iteration, in the normal step grammar: groups are allowed, and terminal ends the workflow before any iteration. Their audit entries carry no loop_counter, because setup is not a sweep. Shares the step id namespace with the workflow’s tasks.

The workflow condition gates setup: a false result skips setup and the loop alike.

§over: Option<Value>

JSONLogic evaluated once, after setup. It must yield an array; anything else, null included, is a workflow error naming loop.over. An empty array runs zero iterations.

The counter indexes the array: iteration k holds over[k] in temp_data.<as>, and the loop stops at max or at the array’s end, whichever comes first. init is therefore a starting offset and increment a stride, and init must be >= 0.

An explicit "over": null is kept as Some(Value::Null) rather than read as absence, so it is refused like any other literal that can never be an array instead of silently turning the loop into a counter loop.

§item: Option<String>

temp_data field holding the current element of Self::over. The JSON key is as. Optional: without it the element is not exposed, but the loop is still bounded by the array’s length. Requires over.

§scratch: Option<String>

temp_data field reset to {} at the start of every iteration, before the condition, so per-item state cannot leak from one element to the next. Left holding the last iteration’s state after the loop, like the counter. Independent of over.

Implementations§

Source§

impl LoopConfig

Source

pub fn bounded(max: i64) -> Self

A loop bounded at max, every other field at its default: counter unnamed, init: 0, increment: 1, no setup, no over.

use dataflow_rs::engine::workflow::LoopConfig;

let mut config = LoopConfig::bounded(10);
config.counter = Some("i".to_string());
assert_eq!((config.init, config.increment, config.max), (0, 1, 10));

Trait Implementations§

Source§

impl Clone for LoopConfig

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for LoopConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for LoopConfig

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.