diff --git a/Cargo.toml b/Cargo.toml index f71b574..6bc713e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ codecov = {repository = "sile/hls_m3u8"} [dependencies] trackable = "0.2" +getset = "0.0.8" [dev-dependencies] clap = "2" diff --git a/src/media_playlist.rs b/src/media_playlist.rs index 2f5b7a4..5eca286 100644 --- a/src/media_playlist.rs +++ b/src/media_playlist.rs @@ -141,7 +141,7 @@ impl MediaPlaylistBuilder { // CHECK: `#EXT-X-BYTE-RANGE` if let Some(tag) = s.byte_range_tag() { - if tag.range().start.is_none() { + if tag.to_range().start().is_none() { let last_uri = track_assert_some!(last_range_uri, ErrorKind::InvalidInput); track_assert_eq!(last_uri, s.uri(), ErrorKind::InvalidInput); } else { diff --git a/src/tags/basic/m3u.rs b/src/tags/basic/m3u.rs index 2a2c1e2..07f95ec 100644 --- a/src/tags/basic/m3u.rs +++ b/src/tags/basic/m3u.rs @@ -1,8 +1,10 @@ -use crate::types::ProtocolVersion; -use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::FromStr; +use crate::types::ProtocolVersion; +use crate::utils::tag; +use crate::Error; + /// [4.3.1.1. EXTM3U] /// /// [4.3.1.1. EXTM3U]: https://tools.ietf.org/html/rfc8216#section-4.3.1.1 @@ -27,8 +29,8 @@ impl fmt::Display for ExtM3u { impl FromStr for ExtM3u { type Err = Error; - fn from_str(s: &str) -> Result { - track_assert_eq!(s, Self::PREFIX, ErrorKind::InvalidInput); + fn from_str(input: &str) -> Result { + tag(input, Self::PREFIX)?; Ok(ExtM3u) } } diff --git a/src/tags/basic/version.rs b/src/tags/basic/version.rs index cfb968d..d81db3f 100644 --- a/src/tags/basic/version.rs +++ b/src/tags/basic/version.rs @@ -1,8 +1,10 @@ -use crate::types::ProtocolVersion; -use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::FromStr; +use crate::types::ProtocolVersion; +use crate::utils::tag; +use crate::Error; + /// [4.3.1.2. EXT-X-VERSION] /// /// [4.3.1.2. EXT-X-VERSION]: https://tools.ietf.org/html/rfc8216#section-4.3.1.2 @@ -37,10 +39,8 @@ impl fmt::Display for ExtXVersion { impl FromStr for ExtXVersion { type Err = Error; - fn from_str(s: &str) -> Result { - track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); - let suffix = s.split_at(Self::PREFIX.len()).1; - let version = track!(suffix.parse())?; + fn from_str(input: &str) -> Result { + let version = tag(input, Self::PREFIX)?.parse()?; Ok(ExtXVersion::new(version)) } } diff --git a/src/tags/master_playlist/i_frame_stream_inf.rs b/src/tags/master_playlist/i_frame_stream_inf.rs index 8b64a3b..f9e38dc 100644 --- a/src/tags/master_playlist/i_frame_stream_inf.rs +++ b/src/tags/master_playlist/i_frame_stream_inf.rs @@ -1,22 +1,35 @@ +use std::fmt; +use std::str::FromStr; + +use getset::{Getters, MutGetters, Setters}; + use crate::attribute::AttributePairs; use crate::types::{DecimalResolution, HdcpLevel, ProtocolVersion}; use crate::utils::parse_u64; -use crate::utils::{quote, unquote}; -use crate::{Error, ErrorKind, Result}; -use std::fmt; -use std::str::FromStr; +use crate::utils::{quote, tag, unquote}; +use crate::{Error, ErrorKind}; /// [4.3.4.3. EXT-X-I-FRAME-STREAM-INF] /// /// [4.3.4.3. EXT-X-I-FRAME-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.3 -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Getters, Setters, MutGetters, Debug, Clone, PartialEq, Eq, Hash)] +#[get = "pub"] +#[set = "pub"] +#[get_mut = "pub"] pub struct ExtXIFrameStreamInf { + /// The URI, that identifies the associated media playlist. uri: String, + /// The peak segment bit rate of the variant stream. bandwidth: u64, + /// The average segment bit rate of the variant stream. average_bandwidth: Option, + /// A string that represents the list of codec types contained the variant stream. codecs: Option, + /// The optimal pixel resolution at which to display all the video in the variant stream. resolution: Option, + /// The HDCP level of the variant stream. hdcp_level: Option, + /// The group identifier for the video in the variant stream. video: Option, } @@ -36,41 +49,6 @@ impl ExtXIFrameStreamInf { } } - /// Returns the URI that identifies the associated media playlist. - pub const fn uri(&self) -> &String { - &self.uri - } - - /// Returns the peak segment bit rate of the variant stream. - pub const fn bandwidth(&self) -> u64 { - self.bandwidth - } - - /// Returns the average segment bit rate of the variant stream. - pub const fn average_bandwidth(&self) -> Option { - self.average_bandwidth - } - - /// Returns a string that represents the list of codec types contained the variant stream. - pub fn codecs(&self) -> Option<&String> { - self.codecs.as_ref() - } - - /// Returns the optimal pixel resolution at which to display all the video in the variant stream. - pub const fn resolution(&self) -> Option { - self.resolution - } - - /// Returns the HDCP level of the variant stream. - pub const fn hdcp_level(&self) -> Option { - self.hdcp_level - } - - /// Returns the group identifier for the video in the variant stream. - pub fn video(&self) -> Option<&String> { - self.video.as_ref() - } - /// Returns the protocol compatibility version that this tag requires. pub const fn requires_version(&self) -> ProtocolVersion { ProtocolVersion::V1 @@ -105,8 +83,8 @@ impl fmt::Display for ExtXIFrameStreamInf { impl FromStr for ExtXIFrameStreamInf { type Err = Error; - fn from_str(s: &str) -> Result { - track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); + fn from_str(input: &str) -> Result { + let input = tag(input, Self::PREFIX)?; let mut uri = None; let mut bandwidth = None; @@ -115,7 +93,8 @@ impl FromStr for ExtXIFrameStreamInf { let mut resolution = None; let mut hdcp_level = None; let mut video = None; - let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1); + + let attrs = AttributePairs::parse(input); for attr in attrs { let (key, value) = track!(attr)?; match key { @@ -167,7 +146,7 @@ mod test { ); assert_eq!(i_frame_stream_inf.uri(), "foo"); - assert_eq!(i_frame_stream_inf.bandwidth(), 1000); + assert_eq!(*i_frame_stream_inf.bandwidth(), 1000); // TODO: test all the optional fields } diff --git a/src/tags/master_playlist/media.rs b/src/tags/master_playlist/media.rs index 7c17b3b..59e2910 100644 --- a/src/tags/master_playlist/media.rs +++ b/src/tags/master_playlist/media.rs @@ -1,7 +1,7 @@ use crate::attribute::AttributePairs; use crate::types::{InStreamId, MediaType, ProtocolVersion}; -use crate::utils::{parse_yes_or_no, quote, unquote}; -use crate::{Error, ErrorKind, Result}; +use crate::utils::{parse_yes_or_no, quote, tag, unquote}; +use crate::{Error, ErrorKind}; use std::fmt; use std::str::FromStr; @@ -114,7 +114,7 @@ impl ExtXMediaBuilder { } /// Builds a `ExtXMedia` instance. - pub fn finish(self) -> Result { + pub fn finish(self) -> crate::Result { let media_type = track_assert_some!(self.media_type, ErrorKind::InvalidInput); let group_id = track_assert_some!(self.group_id, ErrorKind::InvalidInput); let name = track_assert_some!(self.name, ErrorKind::InvalidInput); @@ -309,11 +309,11 @@ impl fmt::Display for ExtXMedia { impl FromStr for ExtXMedia { type Err = Error; - fn from_str(s: &str) -> Result { - track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); + fn from_str(input: &str) -> Result { + let input = tag(input, Self::PREFIX)?; let mut builder = ExtXMediaBuilder::new(); - let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1); + let attrs = AttributePairs::parse(input); for attr in attrs { let (key, value) = track!(attr)?; match key { diff --git a/src/tags/master_playlist/session_data.rs b/src/tags/master_playlist/session_data.rs index 32b1633..97da40d 100644 --- a/src/tags/master_playlist/session_data.rs +++ b/src/tags/master_playlist/session_data.rs @@ -1,17 +1,26 @@ -use crate::attribute::AttributePairs; -use crate::types::{ProtocolVersion, SessionData}; -use crate::utils::{quote, unquote}; -use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::FromStr; +use getset::{Getters, MutGetters, Setters}; + +use crate::attribute::AttributePairs; +use crate::types::{ProtocolVersion, SessionData}; +use crate::utils::{quote, tag, unquote}; +use crate::{Error, ErrorKind}; + /// [4.3.4.4. EXT-X-SESSION-DATA] /// /// [4.3.4.4. EXT-X-SESSION-DATA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.4 -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Getters, MutGetters, Setters, Debug, Clone, PartialEq, Eq, Hash)] +#[get = "pub"] +#[set = "pub"] +#[get_mut = "pub"] pub struct ExtXSessionData { + /// The identifier of the data. data_id: String, + /// The session data. data: SessionData, + /// The language of the data. language: Option, } @@ -36,21 +45,6 @@ impl ExtXSessionData { } } - /// Returns the identifier of the data. - pub const fn data_id(&self) -> &String { - &self.data_id - } - - /// Returns the session data. - pub const fn data(&self) -> &SessionData { - &self.data - } - - /// Returns the language of the data. - pub fn language(&self) -> Option<&String> { - self.language.as_ref() - } - /// Returns the protocol compatibility version that this tag requires. pub const fn requires_version(&self) -> ProtocolVersion { ProtocolVersion::V1 @@ -75,14 +69,15 @@ impl fmt::Display for ExtXSessionData { impl FromStr for ExtXSessionData { type Err = Error; - fn from_str(s: &str) -> Result { - track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); + fn from_str(input: &str) -> Result { + let input = tag(input, Self::PREFIX)?; let mut data_id = None; let mut session_value = None; let mut uri = None; let mut language = None; - let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1); + + let attrs = AttributePairs::parse(input); for attr in attrs { let (key, value) = track!(attr)?; match key { @@ -119,23 +114,38 @@ mod test { use super::*; #[test] - fn ext_x_session_data() { + fn test_display() { let tag = ExtXSessionData::new("foo", SessionData::Value("bar".into())); let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",VALUE="bar""#; - assert_eq!(text.parse().ok(), Some(tag.clone())); assert_eq!(tag.to_string(), text); - assert_eq!(tag.requires_version(), ProtocolVersion::V1); let tag = ExtXSessionData::new("foo", SessionData::Uri("bar".into())); let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",URI="bar""#; - assert_eq!(text.parse().ok(), Some(tag.clone())); assert_eq!(tag.to_string(), text); - assert_eq!(tag.requires_version(), ProtocolVersion::V1); let tag = ExtXSessionData::with_language("foo", SessionData::Value("bar".into()), "baz"); let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",VALUE="bar",LANGUAGE="baz""#; - assert_eq!(text.parse().ok(), Some(tag.clone())); assert_eq!(tag.to_string(), text); + } + + #[test] + fn test_parser() { + let tag = ExtXSessionData::new("foo", SessionData::Value("bar".into())); + let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",VALUE="bar""#; + assert_eq!(text.parse::().unwrap(), tag); + + let tag = ExtXSessionData::new("foo", SessionData::Uri("bar".into())); + let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",URI="bar""#; + assert_eq!(text.parse::().unwrap(), tag); + + let tag = ExtXSessionData::with_language("foo", SessionData::Value("bar".into()), "baz"); + let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",VALUE="bar",LANGUAGE="baz""#; + assert_eq!(text.parse::().unwrap(), tag); + } + + #[test] + fn test_requires_version() { + let tag = ExtXSessionData::new("foo", SessionData::Value("bar".into())); assert_eq!(tag.requires_version(), ProtocolVersion::V1); } } diff --git a/src/tags/master_playlist/session_key.rs b/src/tags/master_playlist/session_key.rs index 26cea97..e2d29ba 100644 --- a/src/tags/master_playlist/session_key.rs +++ b/src/tags/master_playlist/session_key.rs @@ -1,8 +1,9 @@ -use crate::types::{DecryptionKey, ProtocolVersion}; -use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::FromStr; +use crate::types::{DecryptionKey, ProtocolVersion}; +use crate::utils::tag; + /// [4.3.4.5. EXT-X-SESSION-KEY] /// /// [4.3.4.5. EXT-X-SESSION-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.4.5 @@ -37,13 +38,11 @@ impl fmt::Display for ExtXSessionKey { } impl FromStr for ExtXSessionKey { - type Err = Error; + type Err = crate::Error; - fn from_str(s: &str) -> Result { - track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); - let suffix = s.split_at(Self::PREFIX.len()).1; - let key = track!(suffix.parse())?; - Ok(ExtXSessionKey { key }) + fn from_str(input: &str) -> Result { + let key = tag(input, Self::PREFIX)?.parse()?; + Ok(Self::new(key)) } } diff --git a/src/tags/master_playlist/stream_inf.rs b/src/tags/master_playlist/stream_inf.rs index 81c8c28..dbeeef9 100644 --- a/src/tags/master_playlist/stream_inf.rs +++ b/src/tags/master_playlist/stream_inf.rs @@ -1,12 +1,13 @@ +use std::fmt; +use std::str::FromStr; + use crate::attribute::AttributePairs; use crate::types::{ ClosedCaptions, DecimalFloatingPoint, DecimalResolution, HdcpLevel, ProtocolVersion, SingleLineString, }; -use crate::utils::{parse_u64, quote, unquote}; -use crate::{Error, ErrorKind, Result}; -use std::fmt; -use std::str::FromStr; +use crate::utils::{parse_u64, quote, tag, unquote}; +use crate::{Error, ErrorKind}; /// [4.3.4.2. EXT-X-STREAM-INF] /// @@ -146,16 +147,15 @@ impl fmt::Display for ExtXStreamInf { impl FromStr for ExtXStreamInf { type Err = Error; - fn from_str(s: &str) -> Result { - let mut lines = s.splitn(2, '\n'); - let first_line = lines.next().expect("Never fails").trim_end_matches('\r'); - let second_line = track_assert_some!(lines.next(), ErrorKind::InvalidInput); + fn from_str(input: &str) -> Result { + let mut lines = input.lines(); + let first_line = lines.next().ok_or(ErrorKind::InvalidInput)?; // TODO! + let second_line = lines.next().ok_or(ErrorKind::InvalidInput)?; // TODO! + + tag(first_line, Self::PREFIX)?; - track_assert!( - first_line.starts_with(Self::PREFIX), - ErrorKind::InvalidInput - ); let uri = track!(SingleLineString::new(second_line))?; + let mut bandwidth = None; let mut average_bandwidth = None; let mut codecs = None; @@ -186,6 +186,7 @@ impl FromStr for ExtXStreamInf { } } } + let bandwidth = track_assert_some!(bandwidth, ErrorKind::InvalidInput); Ok(ExtXStreamInf { uri, diff --git a/src/tags/media_playlist/discontinuity_sequence.rs b/src/tags/media_playlist/discontinuity_sequence.rs index 07b245b..6619db1 100644 --- a/src/tags/media_playlist/discontinuity_sequence.rs +++ b/src/tags/media_playlist/discontinuity_sequence.rs @@ -1,8 +1,8 @@ -use crate::types::ProtocolVersion; -use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::FromStr; -use trackable::error::ErrorKindExt; + +use crate::types::ProtocolVersion; +use crate::utils::tag; /// [4.3.3.3. EXT-X-DISCONTINUITY-SEQUENCE] /// @@ -39,12 +39,11 @@ impl fmt::Display for ExtXDiscontinuitySequence { } impl FromStr for ExtXDiscontinuitySequence { - type Err = Error; + type Err = crate::Error; - fn from_str(s: &str) -> Result { - track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); - let seq_num = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?; - Ok(ExtXDiscontinuitySequence { seq_num }) + fn from_str(input: &str) -> Result { + let seq_num = tag(input, Self::PREFIX)?.parse().unwrap(); // TODO! + Ok(Self::new(seq_num)) } } diff --git a/src/tags/media_segment/byte_range.rs b/src/tags/media_segment/byte_range.rs index bbc5ab1..9afddfa 100644 --- a/src/tags/media_segment/byte_range.rs +++ b/src/tags/media_segment/byte_range.rs @@ -1,39 +1,72 @@ +use std::fmt; +use std::ops::Deref; +use std::str::FromStr; + +use trackable::error::ErrorKindExt; + use crate::types::{ByteRange, ProtocolVersion}; use crate::{Error, ErrorKind, Result}; -use std::fmt; -use std::str::FromStr; -use trackable::error::ErrorKindExt; /// [4.3.2.2. EXT-X-BYTERANGE] /// /// [4.3.2.2. EXT-X-BYTERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.2 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct ExtXByteRange { - range: ByteRange, -} +pub struct ExtXByteRange(ByteRange); impl ExtXByteRange { pub(crate) const PREFIX: &'static str = "#EXT-X-BYTERANGE:"; /// Makes a new `ExtXByteRange` tag. - pub const fn new(range: ByteRange) -> Self { - ExtXByteRange { range } + /// # Example + /// ``` + /// use hls_m3u8::tags::ExtXByteRange; + /// + /// let byte_range = ExtXByteRange::new(20, Some(5)); + /// ``` + pub const fn new(length: usize, start: Option) -> Self { + Self(ByteRange::new(length, start)) } - /// Returns the range of the associated media segment. - pub const fn range(&self) -> ByteRange { - self.range + /// Converts the [ExtXByteRange] to a [ByteRange]. + /// # Example + /// ``` + /// 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(); + /// ``` + pub const fn to_range(&self) -> ByteRange { + self.0 } /// Returns the protocol compatibility version that this tag requires. + /// # Example + /// ``` + /// use hls_m3u8::tags::ExtXByteRange; + /// use hls_m3u8::types::ProtocolVersion; + /// + /// let byte_range = ExtXByteRange::new(20, Some(5)); + /// assert_eq!(byte_range.requires_version(), ProtocolVersion::V4); + /// ``` pub const fn requires_version(&self) -> ProtocolVersion { ProtocolVersion::V4 } } +impl Deref for ExtXByteRange { + type Target = ByteRange; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + impl fmt::Display for ExtXByteRange { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}{}", Self::PREFIX, self.range) + write!(f, "{}", Self::PREFIX)?; + write!(f, "{}", self.0)?; + Ok(()) } } @@ -41,9 +74,30 @@ impl FromStr for ExtXByteRange { type Err = Error; fn from_str(s: &str) -> Result { + // check if the string starts with the PREFIX track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); - let range = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?; - Ok(ExtXByteRange { range }) + let byte_range = s.split_at(Self::PREFIX.len()).1; + let tokens = byte_range.splitn(2, '@').collect::>(); + if tokens.is_empty() { + Err(ErrorKind::InvalidInput)?; + } + + let length = tokens[0] + .parse() + .map_err(|e| ErrorKind::InvalidInput.cause(e))?; + let start = { + let mut result = None; + if tokens.len() == 2 { + result = Some( + tokens[1] + .parse() + .map_err(|e| ErrorKind::InvalidInput.cause(e))?, + ); + } + result + }; + + Ok(ExtXByteRange::new(length, start)) } } @@ -52,21 +106,40 @@ mod test { use super::*; #[test] - fn ext_x_byterange() { - let tag = ExtXByteRange::new(ByteRange { - length: 3, - start: None, - }); - assert_eq!("#EXT-X-BYTERANGE:3".parse().ok(), Some(tag)); - assert_eq!(tag.to_string(), "#EXT-X-BYTERANGE:3"); - assert_eq!(tag.requires_version(), ProtocolVersion::V4); + 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 tag = ExtXByteRange::new(ByteRange { - length: 3, - start: Some(5), - }); - assert_eq!("#EXT-X-BYTERANGE:3@5".parse().ok(), Some(tag)); - assert_eq!(tag.to_string(), "#EXT-X-BYTERANGE:3@5"); - assert_eq!(tag.requires_version(), ProtocolVersion::V4); + let byte_range = ExtXByteRange::new(99999, Some(2)); + assert_eq!( + byte_range.to_string(), + "#EXT-X-BYTERANGE:99999@2".to_string() + ); + + let byte_range = ExtXByteRange::new(99999, None); + assert_eq!(byte_range.to_string(), "#EXT-X-BYTERANGE:99999".to_string()); + } + + #[test] + fn test_parse() { + let byte_range = ExtXByteRange::new(99999, Some(2)); + assert_eq!( + byte_range, + "#EXT-X-BYTERANGE:99999@2".parse::().unwrap() + ); + + let byte_range = ExtXByteRange::new(99999, None); + assert_eq!( + byte_range, + "#EXT-X-BYTERANGE:99999".parse::().unwrap() + ); + } + + #[test] + fn test_deref() { + let byte_range = ExtXByteRange::new(0, Some(22)); + + assert_eq!(*byte_range.length(), 0); + assert_eq!(*byte_range.start(), Some(22)); } } diff --git a/src/tags/media_segment/discontinuity.rs b/src/tags/media_segment/discontinuity.rs index d0f3563..9571f2c 100644 --- a/src/tags/media_segment/discontinuity.rs +++ b/src/tags/media_segment/discontinuity.rs @@ -1,13 +1,16 @@ -use crate::types::ProtocolVersion; -use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::FromStr; +use crate::types::ProtocolVersion; +use crate::utils::tag; +use crate::{Error, Result}; + /// [4.3.2.3. EXT-X-DISCONTINUITY] /// /// [4.3.2.3. EXT-X-DISCONTINUITY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.3 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ExtXDiscontinuity; + impl ExtXDiscontinuity { pub(crate) const PREFIX: &'static str = "#EXT-X-DISCONTINUITY"; @@ -25,8 +28,9 @@ impl fmt::Display for ExtXDiscontinuity { impl FromStr for ExtXDiscontinuity { type Err = Error; - fn from_str(s: &str) -> Result { - track_assert_eq!(s, Self::PREFIX, ErrorKind::InvalidInput); + + fn from_str(input: &str) -> Result { + tag(input, Self::PREFIX)?; Ok(ExtXDiscontinuity) } } diff --git a/src/tags/media_segment/map.rs b/src/tags/media_segment/map.rs index c5c9933..1ef468b 100644 --- a/src/tags/media_segment/map.rs +++ b/src/tags/media_segment/map.rs @@ -100,13 +100,7 @@ mod test { assert_eq!(tag.to_string(), text); assert_eq!(tag.requires_version(), ProtocolVersion::V6); - let tag = ExtXMap::with_range( - "foo", - ByteRange { - length: 9, - start: Some(2), - }, - ); + let tag = ExtXMap::with_range("foo", ByteRange::new(9, Some(2))); let text = r#"#EXT-X-MAP:URI="foo",BYTERANGE="9@2""#; track_try_unwrap!(ExtXMap::from_str(text)); assert_eq!(text.parse().ok(), Some(tag.clone())); diff --git a/src/types/byte_range.rs b/src/types/byte_range.rs index af26870..443ab53 100644 --- a/src/types/byte_range.rs +++ b/src/types/byte_range.rs @@ -1,18 +1,32 @@ -use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::{self, FromStr}; + +use getset::{Getters, MutGetters, Setters}; use trackable::error::ErrorKindExt; +use crate::{Error, ErrorKind, Result}; + /// Byte range. /// /// See: [4.3.2.2. EXT-X-BYTERANGE] /// /// [4.3.2.2. EXT-X-BYTERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.2 -#[allow(missing_docs)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Getters, Setters, MutGetters, Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[get = "pub"] +#[set = "pub"] +#[get_mut = "pub"] pub struct ByteRange { - pub length: usize, - pub start: Option, + /// The length of the range. + length: usize, + /// The start of the range. + start: Option, +} + +impl ByteRange { + /// Creates a new [ByteRange]. + pub const fn new(length: usize, start: Option) -> Self { + Self { length, start } + } } impl fmt::Display for ByteRange { @@ -27,20 +41,28 @@ impl fmt::Display for ByteRange { impl FromStr for ByteRange { type Err = Error; + fn from_str(s: &str) -> Result { - let mut tokens = s.splitn(2, '@'); - let length = tokens.next().expect("Never fails"); - let start = if let Some(start) = tokens.next() { - Some(track!(start - .parse() - .map_err(|e| ErrorKind::InvalidInput.cause(e)))?) - } else { - None + let tokens = s.splitn(2, '@').collect::>(); + if tokens.is_empty() { + Err(ErrorKind::InvalidInput)?; + } + + let length = tokens[0] + .parse() + .map_err(|e| ErrorKind::InvalidInput.cause(e))?; + let start = { + let mut result = None; + if tokens.len() == 2 { + result = Some( + tokens[1] + .parse() + .map_err(|e| ErrorKind::InvalidInput.cause(e))?, + ); + } + result }; - Ok(ByteRange { - length: track!(length.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?, - start, - }) + Ok(ByteRange::new(length, start)) } } diff --git a/src/utils.rs b/src/utils.rs index 605eb1d..946dab9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -44,6 +44,19 @@ pub(crate) fn quote(value: T) -> String { format!("\"{}\"", value.to_string().replace("\"", "")) } +/// Checks, if the given tag is at the start of the input. If this is the case, it will remove it +/// return the rest of the input, otherwise it will return an error. +pub(crate) fn tag(input: &str, tag: T) -> crate::Result<&str> +where + T: AsRef, +{ + if !input.starts_with(tag.as_ref()) { + Err(ErrorKind::InvalidInput)?; // TODO! + } + let result = input.split_at(tag.as_ref().len()).1; + Ok(result) +} + #[cfg(test)] mod tests { use super::*; @@ -74,4 +87,21 @@ mod tests { assert_eq!(quote("value"), "\"value\"".to_string()); assert_eq!(quote("\"value\""), "\"value\"".to_string()); } + + #[test] + fn test_tag() { + let input = "HelloMyFriendThisIsASampleString"; + + let input = tag(input, "Hello").unwrap(); + assert_eq!(input, "MyFriendThisIsASampleString"); + + let input = tag(input, "My").unwrap(); + assert_eq!(input, "FriendThisIsASampleString"); + + let input = tag(input, "FriendThisIs").unwrap(); + assert_eq!(input, "ASampleString"); + + let input = tag(input, "A").unwrap(); + assert_eq!(input, "SampleString"); + } }