#[non_exhaustive]pub struct Rollout {
pub bucket_start: u8,
pub bucket_end: u8,
}Expand description
Half-open bucket range [bucket_start, bucket_end) over 0..100, giving this
workflow a slice of the traffic on its channel.
Compared against the message’s routing bucket. The engine does not derive the bucket: how a caller maps to one — a sticky hash of some request identity, a per-message random draw, round-robin — is entirely the caller’s policy and deliberately stays outside this crate.
Fields (Non-exhaustive)§
This struct is marked as non-exhaustive
Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.bucket_start: u8Inclusive lower bound.
bucket_end: u8Exclusive upper bound. 100 means “up to and including bucket 99”.
Implementations§
Source§impl Rollout
impl Rollout
Sourcepub const MAX_BUCKET: u8 = 99
pub const MAX_BUCKET: u8 = 99
The highest routing bucket. A run given a larger one is served as
this one, so [x, 100) serves it, as in 3.x.
Sourcepub fn accepts(&self, bucket: u8) -> bool
pub fn accepts(&self, bucket: u8) -> bool
Whether this range serves bucket (0..=99).
[0, 100) accepts everything. An empty or inverted range
(bucket_end <= bucket_start) accepts nothing.
Sourcepub fn partition(percentages: &[u8]) -> Result<Vec<Self>, RolloutError>
pub fn partition(percentages: &[u8]) -> Result<Vec<Self>, RolloutError>
Turn an ordered percentage split into contiguous half-open ranges
covering exactly 0..100.
Entry i gets the range starting where entry i-1 ended, so the input
order is the traffic order. The percentages must sum to exactly 100:
less leaves buckets that match nothing, more pushes later entries past
the end of the bucket space where they can never match. The error names
which.
A 0 entry is allowed and yields an empty range, which accepts nothing
— the natural way to express a version that is staged but takes no
traffic.
use dataflow_rs::def::{Rollout, RolloutError};
let split = Rollout::partition(&[90, 10]).unwrap();
assert_eq!(split[0], Rollout::new(0, 90));
assert_eq!(split[1], Rollout::new(90, 100));
// Anything the engine can route lands in exactly one range.
for bucket in 0u8..=99 {
assert_eq!(split.iter().filter(|r| r.accepts(bucket)).count(), 1);
}
assert_eq!(Rollout::partition(&[90, 9]), Err(RolloutError::Under { total: 99 }));
assert_eq!(Rollout::partition(&[90, 11]), Err(RolloutError::Over { total: 101 }));Sourcepub fn check(&self) -> Result<(), RolloutError>
pub fn check(&self) -> Result<(), RolloutError>
Check this range alone: an inverted range, or one reaching past bucket
100, serves no traffic, so a workflow carrying it silently never runs.
Rollout::partition never builds one; a hand-written definition can.
§Errors
RolloutError::InvalidRange naming this range.
Sourcepub fn validate_set<'a>(
rollouts: impl IntoIterator<Item = &'a Self>,
) -> Result<(), RolloutError>
pub fn validate_set<'a>( rollouts: impl IntoIterator<Item = &'a Self>, ) -> Result<(), RolloutError>
Check that a set of ranges — the versions of one logical workflow —
partitions 0..100: every bucket served, none served twice.
Both failures are silent in production otherwise. A gap blackholes a slice of traffic; an overlap makes which version answers depend on workflow ordering rather than on the rollout.
Ranges are checked individually first, so an inverted range or one reaching past bucket 100 is reported as itself rather than as whatever downstream gap it happens to produce. Coverage is then reported at the lowest affected bucket, so the diagnosis is deterministic.
use dataflow_rs::def::{Rollout, RolloutError};
let good = Rollout::partition(&[50, 50]).unwrap();
assert!(Rollout::validate_set(&good).is_ok());
// Order does not matter — this is a property of the set.
let reversed: Vec<_> = good.iter().rev().copied().collect();
assert!(Rollout::validate_set(&reversed).is_ok());
let gapped = [Rollout::new(0, 40), Rollout::new(41, 100)];
assert_eq!(
Rollout::validate_set(&gapped),
Err(RolloutError::Gap { bucket: 40 }),
);Trait Implementations§
impl Copy for Rollout
impl Eq for Rollout
impl StructuralPartialEq for Rollout
Auto Trait Implementations§
impl Freeze for Rollout
impl RefUnwindSafe for Rollout
impl Send for Rollout
impl Sync for Rollout
impl Unpin for Rollout
impl UnsafeUnpin for Rollout
impl UnwindSafe for Rollout
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.