diff --git a/Cargo.toml b/Cargo.toml index 6bc713e..65c0cf4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,8 +16,8 @@ travis-ci = {repository = "sile/hls_m3u8"} codecov = {repository = "sile/hls_m3u8"} [dependencies] -trackable = "0.2" getset = "0.0.8" +failure = "0.1.5" [dev-dependencies] clap = "2" diff --git a/examples/parse.rs b/examples/parse.rs index 44a8d16..226a22d 100644 --- a/examples/parse.rs +++ b/examples/parse.rs @@ -1,12 +1,9 @@ extern crate clap; extern crate hls_m3u8; -#[macro_use] -extern crate trackable; use clap::{App, Arg}; use hls_m3u8::{MasterPlaylist, MediaPlaylist}; use std::io::{self, Read}; -use trackable::error::Failure; fn main() { let matches = App::new("parse") @@ -19,17 +16,15 @@ fn main() { ) .get_matches(); let mut m3u8 = String::new(); - track_try_unwrap!(io::stdin() - .read_to_string(&mut m3u8) - .map_err(Failure::from_error)); + io::stdin().read_to_string(&mut m3u8).unwrap(); match matches.value_of("M3U8_TYPE").unwrap() { "media" => { - let playlist: MediaPlaylist = track_try_unwrap!(m3u8.parse()); + let playlist: MediaPlaylist = m3u8.parse().unwrap(); println!("{}", playlist); } "master" => { - let playlist: MasterPlaylist = track_try_unwrap!(m3u8.parse()); + let playlist: MasterPlaylist = m3u8.parse().unwrap(); println!("{}", playlist); } _ => unreachable!(), diff --git a/src/attribute.rs b/src/attribute.rs index 8ef7450..212728b 100644 --- a/src/attribute.rs +++ b/src/attribute.rs @@ -1,4 +1,4 @@ -use crate::{ErrorKind, Result}; +use crate::{Error, Result}; use std::collections::HashSet; use std::str; @@ -25,18 +25,13 @@ impl<'a> AttributePairs<'a> { return Ok(key); } b'A'..=b'Z' | b'0'..=b'9' | b'-' => {} - _ => track_panic!( - ErrorKind::InvalidInput, - "Malformed attribute name: {:?}", - self.input - ), + _ => { + return Err(Error::invalid_attribute(self.input.to_string())); + } } } - track_panic!( - ErrorKind::InvalidInput, - "No attribute value: {:?}", - self.input - ); + + Err(Error::missing_value(self.input.to_string())) } fn parse_raw_value(&mut self) -> &'a str { @@ -64,19 +59,15 @@ impl<'a> AttributePairs<'a> { } impl<'a> Iterator for AttributePairs<'a> { type Item = Result<(&'a str, &'a str)>; + fn next(&mut self) -> Option { if self.input.is_empty() { return None; } let result = || -> Result<(&'a str, &'a str)> { - let key = track!(self.parse_name())?; - track_assert!( - self.visited_keys.insert(key), - ErrorKind::InvalidInput, - "Duplicate attribute key: {:?}", - key - ); + let key = self.parse_name()?; + self.visited_keys.insert(key); let value = self.parse_raw_value(); Ok((key, value)) diff --git a/src/error.rs b/src/error.rs index b803bd1..ff1fddf 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,13 +1,179 @@ -use trackable::error::{ErrorKind as TrackableErrorKind, TrackableError}; +use std::error; +use std::fmt; -/// This crate specific `Error` type. -#[derive(Debug, Clone, TrackableError)] -pub struct Error(TrackableError); +use failure::{Backtrace, Context, Fail}; -/// Possible error kinds. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[allow(missing_docs)] -pub enum ErrorKind { - InvalidInput, +/// This crate specific `Result` type. +pub type Result = std::result::Result; + +#[derive(Debug, Fail, Clone)] +pub enum AttributeError { + #[fail(display = "The attribute has an invalid name; {:?}", _0)] + InvalidAttribute(String), + #[fail(display = "A value is missing for the attribute: {}", _0)] + MissingValue(String), +} + +#[derive(Debug, Fail, Clone)] +pub enum ErrorKind { + #[fail(display = "AttributeError: {}", _0)] + AttributeError(AttributeError), + + #[fail(display = "UnknownError: {}", _0)] + UnknownError(String), + + #[fail(display = "A value is missing for the attribute {}", _0)] + MissingValue(String), + + #[fail(display = "Invalid Input")] + InvalidInput, + + #[fail(display = "ParseIntError: {}", _0)] + ParseIntError(String), + + #[fail(display = "ParseFloatError: {}", _0)] + ParseFloatError(String), + + #[fail(display = "MissingTag: Expected {} at the start of {:?}", tag, input)] + MissingTag { tag: String, input: String }, + + #[fail(display = "CustomError: {}", _0)] + Custom(String), + + #[fail(display = "Unmatched Group: {:?}", _0)] + UnmatchedGroup(String), + + #[fail(display = "Unknown Protocol version: {:?}", _0)] + UnknownProtocolVersion(String), + + /// Hints that destructuring should not be exhaustive. + /// + /// This enum may grow additional variants, so this makes sure clients + /// don't count on exhaustive matching. (Otherwise, adding a new variant + /// could break existing code.) + #[doc(hidden)] + #[fail(display = "Invalid error")] + __Nonexhaustive, +} + +#[derive(Debug)] +pub struct Error { + inner: Context, +} + +impl Fail for Error { + fn cause(&self) -> Option<&dyn Fail> { + self.inner.cause() + } + + fn backtrace(&self) -> Option<&Backtrace> { + self.inner.backtrace() + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } +} + +impl From for Error { + fn from(kind: ErrorKind) -> Error { + Error::from(Context::new(kind)) + } +} + +impl From> for Error { + fn from(inner: Context) -> Error { + Error { inner } + } +} + +macro_rules! from_error { + ( $( $f:tt ),* ) => { + $( + impl From<$f> for ErrorKind { + fn from(value: $f) -> Self { + Self::$f(value) + } + } + )* + } +} + +from_error!(AttributeError); + +impl Error { + pub(crate) fn invalid_attribute(value: T) -> Self { + Self::from(ErrorKind::from(AttributeError::InvalidAttribute( + value.to_string(), + ))) + } + + pub(crate) fn missing_attribute_value(value: T) -> Self { + Self::from(ErrorKind::from(AttributeError::MissingValue( + value.to_string(), + ))) + } + + pub(crate) fn unknown(value: T) -> Self + where + T: error::Error, + { + Self::from(ErrorKind::UnknownError(value.to_string())) + } + + pub(crate) fn missing_value(value: T) -> Self { + Self::from(ErrorKind::MissingValue(value.to_string())) + } + + pub(crate) fn invalid_input() -> Self { + Self::from(ErrorKind::InvalidInput) + } + + pub(crate) fn parse_int_error(value: T) -> Self { + Self::from(ErrorKind::ParseIntError(value.to_string())) + } + + pub(crate) fn parse_float_error(value: T) -> Self { + Self::from(ErrorKind::ParseFloatError(value.to_string())) + } + + pub(crate) fn missing_tag(tag: T, input: U) -> Self + where + T: ToString, + U: ToString, + { + Self::from(ErrorKind::MissingTag { + tag: tag.to_string(), + input: input.to_string(), + }) + } + + pub(crate) fn unmatched_group(value: T) -> Self { + Self::from(ErrorKind::UnmatchedGroup(value.to_string())) + } + + pub(crate) fn custom(value: T) -> Self + where + T: fmt::Display, + { + Self::from(ErrorKind::Custom(value.to_string())) + } + + pub(crate) fn unknown_protocol_version(value: T) -> Self { + Self::from(ErrorKind::UnknownProtocolVersion(value.to_string())) + } +} + +impl From for Error { + fn from(value: ::std::num::ParseIntError) -> Self { + Error::parse_int_error(value) + } +} + +impl From for Error { + fn from(value: ::std::num::ParseFloatError) -> Self { + Error::parse_float_error(value) + } } -impl TrackableErrorKind for ErrorKind {} diff --git a/src/lib.rs b/src/lib.rs index 409ba59..135c9ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ clippy::nursery, clippy::cargo )] +#![warn(missing_docs)] //! [HLS] m3u8 parser/generator. //! //! [HLS]: https://tools.ietf.org/html/rfc8216 @@ -25,9 +26,6 @@ //! //! assert!(m3u8.parse::().is_ok()); //! ``` -#![warn(missing_docs)] -#[macro_use] -extern crate trackable; pub use error::{Error, ErrorKind}; pub use master_playlist::{MasterPlaylist, MasterPlaylistBuilder}; @@ -45,5 +43,4 @@ mod media_playlist; mod media_segment; mod utils; -/// This crate specific `Result` type. -pub type Result = std::result::Result; +pub use error::Result; diff --git a/src/line.rs b/src/line.rs index 974d01d..0641bc5 100644 --- a/src/line.rs +++ b/src/line.rs @@ -1,9 +1,10 @@ -use crate::tags; -use crate::types::SingleLineString; -use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::FromStr; +use crate::tags; +use crate::types::SingleLineString; +use crate::Error; + #[derive(Debug)] pub struct Lines<'a> { input: &'a str, @@ -13,11 +14,12 @@ impl<'a> Lines<'a> { Lines { input } } - fn read_line(&mut self) -> Result> { + fn read_line(&mut self) -> crate::Result> { let mut end = self.input.len(); let mut next_start = self.input.len(); let mut adjust = 0; let mut next_line_of_ext_x_stream_inf = false; + for (i, c) in self.input.char_indices() { match c { '\n' => { @@ -36,33 +38,39 @@ impl<'a> Lines<'a> { adjust = 1; } _ => { - track_assert!(!c.is_control(), ErrorKind::InvalidInput); + if c.is_control() { + return Err(Error::invalid_input()); + } adjust = 0; } } } let raw_line = &self.input[..end]; + let line = if raw_line.is_empty() { Line::Blank } else if raw_line.starts_with("#EXT") { - Line::Tag(track!(raw_line.parse())?) + Line::Tag((raw_line.parse())?) } else if raw_line.starts_with('#') { Line::Comment(raw_line) } else { - let uri = track!(SingleLineString::new(raw_line))?; + let uri = SingleLineString::new(raw_line)?; Line::Uri(uri) }; + self.input = &self.input[next_start..]; Ok(line) } } impl<'a> Iterator for Lines<'a> { - type Item = Result>; + type Item = crate::Result>; + fn next(&mut self) -> Option { if self.input.is_empty() { return None; } - match track!(self.read_line()) { + + match self.read_line() { Err(e) => Some(Err(e)), Ok(line) => Some(Ok(line)), } @@ -105,6 +113,7 @@ pub enum Tag { ExtXStart(tags::ExtXStart), Unknown(SingleLineString), } + impl fmt::Display for Tag { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { @@ -134,55 +143,57 @@ impl fmt::Display for Tag { } } } + impl FromStr for Tag { type Err = Error; - fn from_str(s: &str) -> Result { + + fn from_str(s: &str) -> Result { if s.starts_with(tags::ExtM3u::PREFIX) { - track!(s.parse().map(Tag::ExtM3u)) + (s.parse().map(Tag::ExtM3u)) } else if s.starts_with(tags::ExtXVersion::PREFIX) { - track!(s.parse().map(Tag::ExtXVersion)) + (s.parse().map(Tag::ExtXVersion)) } else if s.starts_with(tags::ExtInf::PREFIX) { - track!(s.parse().map(Tag::ExtInf)) + (s.parse().map(Tag::ExtInf)) } else if s.starts_with(tags::ExtXByteRange::PREFIX) { - track!(s.parse().map(Tag::ExtXByteRange)) + (s.parse().map(Tag::ExtXByteRange)) } else if s.starts_with(tags::ExtXDiscontinuity::PREFIX) { - track!(s.parse().map(Tag::ExtXDiscontinuity)) + (s.parse().map(Tag::ExtXDiscontinuity)) } else if s.starts_with(tags::ExtXKey::PREFIX) { - track!(s.parse().map(Tag::ExtXKey)) + (s.parse().map(Tag::ExtXKey)) } else if s.starts_with(tags::ExtXMap::PREFIX) { - track!(s.parse().map(Tag::ExtXMap)) + (s.parse().map(Tag::ExtXMap)) } else if s.starts_with(tags::ExtXProgramDateTime::PREFIX) { - track!(s.parse().map(Tag::ExtXProgramDateTime)) + (s.parse().map(Tag::ExtXProgramDateTime)) } else if s.starts_with(tags::ExtXTargetDuration::PREFIX) { - track!(s.parse().map(Tag::ExtXTargetDuration)) + (s.parse().map(Tag::ExtXTargetDuration)) } else if s.starts_with(tags::ExtXDateRange::PREFIX) { - track!(s.parse().map(Tag::ExtXDateRange)) + (s.parse().map(Tag::ExtXDateRange)) } else if s.starts_with(tags::ExtXMediaSequence::PREFIX) { - track!(s.parse().map(Tag::ExtXMediaSequence)) + (s.parse().map(Tag::ExtXMediaSequence)) } else if s.starts_with(tags::ExtXDiscontinuitySequence::PREFIX) { - track!(s.parse().map(Tag::ExtXDiscontinuitySequence)) + (s.parse().map(Tag::ExtXDiscontinuitySequence)) } else if s.starts_with(tags::ExtXEndList::PREFIX) { - track!(s.parse().map(Tag::ExtXEndList)) + (s.parse().map(Tag::ExtXEndList)) } else if s.starts_with(tags::ExtXPlaylistType::PREFIX) { - track!(s.parse().map(Tag::ExtXPlaylistType)) + (s.parse().map(Tag::ExtXPlaylistType)) } else if s.starts_with(tags::ExtXIFramesOnly::PREFIX) { - track!(s.parse().map(Tag::ExtXIFramesOnly)) + (s.parse().map(Tag::ExtXIFramesOnly)) } else if s.starts_with(tags::ExtXMedia::PREFIX) { - track!(s.parse().map(Tag::ExtXMedia)) + (s.parse().map(Tag::ExtXMedia)) } else if s.starts_with(tags::ExtXStreamInf::PREFIX) { - track!(s.parse().map(Tag::ExtXStreamInf)) + (s.parse().map(Tag::ExtXStreamInf)) } else if s.starts_with(tags::ExtXIFrameStreamInf::PREFIX) { - track!(s.parse().map(Tag::ExtXIFrameStreamInf)) + (s.parse().map(Tag::ExtXIFrameStreamInf)) } else if s.starts_with(tags::ExtXSessionData::PREFIX) { - track!(s.parse().map(Tag::ExtXSessionData)) + (s.parse().map(Tag::ExtXSessionData)) } else if s.starts_with(tags::ExtXSessionKey::PREFIX) { - track!(s.parse().map(Tag::ExtXSessionKey)) + (s.parse().map(Tag::ExtXSessionKey)) } else if s.starts_with(tags::ExtXIndependentSegments::PREFIX) { - track!(s.parse().map(Tag::ExtXIndependentSegments)) + (s.parse().map(Tag::ExtXIndependentSegments)) } else if s.starts_with(tags::ExtXStart::PREFIX) { - track!(s.parse().map(Tag::ExtXStart)) + (s.parse().map(Tag::ExtXStart)) } else { - track!(SingleLineString::new(s)).map(Tag::Unknown) + SingleLineString::new(s).map(Tag::Unknown) } } } diff --git a/src/master_playlist.rs b/src/master_playlist.rs index 48a4453..5ce9a38 100644 --- a/src/master_playlist.rs +++ b/src/master_playlist.rs @@ -1,14 +1,15 @@ +use std::collections::HashSet; +use std::fmt; +use std::iter; +use std::str::FromStr; + use crate::line::{Line, Lines, Tag}; use crate::tags::{ ExtM3u, ExtXIFrameStreamInf, ExtXIndependentSegments, ExtXMedia, ExtXSessionData, ExtXSessionKey, ExtXStart, ExtXStreamInf, ExtXVersion, MasterPlaylistTag, }; use crate::types::{ClosedCaptions, MediaType, ProtocolVersion}; -use crate::{Error, ErrorKind, Result}; -use std::collections::HashSet; -use std::fmt; -use std::iter; -use std::str::FromStr; +use crate::{Error, Result}; /// Master playlist builder. #[derive(Debug, Clone)] @@ -71,18 +72,16 @@ impl MasterPlaylistBuilder { pub fn finish(self) -> Result { let required_version = self.required_version(); let specified_version = self.version.unwrap_or(required_version); - track_assert!( - required_version <= specified_version, - ErrorKind::InvalidInput, - "required_version:{}, specified_version:{}", - required_version, - specified_version, - ); - track!(self.validate_stream_inf_tags())?; - track!(self.validate_i_frame_stream_inf_tags())?; - track!(self.validate_session_data_tags())?; - track!(self.validate_session_key_tags())?; + if required_version <= specified_version { + // "required_version:{}, specified_version:{}" + return Err(Error::invalid_input()); + } + + (self.validate_stream_inf_tags())?; + (self.validate_i_frame_stream_inf_tags())?; + (self.validate_session_data_tags())?; + (self.validate_session_key_tags())?; Ok(MasterPlaylist { version_tag: ExtXVersion::new(specified_version), @@ -121,37 +120,25 @@ impl MasterPlaylistBuilder { let mut has_none_closed_captions = false; for t in &self.stream_inf_tags { if let Some(group_id) = t.audio() { - track_assert!( - self.check_media_group(MediaType::Audio, group_id), - ErrorKind::InvalidInput, - "Unmatched audio group: {:?}", - group_id - ); + if !self.check_media_group(MediaType::Audio, group_id) { + return Err(Error::unmatched_group(group_id)); + } } if let Some(group_id) = t.video() { - track_assert!( - self.check_media_group(MediaType::Video, group_id), - ErrorKind::InvalidInput, - "Unmatched video group: {:?}", - group_id - ); + if !self.check_media_group(MediaType::Video, group_id) { + return Err(Error::unmatched_group(group_id)); + } } if let Some(group_id) = t.subtitles() { - track_assert!( - self.check_media_group(MediaType::Subtitles, group_id), - ErrorKind::InvalidInput, - "Unmatched subtitles group: {:?}", - group_id - ); + if !self.check_media_group(MediaType::Subtitles, group_id) { + return Err(Error::unmatched_group(group_id)); + } } match t.closed_captions() { Some(&ClosedCaptions::GroupId(ref group_id)) => { - track_assert!( - self.check_media_group(MediaType::ClosedCaptions, group_id), - ErrorKind::InvalidInput, - "Unmatched closed-captions group: {:?}", - group_id - ); + if !self.check_media_group(MediaType::ClosedCaptions, group_id) { + return Err(Error::unmatched_group(group_id)); + } } Some(&ClosedCaptions::None) => { has_none_closed_captions = true; @@ -160,53 +147,49 @@ impl MasterPlaylistBuilder { } } if has_none_closed_captions { - track_assert!( - self.stream_inf_tags - .iter() - .all(|t| t.closed_captions() == Some(&ClosedCaptions::None)), - ErrorKind::InvalidInput - ); + if !self + .stream_inf_tags + .iter() + .all(|t| t.closed_captions() == Some(&ClosedCaptions::None)) + { + return Err(Error::invalid_input()); + } } + Ok(()) } fn validate_i_frame_stream_inf_tags(&self) -> Result<()> { for t in &self.i_frame_stream_inf_tags { if let Some(group_id) = t.video() { - track_assert!( - self.check_media_group(MediaType::Video, group_id), - ErrorKind::InvalidInput, - "Unmatched video group: {:?}", - group_id - ); + if !self.check_media_group(MediaType::Video, group_id) { + return Err(Error::unmatched_group(group_id)); + } } } + Ok(()) } fn validate_session_data_tags(&self) -> Result<()> { let mut set = HashSet::new(); for t in &self.session_data_tags { - track_assert!( - set.insert((t.data_id(), t.language())), - ErrorKind::InvalidInput, - "Conflict: {}", - t - ); + if !set.insert((t.data_id(), t.language())) { + return Err(Error::custom(format!("Conflict: {}", t))); + } } + Ok(()) } fn validate_session_key_tags(&self) -> Result<()> { let mut set = HashSet::new(); for t in &self.session_key_tags { - track_assert!( - set.insert(t.key()), - ErrorKind::InvalidInput, - "Conflict: {}", - t - ); + if !set.insert(t.key()) { + return Err(Error::custom(format!("Conflict: {}", t))); + } } + Ok(()) } @@ -308,24 +291,29 @@ impl fmt::Display for MasterPlaylist { Ok(()) } } + impl FromStr for MasterPlaylist { type Err = Error; fn from_str(s: &str) -> Result { let mut builder = MasterPlaylistBuilder::new(); for (i, line) in Lines::new(s).enumerate() { - match track!(line)? { + match (line)? { Line::Blank | Line::Comment(_) => {} Line::Tag(tag) => { if i == 0 { - track_assert_eq!(tag, Tag::ExtM3u(ExtM3u), ErrorKind::InvalidInput); + if tag != Tag::ExtM3u(ExtM3u) { + return Err(Error::invalid_input()); + } continue; } match tag { Tag::ExtM3u(_) => { - track_panic!(ErrorKind::InvalidInput); + return Err(Error::invalid_input()); } Tag::ExtXVersion(t) => { - track_assert_eq!(builder.version, None, ErrorKind::InvalidInput); + if builder.version.is_some() { + return Err(Error::invalid_input()); + } builder.version(t.version()); } Tag::ExtInf(_) @@ -341,7 +329,7 @@ impl FromStr for MasterPlaylist { | Tag::ExtXEndList(_) | Tag::ExtXPlaylistType(_) | Tag::ExtXIFramesOnly(_) => { - track_panic!(ErrorKind::InvalidInput, "{}", tag) + return Err(Error::invalid_input()); // TODO: why? } Tag::ExtXMedia(t) => { builder.tag(t); @@ -359,28 +347,29 @@ impl FromStr for MasterPlaylist { builder.tag(t); } Tag::ExtXIndependentSegments(t) => { - track_assert_eq!( - builder.independent_segments_tag, - None, - ErrorKind::InvalidInput - ); + if builder.independent_segments_tag.is_some() { + return Err(Error::invalid_input()); + } builder.tag(t); } Tag::ExtXStart(t) => { - track_assert_eq!(builder.start_tag, None, ErrorKind::InvalidInput); + if builder.start_tag.is_some() { + return Err(Error::invalid_input()); + } builder.tag(t); } Tag::Unknown(_) => { // [6.3.1. General Client Responsibilities] // > ignore any unrecognized tags. + // TODO: collect custom tags } } } Line::Uri(uri) => { - track_panic!(ErrorKind::InvalidInput, "Unexpected URI: {:?}", uri); + return Err(Error::custom(format!("Unexpected URI: {:?}", uri))); } } } - track!(builder.finish()) + builder.finish() } } diff --git a/src/media_playlist.rs b/src/media_playlist.rs index 5eca286..0b09225 100644 --- a/src/media_playlist.rs +++ b/src/media_playlist.rs @@ -1,3 +1,8 @@ +use std::fmt; +use std::iter; +use std::str::FromStr; +use std::time::Duration; + use crate::line::{Line, Lines, Tag}; use crate::media_segment::{MediaSegment, MediaSegmentBuilder}; use crate::tags::{ @@ -6,11 +11,7 @@ use crate::tags::{ MediaPlaylistTag, }; use crate::types::ProtocolVersion; -use crate::{Error, ErrorKind, Result}; -use std::fmt; -use std::iter; -use std::str::FromStr; -use std::time::Duration; +use crate::Error; /// Media playlist builder. #[derive(Debug, Clone)] @@ -89,20 +90,18 @@ impl MediaPlaylistBuilder { } /// Builds a `MediaPlaylist` instance. - pub fn finish(self) -> Result { + pub fn finish(self) -> crate::Result { let required_version = self.required_version(); let specified_version = self.version.unwrap_or(required_version); - track_assert!( - required_version <= specified_version, - ErrorKind::InvalidInput, - "required_version:{}, specified_version:{}", - required_version, - specified_version, - ); + if !(required_version <= specified_version) { + return Err(Error::custom(format!( + "required_version:{}, specified_version:{}", + required_version, specified_version + ))); + } - let target_duration_tag = - track_assert_some!(self.target_duration_tag, ErrorKind::InvalidInput); - track!(self.validate_media_segments(target_duration_tag.duration()))?; + let target_duration_tag = self.target_duration_tag.ok_or(Error::invalid_input())?; + self.validate_media_segments(target_duration_tag.duration())?; Ok(MediaPlaylist { version_tag: ExtXVersion::new(specified_version), @@ -118,7 +117,7 @@ impl MediaPlaylistBuilder { }) } - fn validate_media_segments(&self, target_duration: Duration) -> Result<()> { + fn validate_media_segments(&self, target_duration: Duration) -> crate::Result<()> { let mut last_range_uri = None; for s in &self.segments { // CHECK: `#EXT-X-TARGETDURATION` @@ -129,21 +128,24 @@ impl MediaPlaylistBuilder { Duration::from_secs(segment_duration.as_secs() + 1) }; let max_segment_duration = target_duration + self.options.allowable_excess_duration; - track_assert!( - rounded_segment_duration <= max_segment_duration, - ErrorKind::InvalidInput, - "Too large segment duration: actual={:?}, max={:?}, target_duration={:?}, uri={:?}", - segment_duration, - max_segment_duration, - target_duration, - s.uri() - ); + + if !(rounded_segment_duration <= max_segment_duration) { + return Err(Error::custom(format!( + "Too large segment duration: actual={:?}, max={:?}, target_duration={:?}, uri={:?}", + segment_duration, + max_segment_duration, + target_duration, + s.uri() + ))); + } // CHECK: `#EXT-X-BYTE-RANGE` if let Some(tag) = s.byte_range_tag() { 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); + let last_uri = last_range_uri.ok_or(Error::invalid_input())?; + if last_uri != s.uri() { + return Err(Error::invalid_input()); + } } else { last_range_uri = Some(s.uri()); } @@ -292,8 +294,9 @@ impl fmt::Display for MediaPlaylist { impl FromStr for MediaPlaylist { type Err = Error; - fn from_str(s: &str) -> Result { - track!(MediaPlaylistOptions::new().parse(s)) + + fn from_str(input: &str) -> Result { + MediaPlaylistOptions::new().parse(input) } } @@ -305,7 +308,7 @@ pub struct MediaPlaylistOptions { impl MediaPlaylistOptions { /// Makes a new `MediaPlaylistOptions` with the default settings. - pub fn new() -> Self { + pub const fn new() -> Self { MediaPlaylistOptions { allowable_excess_duration: Duration::from_secs(0), } @@ -327,7 +330,7 @@ impl MediaPlaylistOptions { } /// Parses the given M3U8 text with the specified settings. - pub fn parse(&self, m3u8: &str) -> Result { + pub fn parse(&self, m3u8: &str) -> crate::Result { let mut builder = MediaPlaylistBuilder::new(); builder.options(self.clone()); @@ -335,17 +338,21 @@ impl MediaPlaylistOptions { let mut has_partial_segment = false; let mut has_discontinuity_tag = false; for (i, line) in Lines::new(m3u8).enumerate() { - match track!(line)? { + match (line)? { Line::Blank | Line::Comment(_) => {} Line::Tag(tag) => { if i == 0 { - track_assert_eq!(tag, Tag::ExtM3u(ExtM3u), ErrorKind::InvalidInput); + if tag != Tag::ExtM3u(ExtM3u) { + return Err(Error::invalid_input()); + } continue; } match tag { - Tag::ExtM3u(_) => track_panic!(ErrorKind::InvalidInput), + Tag::ExtM3u(_) => return Err(Error::invalid_input()), Tag::ExtXVersion(t) => { - track_assert_eq!(builder.version, None, ErrorKind::InvalidInput); + if builder.version.is_some() { + return Err(Error::invalid_input()); + } builder.version(t.version()); } Tag::ExtInf(t) => { @@ -378,45 +385,30 @@ impl MediaPlaylistOptions { segment.tag(t); } Tag::ExtXTargetDuration(t) => { - track_assert_eq!( - builder.target_duration_tag, - None, - ErrorKind::InvalidInput - ); builder.tag(t); } Tag::ExtXMediaSequence(t) => { - track_assert_eq!( - builder.media_sequence_tag, - None, - ErrorKind::InvalidInput - ); - track_assert!(builder.segments.is_empty(), ErrorKind::InvalidInput); + if builder.segments.is_empty() { + return Err(Error::invalid_input()); + } builder.tag(t); } Tag::ExtXDiscontinuitySequence(t) => { - track_assert!(builder.segments.is_empty(), ErrorKind::InvalidInput); - track_assert!(!has_discontinuity_tag, ErrorKind::InvalidInput); + if builder.segments.is_empty() { + return Err(Error::invalid_input()); + } + if has_discontinuity_tag { + return Err(Error::invalid_input()); + } builder.tag(t); } Tag::ExtXEndList(t) => { - track_assert_eq!(builder.end_list_tag, None, ErrorKind::InvalidInput); builder.tag(t); } Tag::ExtXPlaylistType(t) => { - track_assert_eq!( - builder.playlist_type_tag, - None, - ErrorKind::InvalidInput - ); builder.tag(t); } Tag::ExtXIFramesOnly(t) => { - track_assert_eq!( - builder.i_frames_only_tag, - None, - ErrorKind::InvalidInput - ); builder.tag(t); } Tag::ExtXMedia(_) @@ -424,18 +416,12 @@ impl MediaPlaylistOptions { | Tag::ExtXIFrameStreamInf(_) | Tag::ExtXSessionData(_) | Tag::ExtXSessionKey(_) => { - track_panic!(ErrorKind::InvalidInput, "{}", tag) + return Err(Error::custom(tag)); } Tag::ExtXIndependentSegments(t) => { - track_assert_eq!( - builder.independent_segments_tag, - None, - ErrorKind::InvalidInput - ); builder.tag(t); } Tag::ExtXStart(t) => { - track_assert_eq!(builder.start_tag, None, ErrorKind::InvalidInput); builder.tag(t); } Tag::Unknown(_) => { @@ -446,14 +432,16 @@ impl MediaPlaylistOptions { } Line::Uri(uri) => { segment.uri(uri); - builder.segment(track!(segment.finish())?); + builder.segment((segment.finish())?); segment = MediaSegmentBuilder::new(); has_partial_segment = false; } } } - track_assert!(!has_partial_segment, ErrorKind::InvalidInput); - track!(builder.finish()) + if has_partial_segment { + return Err(Error::invalid_input()); + } + builder.finish() } } diff --git a/src/media_segment.rs b/src/media_segment.rs index 602b51d..3f45f5b 100644 --- a/src/media_segment.rs +++ b/src/media_segment.rs @@ -1,11 +1,12 @@ +use std::fmt; +use std::iter; + use crate::tags::{ ExtInf, ExtXByteRange, ExtXDateRange, ExtXDiscontinuity, ExtXKey, ExtXMap, ExtXProgramDateTime, MediaSegmentTag, }; use crate::types::{ProtocolVersion, SingleLineString}; -use crate::{ErrorKind, Result}; -use std::fmt; -use std::iter; +use crate::Error; /// Media segment builder. #[derive(Debug, Clone)] @@ -56,9 +57,10 @@ impl MediaSegmentBuilder { } /// Builds a `MediaSegment` instance. - pub fn finish(self) -> Result { - let uri = track_assert_some!(self.uri, ErrorKind::InvalidInput); - let inf_tag = track_assert_some!(self.inf_tag, ErrorKind::InvalidInput); + pub fn finish(self) -> crate::Result { + let uri = self.uri.ok_or(Error::missing_value("self.uri"))?; + let inf_tag = self.inf_tag.ok_or(Error::missing_value("self.inf_tag"))?; + Ok(MediaSegment { key_tags: self.key_tags, map_tag: self.map_tag, diff --git a/src/tags/master_playlist/i_frame_stream_inf.rs b/src/tags/master_playlist/i_frame_stream_inf.rs index f9e38dc..7f9abd3 100644 --- a/src/tags/master_playlist/i_frame_stream_inf.rs +++ b/src/tags/master_playlist/i_frame_stream_inf.rs @@ -7,7 +7,7 @@ use crate::attribute::AttributePairs; use crate::types::{DecimalResolution, HdcpLevel, ProtocolVersion}; use crate::utils::parse_u64; use crate::utils::{quote, tag, unquote}; -use crate::{Error, ErrorKind}; +use crate::Error; /// [4.3.4.3. EXT-X-I-FRAME-STREAM-INF] /// @@ -96,14 +96,14 @@ impl FromStr for ExtXIFrameStreamInf { let attrs = AttributePairs::parse(input); for attr in attrs { - let (key, value) = track!(attr)?; + let (key, value) = attr?; match key { "URI" => uri = Some(unquote(value)), - "BANDWIDTH" => bandwidth = Some(track!(parse_u64(value))?), - "AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(parse_u64(value))?), + "BANDWIDTH" => bandwidth = Some(parse_u64(value)?), + "AVERAGE-BANDWIDTH" => average_bandwidth = Some(parse_u64(value)?), "CODECS" => codecs = Some(unquote(value)), - "RESOLUTION" => resolution = Some(track!(value.parse())?), - "HDCP-LEVEL" => hdcp_level = Some(track!(value.parse())?), + "RESOLUTION" => resolution = Some(value.parse()?), + "HDCP-LEVEL" => hdcp_level = Some(value.parse()?), "VIDEO" => video = Some(unquote(value)), _ => { // [6.3.1. General Client Responsibilities] @@ -112,8 +112,9 @@ impl FromStr for ExtXIFrameStreamInf { } } - let uri = track_assert_some!(uri, ErrorKind::InvalidInput); - let bandwidth = track_assert_some!(bandwidth, ErrorKind::InvalidInput); + let uri = uri.ok_or(Error::missing_value("URI"))?; + let bandwidth = bandwidth.ok_or(Error::missing_value("BANDWIDTH"))?; + Ok(ExtXIFrameStreamInf { uri, bandwidth, diff --git a/src/tags/master_playlist/media.rs b/src/tags/master_playlist/media.rs index 59e2910..40d1e41 100644 --- a/src/tags/master_playlist/media.rs +++ b/src/tags/master_playlist/media.rs @@ -1,9 +1,10 @@ +use std::fmt; +use std::str::FromStr; + use crate::attribute::AttributePairs; use crate::types::{InStreamId, MediaType, ProtocolVersion}; use crate::utils::{parse_yes_or_no, quote, tag, unquote}; -use crate::{Error, ErrorKind}; -use std::fmt; -use std::str::FromStr; +use crate::Error; /// `ExtXMedia` builder. #[derive(Debug, Clone)] @@ -115,21 +116,38 @@ impl ExtXMediaBuilder { /// Builds a `ExtXMedia` instance. 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); + let media_type = self + .media_type + .ok_or(Error::missing_value("self.media_type"))?; + let group_id = self.group_id.ok_or(Error::missing_value("self.group_id"))?; + let name = self.name.ok_or(Error::missing_value("self.name"))?; + if MediaType::ClosedCaptions == media_type { - track_assert_ne!(self.uri, None, ErrorKind::InvalidInput); - track_assert!(self.instream_id.is_some(), ErrorKind::InvalidInput); + if let None = self.uri { + return Err(Error::missing_value("self.uri")); + } + self.instream_id + .ok_or(Error::missing_value("self.instream_id"))?; } else { - track_assert!(self.instream_id.is_none(), ErrorKind::InvalidInput); + if let Some(_) = &self.instream_id { + Err(Error::invalid_input())?; + } } + if self.default && self.autoselect.is_some() { - track_assert_eq!(self.autoselect, Some(true), ErrorKind::InvalidInput); + if let Some(value) = &self.autoselect { + if *value { + Err(Error::invalid_input())?; + } + } } + if MediaType::Subtitles != media_type { - track_assert_eq!(self.forced, None, ErrorKind::InvalidInput); + if self.forced.is_some() { + Err(Error::invalid_input())?; + } } + Ok(ExtXMedia { media_type, uri: self.uri, @@ -315,10 +333,10 @@ impl FromStr for ExtXMedia { let mut builder = ExtXMediaBuilder::new(); let attrs = AttributePairs::parse(input); for attr in attrs { - let (key, value) = track!(attr)?; + let (key, value) = attr?; match key { "TYPE" => { - builder.media_type(track!(value.parse())?); + builder.media_type(value.parse()?); } "URI" => { builder.uri(unquote(value)); @@ -336,13 +354,13 @@ impl FromStr for ExtXMedia { builder.name(unquote(value)); } "DEFAULT" => { - builder.default(track!(parse_yes_or_no(value))?); + builder.default((parse_yes_or_no(value))?); } "AUTOSELECT" => { - builder.autoselect(track!(parse_yes_or_no(value))?); + builder.autoselect((parse_yes_or_no(value))?); } "FORCED" => { - builder.forced(track!(parse_yes_or_no(value))?); + builder.forced((parse_yes_or_no(value))?); } "INSTREAM-ID" => { builder.instream_id(unquote(value).parse()?); @@ -359,7 +377,7 @@ impl FromStr for ExtXMedia { } } } - track!(builder.finish()) + (builder.finish()) } } diff --git a/src/tags/master_playlist/session_data.rs b/src/tags/master_playlist/session_data.rs index 97da40d..f246862 100644 --- a/src/tags/master_playlist/session_data.rs +++ b/src/tags/master_playlist/session_data.rs @@ -6,7 +6,7 @@ use getset::{Getters, MutGetters, Setters}; use crate::attribute::AttributePairs; use crate::types::{ProtocolVersion, SessionData}; use crate::utils::{quote, tag, unquote}; -use crate::{Error, ErrorKind}; +use crate::Error; /// [4.3.4.4. EXT-X-SESSION-DATA] /// @@ -79,7 +79,7 @@ impl FromStr for ExtXSessionData { let attrs = AttributePairs::parse(input); for attr in attrs { - let (key, value) = track!(attr)?; + let (key, value) = attr?; match key { "DATA-ID" => data_id = Some(unquote(value)), "VALUE" => session_value = Some(unquote(value)), @@ -92,15 +92,21 @@ impl FromStr for ExtXSessionData { } } - let data_id = track_assert_some!(data_id, ErrorKind::InvalidInput); - let data = if let Some(value) = session_value { - track_assert_eq!(uri, None, ErrorKind::InvalidInput); - SessionData::Value(value) - } else if let Some(uri) = uri { - SessionData::Uri(uri) - } else { - track_panic!(ErrorKind::InvalidInput); + let data_id = data_id.ok_or(Error::missing_value("DATA-ID"))?; + let data = { + if let Some(value) = session_value { + if uri.is_some() { + return Err(Error::invalid_input()); + } else { + SessionData::Value(value) + } + } else if let Some(uri) = uri { + SessionData::Uri(uri) + } else { + return Err(Error::invalid_input()); + } }; + Ok(ExtXSessionData { data_id, data, diff --git a/src/tags/master_playlist/stream_inf.rs b/src/tags/master_playlist/stream_inf.rs index dbeeef9..e69df8b 100644 --- a/src/tags/master_playlist/stream_inf.rs +++ b/src/tags/master_playlist/stream_inf.rs @@ -7,7 +7,7 @@ use crate::types::{ SingleLineString, }; use crate::utils::{parse_u64, quote, tag, unquote}; -use crate::{Error, ErrorKind}; +use crate::Error; /// [4.3.4.2. EXT-X-STREAM-INF] /// @@ -149,12 +149,12 @@ impl FromStr for ExtXStreamInf { 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! + let first_line = lines.next().ok_or(Error::invalid_input())?; // TODO! + let second_line = lines.next().ok_or(Error::invalid_input())?; // TODO! tag(first_line, Self::PREFIX)?; - let uri = track!(SingleLineString::new(second_line))?; + let uri = SingleLineString::new(second_line)?; let mut bandwidth = None; let mut average_bandwidth = None; @@ -166,20 +166,21 @@ impl FromStr for ExtXStreamInf { let mut video = None; let mut subtitles = None; let mut closed_captions = None; + let attrs = AttributePairs::parse(first_line.split_at(Self::PREFIX.len()).1); for attr in attrs { - let (key, value) = track!(attr)?; + let (key, value) = (attr)?; match key { - "BANDWIDTH" => bandwidth = Some(track!(parse_u64(value))?), - "AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(parse_u64(value))?), + "BANDWIDTH" => bandwidth = Some((parse_u64(value))?), + "AVERAGE-BANDWIDTH" => average_bandwidth = Some((parse_u64(value))?), "CODECS" => codecs = Some(unquote(value)), - "RESOLUTION" => resolution = Some(track!(value.parse())?), - "FRAME-RATE" => frame_rate = Some(track!(value.parse())?), - "HDCP-LEVEL" => hdcp_level = Some(track!(value.parse())?), + "RESOLUTION" => resolution = Some((value.parse())?), + "FRAME-RATE" => frame_rate = Some((value.parse())?), + "HDCP-LEVEL" => hdcp_level = Some((value.parse())?), "AUDIO" => audio = Some(unquote(value)), "VIDEO" => video = Some(unquote(value)), "SUBTITLES" => subtitles = Some(unquote(value)), - "CLOSED-CAPTIONS" => closed_captions = Some(track!(value.parse())?), + "CLOSED-CAPTIONS" => closed_captions = Some((value.parse())?), _ => { // [6.3.1. General Client Responsibilities] // > ignore any attribute/value pair with an unrecognized AttributeName. @@ -187,7 +188,8 @@ impl FromStr for ExtXStreamInf { } } - let bandwidth = track_assert_some!(bandwidth, ErrorKind::InvalidInput); + let bandwidth = bandwidth.ok_or(Error::missing_value("BANDWIDTH"))?; + Ok(ExtXStreamInf { uri, bandwidth, diff --git a/src/tags/media_playlist/end_list.rs b/src/tags/media_playlist/end_list.rs index ddeb5c0..b34b357 100644 --- a/src/tags/media_playlist/end_list.rs +++ b/src/tags/media_playlist/end_list.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.3.4. EXT-X-ENDLIST] /// /// [4.3.3.4. EXT-X-ENDLIST]: https://tools.ietf.org/html/rfc8216#section-4.3.3.4 @@ -26,8 +28,8 @@ impl fmt::Display for ExtXEndList { impl FromStr for ExtXEndList { 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(ExtXEndList) } } diff --git a/src/tags/media_playlist/i_frames_only.rs b/src/tags/media_playlist/i_frames_only.rs index 90db7f4..dfe3abc 100644 --- a/src/tags/media_playlist/i_frames_only.rs +++ b/src/tags/media_playlist/i_frames_only.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.3.6. EXT-X-I-FRAMES-ONLY] /// /// [4.3.3.6. EXT-X-I-FRAMES-ONLY]: https://tools.ietf.org/html/rfc8216#section-4.3.3.6 @@ -27,8 +29,8 @@ impl fmt::Display for ExtXIFramesOnly { impl FromStr for ExtXIFramesOnly { 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(ExtXIFramesOnly) } } diff --git a/src/tags/media_playlist/media_sequence.rs b/src/tags/media_playlist/media_sequence.rs index 47f75bf..1f328f0 100644 --- a/src/tags/media_playlist/media_sequence.rs +++ b/src/tags/media_playlist/media_sequence.rs @@ -1,8 +1,9 @@ -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; +use crate::Error; /// [4.3.3.2. EXT-X-MEDIA-SEQUENCE] /// @@ -40,10 +41,10 @@ impl fmt::Display for ExtXMediaSequence { impl FromStr for ExtXMediaSequence { type Err = 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(ExtXMediaSequence { seq_num }) + fn from_str(input: &str) -> Result { + let seq_num = tag(input, Self::PREFIX)?.parse()?; + + Ok(ExtXMediaSequence::new(seq_num)) } } diff --git a/src/tags/media_playlist/playlist_type.rs b/src/tags/media_playlist/playlist_type.rs index 16d9672..f9911b7 100644 --- a/src/tags/media_playlist/playlist_type.rs +++ b/src/tags/media_playlist/playlist_type.rs @@ -1,8 +1,9 @@ -use crate::types::{PlaylistType, ProtocolVersion}; -use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::FromStr; -use trackable::error::ErrorKindExt; + +use crate::types::{PlaylistType, ProtocolVersion}; +use crate::utils::tag; +use crate::Error; /// [4.3.3.5. EXT-X-PLAYLIST-TYPE] /// @@ -40,10 +41,10 @@ impl fmt::Display for ExtXPlaylistType { impl FromStr for ExtXPlaylistType { type Err = Error; - fn from_str(s: &str) -> Result { - track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); - let playlist_type = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?; - Ok(ExtXPlaylistType { playlist_type }) + fn from_str(input: &str) -> Result { + let input = tag(input, Self::PREFIX)?.parse()?; + + Ok(ExtXPlaylistType::new(input)) } } diff --git a/src/tags/media_playlist/target_duration.rs b/src/tags/media_playlist/target_duration.rs index 2f759b9..32ca2a9 100644 --- a/src/tags/media_playlist/target_duration.rs +++ b/src/tags/media_playlist/target_duration.rs @@ -1,9 +1,10 @@ -use crate::types::ProtocolVersion; -use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::FromStr; use std::time::Duration; -use trackable::error::ErrorKindExt; + +use crate::types::ProtocolVersion; +use crate::utils::tag; +use crate::Error; /// [4.3.3.1. EXT-X-TARGETDURATION] /// @@ -43,11 +44,11 @@ impl fmt::Display for ExtXTargetDuration { impl FromStr for ExtXTargetDuration { type Err = Error; - fn from_str(s: &str) -> Result { - track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); - let duration = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?; + + fn from_str(input: &str) -> Result { + let input = tag(input, Self::PREFIX)?.parse()?; Ok(ExtXTargetDuration { - duration: Duration::from_secs(duration), + duration: Duration::from_secs(input), }) } } diff --git a/src/tags/media_segment/byte_range.rs b/src/tags/media_segment/byte_range.rs index 9afddfa..e7a895a 100644 --- a/src/tags/media_segment/byte_range.rs +++ b/src/tags/media_segment/byte_range.rs @@ -2,10 +2,9 @@ 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 crate::utils::tag; +use crate::Error; /// [4.3.2.2. EXT-X-BYTERANGE] /// @@ -73,26 +72,20 @@ impl fmt::Display for ExtXByteRange { 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 byte_range = s.split_at(Self::PREFIX.len()).1; - let tokens = byte_range.splitn(2, '@').collect::>(); + fn from_str(input: &str) -> Result { + let input = tag(input, Self::PREFIX)?; + + let tokens = input.splitn(2, '@').collect::>(); if tokens.is_empty() { - Err(ErrorKind::InvalidInput)?; + return Err(Error::invalid_input()); } - let length = tokens[0] - .parse() - .map_err(|e| ErrorKind::InvalidInput.cause(e))?; + let length = tokens[0].parse()?; + let start = { let mut result = None; if tokens.len() == 2 { - result = Some( - tokens[1] - .parse() - .map_err(|e| ErrorKind::InvalidInput.cause(e))?, - ); + result = Some(tokens[1].parse()?); } result }; diff --git a/src/tags/media_segment/date_range.rs b/src/tags/media_segment/date_range.rs index dae13ce..8fa6f11 100644 --- a/src/tags/media_segment/date_range.rs +++ b/src/tags/media_segment/date_range.rs @@ -1,12 +1,13 @@ -use crate::attribute::AttributePairs; -use crate::types::{DecimalFloatingPoint, ProtocolVersion}; -use crate::utils::{quote, unquote}; -use crate::{Error, ErrorKind, Result}; use std::collections::BTreeMap; use std::fmt; use std::str::FromStr; use std::time::Duration; +use crate::attribute::AttributePairs; +use crate::types::{DecimalFloatingPoint, ProtocolVersion}; +use crate::utils::{quote, tag, unquote}; +use crate::Error; + /// [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 @@ -79,8 +80,9 @@ impl fmt::Display for ExtXDateRange { impl FromStr for ExtXDateRange { 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 id = None; let mut class = None; @@ -92,28 +94,32 @@ impl FromStr for ExtXDateRange { let mut scte35_out = None; let mut scte35_in = None; let mut end_on_next = false; + let mut client_attributes = BTreeMap::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)?; + let (key, value) = attr?; match key { "ID" => id = Some(unquote(value)), "CLASS" => class = Some(unquote(value)), "START-DATE" => start_date = Some(unquote(value)), "END-DATE" => end_date = Some(unquote(value)), "DURATION" => { - let seconds: DecimalFloatingPoint = track!(value.parse())?; + let seconds: DecimalFloatingPoint = (value.parse())?; duration = Some(seconds.to_duration()); } "PLANNED-DURATION" => { - let seconds: DecimalFloatingPoint = track!(value.parse())?; + let seconds: DecimalFloatingPoint = (value.parse())?; planned_duration = Some(seconds.to_duration()); } "SCTE35-CMD" => scte35_cmd = Some(unquote(value)), "SCTE35-OUT" => scte35_out = Some(unquote(value)), "SCTE35-IN" => scte35_in = Some(unquote(value)), "END-ON-NEXT" => { - track_assert_eq!(value, "YES", ErrorKind::InvalidInput); + if value != "YES" { + return Err(Error::invalid_input()); + } end_on_next = true; } _ => { @@ -127,10 +133,12 @@ impl FromStr for ExtXDateRange { } } - let id = track_assert_some!(id, ErrorKind::InvalidInput); - let start_date = track_assert_some!(start_date, ErrorKind::InvalidInput); + let id = id.ok_or(Error::missing_value("EXT-X-ID"))?; + let start_date = start_date.ok_or(Error::missing_value("EXT-X-START-DATE"))?; if end_on_next { - track_assert!(class.is_some(), ErrorKind::InvalidInput); + if class.is_none() { + return Err(Error::invalid_input()); + } } Ok(ExtXDateRange { id, diff --git a/src/tags/media_segment/inf.rs b/src/tags/media_segment/inf.rs index ac215af..ddf305c 100644 --- a/src/tags/media_segment/inf.rs +++ b/src/tags/media_segment/inf.rs @@ -1,9 +1,10 @@ -use crate::types::{DecimalFloatingPoint, ProtocolVersion, SingleLineString}; -use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::FromStr; use std::time::Duration; -use trackable::error::ErrorKindExt; + +use crate::types::{DecimalFloatingPoint, ProtocolVersion, SingleLineString}; +use crate::utils::tag; +use crate::Error; /// [4.3.2.1. EXTINF] /// @@ -70,18 +71,20 @@ impl fmt::Display for ExtInf { impl FromStr for ExtInf { type Err = Error; - fn from_str(s: &str) -> Result { - track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); - let mut tokens = s.split_at(Self::PREFIX.len()).1.splitn(2, ','); - let seconds: DecimalFloatingPoint = - may_invalid!(tokens.next().expect("Never fails").parse())?; + fn from_str(input: &str) -> Result { + let input = tag(input, Self::PREFIX)?; + let mut tokens = input.splitn(2, ','); + + let seconds: DecimalFloatingPoint = tokens.next().expect("Never fails").parse()?; let duration = seconds.to_duration(); - let title = if let Some(title) = tokens.next() { - Some(track!(SingleLineString::new(title))?) - } else { - None + let title = { + if let Some(title) = tokens.next() { + Some((SingleLineString::new(title))?) + } else { + None + } }; Ok(ExtInf { duration, title }) } diff --git a/src/tags/media_segment/key.rs b/src/tags/media_segment/key.rs index e9dda27..fb47c83 100644 --- a/src/tags/media_segment/key.rs +++ b/src/tags/media_segment/key.rs @@ -1,9 +1,11 @@ -use crate::attribute::AttributePairs; -use crate::types::{DecryptionKey, ProtocolVersion}; -use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::FromStr; +use crate::attribute::AttributePairs; +use crate::types::{DecryptionKey, ProtocolVersion}; +use crate::utils::tag; +use crate::Error; + /// [4.3.2.4. EXT-X-KEY] /// /// [4.3.2.4. EXT-X-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.4 @@ -55,21 +57,19 @@ impl fmt::Display for ExtXKey { impl FromStr for ExtXKey { 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; + fn from_str(input: &str) -> Result { + let input = tag(input, Self::PREFIX)?; - if AttributePairs::parse(suffix).any(|a| a.as_ref().ok() == Some(&("METHOD", "NONE"))) { - for attr in AttributePairs::parse(suffix) { - let (key, _) = track!(attr)?; - track_assert_ne!(key, "URI", ErrorKind::InvalidInput); - track_assert_ne!(key, "IV", ErrorKind::InvalidInput); - track_assert_ne!(key, "KEYFORMAT", ErrorKind::InvalidInput); - track_assert_ne!(key, "KEYFORMATVERSIONS", ErrorKind::InvalidInput); + if AttributePairs::parse(input).any(|a| a.as_ref().ok() == Some(&("METHOD", "NONE"))) { + for attr in AttributePairs::parse(input) { + let (key, _) = attr?; + if key == "URI" || key == "IV" || key == "KEYFORMAT" || key == "KEYFORMATVERSIONS" { + return Err(Error::invalid_input()); + } } Ok(ExtXKey { key: None }) } else { - let key = track!(suffix.parse())?; + let key = input.parse()?; Ok(ExtXKey { key: Some(key) }) } } diff --git a/src/tags/media_segment/map.rs b/src/tags/media_segment/map.rs index 1ef468b..728afa4 100644 --- a/src/tags/media_segment/map.rs +++ b/src/tags/media_segment/map.rs @@ -1,10 +1,11 @@ -use crate::attribute::AttributePairs; -use crate::types::{ByteRange, ProtocolVersion}; -use crate::utils::{quote, unquote}; -use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::FromStr; +use crate::attribute::AttributePairs; +use crate::types::{ByteRange, ProtocolVersion}; +use crate::utils::{quote, tag, unquote}; +use crate::Error; + /// [4.3.2.5. EXT-X-MAP] /// /// [4.3.2.5. EXT-X-MAP]: https://tools.ietf.org/html/rfc8216#section-4.3.2.5 @@ -63,18 +64,18 @@ impl fmt::Display for ExtXMap { impl FromStr for ExtXMap { 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 range = 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)?; + let (key, value) = (attr)?; match key { "URI" => uri = Some(unquote(value)), "BYTERANGE" => { - range = Some(track!(unquote(value).parse())?); + range = Some((unquote(value).parse())?); } _ => { // [6.3.1. General Client Responsibilities] @@ -83,7 +84,7 @@ impl FromStr for ExtXMap { } } - let uri = track_assert_some!(uri, ErrorKind::InvalidInput); + let uri = uri.ok_or(Error::missing_value("EXT-X-URI"))?; Ok(ExtXMap { uri, range }) } } @@ -102,7 +103,8 @@ mod test { 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)); + ExtXMap::from_str(text).unwrap(); + assert_eq!(text.parse().ok(), Some(tag.clone())); assert_eq!(tag.to_string(), text); assert_eq!(tag.requires_version(), ProtocolVersion::V6); diff --git a/src/tags/media_segment/program_date_time.rs b/src/tags/media_segment/program_date_time.rs index 625fdeb..a4be50d 100644 --- a/src/tags/media_segment/program_date_time.rs +++ b/src/tags/media_segment/program_date_time.rs @@ -1,8 +1,10 @@ -use crate::types::{ProtocolVersion, SingleLineString}; -use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::FromStr; +use crate::types::{ProtocolVersion, SingleLineString}; +use crate::utils::tag; +use crate::Error; + /// [4.3.2.6. EXT-X-PROGRAM-DATE-TIME] /// /// [4.3.2.6. EXT-X-PROGRAM-DATE-TIME]: https://tools.ietf.org/html/rfc8216#section-4.3.2.6 @@ -39,11 +41,13 @@ impl fmt::Display for ExtXProgramDateTime { impl FromStr for ExtXProgramDateTime { 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; + fn from_str(input: &str) -> Result { + let input = tag(input, Self::PREFIX)?; + + // TODO: parse with chrono + Ok(ExtXProgramDateTime { - date_time: track!(SingleLineString::new(suffix))?, + date_time: (SingleLineString::new(input))?, }) } } diff --git a/src/tags/mod.rs b/src/tags/mod.rs index b4a72fa..ebad0b7 100644 --- a/src/tags/mod.rs +++ b/src/tags/mod.rs @@ -2,12 +2,6 @@ //! //! [4.3. Playlist Tags]: https://tools.ietf.org/html/rfc8216#section-4.3 -macro_rules! may_invalid { - ($expr:expr) => { - $expr.map_err(|e| track!(Error::from(ErrorKind::InvalidInput.cause(e)))) - }; -} - macro_rules! impl_from { ($to:ident, $from:ident) => { impl From<$from> for $to { diff --git a/src/tags/shared/independent_segments.rs b/src/tags/shared/independent_segments.rs index 2aa3e82..5268dcd 100644 --- a/src/tags/shared/independent_segments.rs +++ b/src/tags/shared/independent_segments.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.5.1. EXT-X-INDEPENDENT-SEGMENTS] /// /// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS]: https://tools.ietf.org/html/rfc8216#section-4.3.5.1 @@ -26,8 +28,8 @@ impl fmt::Display for ExtXIndependentSegments { impl FromStr for ExtXIndependentSegments { 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(ExtXIndependentSegments) } } diff --git a/src/tags/shared/start.rs b/src/tags/shared/start.rs index 560b144..f946992 100644 --- a/src/tags/shared/start.rs +++ b/src/tags/shared/start.rs @@ -1,10 +1,11 @@ -use crate::attribute::AttributePairs; -use crate::types::{ProtocolVersion, SignedDecimalFloatingPoint}; -use crate::utils::parse_yes_or_no; -use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::FromStr; +use crate::attribute::AttributePairs; +use crate::types::{ProtocolVersion, SignedDecimalFloatingPoint}; +use crate::utils::{parse_yes_or_no, tag}; +use crate::Error; + /// [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 @@ -64,17 +65,19 @@ impl fmt::Display for ExtXStart { impl FromStr for ExtXStart { 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 time_offset = None; let mut precise = false; - 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)?; + let (key, value) = (attr)?; match key { - "TIME-OFFSET" => time_offset = Some(track!(value.parse())?), - "PRECISE" => precise = track!(parse_yes_or_no(value))?, + "TIME-OFFSET" => time_offset = Some((value.parse())?), + "PRECISE" => precise = (parse_yes_or_no(value))?, _ => { // [6.3.1. General Client Responsibilities] // > ignore any attribute/value pair with an unrecognized AttributeName. @@ -82,7 +85,8 @@ impl FromStr for ExtXStart { } } - let time_offset = track_assert_some!(time_offset, ErrorKind::InvalidInput); + let time_offset = time_offset.ok_or(Error::missing_value("EXT-X-TIME-OFFSET"))?; + Ok(ExtXStart { time_offset, precise, diff --git a/src/types/byte_range.rs b/src/types/byte_range.rs index 443ab53..09f3535 100644 --- a/src/types/byte_range.rs +++ b/src/types/byte_range.rs @@ -1,10 +1,9 @@ use std::fmt; -use std::str::{self, FromStr}; +use std::str::FromStr; use getset::{Getters, MutGetters, Setters}; -use trackable::error::ErrorKindExt; -use crate::{Error, ErrorKind, Result}; +use crate::Error; /// Byte range. /// @@ -42,23 +41,18 @@ impl fmt::Display for ByteRange { impl FromStr for ByteRange { type Err = Error; - fn from_str(s: &str) -> Result { + fn from_str(s: &str) -> Result { let tokens = s.splitn(2, '@').collect::>(); if tokens.is_empty() { - Err(ErrorKind::InvalidInput)?; + return Err(Error::invalid_input()); } - let length = tokens[0] - .parse() - .map_err(|e| ErrorKind::InvalidInput.cause(e))?; + let length = tokens[0].parse()?; + let start = { let mut result = None; if tokens.len() == 2 { - result = Some( - tokens[1] - .parse() - .map_err(|e| ErrorKind::InvalidInput.cause(e))?, - ); + result = Some(tokens[1].parse()?); } result }; diff --git a/src/types/decimal_floating_point.rs b/src/types/decimal_floating_point.rs index aa555e9..b044cc4 100644 --- a/src/types/decimal_floating_point.rs +++ b/src/types/decimal_floating_point.rs @@ -1,8 +1,8 @@ -use crate::{Error, ErrorKind, Result}; use std::fmt; -use std::str::{self, FromStr}; +use std::str::FromStr; use std::time::Duration; -use trackable::error::ErrorKindExt; + +use crate::Error; /// Non-negative decimal floating-point number. /// @@ -19,9 +19,10 @@ impl DecimalFloatingPoint { /// /// 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(n: f64) -> Result { - track_assert!(n.is_sign_positive(), ErrorKind::InvalidInput); - track_assert!(n.is_finite(), ErrorKind::InvalidInput); + pub fn new(n: f64) -> crate::Result { + if !n.is_sign_positive() || !n.is_finite() { + return Err(Error::invalid_input()); + } Ok(DecimalFloatingPoint(n)) } @@ -59,12 +60,12 @@ impl fmt::Display for DecimalFloatingPoint { impl FromStr for DecimalFloatingPoint { type Err = Error; - fn from_str(s: &str) -> Result { - track_assert!( - s.chars().all(|c| c.is_digit(10) || c == '.'), - ErrorKind::InvalidInput - ); - let n = track!(s.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?; + + fn from_str(input: &str) -> Result { + if !input.chars().all(|c| c.is_digit(10) || c == '.') { + return Err(Error::invalid_input()); + } + let n = input.parse()?; Ok(DecimalFloatingPoint(n)) } } diff --git a/src/types/decimal_resolution.rs b/src/types/decimal_resolution.rs index f323577..67128ff 100644 --- a/src/types/decimal_resolution.rs +++ b/src/types/decimal_resolution.rs @@ -1,7 +1,7 @@ -use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::{self, FromStr}; -use trackable::error::ErrorKindExt; + +use crate::Error; /// Decimal resolution. /// @@ -25,13 +25,15 @@ impl fmt::Display for DecimalResolution { impl FromStr for DecimalResolution { type Err = Error; - fn from_str(s: &str) -> Result { - let mut tokens = s.splitn(2, 'x'); - let width = tokens.next().expect("Never fails"); - let height = track_assert_some!(tokens.next(), ErrorKind::InvalidInput); + + fn from_str(input: &str) -> Result { + let mut tokens = input.splitn(2, 'x'); + let width = tokens.next().ok_or(Error::missing_value("width"))?; + let height = tokens.next().ok_or(Error::missing_value("height"))?; + Ok(DecimalResolution { - width: track!(width.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?, - height: track!(height.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?, + width: width.parse().map_err(|e| Error::custom(e))?, + height: height.parse().map_err(|e| Error::custom(e))?, }) } } diff --git a/src/types/decryption_key.rs b/src/types/decryption_key.rs index 15a6f35..87c7116 100644 --- a/src/types/decryption_key.rs +++ b/src/types/decryption_key.rs @@ -1,9 +1,10 @@ +use std::fmt; +use std::str::{self, FromStr}; + use crate::attribute::AttributePairs; use crate::types::{EncryptionMethod, InitializationVector, ProtocolVersion}; use crate::utils::{quote, unquote}; -use crate::{Error, ErrorKind, Result}; -use std::fmt; -use std::str::{self, FromStr}; +use crate::Error; /// Decryption key. /// @@ -51,19 +52,21 @@ impl fmt::Display for DecryptionKey { impl FromStr for DecryptionKey { type Err = Error; - fn from_str(s: &str) -> Result { + + fn from_str(input: &str) -> Result { let mut method = None; let mut uri = None; let mut iv = None; let mut key_format = None; let mut key_format_versions = None; - let attrs = AttributePairs::parse(s); + + let attrs = AttributePairs::parse(input); for attr in attrs { - let (key, value) = track!(attr)?; + let (key, value) = (attr)?; match key { - "METHOD" => method = Some(track!(value.parse())?), + "METHOD" => method = Some((value.parse())?), "URI" => uri = Some(unquote(value)), - "IV" => iv = Some(track!(value.parse())?), + "IV" => iv = Some((value.parse())?), "KEYFORMAT" => key_format = Some(unquote(value)), "KEYFORMATVERSIONS" => key_format_versions = Some(unquote(value)), _ => { @@ -72,8 +75,10 @@ impl FromStr for DecryptionKey { } } } - let method = track_assert_some!(method, ErrorKind::InvalidInput); - let uri = track_assert_some!(uri, ErrorKind::InvalidInput); + + let method = method.ok_or(Error::missing_value("EXT-X-METHOD"))?; + let uri = uri.ok_or(Error::missing_value("EXT-X-URI"))?; + Ok(DecryptionKey { method, uri, diff --git a/src/types/encryption_method.rs b/src/types/encryption_method.rs index 971e7bb..379e251 100644 --- a/src/types/encryption_method.rs +++ b/src/types/encryption_method.rs @@ -1,6 +1,7 @@ -use crate::{Error, ErrorKind, Result}; use std::fmt; -use std::str::{self, FromStr}; +use std::str::FromStr; + +use crate::Error; /// Encryption method. /// @@ -25,15 +26,15 @@ impl fmt::Display for EncryptionMethod { impl FromStr for EncryptionMethod { type Err = Error; - fn from_str(s: &str) -> Result { - match s { + + fn from_str(input: &str) -> Result { + match input { "AES-128" => Ok(EncryptionMethod::Aes128), "SAMPLE-AES" => Ok(EncryptionMethod::SampleAes), - _ => track_panic!( - ErrorKind::InvalidInput, + _ => Err(Error::custom(format!( "Unknown encryption method: {:?}", - s - ), + input + ))), } } } diff --git a/src/types/hdcp_level.rs b/src/types/hdcp_level.rs index 436178f..fc67f9e 100644 --- a/src/types/hdcp_level.rs +++ b/src/types/hdcp_level.rs @@ -1,6 +1,7 @@ -use crate::{Error, ErrorKind, Result}; use std::fmt; -use std::str::{self, FromStr}; +use std::str::FromStr; + +use crate::Error; /// HDCP level. /// @@ -25,11 +26,12 @@ impl fmt::Display for HdcpLevel { impl FromStr for HdcpLevel { type Err = Error; - fn from_str(s: &str) -> Result { - match s { + + fn from_str(input: &str) -> Result { + match input { "TYPE-0" => Ok(HdcpLevel::Type0), "NONE" => Ok(HdcpLevel::None), - _ => track_panic!(ErrorKind::InvalidInput, "Unknown HDCP level: {:?}", s), + _ => Err(Error::custom(format!("Unknown HDCP level: {:?}", input))), } } } diff --git a/src/types/hexadecimal_sequence.rs b/src/types/hexadecimal_sequence.rs index 930adca..fd6d69e 100644 --- a/src/types/hexadecimal_sequence.rs +++ b/src/types/hexadecimal_sequence.rs @@ -1,8 +1,8 @@ -use crate::{Error, ErrorKind, Result}; use std::fmt; use std::ops::Deref; -use std::str::{self, FromStr}; -use trackable::error::ErrorKindExt; +use std::str::FromStr; + +use crate::Error; /// Hexadecimal sequence. /// @@ -49,20 +49,24 @@ impl fmt::Display for HexadecimalSequence { impl FromStr for HexadecimalSequence { type Err = Error; - fn from_str(s: &str) -> Result { - track_assert!( - s.starts_with("0x") || s.starts_with("0X"), - ErrorKind::InvalidInput - ); - track_assert!(s.len() % 2 == 0, ErrorKind::InvalidInput); - let mut v = Vec::with_capacity(s.len() / 2 - 1); - for c in s.as_bytes().chunks(2).skip(1) { - let d = track!(str::from_utf8(c).map_err(|e| ErrorKind::InvalidInput.cause(e)))?; - let b = - track!(u8::from_str_radix(d, 16).map_err(|e| ErrorKind::InvalidInput.cause(e)))?; - v.push(b); + fn from_str(input: &str) -> Result { + if !(input.starts_with("0x") || input.starts_with("0X")) { + return Err(Error::invalid_input()); } - Ok(HexadecimalSequence(v)) + + if input.len() % 2 != 0 { + return Err(Error::invalid_input()); + } + + let mut result = Vec::with_capacity(input.len() / 2 - 1); + + for c in input.as_bytes().chunks(2).skip(1) { + let d = String::from_utf8(c.to_vec()).map_err(|e| Error::custom(e))?; + let b = u8::from_str_radix(d.as_str(), 16)?; + result.push(b); + } + + Ok(HexadecimalSequence(result)) } } diff --git a/src/types/in_stream_id.rs b/src/types/in_stream_id.rs index 8b4aa56..cdff1f5 100644 --- a/src/types/in_stream_id.rs +++ b/src/types/in_stream_id.rs @@ -1,6 +1,7 @@ -use crate::{Error, ErrorKind, Result}; use std::fmt; -use std::str::{self, FromStr}; +use std::str::FromStr; + +use crate::Error; /// Identifier of a rendition within the segments in a media playlist. /// @@ -87,8 +88,9 @@ impl fmt::Display for InStreamId { impl FromStr for InStreamId { type Err = Error; - fn from_str(s: &str) -> Result { - Ok(match s { + + fn from_str(input: &str) -> Result { + Ok(match input { "CC1" => InStreamId::Cc1, "CC2" => InStreamId::Cc2, "CC3" => InStreamId::Cc3, @@ -156,7 +158,7 @@ impl FromStr for InStreamId { "SERVICE61" => InStreamId::Service61, "SERVICE62" => InStreamId::Service62, "SERVICE63" => InStreamId::Service63, - _ => track_panic!(ErrorKind::InvalidInput, "Unknown instream id: {:?}", s), + _ => return Err(Error::custom(format!("Unknown instream id: {:?}", input))), }) } } diff --git a/src/types/initialization_vector.rs b/src/types/initialization_vector.rs index 9cb64fe..9743f34 100644 --- a/src/types/initialization_vector.rs +++ b/src/types/initialization_vector.rs @@ -1,8 +1,8 @@ -use crate::{Error, ErrorKind, Result}; use std::fmt; use std::ops::Deref; use std::str::{self, FromStr}; -use trackable::error::ErrorKindExt; + +use crate::Error; /// Initialization vector. /// @@ -37,20 +37,22 @@ impl fmt::Display for InitializationVector { impl FromStr for InitializationVector { type Err = Error; - fn from_str(s: &str) -> Result { - track_assert!( - s.starts_with("0x") || s.starts_with("0X"), - ErrorKind::InvalidInput - ); - track_assert_eq!(s.len() - 2, 32, ErrorKind::InvalidInput); + + fn from_str(s: &str) -> Result { + if !(s.starts_with("0x") || s.starts_with("0X")) { + return Err(Error::invalid_input()); + } + if s.len() - 2 != 32 { + return Err(Error::invalid_input()); + } let mut v = [0; 16]; for (i, c) in s.as_bytes().chunks(2).skip(1).enumerate() { - let d = track!(str::from_utf8(c).map_err(|e| ErrorKind::InvalidInput.cause(e)))?; - let b = - track!(u8::from_str_radix(d, 16).map_err(|e| ErrorKind::InvalidInput.cause(e)))?; + let d = str::from_utf8(c).map_err(|e| Error::custom(e))?; + let b = u8::from_str_radix(d, 16).map_err(|e| Error::custom(e))?; v[i] = b; } + Ok(InitializationVector(v)) } } diff --git a/src/types/media_type.rs b/src/types/media_type.rs index 103abde..f47b7d3 100644 --- a/src/types/media_type.rs +++ b/src/types/media_type.rs @@ -1,7 +1,8 @@ -use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::{self, FromStr}; +use crate::Error; + /// Media type. /// /// See: [4.3.4.1. EXT-X-MEDIA] @@ -29,13 +30,16 @@ impl fmt::Display for MediaType { impl FromStr for MediaType { type Err = Error; - fn from_str(s: &str) -> Result { - Ok(match s { + + fn from_str(input: &str) -> Result { + Ok(match input { "AUDIO" => MediaType::Audio, "VIDEO" => MediaType::Video, "SUBTITLES" => MediaType::Subtitles, "CLOSED-CAPTIONS" => MediaType::ClosedCaptions, - _ => track_panic!(ErrorKind::InvalidInput, "Unknown media type: {:?}", s), + _ => { + return Err(Error::invalid_input()); + } }) } } diff --git a/src/types/playlist_type.rs b/src/types/playlist_type.rs index e9dbb00..29efa54 100644 --- a/src/types/playlist_type.rs +++ b/src/types/playlist_type.rs @@ -1,6 +1,7 @@ -use crate::{Error, ErrorKind, Result}; use std::fmt; -use std::str::{self, FromStr}; +use std::str::FromStr; + +use crate::Error; /// Playlist type. /// @@ -25,11 +26,12 @@ impl fmt::Display for PlaylistType { impl FromStr for PlaylistType { type Err = Error; - fn from_str(s: &str) -> Result { - match s { + + fn from_str(input: &str) -> Result { + match input { "EVENT" => Ok(PlaylistType::Event), "VOD" => Ok(PlaylistType::Vod), - _ => track_panic!(ErrorKind::InvalidInput, "Unknown playlist type: {:?}", s), + _ => Err(Error::custom(format!("Unknown playlist type: {:?}", input))), } } } diff --git a/src/types/protocol_version.rs b/src/types/protocol_version.rs index 312707b..626e13e 100644 --- a/src/types/protocol_version.rs +++ b/src/types/protocol_version.rs @@ -1,7 +1,8 @@ -use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::{self, FromStr}; +use crate::Error; + /// [7. Protocol Version Compatibility] /// /// [7. Protocol Version Compatibility]: https://tools.ietf.org/html/rfc8216#section-7 @@ -32,8 +33,9 @@ impl fmt::Display for ProtocolVersion { } impl FromStr for ProtocolVersion { type Err = Error; - fn from_str(s: &str) -> Result { - Ok(match s { + + fn from_str(input: &str) -> Result { + Ok(match input { "1" => ProtocolVersion::V1, "2" => ProtocolVersion::V2, "3" => ProtocolVersion::V3, @@ -41,7 +43,7 @@ impl FromStr for ProtocolVersion { "5" => ProtocolVersion::V5, "6" => ProtocolVersion::V6, "7" => ProtocolVersion::V7, - _ => track_panic!(ErrorKind::InvalidInput, "Unknown protocol version: {:?}", s), + _ => return Err(Error::unknown_protocol_version(input)), }) } } diff --git a/src/types/signed_decimal_floating_point.rs b/src/types/signed_decimal_floating_point.rs index 999d0a2..0c3691c 100644 --- a/src/types/signed_decimal_floating_point.rs +++ b/src/types/signed_decimal_floating_point.rs @@ -1,7 +1,7 @@ -use crate::{Error, ErrorKind, Result}; use std::fmt; use std::str::{self, FromStr}; -use trackable::error::ErrorKindExt; + +use crate::Error; /// Signed decimal floating-point number. /// @@ -18,9 +18,12 @@ impl SignedDecimalFloatingPoint { /// /// The given value must be finite, /// otherwise this function will return an error that has the kind `ErrorKind::InvalidInput`. - pub fn new(n: f64) -> Result { - track_assert!(n.is_finite(), ErrorKind::InvalidInput); - Ok(SignedDecimalFloatingPoint(n)) + pub fn new(n: f64) -> crate::Result { + if !n.is_finite() { + Err(Error::invalid_input()) + } else { + Ok(SignedDecimalFloatingPoint(n)) + } } /// Converts `DecimalFloatingPoint` to `f64`. @@ -45,12 +48,8 @@ impl fmt::Display for SignedDecimalFloatingPoint { impl FromStr for SignedDecimalFloatingPoint { type Err = Error; - fn from_str(s: &str) -> Result { - track_assert!( - s.chars().all(|c| c.is_digit(10) || c == '.' || c == '-'), - ErrorKind::InvalidInput - ); - let n = track!(s.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?; - Ok(SignedDecimalFloatingPoint(n)) + + fn from_str(input: &str) -> Result { + SignedDecimalFloatingPoint::new(input.parse().map_err(Error::parse_float_error)?) } } diff --git a/src/types/single_line_string.rs b/src/types/single_line_string.rs index 3f99d35..fea3dbb 100644 --- a/src/types/single_line_string.rs +++ b/src/types/single_line_string.rs @@ -1,4 +1,4 @@ -use crate::{ErrorKind, Result}; +use crate::Error; use std::fmt; use std::ops::Deref; @@ -17,10 +17,13 @@ impl SingleLineString { /// /// If the given string contains any control characters, /// this function will return an error which has the kind `ErrorKind::InvalidInput`. - pub fn new>(s: T) -> Result { + pub fn new>(s: T) -> crate::Result { let s = s.into(); - track_assert!(!s.chars().any(|c| c.is_control()), ErrorKind::InvalidInput); - Ok(SingleLineString(s)) + if s.chars().any(|c| c.is_control()) { + Err(Error::invalid_input()) + } else { + Ok(SingleLineString(s)) + } } } diff --git a/src/utils.rs b/src/utils.rs index 946dab9..7f557ec 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,23 +1,15 @@ -use crate::{ErrorKind, Result}; -use trackable::error::ErrorKindExt; +use crate::{Error, Result}; pub(crate) fn parse_yes_or_no>(s: T) -> Result { match s.as_ref() { "YES" => Ok(true), "NO" => Ok(false), - _ => track_panic!( - ErrorKind::InvalidInput, - "Unexpected value: {:?}", - s.as_ref() - ), + _ => Err(Error::invalid_input()), } } pub(crate) fn parse_u64>(s: T) -> Result { - let n = track!(s - .as_ref() - .parse() - .map_err(|e| ErrorKind::InvalidInput.cause(e)))?; + let n = s.as_ref().parse().map_err(Error::unknown)?; // TODO: Error::number Ok(n) } @@ -45,13 +37,17 @@ pub(crate) fn quote(value: T) -> String { } /// 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. +/// and return the rest of the input. +/// +/// # Error +/// This function will return `Error::MissingTag`, if the input doesn't start with the tag, that +/// has been passed to this function. 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! + return Err(Error::missing_tag(tag.as_ref(), input)); } let result = input.split_at(tag.as_ref().len()).1; Ok(result)