mirror of
https://github.com/sile/hls_m3u8.git
synced 2024-11-21 23:01:00 +00:00
implement Float and UFloat
This commit is contained in:
parent
101878a083
commit
90ff18e2b3
7 changed files with 409 additions and 310 deletions
|
@ -1,18 +1,35 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use shorthand::ShortHand;
|
||||
|
||||
use crate::attribute::AttributePairs;
|
||||
use crate::types::{ProtocolVersion, SignedDecimalFloatingPoint};
|
||||
use crate::types::{Float, ProtocolVersion};
|
||||
use crate::utils::{parse_yes_or_no, tag};
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// # [4.3.5.2. EXT-X-START]
|
||||
///
|
||||
/// [4.3.5.2. EXT-X-START]: https://tools.ietf.org/html/rfc8216#section-4.3.5.2
|
||||
#[derive(PartialOrd, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(ShortHand, PartialOrd, Debug, Clone, Copy, PartialEq)]
|
||||
#[shorthand(enable(must_use))]
|
||||
pub struct ExtXStart {
|
||||
time_offset: SignedDecimalFloatingPoint,
|
||||
precise: bool,
|
||||
#[shorthand(enable(skip))]
|
||||
time_offset: Float,
|
||||
/// Returns whether clients should not render media stream whose
|
||||
/// presentation times are prior to the specified time offset.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXStart;
|
||||
/// let mut start = ExtXStart::new(20.123456);
|
||||
/// # assert_eq!(start.is_precise(), false);
|
||||
/// start.set_is_precise(true);
|
||||
///
|
||||
/// assert_eq!(start.is_precise(), true);
|
||||
/// ```
|
||||
is_precise: bool,
|
||||
}
|
||||
|
||||
impl ExtXStart {
|
||||
|
@ -20,9 +37,9 @@ impl ExtXStart {
|
|||
|
||||
/// Makes a new [`ExtXStart`] tag.
|
||||
///
|
||||
/// # Panic
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the time_offset value is infinite.
|
||||
/// Panics if the `time_offset` is infinite or [`NaN`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
|
@ -30,30 +47,34 @@ impl ExtXStart {
|
|||
/// # use hls_m3u8::tags::ExtXStart;
|
||||
/// let start = ExtXStart::new(20.123456);
|
||||
/// ```
|
||||
pub fn new(time_offset: f64) -> Self {
|
||||
///
|
||||
/// [`NaN`]: core::f64::NAN
|
||||
pub fn new(time_offset: f32) -> Self {
|
||||
Self {
|
||||
time_offset: SignedDecimalFloatingPoint::new(time_offset),
|
||||
precise: false,
|
||||
time_offset: Float::new(time_offset),
|
||||
is_precise: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new [`ExtXStart`] tag with the given `precise` flag.
|
||||
///
|
||||
/// # Panic
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the time_offset value is infinite.
|
||||
/// Panics if the `time_offset` is infinite or [`NaN`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXStart;
|
||||
/// let start = ExtXStart::with_precise(20.123456, true);
|
||||
/// assert_eq!(start.precise(), true);
|
||||
/// assert_eq!(start.is_precise(), true);
|
||||
/// ```
|
||||
pub fn with_precise(time_offset: f64, precise: bool) -> Self {
|
||||
///
|
||||
/// [`NaN`]: core::f64::NAN
|
||||
pub fn with_precise(time_offset: f32, is_precise: bool) -> Self {
|
||||
Self {
|
||||
time_offset: SignedDecimalFloatingPoint::new(time_offset),
|
||||
precise,
|
||||
time_offset: Float::new(time_offset),
|
||||
is_precise,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,9 +85,10 @@ impl ExtXStart {
|
|||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXStart;
|
||||
/// let start = ExtXStart::new(20.123456);
|
||||
///
|
||||
/// assert_eq!(start.time_offset(), 20.123456);
|
||||
/// ```
|
||||
pub const fn time_offset(&self) -> f64 { self.time_offset.as_f64() }
|
||||
pub const fn time_offset(self) -> f32 { self.time_offset.as_f32() }
|
||||
|
||||
/// Sets the time offset of the media segments in the playlist.
|
||||
///
|
||||
|
@ -81,38 +103,8 @@ impl ExtXStart {
|
|||
///
|
||||
/// assert_eq!(start.time_offset(), 1.0);
|
||||
/// ```
|
||||
pub fn set_time_offset(&mut self, value: f64) -> &mut Self {
|
||||
self.time_offset = SignedDecimalFloatingPoint::new(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns whether clients should not render media stream whose
|
||||
/// presentation times are prior to the specified time offset.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXStart;
|
||||
/// let start = ExtXStart::with_precise(20.123456, true);
|
||||
/// assert_eq!(start.precise(), true);
|
||||
/// ```
|
||||
pub const fn precise(&self) -> bool { self.precise }
|
||||
|
||||
/// Sets the `precise` flag.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXStart;
|
||||
/// let mut start = ExtXStart::new(20.123456);
|
||||
/// # assert_eq!(start.precise(), false);
|
||||
///
|
||||
/// start.set_precise(true);
|
||||
///
|
||||
/// assert_eq!(start.precise(), true);
|
||||
/// ```
|
||||
pub fn set_precise(&mut self, value: bool) -> &mut Self {
|
||||
self.precise = value;
|
||||
pub fn set_time_offset(&mut self, value: f32) -> &mut Self {
|
||||
self.time_offset = Float::new(value);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -126,9 +118,11 @@ impl fmt::Display for ExtXStart {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", Self::PREFIX)?;
|
||||
write!(f, "TIME-OFFSET={}", self.time_offset)?;
|
||||
if self.precise {
|
||||
|
||||
if self.is_precise {
|
||||
write!(f, ",PRECISE=YES")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -140,12 +134,12 @@ impl FromStr for ExtXStart {
|
|||
let input = tag(input, Self::PREFIX)?;
|
||||
|
||||
let mut time_offset = None;
|
||||
let mut precise = false;
|
||||
let mut is_precise = false;
|
||||
|
||||
for (key, value) in AttributePairs::new(input) {
|
||||
match key {
|
||||
"TIME-OFFSET" => time_offset = Some((value.parse())?),
|
||||
"PRECISE" => precise = (parse_yes_or_no(value))?,
|
||||
"TIME-OFFSET" => time_offset = Some(value.parse()?),
|
||||
"PRECISE" => is_precise = parse_yes_or_no(value)?,
|
||||
_ => {
|
||||
// [6.3.1. General Client Responsibilities]
|
||||
// > ignore any attribute/value pair with an unrecognized
|
||||
|
@ -154,11 +148,11 @@ impl FromStr for ExtXStart {
|
|||
}
|
||||
}
|
||||
|
||||
let time_offset = time_offset.ok_or_else(|| Error::missing_value("EXT-X-TIME-OFFSET"))?;
|
||||
let time_offset = time_offset.ok_or_else(|| Error::missing_value("TIME-OFFSET"))?;
|
||||
|
||||
Ok(Self {
|
||||
time_offset,
|
||||
precise,
|
||||
is_precise,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,137 +0,0 @@
|
|||
use core::str::FromStr;
|
||||
|
||||
use derive_more::{Deref, Display};
|
||||
|
||||
use crate::Error;
|
||||
|
||||
/// Non-negative decimal floating-point number.
|
||||
///
|
||||
/// See: [4.2. Attribute Lists]
|
||||
///
|
||||
/// [4.2. Attribute Lists]:
|
||||
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.2
|
||||
#[derive(Deref, Default, Debug, Clone, Copy, PartialEq, PartialOrd, Display)]
|
||||
pub(crate) struct DecimalFloatingPoint(f64);
|
||||
|
||||
impl DecimalFloatingPoint {
|
||||
/// Makes a new [`DecimalFloatingPoint`] instance.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The given value must have a positive sign and be finite,
|
||||
/// otherwise this function will return an error that has the kind
|
||||
/// `ErrorKind::InvalidInput`.
|
||||
pub fn new(value: f64) -> crate::Result<Self> {
|
||||
if value.is_sign_negative() || value.is_infinite() || value.is_nan() {
|
||||
return Err(Error::invalid_input());
|
||||
}
|
||||
Ok(Self(value))
|
||||
}
|
||||
|
||||
pub(crate) const fn from_f64_unchecked(value: f64) -> Self { Self(value) }
|
||||
|
||||
/// Converts [`DecimalFloatingPoint`] to [`f64`].
|
||||
pub const fn as_f64(self) -> f64 { self.0 }
|
||||
}
|
||||
|
||||
// this trait is implemented manually, so it doesn't construct a
|
||||
// [`DecimalFloatingPoint`], with a negative value.
|
||||
impl FromStr for DecimalFloatingPoint {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
Self::new(input.parse().map_err(Error::parse_float)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl From<f64> for DecimalFloatingPoint {
|
||||
fn from(value: f64) -> Self {
|
||||
let mut result = value;
|
||||
|
||||
// guard against the unlikely case of an infinite value...
|
||||
if result.is_infinite() || result.is_nan() {
|
||||
result = 0.0;
|
||||
}
|
||||
|
||||
Self(result.abs())
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl From<f32> for DecimalFloatingPoint {
|
||||
fn from(value: f32) -> Self { f64::from(value).into() }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
macro_rules! test_from {
|
||||
( $($input:expr),* ) => {
|
||||
use ::core::convert::From;
|
||||
|
||||
#[test]
|
||||
fn test_from() {
|
||||
$(
|
||||
assert_eq!(
|
||||
DecimalFloatingPoint::from($input),
|
||||
DecimalFloatingPoint::new(1.0).unwrap(),
|
||||
);
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test_from![1_u8, 1_u16, 1_u32, 1.0_f32, -1.0_f32, 1.0_f64, -1.0_f64];
|
||||
|
||||
#[test]
|
||||
pub fn test_display() {
|
||||
let decimal_floating_point = DecimalFloatingPoint::new(22.0).unwrap();
|
||||
assert_eq!(decimal_floating_point.to_string(), "22".to_string());
|
||||
|
||||
let decimal_floating_point = DecimalFloatingPoint::new(4.1).unwrap();
|
||||
assert_eq!(decimal_floating_point.to_string(), "4.1".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_parser() {
|
||||
assert_eq!(
|
||||
DecimalFloatingPoint::new(22.0).unwrap(),
|
||||
"22".parse::<DecimalFloatingPoint>().unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
DecimalFloatingPoint::new(4.1).unwrap(),
|
||||
"4.1".parse::<DecimalFloatingPoint>().unwrap()
|
||||
);
|
||||
|
||||
assert!("1#".parse::<DecimalFloatingPoint>().is_err());
|
||||
assert!("-1.0".parse::<DecimalFloatingPoint>().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new() {
|
||||
assert!(DecimalFloatingPoint::new(::std::f64::INFINITY).is_err());
|
||||
assert!(DecimalFloatingPoint::new(-1.0).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_as_f64() {
|
||||
assert_eq!(DecimalFloatingPoint::new(1.0).unwrap().as_f64(), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_inf() {
|
||||
assert_eq!(
|
||||
DecimalFloatingPoint::from(::std::f64::INFINITY),
|
||||
DecimalFloatingPoint::new(0.0).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deref() {
|
||||
assert_eq!(DecimalFloatingPoint::from(0.1).floor(), 0.0);
|
||||
}
|
||||
}
|
173
src/types/float.rs
Normal file
173
src/types/float.rs
Normal file
|
@ -0,0 +1,173 @@
|
|||
use core::convert::TryFrom;
|
||||
use core::str::FromStr;
|
||||
|
||||
use derive_more::{Deref, Display};
|
||||
|
||||
use crate::Error;
|
||||
|
||||
/// This is a wrapper type around an [`f32`] that can not be constructed
|
||||
/// with [`NaN`], [`INFINITY`] or [`NEG_INFINITY`].
|
||||
///
|
||||
/// [`NaN`]: core::f32::NAN
|
||||
/// [`INFINITY`]: core::f32::INFINITY
|
||||
/// [`NEG_INFINITY`]: core::f32::NEG_INFINITY
|
||||
#[derive(Deref, Default, Debug, Copy, Clone, PartialEq, PartialOrd, Display)]
|
||||
pub struct Float(f32);
|
||||
|
||||
impl Float {
|
||||
/// Makes a new [`Float`] from an [`f32`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If the given float is infinite or [`NaN`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hls_m3u8::types::Float;
|
||||
///
|
||||
/// let float = Float::new(1.0);
|
||||
/// ```
|
||||
///
|
||||
/// This would panic:
|
||||
///
|
||||
/// ```should_panic
|
||||
/// use core::f32::NAN;
|
||||
/// use hls_m3u8::types::Float;
|
||||
///
|
||||
/// let float = Float::new(NAN);
|
||||
/// ```
|
||||
///
|
||||
/// [`NaN`]: core::f32::NAN
|
||||
pub fn new(float: f32) -> Self {
|
||||
if float.is_infinite() {
|
||||
panic!("float must be finite: `{}`", float);
|
||||
}
|
||||
|
||||
if float.is_nan() {
|
||||
panic!("float must not be `NaN`");
|
||||
}
|
||||
|
||||
Self(float)
|
||||
}
|
||||
|
||||
/// Returns the underlying [`f32`].
|
||||
pub const fn as_f32(self) -> f32 { self.0 }
|
||||
}
|
||||
|
||||
impl FromStr for Float {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let float = f32::from_str(input).map_err(Error::parse_float)?;
|
||||
Self::try_from(float)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<f32> for Float {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(float: f32) -> Result<Self, Self::Error> {
|
||||
if float.is_infinite() {
|
||||
return Err(Error::custom(format!("float must be finite: `{}`", float)));
|
||||
}
|
||||
|
||||
if float.is_nan() {
|
||||
return Err(Error::custom("float must not be `NaN`"));
|
||||
}
|
||||
|
||||
Ok(Self(float))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! implement_from {
|
||||
( $( $type:tt ),+ ) => {
|
||||
$(
|
||||
impl ::core::convert::From<$type> for Float {
|
||||
fn from(value: $type) -> Self {
|
||||
Self(value as f32)
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
implement_from!(i16, u16, i8, u8);
|
||||
|
||||
// convenience implementation to compare f32 with a Float.
|
||||
impl PartialEq<f32> for Float {
|
||||
fn eq(&self, other: &f32) -> bool { &self.0 == other }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_display() {
|
||||
assert_eq!(Float::new(22.0).to_string(), "22".to_string());
|
||||
assert_eq!(
|
||||
Float::new(3.14159265359).to_string(),
|
||||
"3.1415927".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
Float::new(-3.14159265359).to_string(),
|
||||
"-3.1415927".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parser() {
|
||||
assert_eq!(Float::new(22.0), Float::from_str("22").unwrap());
|
||||
assert_eq!(Float::new(-22.0), Float::from_str("-22").unwrap());
|
||||
assert_eq!(
|
||||
Float::new(3.14159265359),
|
||||
Float::from_str("3.14159265359").unwrap()
|
||||
);
|
||||
assert!(Float::from_str("1#").is_err());
|
||||
assert!(Float::from_str("NaN").is_err());
|
||||
assert!(Float::from_str("inf").is_err());
|
||||
assert!(Float::from_str("-inf").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "float must be finite: `inf`"]
|
||||
fn test_new_infinite() { Float::new(::core::f32::INFINITY); }
|
||||
|
||||
#[test]
|
||||
#[should_panic = "float must be finite: `-inf`"]
|
||||
fn test_new_neg_infinite() { Float::new(::core::f32::NEG_INFINITY); }
|
||||
|
||||
#[test]
|
||||
#[should_panic = "float must not be `NaN`"]
|
||||
fn test_new_nan() { Float::new(::core::f32::NAN); }
|
||||
|
||||
#[test]
|
||||
fn test_partial_eq() {
|
||||
assert_eq!(Float::new(1.1), 1.1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_as_f32() {
|
||||
assert_eq!(Float::new(1.1).as_f32(), 1.1_f32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from() {
|
||||
assert_eq!(Float::from(-1_i8), Float::new(-1.0));
|
||||
assert_eq!(Float::from(1_u8), Float::new(1.0));
|
||||
assert_eq!(Float::from(-1_i16), Float::new(-1.0));
|
||||
assert_eq!(Float::from(1_u16), Float::new(1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_try_from() {
|
||||
assert_eq!(Float::try_from(1.1_f32).unwrap(), Float::new(1.1));
|
||||
assert_eq!(Float::try_from(-1.1_f32).unwrap(), Float::new(-1.1));
|
||||
|
||||
assert!(Float::try_from(::core::f32::INFINITY).is_err());
|
||||
assert!(Float::try_from(::core::f32::NAN).is_err());
|
||||
assert!(Float::try_from(::core::f32::NEG_INFINITY).is_err());
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@
|
|||
mod byte_range;
|
||||
mod channels;
|
||||
mod closed_captions;
|
||||
mod decimal_floating_point;
|
||||
mod decryption_key;
|
||||
mod encryption_method;
|
||||
mod hdcp_level;
|
||||
|
@ -12,14 +11,15 @@ mod key_format_versions;
|
|||
mod media_type;
|
||||
mod protocol_version;
|
||||
mod resolution;
|
||||
mod signed_decimal_floating_point;
|
||||
mod stream_inf;
|
||||
mod value;
|
||||
|
||||
mod float;
|
||||
mod ufloat;
|
||||
|
||||
pub use byte_range::*;
|
||||
pub use channels::*;
|
||||
pub use closed_captions::*;
|
||||
pub(crate) use decimal_floating_point::*;
|
||||
pub use decryption_key::*;
|
||||
pub use encryption_method::*;
|
||||
pub use hdcp_level::*;
|
||||
|
@ -29,6 +29,8 @@ pub use key_format_versions::*;
|
|||
pub use media_type::*;
|
||||
pub use protocol_version::*;
|
||||
pub use resolution::*;
|
||||
pub(crate) use signed_decimal_floating_point::*;
|
||||
pub use stream_inf::*;
|
||||
pub use value::*;
|
||||
|
||||
pub use float::Float;
|
||||
pub use ufloat::UFloat;
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
use derive_more::Deref;
|
||||
use derive_more::{Display, FromStr};
|
||||
|
||||
/// Signed decimal floating-point number.
|
||||
///
|
||||
/// See: [4.2. Attribute Lists]
|
||||
///
|
||||
/// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2
|
||||
#[derive(Deref, Default, Debug, Clone, Copy, PartialEq, PartialOrd, Display, FromStr)]
|
||||
pub(crate) struct SignedDecimalFloatingPoint(f64);
|
||||
|
||||
impl SignedDecimalFloatingPoint {
|
||||
/// Makes a new [`SignedDecimalFloatingPoint`] instance.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// The given value must be finite, otherwise this function will panic!
|
||||
pub fn new(value: f64) -> Self {
|
||||
if value.is_infinite() || value.is_nan() {
|
||||
panic!("Floating point value must be finite and not NaN!");
|
||||
}
|
||||
Self(value)
|
||||
}
|
||||
|
||||
pub(crate) const fn from_f64_unchecked(value: f64) -> Self { Self(value) }
|
||||
|
||||
/// Converts [`DecimalFloatingPoint`] to [`f64`].
|
||||
pub const fn as_f64(self) -> f64 { self.0 }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
macro_rules! test_from {
|
||||
( $( $input:expr => $output:expr ),* ) => {
|
||||
use ::core::convert::From;
|
||||
|
||||
#[test]
|
||||
fn test_from() {
|
||||
$(
|
||||
assert_eq!(
|
||||
$input,
|
||||
$output,
|
||||
);
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test_from![
|
||||
SignedDecimalFloatingPoint::from(1_u8) => SignedDecimalFloatingPoint::new(1.0),
|
||||
SignedDecimalFloatingPoint::from(1_i8) => SignedDecimalFloatingPoint::new(1.0),
|
||||
SignedDecimalFloatingPoint::from(1_u16) => SignedDecimalFloatingPoint::new(1.0),
|
||||
SignedDecimalFloatingPoint::from(1_i16) => SignedDecimalFloatingPoint::new(1.0),
|
||||
SignedDecimalFloatingPoint::from(1_u32) => SignedDecimalFloatingPoint::new(1.0),
|
||||
SignedDecimalFloatingPoint::from(1_i32) => SignedDecimalFloatingPoint::new(1.0),
|
||||
SignedDecimalFloatingPoint::from(1.0_f32) => SignedDecimalFloatingPoint::new(1.0),
|
||||
SignedDecimalFloatingPoint::from(1.0_f64) => SignedDecimalFloatingPoint::new(1.0)
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn test_display() {
|
||||
assert_eq!(
|
||||
SignedDecimalFloatingPoint::new(1.0).to_string(),
|
||||
1.0_f64.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_new_panic() { SignedDecimalFloatingPoint::new(::std::f64::INFINITY); }
|
||||
|
||||
#[test]
|
||||
fn test_parser() {
|
||||
assert_eq!(
|
||||
SignedDecimalFloatingPoint::new(1.0),
|
||||
"1.0".parse::<SignedDecimalFloatingPoint>().unwrap()
|
||||
);
|
||||
|
||||
assert!("garbage".parse::<SignedDecimalFloatingPoint>().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_as_f64() {
|
||||
assert_eq!(SignedDecimalFloatingPoint::new(1.0).as_f64(), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deref() {
|
||||
assert_eq!(SignedDecimalFloatingPoint::from(0.1).floor(), 0.0);
|
||||
}
|
||||
}
|
182
src/types/ufloat.rs
Normal file
182
src/types/ufloat.rs
Normal file
|
@ -0,0 +1,182 @@
|
|||
use core::convert::TryFrom;
|
||||
use core::str::FromStr;
|
||||
|
||||
use derive_more::{Deref, Display};
|
||||
|
||||
use crate::Error;
|
||||
|
||||
/// This is a wrapper type around an [`f32`] that can not be constructed
|
||||
/// with a negative float (ex. `-1.1`), [`NaN`], [`INFINITY`] or
|
||||
/// [`NEG_INFINITY`].
|
||||
///
|
||||
/// [`NaN`]: core::f32::NaN
|
||||
/// [`INFINITY`]: core::f32::INFINITY
|
||||
/// [`NEG_INFINITY`]: core::f32::NEG_INFINITY
|
||||
#[derive(Deref, Default, Debug, Copy, Clone, PartialEq, PartialOrd, Display)]
|
||||
pub struct UFloat(f32);
|
||||
|
||||
impl UFloat {
|
||||
/// Makes a new [`UFloat`] from an [`f32`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If the given float is negative, infinite or [`NaN`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hls_m3u8::types::UFloat;
|
||||
///
|
||||
/// let float = UFloat::new(1.0);
|
||||
/// ```
|
||||
///
|
||||
/// This would panic:
|
||||
///
|
||||
/// ```should_panic
|
||||
/// use hls_m3u8::types::UFloat;
|
||||
///
|
||||
/// let float = UFloat::new(-1.0);
|
||||
/// ```
|
||||
///
|
||||
/// [`NaN`]: core::f32::NAN
|
||||
pub fn new(float: f32) -> Self {
|
||||
if float.is_infinite() {
|
||||
panic!("float must be finite: `{}`", float);
|
||||
}
|
||||
|
||||
if float.is_nan() {
|
||||
panic!("float must not be `NaN`");
|
||||
}
|
||||
|
||||
if float.is_sign_negative() {
|
||||
panic!("float must be positive: `{}`", float);
|
||||
}
|
||||
|
||||
Self(float)
|
||||
}
|
||||
|
||||
/// Returns the underlying [`f32`].
|
||||
pub const fn as_f32(self) -> f32 { self.0 }
|
||||
}
|
||||
|
||||
impl FromStr for UFloat {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let float = f32::from_str(input).map_err(Error::parse_float)?;
|
||||
Self::try_from(float)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<f32> for UFloat {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(float: f32) -> Result<Self, Self::Error> {
|
||||
if float.is_infinite() {
|
||||
return Err(Error::custom(format!("float must be finite: `{}`", float)));
|
||||
}
|
||||
|
||||
if float.is_nan() {
|
||||
return Err(Error::custom("float must not be `NaN`"));
|
||||
}
|
||||
|
||||
if float.is_sign_negative() {
|
||||
return Err(Error::custom(format!(
|
||||
"float must be positive: `{}`",
|
||||
float
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(Self(float))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! implement_from {
|
||||
( $( $type:tt ),+ ) => {
|
||||
$(
|
||||
impl ::core::convert::From<$type> for UFloat {
|
||||
fn from(value: $type) -> Self {
|
||||
Self(value as f32)
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
implement_from!(u16, u8);
|
||||
|
||||
// convenience implementation to compare f32 with a Float.
|
||||
impl PartialEq<f32> for UFloat {
|
||||
fn eq(&self, other: &f32) -> bool { &self.0 == other }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_display() {
|
||||
assert_eq!(UFloat::new(22.0).to_string(), "22".to_string());
|
||||
assert_eq!(
|
||||
UFloat::new(3.14159265359).to_string(),
|
||||
"3.1415927".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parser() {
|
||||
assert_eq!(UFloat::new(22.0), UFloat::from_str("22").unwrap());
|
||||
assert_eq!(
|
||||
UFloat::new(3.14159265359),
|
||||
UFloat::from_str("3.14159265359").unwrap()
|
||||
);
|
||||
assert!(UFloat::from_str("1#").is_err());
|
||||
assert!(UFloat::from_str("-1.0").is_err());
|
||||
assert!(UFloat::from_str("NaN").is_err());
|
||||
assert!(UFloat::from_str("inf").is_err());
|
||||
assert!(UFloat::from_str("-inf").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "float must be positive: `-1.1`"]
|
||||
fn test_new_negative() { UFloat::new(-1.1); }
|
||||
|
||||
#[test]
|
||||
#[should_panic = "float must be finite: `inf`"]
|
||||
fn test_new_infinite() { UFloat::new(::core::f32::INFINITY); }
|
||||
|
||||
#[test]
|
||||
#[should_panic = "float must be finite: `-inf`"]
|
||||
fn test_new_neg_infinite() { UFloat::new(::core::f32::NEG_INFINITY); }
|
||||
|
||||
#[test]
|
||||
#[should_panic = "float must not be `NaN`"]
|
||||
fn test_new_nan() { UFloat::new(::core::f32::NAN); }
|
||||
|
||||
#[test]
|
||||
fn test_partial_eq() {
|
||||
assert_eq!(UFloat::new(1.1), 1.1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_as_f32() {
|
||||
assert_eq!(UFloat::new(1.1).as_f32(), 1.1_f32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from() {
|
||||
assert_eq!(UFloat::from(1_u8), UFloat::new(1.0));
|
||||
assert_eq!(UFloat::from(1_u16), UFloat::new(1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_try_from() {
|
||||
assert_eq!(UFloat::try_from(1.1_f32).unwrap(), UFloat::new(1.1));
|
||||
|
||||
assert!(UFloat::try_from(-1.1_f32).is_err());
|
||||
assert!(UFloat::try_from(::core::f32::INFINITY).is_err());
|
||||
assert!(UFloat::try_from(::core::f32::NAN).is_err());
|
||||
assert!(UFloat::try_from(::core::f32::NEG_INFINITY).is_err());
|
||||
}
|
||||
}
|
21
src/utils.rs
21
src/utils.rs
|
@ -11,27 +11,6 @@ macro_rules! required_version {
|
|||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_from {
|
||||
( $($( $type:tt ),* => $target:path ),* ) => {
|
||||
use ::core::convert::From;
|
||||
|
||||
$( // repeat $target
|
||||
$( // repeat $type
|
||||
impl From<$type> for $target {
|
||||
fn from(value: $type) -> Self {
|
||||
Self::from_f64_unchecked(value.into())
|
||||
}
|
||||
}
|
||||
)*
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_from![
|
||||
u8, u16, u32 => crate::types::DecimalFloatingPoint,
|
||||
u8, i8, u16, i16, u32, i32, f32, f64 => crate::types::SignedDecimalFloatingPoint
|
||||
];
|
||||
|
||||
pub(crate) fn parse_iv_from_str(input: &str) -> crate::Result<[u8; 16]> {
|
||||
if !(input.starts_with("0x") || input.starts_with("0X")) {
|
||||
return Err(Error::invalid_input());
|
||||
|
|
Loading…
Reference in a new issue