mirror of
https://github.com/sile/hls_m3u8.git
synced 2025-01-10 20:25:25 +00:00
implemented ExtXDateRange
This commit is contained in:
parent
b18e6ea4fb
commit
3dad1277ca
5 changed files with 893 additions and 88 deletions
|
@ -21,6 +21,7 @@ derive_builder = "0.7.2"
|
|||
chrono = "0.4.9"
|
||||
strum = { version = "0.16.0", features = ["derive"] }
|
||||
derive_more = "0.15.0"
|
||||
hex = "0.4.0"
|
||||
|
||||
[dev-dependencies]
|
||||
clap = "2"
|
||||
|
|
|
@ -209,3 +209,9 @@ impl From<::core::convert::Infallible> for Error {
|
|||
Self::custom("An Infallible error has been returned! (this should never happen!)")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<::hex::FromHexError> for Error {
|
||||
fn from(value: ::hex::FromHexError) -> Self {
|
||||
Self::custom(value) // TODO!
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,63 +3,119 @@ use std::fmt;
|
|||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use chrono::{DateTime, FixedOffset, SecondsFormat};
|
||||
use derive_builder::Builder;
|
||||
|
||||
use crate::attribute::AttributePairs;
|
||||
use crate::types::ProtocolVersion;
|
||||
use crate::types::{ProtocolVersion, Value};
|
||||
use crate::utils::{quote, tag, unquote};
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// # [4.3.2.7. EXT-X-DATERANGE]
|
||||
///
|
||||
/// [4.3.2.7. EXT-X-DATERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.7
|
||||
///
|
||||
/// TODO: Implement properly
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Builder, Debug, Clone, PartialEq)]
|
||||
#[builder(setter(into))]
|
||||
pub struct ExtXDateRange {
|
||||
/// A string that uniquely identifies a [`ExtXDateRange`] in the Playlist.
|
||||
/// A string that uniquely identifies an [`ExtXDateRange`] in the Playlist.
|
||||
///
|
||||
/// # Note
|
||||
/// This attribute is required.
|
||||
id: String,
|
||||
#[builder(setter(strip_option), default)]
|
||||
/// A client-defined string that specifies some set of attributes and their
|
||||
/// associated value semantics. All Date Ranges with the same CLASS
|
||||
/// attribute value MUST adhere to these semantics. This attribute is
|
||||
/// OPTIONAL.
|
||||
/// associated value semantics. All [`ExtXDateRange`]s with the same class
|
||||
/// attribute value must adhere to these semantics.
|
||||
///
|
||||
/// # Note
|
||||
/// This attribute is optional.
|
||||
class: Option<String>,
|
||||
/// The date at which the Date Range begins. This attribute is REQUIRED.
|
||||
/// The date at which the [`ExtXDateRange`] begins.
|
||||
///
|
||||
/// # Note
|
||||
/// This attribute is required.
|
||||
start_date: DateTime<FixedOffset>,
|
||||
/// The date at which the Date Range ends. It MUST be equal to or later than
|
||||
/// the value of the START-DATE attribute. This attribute is OPTIONAL.
|
||||
#[builder(setter(strip_option), default)]
|
||||
/// The date at which the [`ExtXDateRange`] ends. It must be equal to or
|
||||
/// later than the value of the [`start-date`] attribute.
|
||||
///
|
||||
/// # Note
|
||||
/// This attribute is optional.
|
||||
///
|
||||
/// [`start-date`]: #method.start_date
|
||||
end_date: Option<DateTime<FixedOffset>>,
|
||||
/// The duration of the Date Range. It MUST NOT be negative. A single
|
||||
/// instant in time (e.g., crossing a finish line) SHOULD be
|
||||
/// represented with a duration of 0. This attribute is OPTIONAL.
|
||||
#[builder(setter(strip_option), default)]
|
||||
/// The duration of the [`ExtXDateRange`]. A single instant in time (e.g.,
|
||||
/// crossing a finish line) should be represented with a duration of 0.
|
||||
///
|
||||
/// # Note
|
||||
/// This attribute is optional.
|
||||
duration: Option<Duration>,
|
||||
/// The expected duration of the Date Range. It MUST NOT be negative. This
|
||||
/// attribute SHOULD be used to indicate the expected duration of a
|
||||
/// Date Range whose actual duration is not yet known.
|
||||
/// It is OPTIONAL.
|
||||
#[builder(setter(strip_option), default)]
|
||||
/// The expected duration of the [`ExtXDateRange`].
|
||||
/// This attribute should be used to indicate the expected duration of a
|
||||
/// [`ExtXDateRange`] whose actual duration is not yet known.
|
||||
///
|
||||
/// # Note
|
||||
/// This attribute is optional.
|
||||
planned_duration: Option<Duration>,
|
||||
#[builder(setter(strip_option), default)]
|
||||
/// https://tools.ietf.org/html/rfc8216#section-4.3.2.7.1
|
||||
///
|
||||
/// # Note
|
||||
/// This attribute is optional.
|
||||
scte35_cmd: Option<String>,
|
||||
#[builder(setter(strip_option), default)]
|
||||
/// https://tools.ietf.org/html/rfc8216#section-4.3.2.7.1
|
||||
///
|
||||
/// # Note
|
||||
/// This attribute is optional.
|
||||
scte35_out: Option<String>,
|
||||
#[builder(setter(strip_option), default)]
|
||||
/// https://tools.ietf.org/html/rfc8216#section-4.3.2.7.1
|
||||
///
|
||||
/// # Note
|
||||
/// This attribute is optional.
|
||||
scte35_in: Option<String>,
|
||||
#[builder(default)]
|
||||
/// This attribute indicates that the end of the range containing it is
|
||||
/// equal to the START-DATE of its Following Range. The Following Range
|
||||
/// is the Date Range of the same CLASS, that has the earliest
|
||||
/// START-DATE after the START-DATE of the range in question. This
|
||||
/// attribute is OPTIONAL.
|
||||
/// equal to the [`start-date`] of its following range. The following range
|
||||
/// is the [`ExtXDateRange`] of the same class, that has the earliest
|
||||
/// [`start-date`] after the [`start-date`] of the range in question.
|
||||
///
|
||||
/// # Note
|
||||
/// This attribute is optional.
|
||||
end_on_next: bool,
|
||||
#[builder(default)]
|
||||
/// The "X-" prefix defines a namespace reserved for client-defined
|
||||
/// attributes. The client-attribute MUST be a legal AttributeName.
|
||||
/// Clients SHOULD use a reverse-DNS syntax when defining their own
|
||||
/// attribute names to avoid collisions. The attribute value MUST be
|
||||
/// a quoted-string, a hexadecimal-sequence, or a decimal-floating-
|
||||
/// point. An example of a client-defined attribute is X-COM-EXAMPLE-
|
||||
/// AD-ID="XYZ123". These attributes are OPTIONAL.
|
||||
client_attributes: BTreeMap<String, String>,
|
||||
/// attributes. The client-attribute must be a uppercase characters.
|
||||
/// Clients should use a reverse-DNS syntax when defining their own
|
||||
/// attribute names to avoid collisions. An example of a client-defined
|
||||
/// attribute is `X-COM-EXAMPLE-AD-ID="XYZ123"`.
|
||||
///
|
||||
/// # Note
|
||||
/// This attribute is optional.
|
||||
client_attributes: BTreeMap<String, Value>,
|
||||
}
|
||||
|
||||
impl ExtXDateRangeBuilder {
|
||||
/// Inserts a key value pair.
|
||||
pub fn insert_client_attribute<K: ToString, V: Into<Value>>(
|
||||
&mut self,
|
||||
key: K,
|
||||
value: V,
|
||||
) -> &mut Self {
|
||||
if self.client_attributes.is_none() {
|
||||
self.client_attributes = Some(BTreeMap::new());
|
||||
}
|
||||
|
||||
if let Some(client_attributes) = &mut self.client_attributes {
|
||||
client_attributes.insert(key.to_string(), value.into());
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtXDateRange {
|
||||
|
@ -97,68 +153,542 @@ impl ExtXDateRange {
|
|||
client_attributes: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a builder for [`ExtXDateRange`].
|
||||
pub fn builder() -> ExtXDateRangeBuilder { ExtXDateRangeBuilder::default() }
|
||||
|
||||
/// A string that uniquely identifies an [`ExtXDateRange`] in the Playlist.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXDateRange;
|
||||
/// use chrono::offset::TimeZone;
|
||||
/// use chrono::{DateTime, FixedOffset};
|
||||
///
|
||||
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||
///
|
||||
/// let date_range = ExtXDateRange::new(
|
||||
/// "id",
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 2, 19)
|
||||
/// .and_hms_milli(14, 54, 23, 31),
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(date_range.id(), &"id".to_string());
|
||||
/// ```
|
||||
pub const fn id(&self) -> &String { &self.id }
|
||||
|
||||
/// A string that uniquely identifies an [`ExtXDateRange`] in the Playlist.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXDateRange;
|
||||
/// use chrono::offset::TimeZone;
|
||||
/// use chrono::{DateTime, FixedOffset};
|
||||
///
|
||||
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||
///
|
||||
/// let mut date_range = ExtXDateRange::new(
|
||||
/// "id",
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 2, 19)
|
||||
/// .and_hms_milli(14, 54, 23, 31),
|
||||
/// );
|
||||
///
|
||||
/// date_range.set_id("new_id");
|
||||
/// assert_eq!(date_range.id(), &"new_id".to_string());
|
||||
/// ```
|
||||
pub fn set_id<T: ToString>(&mut self, value: T) -> &mut Self {
|
||||
self.id = value.to_string();
|
||||
self
|
||||
}
|
||||
|
||||
/// A client-defined string that specifies some set of attributes and their
|
||||
/// associated value semantics. All [`ExtXDateRange`]s with the same class
|
||||
/// attribute value must adhere to these semantics.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXDateRange;
|
||||
/// use chrono::offset::TimeZone;
|
||||
/// use chrono::{DateTime, FixedOffset};
|
||||
///
|
||||
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||
///
|
||||
/// let mut date_range = ExtXDateRange::new(
|
||||
/// "id",
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 2, 19)
|
||||
/// .and_hms_milli(14, 54, 23, 31),
|
||||
/// );
|
||||
/// # assert_eq!(date_range.class(), &None);
|
||||
///
|
||||
/// date_range.set_class(Some("example_class"));
|
||||
/// assert_eq!(date_range.class(), &Some("example_class".to_string()));
|
||||
/// ```
|
||||
pub const fn class(&self) -> &Option<String> { &self.class }
|
||||
|
||||
/// A client-defined string that specifies some set of attributes and their
|
||||
/// associated value semantics. All [`ExtXDateRange`]s with the same class
|
||||
/// attribute value must adhere to these semantics.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXDateRange;
|
||||
/// use chrono::offset::TimeZone;
|
||||
/// use chrono::{DateTime, FixedOffset};
|
||||
///
|
||||
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||
///
|
||||
/// let mut date_range = ExtXDateRange::new(
|
||||
/// "id",
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 2, 19)
|
||||
/// .and_hms_milli(14, 54, 23, 31),
|
||||
/// );
|
||||
/// # assert_eq!(date_range.class(), &None);
|
||||
///
|
||||
/// date_range.set_class(Some("example_class"));
|
||||
/// assert_eq!(date_range.class(), &Some("example_class".to_string()));
|
||||
/// ```
|
||||
pub fn set_class<T: ToString>(&mut self, value: Option<T>) -> &mut Self {
|
||||
self.class = value.map(|v| v.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// The date at which the [`ExtXDateRange`] begins.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXDateRange;
|
||||
/// use chrono::offset::TimeZone;
|
||||
/// use chrono::{DateTime, FixedOffset};
|
||||
///
|
||||
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||
///
|
||||
/// let date_range = ExtXDateRange::new(
|
||||
/// "id",
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 2, 19)
|
||||
/// .and_hms_milli(14, 54, 23, 31),
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// date_range.start_date(),
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 2, 19)
|
||||
/// .and_hms_milli(14, 54, 23, 31)
|
||||
/// );
|
||||
/// ```
|
||||
pub const fn start_date(&self) -> DateTime<FixedOffset> { self.start_date }
|
||||
|
||||
/// The date at which the [`ExtXDateRange`] begins.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXDateRange;
|
||||
/// use chrono::offset::TimeZone;
|
||||
/// use chrono::{DateTime, FixedOffset};
|
||||
///
|
||||
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||
///
|
||||
/// let mut date_range = ExtXDateRange::new(
|
||||
/// "id",
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 2, 19)
|
||||
/// .and_hms_milli(14, 54, 23, 31),
|
||||
/// );
|
||||
///
|
||||
/// date_range.set_start_date(
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 10, 10)
|
||||
/// .and_hms_milli(10, 10, 10, 10),
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// date_range.start_date(),
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 10, 10)
|
||||
/// .and_hms_milli(10, 10, 10, 10)
|
||||
/// );
|
||||
/// ```
|
||||
pub fn set_start_date<T>(&mut self, value: T) -> &mut Self
|
||||
where
|
||||
T: Into<DateTime<FixedOffset>>,
|
||||
{
|
||||
self.start_date = value.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// The date at which the [`ExtXDateRange`] ends. It must be equal to or
|
||||
/// later than the value of the [`start-date`] attribute.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXDateRange;
|
||||
/// use chrono::offset::TimeZone;
|
||||
/// use chrono::{DateTime, FixedOffset};
|
||||
///
|
||||
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||
///
|
||||
/// let mut date_range = ExtXDateRange::new(
|
||||
/// "id",
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 2, 19)
|
||||
/// .and_hms_milli(14, 54, 23, 31),
|
||||
/// );
|
||||
/// # assert_eq!(date_range.end_date(), None);
|
||||
///
|
||||
/// date_range.set_end_date(Some(
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 10, 10)
|
||||
/// .and_hms_milli(10, 10, 10, 10),
|
||||
/// ));
|
||||
/// assert_eq!(
|
||||
/// date_range.end_date(),
|
||||
/// Some(
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 10, 10)
|
||||
/// .and_hms_milli(10, 10, 10, 10)
|
||||
/// )
|
||||
/// );
|
||||
/// ```
|
||||
pub const fn end_date(&self) -> Option<DateTime<FixedOffset>> { self.end_date }
|
||||
|
||||
/// The date at which the [`ExtXDateRange`] ends. It must be equal to or
|
||||
/// later than the value of the [`start-date`] attribute.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXDateRange;
|
||||
/// use chrono::offset::TimeZone;
|
||||
/// use chrono::{DateTime, FixedOffset};
|
||||
///
|
||||
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||
///
|
||||
/// let mut date_range = ExtXDateRange::new(
|
||||
/// "id",
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 2, 19)
|
||||
/// .and_hms_milli(14, 54, 23, 31),
|
||||
/// );
|
||||
/// # assert_eq!(date_range.end_date(), None);
|
||||
///
|
||||
/// date_range.set_end_date(Some(
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 10, 10)
|
||||
/// .and_hms_milli(10, 10, 10, 10),
|
||||
/// ));
|
||||
/// assert_eq!(
|
||||
/// date_range.end_date(),
|
||||
/// Some(
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 10, 10)
|
||||
/// .and_hms_milli(10, 10, 10, 10)
|
||||
/// )
|
||||
/// );
|
||||
/// ```
|
||||
pub fn set_end_date<T>(&mut self, value: Option<T>) -> &mut Self
|
||||
where
|
||||
T: Into<DateTime<FixedOffset>>,
|
||||
{
|
||||
self.end_date = value.map(Into::into);
|
||||
self
|
||||
}
|
||||
|
||||
/// The duration of the [`ExtXDateRange`]. A single instant in time (e.g.,
|
||||
/// crossing a finish line) should be represented with a duration of 0.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXDateRange;
|
||||
/// use chrono::offset::TimeZone;
|
||||
/// use chrono::{DateTime, FixedOffset};
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||
///
|
||||
/// let mut date_range = ExtXDateRange::new(
|
||||
/// "id",
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 2, 19)
|
||||
/// .and_hms_milli(14, 54, 23, 31),
|
||||
/// );
|
||||
/// # assert_eq!(date_range.duration(), None);
|
||||
///
|
||||
/// date_range.set_duration(Some(Duration::from_secs_f64(1.234)));
|
||||
/// assert_eq!(date_range.duration(), Some(Duration::from_secs_f64(1.234)));
|
||||
/// ```
|
||||
pub const fn duration(&self) -> Option<Duration> { self.duration }
|
||||
|
||||
/// The duration of the [`ExtXDateRange`]. A single instant in time (e.g.,
|
||||
/// crossing a finish line) should be represented with a duration of 0.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXDateRange;
|
||||
/// use chrono::offset::TimeZone;
|
||||
/// use chrono::{DateTime, FixedOffset};
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||
///
|
||||
/// let mut date_range = ExtXDateRange::new(
|
||||
/// "id",
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 2, 19)
|
||||
/// .and_hms_milli(14, 54, 23, 31),
|
||||
/// );
|
||||
/// # assert_eq!(date_range.duration(), None);
|
||||
///
|
||||
/// date_range.set_duration(Some(Duration::from_secs_f64(1.234)));
|
||||
/// assert_eq!(date_range.duration(), Some(Duration::from_secs_f64(1.234)));
|
||||
/// ```
|
||||
pub fn set_duration(&mut self, value: Option<Duration>) -> &mut Self {
|
||||
self.duration = value;
|
||||
self
|
||||
}
|
||||
|
||||
/// The expected duration of the [`ExtXDateRange`].
|
||||
/// This attribute should be used to indicate the expected duration of a
|
||||
/// [`ExtXDateRange`] whose actual duration is not yet known.
|
||||
/// The date at which the [`ExtXDateRange`] ends. It must be equal to or
|
||||
/// later than the value of the [`start-date`] attribute.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXDateRange;
|
||||
/// use chrono::offset::TimeZone;
|
||||
/// use chrono::{DateTime, FixedOffset};
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||
///
|
||||
/// let mut date_range = ExtXDateRange::new(
|
||||
/// "id",
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 2, 19)
|
||||
/// .and_hms_milli(14, 54, 23, 31),
|
||||
/// );
|
||||
/// # assert_eq!(date_range.planned_duration(), None);
|
||||
///
|
||||
/// date_range.set_planned_duration(Some(Duration::from_secs_f64(1.2345)));
|
||||
/// assert_eq!(
|
||||
/// date_range.planned_duration(),
|
||||
/// Some(Duration::from_secs_f64(1.2345))
|
||||
/// );
|
||||
/// ```
|
||||
pub const fn planned_duration(&self) -> Option<Duration> { self.planned_duration }
|
||||
|
||||
/// The expected duration of the [`ExtXDateRange`].
|
||||
/// This attribute should be used to indicate the expected duration of a
|
||||
/// [`ExtXDateRange`] whose actual duration is not yet known.
|
||||
/// The date at which the [`ExtXDateRange`] ends. It must be equal to or
|
||||
/// later than the value of the [`start-date`] attribute.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXDateRange;
|
||||
/// use chrono::offset::TimeZone;
|
||||
/// use chrono::{DateTime, FixedOffset};
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||
///
|
||||
/// let mut date_range = ExtXDateRange::new(
|
||||
/// "id",
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 2, 19)
|
||||
/// .and_hms_milli(14, 54, 23, 31),
|
||||
/// );
|
||||
/// # assert_eq!(date_range.planned_duration(), None);
|
||||
///
|
||||
/// date_range.set_planned_duration(Some(Duration::from_secs_f64(1.2345)));
|
||||
/// assert_eq!(
|
||||
/// date_range.planned_duration(),
|
||||
/// Some(Duration::from_secs_f64(1.2345))
|
||||
/// );
|
||||
/// ```
|
||||
pub fn set_planned_duration(&mut self, value: Option<Duration>) -> &mut Self {
|
||||
self.planned_duration = value;
|
||||
self
|
||||
}
|
||||
|
||||
pub const fn scte35_cmd(&self) -> &Option<String> { &self.scte35_cmd }
|
||||
|
||||
pub const fn scte35_in(&self) -> &Option<String> { &self.scte35_in }
|
||||
|
||||
pub const fn scte35_out(&self) -> &Option<String> { &self.scte35_out }
|
||||
|
||||
/// This attribute indicates that the end of the range containing it is
|
||||
/// equal to the [`start-date`] of its following range. The following range
|
||||
/// is the [`ExtXDateRange`] of the same class, that has the earliest
|
||||
/// [`start-date`] after the [`start-date`] of the range in question.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXDateRange;
|
||||
/// use chrono::offset::TimeZone;
|
||||
/// use chrono::{DateTime, FixedOffset};
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||
///
|
||||
/// let mut date_range = ExtXDateRange::new(
|
||||
/// "id",
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 2, 19)
|
||||
/// .and_hms_milli(14, 54, 23, 31),
|
||||
/// );
|
||||
/// # assert_eq!(date_range.end_on_next(), false);
|
||||
///
|
||||
/// date_range.set_end_on_next(true);
|
||||
/// assert_eq!(date_range.end_on_next(), true);
|
||||
/// ```
|
||||
pub const fn end_on_next(&self) -> bool { self.end_on_next }
|
||||
|
||||
/// This attribute indicates that the end of the range containing it is
|
||||
/// equal to the [`start-date`] of its following range. The following range
|
||||
/// is the [`ExtXDateRange`] of the same class, that has the earliest
|
||||
/// [`start-date`] after the [`start-date`] of the range in question.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXDateRange;
|
||||
/// use chrono::offset::TimeZone;
|
||||
/// use chrono::{DateTime, FixedOffset};
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||
///
|
||||
/// let mut date_range = ExtXDateRange::new(
|
||||
/// "id",
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 2, 19)
|
||||
/// .and_hms_milli(14, 54, 23, 31),
|
||||
/// );
|
||||
/// # assert_eq!(date_range.end_on_next(), false);
|
||||
///
|
||||
/// date_range.set_end_on_next(true);
|
||||
/// assert_eq!(date_range.end_on_next(), true);
|
||||
/// ```
|
||||
pub fn set_end_on_next(&mut self, value: bool) -> &mut Self {
|
||||
self.end_on_next = value;
|
||||
self
|
||||
}
|
||||
|
||||
/// The "X-" prefix defines a namespace reserved for client-defined
|
||||
/// attributes. The client-attribute must be a uppercase characters.
|
||||
/// Clients should use a reverse-DNS syntax when defining their own
|
||||
/// attribute names to avoid collisions. An example of a client-defined
|
||||
/// attribute is `X-COM-EXAMPLE-AD-ID="XYZ123"`.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXDateRange;
|
||||
/// use std::collections::BTreeMap;
|
||||
///
|
||||
/// use chrono::offset::TimeZone;
|
||||
/// use chrono::{DateTime, FixedOffset};
|
||||
/// use hls_m3u8::types::Value;
|
||||
///
|
||||
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||
///
|
||||
/// let mut date_range = ExtXDateRange::new(
|
||||
/// "id",
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 2, 19)
|
||||
/// .and_hms_milli(14, 54, 23, 31),
|
||||
/// );
|
||||
/// # assert_eq!(date_range.client_attributes(), &BTreeMap::new());
|
||||
///
|
||||
/// let mut attributes = BTreeMap::new();
|
||||
/// attributes.insert("X-COM-EXAMPLE-FLOAT".to_string(), Value::Float(1.1));
|
||||
///
|
||||
/// date_range.set_client_attributes(attributes.clone());
|
||||
/// assert_eq!(date_range.client_attributes(), &attributes);
|
||||
/// ```
|
||||
pub const fn client_attributes(&self) -> &BTreeMap<String, Value> { &self.client_attributes }
|
||||
|
||||
/// The "X-" prefix defines a namespace reserved for client-defined
|
||||
/// attributes. The client-attribute must be a uppercase characters.
|
||||
/// Clients should use a reverse-DNS syntax when defining their own
|
||||
/// attribute names to avoid collisions. An example of a client-defined
|
||||
/// attribute is `X-COM-EXAMPLE-AD-ID="XYZ123"`.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXDateRange;
|
||||
/// use std::collections::BTreeMap;
|
||||
///
|
||||
/// use chrono::offset::TimeZone;
|
||||
/// use chrono::{DateTime, FixedOffset};
|
||||
/// use hls_m3u8::types::Value;
|
||||
///
|
||||
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||
///
|
||||
/// let mut date_range = ExtXDateRange::new(
|
||||
/// "id",
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 2, 19)
|
||||
/// .and_hms_milli(14, 54, 23, 31),
|
||||
/// );
|
||||
/// # assert_eq!(date_range.client_attributes(), &BTreeMap::new());
|
||||
///
|
||||
/// let mut attributes = BTreeMap::new();
|
||||
/// attributes.insert("X-COM-EXAMPLE-FLOAT".to_string(), Value::Float(1.1));
|
||||
///
|
||||
/// date_range
|
||||
/// .client_attributes_mut()
|
||||
/// .insert("X-COM-EXAMPLE-FLOAT".to_string(), Value::Float(1.1));
|
||||
///
|
||||
/// assert_eq!(date_range.client_attributes(), &attributes);
|
||||
/// ```
|
||||
pub fn client_attributes_mut(&mut self) -> &mut BTreeMap<String, Value> {
|
||||
&mut self.client_attributes
|
||||
}
|
||||
|
||||
/// The "X-" prefix defines a namespace reserved for client-defined
|
||||
/// attributes. The client-attribute must be a uppercase characters.
|
||||
/// Clients should use a reverse-DNS syntax when defining their own
|
||||
/// attribute names to avoid collisions. An example of a client-defined
|
||||
/// attribute is `X-COM-EXAMPLE-AD-ID="XYZ123"`.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXDateRange;
|
||||
/// use std::collections::BTreeMap;
|
||||
///
|
||||
/// use chrono::offset::TimeZone;
|
||||
/// use chrono::{DateTime, FixedOffset};
|
||||
/// use hls_m3u8::types::Value;
|
||||
///
|
||||
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||
///
|
||||
/// let mut date_range = ExtXDateRange::new(
|
||||
/// "id",
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 2, 19)
|
||||
/// .and_hms_milli(14, 54, 23, 31),
|
||||
/// );
|
||||
/// # assert_eq!(date_range.client_attributes(), &BTreeMap::new());
|
||||
///
|
||||
/// let mut attributes = BTreeMap::new();
|
||||
/// attributes.insert("X-COM-EXAMPLE-FLOAT".to_string(), Value::Float(1.1));
|
||||
///
|
||||
/// date_range.set_client_attributes(attributes.clone());
|
||||
/// assert_eq!(date_range.client_attributes(), &attributes);
|
||||
/// ```
|
||||
pub fn set_client_attributes(&mut self, value: BTreeMap<String, Value>) -> &mut Self {
|
||||
self.client_attributes = value;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// This tag requires [`ProtocolVersion::V1`].
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use hls_m3u8::tags::ExtXDateRange;
|
||||
/// use chrono::offset::TimeZone;
|
||||
/// use chrono::{DateTime, FixedOffset};
|
||||
/// use hls_m3u8::types::ProtocolVersion;
|
||||
/// use hls_m3u8::RequiredVersion;
|
||||
///
|
||||
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||
///
|
||||
/// let date_range = ExtXDateRange::new(
|
||||
/// "id",
|
||||
/// FixedOffset::east(8 * HOURS_IN_SECS)
|
||||
/// .ymd(2010, 2, 19)
|
||||
/// .and_hms_milli(14, 54, 23, 31),
|
||||
/// );
|
||||
/// assert_eq!(date_range.required_version(), ProtocolVersion::V1);
|
||||
/// ```
|
||||
impl RequiredVersion for ExtXDateRange {
|
||||
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 }
|
||||
}
|
||||
|
||||
impl fmt::Display for ExtXDateRange {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", Self::PREFIX)?;
|
||||
write!(f, "ID={}", quote(&self.id))?;
|
||||
if let Some(value) = &self.class {
|
||||
write!(f, ",CLASS={}", quote(value))?;
|
||||
}
|
||||
write!(f, ",START-DATE={}", quote(&self.start_date))?;
|
||||
if let Some(value) = &self.end_date {
|
||||
write!(f, ",END-DATE={}", quote(value))?;
|
||||
}
|
||||
if let Some(value) = &self.duration {
|
||||
write!(f, ",DURATION={}", value.as_secs_f64())?;
|
||||
}
|
||||
if let Some(value) = &self.planned_duration {
|
||||
write!(f, ",PLANNED-DURATION={}", value.as_secs_f64())?;
|
||||
}
|
||||
if let Some(value) = &self.scte35_cmd {
|
||||
write!(f, ",SCTE35-CMD={}", quote(value))?;
|
||||
}
|
||||
if let Some(value) = &self.scte35_out {
|
||||
write!(f, ",SCTE35-OUT={}", quote(value))?;
|
||||
}
|
||||
if let Some(value) = &self.scte35_in {
|
||||
write!(f, ",SCTE35-IN={}", quote(value))?;
|
||||
}
|
||||
if self.end_on_next {
|
||||
write!(f, ",END-ON-NEXT=YES",)?;
|
||||
}
|
||||
for (k, v) in &self.client_attributes {
|
||||
write!(f, ",{}={}", k, v)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ExtXDateRange {
|
||||
type Err = Error;
|
||||
|
||||
|
@ -195,13 +725,13 @@ impl FromStr for ExtXDateRange {
|
|||
"SCTE35-IN" => scte35_in = Some(unquote(value)),
|
||||
"END-ON-NEXT" => {
|
||||
if value != "YES" {
|
||||
return Err(Error::invalid_input());
|
||||
return Err(Error::custom("The value of `END-ON-NEXT` has to be `YES`!"));
|
||||
}
|
||||
end_on_next = true;
|
||||
}
|
||||
_ => {
|
||||
if key.starts_with("X-") {
|
||||
client_attributes.insert(key.split_at(2).1.to_owned(), value.to_owned());
|
||||
client_attributes.insert(key.to_ascii_uppercase(), value.parse()?);
|
||||
} else {
|
||||
// [6.3.1. General Client Responsibilities]
|
||||
// > ignore any attribute/value pair with an
|
||||
|
@ -235,6 +765,60 @@ impl FromStr for ExtXDateRange {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ExtXDateRange {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", Self::PREFIX)?;
|
||||
write!(f, "ID={}", quote(&self.id))?;
|
||||
if let Some(value) = &self.class {
|
||||
write!(f, ",CLASS={}", quote(value))?;
|
||||
}
|
||||
|
||||
write!(
|
||||
f,
|
||||
",START-DATE={}",
|
||||
quote(&self.start_date.to_rfc3339_opts(SecondsFormat::AutoSi, true))
|
||||
)?;
|
||||
|
||||
if let Some(value) = &self.end_date {
|
||||
write!(
|
||||
f,
|
||||
",END-DATE={}",
|
||||
quote(value.to_rfc3339_opts(SecondsFormat::AutoSi, true))
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(value) = &self.duration {
|
||||
write!(f, ",DURATION={}", value.as_secs_f64())?;
|
||||
}
|
||||
|
||||
if let Some(value) = &self.planned_duration {
|
||||
write!(f, ",PLANNED-DURATION={}", value.as_secs_f64())?;
|
||||
}
|
||||
|
||||
if let Some(value) = &self.scte35_cmd {
|
||||
write!(f, ",SCTE35-CMD={}", value)?;
|
||||
}
|
||||
|
||||
if let Some(value) = &self.scte35_out {
|
||||
write!(f, ",SCTE35-OUT={}", value)?;
|
||||
}
|
||||
|
||||
if let Some(value) = &self.scte35_in {
|
||||
write!(f, ",SCTE35-IN={}", value)?;
|
||||
}
|
||||
|
||||
for (k, v) in &self.client_attributes {
|
||||
write!(f, ",{}={}", k, v)?;
|
||||
}
|
||||
|
||||
if self.end_on_next {
|
||||
write!(f, ",END-ON-NEXT=YES",)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
@ -242,6 +826,112 @@ mod test {
|
|||
|
||||
const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
|
||||
|
||||
#[test]
|
||||
fn test_parser() {
|
||||
assert_eq!(
|
||||
"#EXT-X-DATERANGE:\
|
||||
ID=\"splice-6FFFFFF0\",\
|
||||
START-DATE=\"2014-03-05T11:15:00Z\",\
|
||||
PLANNED-DURATION=59.993,\
|
||||
SCTE35-OUT=0xFC002F0000000000FF000014056F\
|
||||
FFFFF000E011622DCAFF000052636200000000000\
|
||||
A0008029896F50000008700000000"
|
||||
.parse::<ExtXDateRange>()
|
||||
.unwrap(),
|
||||
ExtXDateRange::builder()
|
||||
.id("splice-6FFFFFF0")
|
||||
.start_date(FixedOffset::east(0).ymd(2014, 3, 5).and_hms(11, 15, 0))
|
||||
.planned_duration(Duration::from_secs_f64(59.993))
|
||||
.scte35_out(
|
||||
"0xFC002F0000000000FF00001\
|
||||
4056FFFFFF000E011622DCAFF0\
|
||||
00052636200000000000A00080\
|
||||
29896F50000008700000000"
|
||||
)
|
||||
.build()
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"#EXT-X-DATERANGE:\
|
||||
ID=\"test_id\",\
|
||||
CLASS=\"test_class\",\
|
||||
START-DATE=\"2014-03-05T11:15:00Z\",\
|
||||
END-DATE=\"2014-03-05T11:16:00Z\",\
|
||||
DURATION=60.1,\
|
||||
PLANNED-DURATION=59.993,\
|
||||
X-CUSTOM=45.3,\
|
||||
SCTE35-CMD=0xFC002F0000000000FF2,\
|
||||
SCTE35-OUT=0xFC002F0000000000FF0,\
|
||||
SCTE35-IN=0xFC002F0000000000FF1,\
|
||||
END-ON-NEXT=YES,\
|
||||
UNKNOWN=PHANTOM"
|
||||
.parse::<ExtXDateRange>()
|
||||
.unwrap(),
|
||||
ExtXDateRange::builder()
|
||||
.id("test_id")
|
||||
.class("test_class")
|
||||
.start_date(FixedOffset::east(0).ymd(2014, 3, 5).and_hms(11, 15, 0))
|
||||
.end_date(FixedOffset::east(0).ymd(2014, 3, 5).and_hms(11, 16, 0))
|
||||
.duration(Duration::from_secs_f64(60.1))
|
||||
.planned_duration(Duration::from_secs_f64(59.993))
|
||||
.insert_client_attribute("X-CUSTOM", 45.3)
|
||||
.scte35_cmd("0xFC002F0000000000FF2")
|
||||
.scte35_out("0xFC002F0000000000FF0")
|
||||
.scte35_in("0xFC002F0000000000FF1")
|
||||
.end_on_next(true)
|
||||
.build()
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
assert!("#EXT-X-DATERANGE:END-ON-NEXT=NO"
|
||||
.parse::<ExtXDateRange>()
|
||||
.is_err());
|
||||
|
||||
assert!("garbage".parse::<ExtXDateRange>().is_err());
|
||||
assert!("".parse::<ExtXDateRange>().is_err());
|
||||
|
||||
assert!("#EXT-X-DATERANGE:\
|
||||
ID=\"test_id\",\
|
||||
START-DATE=\"2014-03-05T11:15:00Z\",\
|
||||
END-ON-NEXT=YES"
|
||||
.parse::<ExtXDateRange>()
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display() {
|
||||
assert_eq!(
|
||||
ExtXDateRange::builder()
|
||||
.id("test_id")
|
||||
.class("test_class")
|
||||
.start_date(FixedOffset::east(0).ymd(2014, 3, 5).and_hms(11, 15, 0))
|
||||
.end_date(FixedOffset::east(0).ymd(2014, 3, 5).and_hms(11, 16, 0))
|
||||
.duration(Duration::from_secs_f64(60.1))
|
||||
.planned_duration(Duration::from_secs_f64(59.993))
|
||||
.insert_client_attribute("X-CUSTOM", 45.3)
|
||||
.scte35_cmd("0xFC002F0000000000FF2")
|
||||
.scte35_out("0xFC002F0000000000FF0")
|
||||
.scte35_in("0xFC002F0000000000FF1")
|
||||
.end_on_next(true)
|
||||
.build()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"#EXT-X-DATERANGE:\
|
||||
ID=\"test_id\",\
|
||||
CLASS=\"test_class\",\
|
||||
START-DATE=\"2014-03-05T11:15:00Z\",\
|
||||
END-DATE=\"2014-03-05T11:16:00Z\",\
|
||||
DURATION=60.1,\
|
||||
PLANNED-DURATION=59.993,\
|
||||
SCTE35-CMD=0xFC002F0000000000FF2,\
|
||||
SCTE35-OUT=0xFC002F0000000000FF0,\
|
||||
SCTE35-IN=0xFC002F0000000000FF1,\
|
||||
X-CUSTOM=45.3,\
|
||||
END-ON-NEXT=YES"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_required_version() {
|
||||
assert_eq!(
|
||||
|
|
|
@ -15,6 +15,7 @@ mod media_type;
|
|||
mod protocol_version;
|
||||
mod signed_decimal_floating_point;
|
||||
mod stream_inf;
|
||||
mod value;
|
||||
|
||||
pub use byte_range::*;
|
||||
pub use channels::*;
|
||||
|
@ -32,3 +33,4 @@ pub use media_type::*;
|
|||
pub use protocol_version::*;
|
||||
pub(crate) use signed_decimal_floating_point::*;
|
||||
pub use stream_inf::*;
|
||||
pub use value::*;
|
||||
|
|
106
src/types/value.rs
Normal file
106
src/types/value.rs
Normal file
|
@ -0,0 +1,106 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use hex;
|
||||
|
||||
use crate::utils::{quote, unquote};
|
||||
use crate::Error;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd)]
|
||||
/// A [`Value`].
|
||||
pub enum Value {
|
||||
/// A [`String`].
|
||||
String(String),
|
||||
/// A sequence of bytes.
|
||||
Hex(Vec<u8>),
|
||||
/// A floating point number, that's neither NaN nor infinite!
|
||||
Float(f64),
|
||||
}
|
||||
|
||||
impl fmt::Display for Value {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match &self {
|
||||
Self::String(value) => write!(f, "{}", quote(value)),
|
||||
Self::Hex(value) => write!(f, "0x{}", hex::encode_upper(value)),
|
||||
Self::Float(value) => write!(f, "{}", value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Value {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
if input.starts_with("0x") || input.starts_with("0X") {
|
||||
Ok(Self::Hex(hex::decode(
|
||||
input.trim_start_matches("0x").trim_start_matches("0X"),
|
||||
)?))
|
||||
} else {
|
||||
match input.parse() {
|
||||
Ok(value) => Ok(Self::Float(value)),
|
||||
Err(_) => Ok(Self::String(unquote(input))),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for Value {
|
||||
fn from(value: f64) -> Self { Self::Float(value) }
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for Value {
|
||||
fn from(value: Vec<u8>) -> Self { Self::Hex(value) }
|
||||
}
|
||||
|
||||
impl From<String> for Value {
|
||||
fn from(value: String) -> Self { Self::String(unquote(value)) }
|
||||
}
|
||||
|
||||
impl From<&str> for Value {
|
||||
fn from(value: &str) -> Self { Self::String(unquote(value)) }
|
||||
}
|
||||
|
||||
// impl<T: AsRef<[u8]>> From<T> for Value {
|
||||
// fn from(value: T) -> Self { Self::Hex(value.as_ref().into()) }
|
||||
// }
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_display() {
|
||||
assert_eq!(Value::Float(1.1).to_string(), "1.1".to_string());
|
||||
assert_eq!(
|
||||
Value::String("&str".to_string()).to_string(),
|
||||
"\"&str\"".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
Value::Hex(vec![1, 2, 3]).to_string(),
|
||||
"0x010203".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parser() {
|
||||
assert_eq!(Value::Float(1.1), "1.1".parse().unwrap());
|
||||
assert_eq!(
|
||||
Value::String("&str".to_string()),
|
||||
"\"&str\"".parse().unwrap()
|
||||
);
|
||||
assert_eq!(Value::Hex(vec![1, 2, 3]), "0x010203".parse().unwrap());
|
||||
assert_eq!(Value::Hex(vec![1, 2, 3]), "0X010203".parse().unwrap());
|
||||
assert!("0x010203Z".parse::<Value>().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from() {
|
||||
assert_eq!(Value::from(1.0_f64), Value::Float(1.0));
|
||||
assert_eq!(Value::from("\"&str\""), Value::String("&str".to_string()));
|
||||
assert_eq!(
|
||||
Value::from("&str".to_string()),
|
||||
Value::String("&str".to_string())
|
||||
);
|
||||
assert_eq!(Value::from(vec![1, 2, 3]), Value::Hex(vec![1, 2, 3]));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue