Skip to content

Commit 81a4c23

Browse files
committed
Add Ranges::from_normalized
1 parent 216f3fd commit 81a4c23

File tree

1 file changed

+109
-0
lines changed

1 file changed

+109
-0
lines changed

version-ranges/src/lib.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,39 @@ impl<V: Ord> Ranges<V> {
283283
}
284284
}
285285

286+
/// Construct from segments already fulfilling the [`Ranges`] invariants.
287+
///
288+
/// 1. The segments are sorted, from lowest to highest (through `Ord`).
289+
/// 2. Each segment contains at least one version (start < end).
290+
/// 3. There is at least one version between two segments.
291+
pub fn from_normalized(
292+
into_iter: impl IntoIterator<Item = (Bound<V>, Bound<V>)>,
293+
) -> Result<Self, FromIterError> {
294+
let mut iter = into_iter.into_iter();
295+
let Some(mut previous) = iter.next() else {
296+
return Ok(Self {
297+
segments: SmallVec::new(),
298+
});
299+
};
300+
let mut segments = SmallVec::with_capacity(iter.size_hint().0);
301+
for current in iter {
302+
if !valid_segment(&previous.start_bound(), &previous.end_bound()) {
303+
return Err(FromIterError::InvalidSegment);
304+
}
305+
if !end_before_start_with_gap(&previous.end_bound(), &current.start_bound()) {
306+
return Err(FromIterError::OverlappingSegments);
307+
}
308+
segments.push(previous);
309+
previous = current;
310+
}
311+
if !valid_segment(&previous.start_bound(), &previous.end_bound()) {
312+
return Err(FromIterError::InvalidSegment);
313+
}
314+
segments.push(previous);
315+
Ok(Self { segments })
316+
}
317+
318+
/// See `Ranges` docstring for the invariants.
286319
fn check_invariants(self) -> Self {
287320
if cfg!(debug_assertions) {
288321
for p in self.segments.as_slice().windows(2) {
@@ -418,6 +451,31 @@ fn cmp_bounds_end<V: PartialOrd>(left: Bound<&V>, right: Bound<&V>) -> Option<Or
418451
})
419452
}
420453

454+
/// User provided segment iterator breaks [`Ranges`] invariants.
455+
///
456+
/// Not user accessible since `FromIterator<(Bound<V>, Bound<V>)>` panics and `iterator_try_collect`
457+
/// is unstable.
458+
#[derive(Debug, PartialEq, Eq)]
459+
pub enum FromIterError {
460+
/// The start of a segment must be before its end, and a segment must contain at least one
461+
/// version.
462+
InvalidSegment,
463+
/// The end of a segment is not before the start of the next segment, leaving at least one
464+
/// version space.
465+
OverlappingSegments,
466+
}
467+
468+
impl Display for FromIterError {
469+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
470+
match self {
471+
FromIterError::InvalidSegment => f.write_str("segment must be valid"),
472+
FromIterError::OverlappingSegments => {
473+
f.write_str("end of a segment and start of the next segment must not overlap")
474+
}
475+
}
476+
}
477+
}
478+
421479
impl<V: PartialOrd> PartialOrd for Ranges<V> {
422480
/// A simple ordering scheme where we zip the segments and compare all bounds in order. If all
423481
/// bounds are equal, the longer range is considered greater. (And if all zipped bounds are
@@ -1130,6 +1188,24 @@ pub mod tests {
11301188
}
11311189
assert!(simp.segments.len() <= range.segments.len())
11321190
}
1191+
1192+
#[test]
1193+
fn from_normalized_valid(segments in proptest::collection::vec(any::<(Bound<u32>, Bound<u32>)>(), ..30)) {
1194+
match Ranges::from_normalized(segments.clone()) {
1195+
Ok(ranges) => {
1196+
ranges.check_invariants();
1197+
}
1198+
Err(_) => {
1199+
assert!(
1200+
segments
1201+
.as_slice()
1202+
.windows(2)
1203+
.any(|p| !end_before_start_with_gap(&p[0].1, &p[1].0))
1204+
|| segments.iter().any(|(start, end)| !valid_segment(start, end))
1205+
);
1206+
}
1207+
}
1208+
}
11331209
}
11341210

11351211
#[test]
@@ -1194,4 +1270,37 @@ pub mod tests {
11941270
version_reverse_sorted.sort();
11951271
assert_eq!(version_reverse_sorted, versions);
11961272
}
1273+
1274+
/// Test all error conditions in [`Ranges::from_normalized`].
1275+
#[test]
1276+
fn from_iter_errors() {
1277+
// Unbounded in not at an end
1278+
let result = Ranges::from_normalized([
1279+
(Bound::Included(1), Bound::Unbounded),
1280+
(Bound::Included(2), Bound::Unbounded),
1281+
]);
1282+
assert_eq!(result, Err(FromIterError::OverlappingSegments));
1283+
// Not a version in between
1284+
let result = Ranges::from_normalized([
1285+
(Bound::Included(1), Bound::Excluded(2)),
1286+
(Bound::Included(2), Bound::Unbounded),
1287+
]);
1288+
assert_eq!(result, Err(FromIterError::OverlappingSegments));
1289+
// First segment
1290+
let result = Ranges::from_normalized([(Bound::Excluded(2), Bound::Included(2))]);
1291+
assert_eq!(result, Err(FromIterError::InvalidSegment));
1292+
// Middle segment
1293+
let result = Ranges::from_normalized([
1294+
(Bound::Included(1), Bound::Included(2)),
1295+
(Bound::Included(3), Bound::Included(2)),
1296+
(Bound::Included(4), Bound::Included(5)),
1297+
]);
1298+
assert_eq!(result, Err(FromIterError::InvalidSegment));
1299+
// Last segment
1300+
let result = Ranges::from_normalized([
1301+
(Bound::Included(1), Bound::Included(2)),
1302+
(Bound::Included(3), Bound::Included(2)),
1303+
]);
1304+
assert_eq!(result, Err(FromIterError::InvalidSegment));
1305+
}
11971306
}

0 commit comments

Comments
 (0)