1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-05-18 16:28:20 +00:00

improve documentation and tests of ByteRange

This commit is contained in:
Luro02 2020-02-23 18:56:41 +01:00
parent 651db2e18b
commit 5972216323
No known key found for this signature in database
GPG key ID: B66FD4F74501A9CF
6 changed files with 764 additions and 138 deletions

View file

@ -153,8 +153,8 @@ impl MediaPlaylistBuilder {
}
// CHECK: `#EXT-X-BYTE-RANGE`
if let Some(tag) = s.byte_range() {
if tag.to_range().start().is_none() {
if let Some(range) = s.byte_range() {
if range.start().is_none() {
let last_uri = last_range_uri.ok_or_else(Error::invalid_input)?;
if last_uri != s.uri() {
return Err(Error::invalid_input());

View file

@ -120,7 +120,7 @@ mod tests {
MediaSegment::builder()
//.keys(vec![ExtXKey::empty()])
.map(ExtXMap::new("https://www.example.com/"))
.byte_range(ExtXByteRange::new(20, Some(5)))
.byte_range(ExtXByteRange::from(5..25))
//.date_range() // TODO!
.discontinuity(ExtXDiscontinuity)
.inf(ExtInf::new(Duration::from_secs(4)))

View file

@ -1,39 +1,120 @@
use std::fmt;
use std::str::FromStr;
use derive_more::{Deref, DerefMut};
use core::ops::{Add, AddAssign, Sub, SubAssign};
use derive_more::{AsMut, AsRef, Deref, DerefMut, From};
use crate::types::{ByteRange, ProtocolVersion};
use crate::utils::tag;
use crate::{Error, RequiredVersion};
/// # [4.4.2.2. EXT-X-BYTERANGE]
/// Indicates that a [`MediaSegment`] is a sub-range of the resource identified
/// by its `URI`.
///
/// The [`ExtXByteRange`] tag indicates that a [`Media Segment`] is a sub-range
/// of the resource identified by its `URI`.
/// # Example
///
/// [`Media Segment`]: crate::MediaSegment
/// [4.4.2.2. EXT-X-BYTERANGE]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.2
#[derive(Deref, DerefMut, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
/// Constructing an [`ExtXByteRange`]:
///
/// ```
/// # use hls_m3u8::tags::ExtXByteRange;
/// assert_eq!(ExtXByteRange::from(22..55), ExtXByteRange::from(22..=54));
/// ```
///
/// It is also possible to omit the start, in which case it assumes that the
/// [`ExtXByteRange`] starts at the byte after the end of the previous
/// [`ExtXByteRange`] or 0 if there is no previous one.
///
/// ```
/// # use hls_m3u8::tags::ExtXByteRange;
/// assert_eq!(ExtXByteRange::from(..55), ExtXByteRange::from(..=54));
/// ```
///
/// [`MediaSegment`]: crate::MediaSegment
#[derive(
AsRef, AsMut, From, Deref, DerefMut, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord,
)]
#[from(forward)]
pub struct ExtXByteRange(ByteRange);
impl ExtXByteRange {
pub(crate) const PREFIX: &'static str = "#EXT-X-BYTERANGE:";
/// Makes a new [`ExtXByteRange`] tag.
/// Adds `num` to the `start` and `end` of the range.
///
/// # Example
///
/// ```
/// # use hls_m3u8::tags::ExtXByteRange;
/// let byte_range = ExtXByteRange::new(20, Some(5));
/// let range = ExtXByteRange::from(10..22);
/// let nrange = range.saturating_add(5);
///
/// assert_eq!(nrange.len(), range.len());
/// assert_eq!(nrange.start(), range.start().map(|c| c + 5));
/// ```
pub const fn new(length: usize, start: Option<usize>) -> Self {
Self(ByteRange::new(length, start))
}
///
/// # Overflow
///
/// If the range is saturated it will not overflow and instead
/// stay at it's current value.
///
/// ```
/// # use hls_m3u8::tags::ExtXByteRange;
/// let range = ExtXByteRange::from(5..usize::max_value());
///
/// // this would cause the end to overflow
/// let nrange = range.saturating_add(1);
///
/// // but the range remains unchanged
/// assert_eq!(range, nrange);
/// ```
///
/// # Note
///
/// The length of the range will remain unchanged,
/// if the `start` is `Some`.
#[inline]
#[must_use]
pub fn saturating_add(self, num: usize) -> Self { Self(self.0.saturating_add(num)) }
/// Converts the [`ExtXByteRange`] to a [`ByteRange`].
/// Subtracts `num` from the `start` and `end` of the range.
///
/// # Example
///
/// ```
/// # use hls_m3u8::tags::ExtXByteRange;
/// let range = ExtXByteRange::from(10..22);
/// let nrange = range.saturating_sub(5);
///
/// assert_eq!(nrange.len(), range.len());
/// assert_eq!(nrange.start(), range.start().map(|c| c - 5));
/// ```
///
/// # Underflow
///
/// If the range is saturated it will not underflow and instead stay
/// at it's current value.
///
/// ```
/// # use hls_m3u8::tags::ExtXByteRange;
/// let range = ExtXByteRange::from(0..10);
///
/// // this would cause the start to underflow
/// let nrange = range.saturating_sub(1);
///
/// // but the range remains unchanged
/// assert_eq!(range, nrange);
/// ```
///
/// # Note
///
/// The length of the range will remain unchanged,
/// if the `start` is `Some`.
#[inline]
#[must_use]
pub fn saturating_sub(self, num: usize) -> Self { Self(self.0.saturating_sub(num)) }
/// Returns a shared reference to the underlying [`ByteRange`].
///
/// # Example
///
@ -41,10 +122,14 @@ impl ExtXByteRange {
/// # use hls_m3u8::tags::ExtXByteRange;
/// use hls_m3u8::types::ByteRange;
///
/// let byte_range = ExtXByteRange::new(20, Some(5));
/// let range: ByteRange = byte_range.to_range();
/// assert_eq!(
/// ExtXByteRange::from(2..11).as_byte_range(),
/// &ByteRange::from(2..11)
/// );
/// ```
pub const fn to_range(&self) -> ByteRange { self.0 }
#[inline]
#[must_use]
pub const fn as_byte_range(&self) -> &ByteRange { &self.0 }
}
/// This tag requires [`ProtocolVersion::V4`].
@ -52,6 +137,48 @@ impl RequiredVersion for ExtXByteRange {
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V4 }
}
impl Into<ByteRange> for ExtXByteRange {
fn into(self) -> ByteRange { self.0 }
}
impl<T> Sub<T> for ExtXByteRange
where
ByteRange: Sub<T, Output = ByteRange>,
{
type Output = Self;
#[must_use]
#[inline]
fn sub(self, rhs: T) -> Self::Output { Self(self.0.sub(rhs)) }
}
impl<T> SubAssign<T> for ExtXByteRange
where
ByteRange: SubAssign<T>,
{
#[inline]
fn sub_assign(&mut self, other: T) { self.0.sub_assign(other); }
}
impl<T> Add<T> for ExtXByteRange
where
ByteRange: Add<T, Output = ByteRange>,
{
type Output = Self;
#[must_use]
#[inline]
fn add(self, rhs: T) -> Self::Output { Self(self.0.add(rhs)) }
}
impl<T> AddAssign<T> for ExtXByteRange
where
ByteRange: AddAssign<T>,
{
#[inline]
fn add_assign(&mut self, other: T) { self.0.add_assign(other); }
}
impl fmt::Display for ExtXByteRange {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Self::PREFIX)?;
@ -77,57 +204,52 @@ mod test {
#[test]
fn test_display() {
let byte_range = ExtXByteRange::new(0, Some(5));
assert_eq!(byte_range.to_string(), "#EXT-X-BYTERANGE:0@5".to_string());
let byte_range = ExtXByteRange::new(99999, Some(2));
assert_eq!(
byte_range.to_string(),
"#EXT-X-BYTERANGE:99999@2".to_string()
ExtXByteRange::from(2..15).to_string(),
"#EXT-X-BYTERANGE:13@2".to_string()
);
let byte_range = ExtXByteRange::new(99999, None);
assert_eq!(byte_range.to_string(), "#EXT-X-BYTERANGE:99999".to_string());
assert_eq!(
ExtXByteRange::from(..22).to_string(),
"#EXT-X-BYTERANGE:22".to_string()
);
}
#[test]
fn test_parser() {
let byte_range = ExtXByteRange::new(99999, Some(2));
assert_eq!(
byte_range,
"#EXT-X-BYTERANGE:99999@2".parse::<ExtXByteRange>().unwrap()
ExtXByteRange::from(2..15),
"#EXT-X-BYTERANGE:13@2".parse().unwrap()
);
let byte_range = ExtXByteRange::new(99999, None);
assert_eq!(
byte_range,
"#EXT-X-BYTERANGE:99999".parse::<ExtXByteRange>().unwrap()
ExtXByteRange::from(..22),
"#EXT-X-BYTERANGE:22".parse().unwrap()
);
}
#[test]
fn test_deref() {
let byte_range = ExtXByteRange::new(0, Some(22));
let byte_range = ExtXByteRange::from(0..22);
assert_eq!(byte_range.length(), 0);
assert_eq!(byte_range.start(), Some(22));
assert_eq!(byte_range.len(), 22);
assert_eq!(byte_range.start(), Some(0));
}
#[test]
fn test_deref_mut() {
let mut byte_range = ExtXByteRange::new(0, Some(22));
let mut byte_range = ExtXByteRange::from(10..110);
byte_range.set_length(100);
byte_range.set_start(Some(50));
assert_eq!(byte_range.length(), 100);
assert_eq!(byte_range.len(), 60);
assert_eq!(byte_range.start(), Some(50));
}
#[test]
fn test_required_version() {
assert_eq!(
ExtXByteRange::new(20, Some(5)).required_version(),
ExtXByteRange::from(5..20).required_version(),
ProtocolVersion::V4
);
}

View file

@ -44,13 +44,10 @@ pub struct ExtXMap {
/// # use hls_m3u8::tags::ExtXMap;
/// use hls_m3u8::types::ByteRange;
///
/// let mut map = ExtXMap::with_range(
/// "https://prod.mediaspace.com/init.bin",
/// ByteRange::new(9, Some(2)),
/// );
/// let mut map = ExtXMap::with_range("https://prod.mediaspace.com/init.bin", ..9);
///
/// map.set_range(Some(ByteRange::new(1, None)));
/// assert_eq!(map.range(), Some(ByteRange::new(1, None)));
/// map.set_range(Some(2..5));
/// assert_eq!(map.range(), Some(ByteRange::from(2..5)));
/// ```
#[shorthand(enable(copy))]
range: Option<ByteRange>,
@ -85,15 +82,12 @@ impl ExtXMap {
/// # use hls_m3u8::tags::ExtXMap;
/// use hls_m3u8::types::ByteRange;
///
/// let map = ExtXMap::with_range(
/// "https://prod.mediaspace.com/init.bin",
/// ByteRange::new(9, Some(2)),
/// );
/// ExtXMap::with_range("https://prod.mediaspace.com/init.bin", 2..11);
/// ```
pub fn with_range<T: Into<String>>(uri: T, range: ByteRange) -> Self {
pub fn with_range<I: Into<String>, B: Into<ByteRange>>(uri: I, range: B) -> Self {
Self {
uri: uri.into(),
range: Some(range),
range: Some(range.into()),
keys: vec![],
}
}
@ -173,7 +167,7 @@ mod test {
);
assert_eq!(
ExtXMap::with_range("foo", ByteRange::new(9, Some(2))).to_string(),
ExtXMap::with_range("foo", ByteRange::from(2..11)).to_string(),
"#EXT-X-MAP:URI=\"foo\",BYTERANGE=\"9@2\"".to_string(),
);
}
@ -186,11 +180,11 @@ mod test {
);
assert_eq!(
ExtXMap::with_range("foo", ByteRange::new(9, Some(2))),
ExtXMap::with_range("foo", ByteRange::from(2..11)),
"#EXT-X-MAP:URI=\"foo\",BYTERANGE=\"9@2\"".parse().unwrap()
);
assert_eq!(
ExtXMap::with_range("foo", ByteRange::new(9, Some(2))),
ExtXMap::with_range("foo", ByteRange::from(2..11)),
"#EXT-X-MAP:URI=\"foo\",BYTERANGE=\"9@2\",UNKNOWN=IGNORED"
.parse()
.unwrap()
@ -201,7 +195,7 @@ mod test {
fn test_required_version() {
assert_eq!(ExtXMap::new("foo").required_version(), ProtocolVersion::V6);
assert_eq!(
ExtXMap::with_range("foo", ByteRange::new(9, Some(2))).required_version(),
ExtXMap::with_range("foo", ByteRange::from(2..11)).required_version(),
ProtocolVersion::V6
);
}

View file

@ -1,67 +1,394 @@
use std::fmt;
use std::str::FromStr;
use core::convert::TryInto;
use core::fmt;
use core::ops::{
Add, AddAssign, Bound, Range, RangeBounds, RangeInclusive, RangeTo, RangeToInclusive, Sub,
SubAssign,
};
use core::str::FromStr;
use shorthand::ShortHand;
use crate::Error;
/// Byte range.
/// A range of bytes, which can be seen as either `..end` or `start..end`.
///
/// See: [4.3.2.2. EXT-X-BYTERANGE]
/// It can be constructed from `..end` and `start..end`:
///
/// [4.3.2.2. EXT-X-BYTERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.2
/// ```
/// use hls_m3u8::types::ByteRange;
///
/// let range = ByteRange::from(10..20);
/// let range = ByteRange::from(..20);
/// ```
#[derive(ShortHand, Copy, Hash, Eq, Ord, Debug, PartialEq, Clone, PartialOrd)]
#[shorthand(enable(must_use))]
#[shorthand(enable(must_use, copy), disable(option_as_ref, set))]
pub struct ByteRange {
/// The length of the range.
/// Returns the `start` of the [`ByteRange`], if there is one.
///
/// # Example
///
/// ```
/// # use hls_m3u8::types::ByteRange;
/// #
/// let mut range = ByteRange::new(20, Some(3));
/// # assert_eq!(range.length(), 20);
///
/// range.set_length(10);
/// assert_eq!(range.length(), 10);
/// assert_eq!(ByteRange::from(0..5).start(), Some(0));
/// assert_eq!(ByteRange::from(..5).start(), None);
/// ```
length: usize,
/// The start of the range.
///
/// # Example
///
/// ```
/// # use hls_m3u8::types::ByteRange;
/// #
/// let mut range = ByteRange::new(20, None);
/// # assert_eq!(range.start(), None);
///
/// range.set_start(Some(3));
/// assert_eq!(range.start(), Some(3));
/// ```
//
// this is a workaround until this issue is fixed:
// https://github.com/Luro02/shorthand/issues/20
#[shorthand(enable(copy), disable(option_as_ref))]
start: Option<usize>,
/// Returns the `end` of the [`ByteRange`].
///
/// # Example
///
/// ```
/// # use hls_m3u8::types::ByteRange;
/// assert_eq!(ByteRange::from(0..5).start(), Some(0));
/// assert_eq!(ByteRange::from(..5).start(), None);
/// ```
end: usize,
}
impl ByteRange {
/// Creates a new [`ByteRange`].
/// Changes the length of the [`ByteRange`].
///
/// # Example
///
/// ```
/// # use hls_m3u8::types::ByteRange;
/// ByteRange::new(22, Some(12));
/// let mut range = ByteRange::from(0..5);
/// range.set_len(2);
///
/// assert_eq!(range, ByteRange::from(0..2));
///
/// range.set_len(200);
/// assert_eq!(range, ByteRange::from(0..200));
/// ```
pub const fn new(length: usize, start: Option<usize>) -> Self { Self { length, start } }
///
/// # Note
///
/// The `start` will not be changed.
pub fn set_len(&mut self, new_len: usize) {
// the new_len can be either greater or smaller than `self.len()`.
// if new_len is larger `checked_sub` will return `None`
if let Some(value) = self.len().checked_sub(new_len) {
self.end -= value;
} else {
self.end += new_len.saturating_sub(self.len());
}
}
/// Sets the `start` of the [`ByteRange`].
///
/// # Example
///
/// ```
/// # use hls_m3u8::types::ByteRange;
/// assert_eq!(ByteRange::from(0..5).set_start(Some(5)).start(), Some(5));
/// assert_eq!(ByteRange::from(..5).set_start(Some(2)).start(), Some(2));
/// ```
///
/// # Panics
///
/// This function will panic, if the `new_start` is larger, than the
/// [`end`](ByteRange::end).
pub fn set_start(&mut self, new_start: Option<usize>) -> &mut Self {
if new_start.map_or(false, |s| s > self.end) {
panic!("attempt to make the start larger than the end");
}
self.start = new_start;
self
}
/// Adds `num` to the `start` and `end` of the range.
///
/// # Example
///
/// ```
/// # use hls_m3u8::types::ByteRange;
/// let range = ByteRange::from(10..22);
/// let nrange = range.saturating_add(5);
///
/// assert_eq!(nrange.len(), range.len());
/// assert_eq!(nrange.start(), range.start().map(|c| c + 5));
/// ```
///
/// # Overflow
///
/// If the range is saturated it will not overflow and instead stay
/// at it's current value.
///
/// ```
/// # use hls_m3u8::types::ByteRange;
/// let range = ByteRange::from(5..usize::max_value());
///
/// // this would cause the end to overflow
/// let nrange = range.saturating_add(1);
///
/// // but the range remains unchanged
/// assert_eq!(range, nrange);
/// ```
///
/// # Note
///
/// The length of the range will remain unchanged,
/// if the `start` is `Some`.
#[must_use]
pub fn saturating_add(mut self, num: usize) -> Self {
if let Some(start) = self.start {
// add the number to the start
if let (Some(start), Some(end)) = (start.checked_add(num), self.end.checked_add(num)) {
self.start = Some(start);
self.end = end;
} else {
// it is ensured at construction that the start will never be larger than the
// end. This clause can therefore be only reached if the end overflowed.
// -> It is only possible to add `usize::max_value() - end` to the start.
if let Some(start) = start.checked_add(usize::max_value() - self.end) {
self.start = Some(start);
self.end = usize::max_value();
} else {
// both end + start overflowed -> do not change anything
}
}
} else {
self.end = self.end.saturating_add(num);
}
self
}
/// Subtracts `num` from the `start` and `end` of the range.
///
/// # Example
///
/// ```
/// # use hls_m3u8::types::ByteRange;
/// let range = ByteRange::from(10..22);
/// let nrange = range.saturating_sub(5);
///
/// assert_eq!(nrange.len(), range.len());
/// assert_eq!(nrange.start(), range.start().map(|c| c - 5));
/// ```
///
/// # Underflow
///
/// If the range is saturated it will not underflow and instead stay
/// at it's current value.
///
/// ```
/// # use hls_m3u8::types::ByteRange;
/// let range = ByteRange::from(0..10);
///
/// // this would cause the start to underflow
/// let nrange = range.saturating_sub(1);
///
/// // but the range remains unchanged
/// assert_eq!(range, nrange);
/// ```
///
/// # Note
///
/// The length of the range will remain unchanged,
/// if the `start` is `Some`.
#[must_use]
pub fn saturating_sub(mut self, num: usize) -> Self {
if let Some(start) = self.start {
// subtract the number from the start
if let (Some(start), Some(end)) = (start.checked_sub(num), self.end.checked_sub(num)) {
self.start = Some(start);
self.end = end;
} else {
// it is ensured at construction that the start will never be larger, than the
// end so this clause will only be reached, if the start underflowed.
// -> can at most subtract `start` from `end`
if let Some(end) = self.end.checked_sub(start) {
self.start = Some(0);
self.end = end;
} else {
// both end + start underflowed
// -> do not change anything
}
}
} else {
self.end = self.end.saturating_sub(num);
}
self
}
/// Returns the length, which is calculated by subtracting the `end` from
/// the `start`. If the `start` is `None` a 0 is assumed.
///
/// # Example
///
/// ```
/// # use hls_m3u8::types::ByteRange;
/// let range = ByteRange::from(1..16);
///
/// assert_eq!(range.len(), 15);
/// ```
#[inline]
#[must_use]
pub fn len(&self) -> usize { self.end.saturating_sub(self.start.unwrap_or(0)) }
/// Returns `true` if the length is zero.
///
/// # Example
///
/// ```
/// # use hls_m3u8::types::ByteRange;
/// let range = ByteRange::from(12..12);
///
/// assert_eq!(range.is_empty(), true);
/// ```
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool { self.len() == 0 }
}
impl Sub<usize> for ByteRange {
type Output = Self;
#[must_use]
#[inline]
fn sub(self, rhs: usize) -> Self::Output {
Self {
start: self.start.map(|lhs| lhs - rhs),
end: self.end - rhs,
}
}
}
impl SubAssign<usize> for ByteRange {
#[inline]
fn sub_assign(&mut self, other: usize) { *self = <Self as Sub<usize>>::sub(*self, other); }
}
impl Add<usize> for ByteRange {
type Output = Self;
#[must_use]
#[inline]
fn add(self, rhs: usize) -> Self::Output {
Self {
start: self.start.map(|lhs| lhs + rhs),
end: self.end + rhs,
}
}
}
impl AddAssign<usize> for ByteRange {
#[inline]
fn add_assign(&mut self, other: usize) { *self = <Self as Add<usize>>::add(*self, other); }
}
macro_rules! impl_from_ranges {
( $( $type:tt ),* ) => {
$(
#[allow(trivial_numeric_casts, clippy::fallible_impl_from)]
impl From<Range<$type>> for ByteRange {
fn from(range: Range<$type>) -> Self {
if range.start > range.end {
panic!("the range start must be smaller than the end");
}
Self {
start: Some(range.start as usize),
end: range.end as usize,
}
}
}
#[allow(trivial_numeric_casts, clippy::fallible_impl_from)]
impl From<RangeInclusive<$type>> for ByteRange {
fn from(range: RangeInclusive<$type>) -> Self {
let (start, end) = range.into_inner();
if start > end {
panic!("the range start must be smaller than the end");
}
Self {
start: Some(start as usize),
end: (end as usize).saturating_add(1),
}
}
}
#[allow(trivial_numeric_casts, clippy::fallible_impl_from)]
impl From<RangeTo<$type>> for ByteRange {
fn from(range: RangeTo<$type>) -> Self {
Self {
start: None,
end: range.end as usize,
}
}
}
#[allow(trivial_numeric_casts, clippy::fallible_impl_from)]
impl From<RangeToInclusive<$type>> for ByteRange {
fn from(range: RangeToInclusive<$type>) -> Self {
Self {
start: None,
end: (range.end as usize).saturating_add(1),
}
}
}
)*
}
}
// TODO: replace with generics as soon as overlapping trait implementations are
// stable (`Into<i64> for usize` is reserved for upstream crates ._.)
impl_from_ranges![u64, u32, u16, u8, usize, i32];
#[must_use]
impl RangeBounds<usize> for ByteRange {
fn start_bound(&self) -> Bound<&usize> {
if let Some(start) = &self.start {
Bound::Included(start)
} else {
Bound::Unbounded
}
}
#[inline]
fn end_bound(&self) -> Bound<&usize> { Bound::Excluded(&self.end) }
}
/// This conversion will fail if the start of the [`ByteRange`] is `Some`.
impl TryInto<RangeTo<usize>> for ByteRange {
type Error = Error;
fn try_into(self) -> Result<RangeTo<usize>, Self::Error> {
if self.start.is_some() {
return Err(Error::custom("A `RangeTo` (`..end`) does not have a start"));
}
Ok(RangeTo { end: self.end })
}
}
/// This conversion will fail if the start of the [`ByteRange`] is `None`.
impl TryInto<Range<usize>> for ByteRange {
type Error = Error;
fn try_into(self) -> Result<Range<usize>, Self::Error> {
if self.start.is_none() {
return Err(Error::custom(
"A `Range` (`start..end`) has to have a start.",
));
}
Ok(Range {
start: self.start.unwrap(),
end: self.end,
})
}
}
impl fmt::Display for ByteRange {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.length)?;
write!(f, "{}", self.len())?;
if let Some(value) = self.start {
write!(f, "@{}", value)?;
@ -77,17 +404,20 @@ impl FromStr for ByteRange {
fn from_str(input: &str) -> Result<Self, Self::Err> {
let mut input = input.splitn(2, '@');
let length = input
.next()
.ok_or_else(|| Error::custom("missing length"))?;
let length = length.parse().map_err(|e| Error::parse_int(length, e))?;
let length = input.next().unwrap();
let length = length
.parse::<usize>()
.map_err(|e| Error::parse_int(length, e))?;
let start = input
.next()
.map(|v| v.parse().map_err(|e| Error::parse_int(v, e)))
.map(|v| v.parse::<usize>().map_err(|e| Error::parse_int(v, e)))
.transpose()?;
Ok(Self::new(length, start))
Ok(Self {
start,
end: start.unwrap_or(0) + length,
})
}
}
@ -97,59 +427,239 @@ mod tests {
use pretty_assertions::assert_eq;
#[test]
fn test_display() {
#[should_panic = "the range start must be smaller than the end"]
fn test_from_range_panic() { let _ = ByteRange::from(6..0); }
#[test]
#[should_panic = "the range start must be smaller than the end"]
fn test_from_range_inclusive_panic() { let _ = ByteRange::from(6..=0); }
#[test]
fn test_from_ranges() {
assert_eq!(ByteRange::from(1..10), ByteRange::from(1..=9));
assert_eq!(ByteRange::from(..10), ByteRange::from(..=9));
}
#[test]
fn test_range_bounds() {
assert_eq!(ByteRange::from(0..10).start_bound(), Bound::Included(&0));
assert_eq!(ByteRange::from(..10).start_bound(), Bound::Unbounded);
assert_eq!(ByteRange::from(0..10).end_bound(), Bound::Excluded(&10));
assert_eq!(ByteRange::from(..10).end_bound(), Bound::Excluded(&10));
}
#[test]
fn test_try_into() {
assert_eq!(ByteRange::from(1..4).try_into(), Ok(1..4));
assert_eq!(ByteRange::from(..4).try_into(), Ok(..4));
assert!(TryInto::<RangeTo<usize>>::try_into(ByteRange::from(1..4)).is_err());
assert!(TryInto::<Range<usize>>::try_into(ByteRange::from(..4)).is_err());
}
#[test]
fn test_add_assign() {
let mut range = ByteRange::from(5..10);
range += 5;
assert_eq!(range, ByteRange::from(10..15));
}
#[test]
#[should_panic = "attempt to add with overflow"]
fn test_add_assign_panic() {
let mut range = ByteRange::from(4..usize::max_value());
range += 5;
unreachable!();
}
#[test]
fn test_sub_assign() {
let mut range = ByteRange::from(10..20);
range -= 5;
assert_eq!(range, ByteRange::from(5..15));
}
#[test]
#[should_panic = "attempt to subtract with overflow"]
fn test_sub_assign_panic() {
let mut range = ByteRange::from(4..10);
range -= 5;
unreachable!();
}
#[test]
#[should_panic = "attempt to make the start larger than the end"]
fn test_set_start() { let _ = ByteRange::from(4..10).set_start(Some(11)); }
#[test]
fn test_add() {
// normal addition
assert_eq!(ByteRange::from(5..10) + 5, ByteRange::from(10..15));
assert_eq!(ByteRange::from(..10) + 5, ByteRange::from(..15));
// adding 0
assert_eq!(ByteRange::from(5..10) + 0, ByteRange::from(5..10));
assert_eq!(ByteRange::from(..10) + 0, ByteRange::from(..10));
}
#[test]
#[should_panic = "attempt to add with overflow"]
fn test_add_panic() { let _ = ByteRange::from(usize::max_value()..usize::max_value()) + 1; }
#[test]
fn test_sub() {
// normal subtraction
assert_eq!(ByteRange::from(5..10) - 4, ByteRange::from(1..6));
assert_eq!(ByteRange::from(..10) - 4, ByteRange::from(..6));
// subtracting 0
assert_eq!(ByteRange::from(0..0) - 0, ByteRange::from(0..0));
assert_eq!(ByteRange::from(2..3) - 0, ByteRange::from(2..3));
assert_eq!(ByteRange::from(..0) - 0, ByteRange::from(..0));
assert_eq!(ByteRange::from(..3) - 0, ByteRange::from(..3));
}
#[test]
#[should_panic = "attempt to subtract with overflow"]
fn test_sub_panic() { let _ = ByteRange::from(0..0) - 1; }
#[test]
fn test_saturating_add() {
// normal addition
assert_eq!(
ByteRange {
length: 0,
start: Some(5),
}
.to_string(),
"0@5".to_string()
ByteRange::from(5..10).saturating_add(5),
ByteRange::from(10..15)
);
assert_eq!(
ByteRange::from(..10).saturating_add(5),
ByteRange::from(..15)
);
// adding 0
assert_eq!(
ByteRange::from(6..11).saturating_add(0),
ByteRange::from(6..11)
);
assert_eq!(
ByteRange::from(..11).saturating_add(0),
ByteRange::from(..11)
);
assert_eq!(
ByteRange {
length: 99999,
start: Some(2),
}
.to_string(),
"99999@2".to_string()
ByteRange::from(0..0).saturating_add(0),
ByteRange::from(0..0)
);
assert_eq!(ByteRange::from(..0).saturating_add(0), ByteRange::from(..0));
// overflow
assert_eq!(
ByteRange::from(usize::max_value()..usize::max_value()).saturating_add(1),
ByteRange::from(usize::max_value()..usize::max_value())
);
assert_eq!(
ByteRange::from(..usize::max_value()).saturating_add(1),
ByteRange::from(..usize::max_value())
);
assert_eq!(
ByteRange {
length: 99999,
start: None,
}
.to_string(),
"99999".to_string()
ByteRange::from(usize::max_value() - 5..usize::max_value()).saturating_add(1),
ByteRange::from(usize::max_value() - 5..usize::max_value())
);
// overflow, but something can be added to the range:
assert_eq!(
ByteRange::from(usize::max_value() - 5..usize::max_value() - 3).saturating_add(4),
ByteRange::from(usize::max_value() - 2..usize::max_value())
);
assert_eq!(
ByteRange::from(..usize::max_value() - 3).saturating_add(4),
ByteRange::from(..usize::max_value())
);
}
#[test]
fn test_saturating_sub() {
// normal subtraction
assert_eq!(
ByteRange::from(5..10).saturating_sub(4),
ByteRange::from(1..6)
);
// subtracting 0
assert_eq!(
ByteRange::from(0..0).saturating_sub(0),
ByteRange::from(0..0)
);
assert_eq!(
ByteRange::from(2..3).saturating_sub(0),
ByteRange::from(2..3)
);
// the start underflows
assert_eq!(
ByteRange::from(0..5).saturating_sub(4),
ByteRange::from(0..5)
);
// the start underflows, but one can still subtract something from it
assert_eq!(
ByteRange::from(1..5).saturating_sub(2),
ByteRange::from(0..4)
);
// both start and end underflow
assert_eq!(
ByteRange::from(1..3).saturating_sub(5),
ByteRange::from(0..2)
);
// both start + end are 0 + underflow
assert_eq!(
ByteRange::from(0..0).saturating_sub(1),
ByteRange::from(0..0)
);
// half open ranges:
assert_eq!(ByteRange::from(..6).saturating_sub(2), ByteRange::from(..4));
assert_eq!(ByteRange::from(..5).saturating_sub(0), ByteRange::from(..5));
assert_eq!(ByteRange::from(..0).saturating_sub(0), ByteRange::from(..0));
assert_eq!(ByteRange::from(..0).saturating_sub(1), ByteRange::from(..0));
}
#[test]
fn test_display() {
assert_eq!(ByteRange::from(0..5).to_string(), "5@0".to_string());
assert_eq!(
ByteRange::from(2..100001).to_string(),
"99999@2".to_string()
);
assert_eq!(ByteRange::from(..99999).to_string(), "99999".to_string());
}
#[test]
fn test_parser() {
assert_eq!(ByteRange::from(2..22), "20@2".parse().unwrap());
assert_eq!(ByteRange::from(..300), "300".parse().unwrap());
assert_eq!(
ByteRange {
length: 99999,
start: Some(2),
},
"99999@2".parse::<ByteRange>().unwrap()
ByteRange::from_str("a"),
Err(Error::parse_int("a", "a".parse::<usize>().unwrap_err()))
);
assert_eq!(
ByteRange {
length: 99999,
start: Some(2),
},
"99999@2".parse::<ByteRange>().unwrap()
);
assert_eq!(
ByteRange {
length: 99999,
start: None,
},
"99999".parse::<ByteRange>().unwrap()
ByteRange::from_str("1@a"),
Err(Error::parse_int("a", "a".parse::<usize>().unwrap_err()))
);
assert!("".parse::<ByteRange>().is_err());

View file

@ -13,19 +13,19 @@ fn test_media_playlist_with_byterange() {
.segments(vec![
MediaSegment::builder()
.inf(ExtInf::new(Duration::from_secs_f64(10.0)))
.byte_range(ExtXByteRange::new(75232, Some(0)))
.byte_range(ExtXByteRange::from(0..75232))
.uri("video.ts")
.build()
.unwrap(),
MediaSegment::builder()
.inf(ExtInf::new(Duration::from_secs_f64(10.0)))
.byte_range(ExtXByteRange::new(82112, Some(752321)))
.byte_range(ExtXByteRange::from(752321..82112 + 752321))
.uri("video.ts")
.build()
.unwrap(),
MediaSegment::builder()
.inf(ExtInf::new(Duration::from_secs_f64(10.0)))
.byte_range(ExtXByteRange::new(69864, None))
.byte_range(ExtXByteRange::from(..69864))
.uri("video.ts")
.build()
.unwrap(),