diff --git a/Cargo.toml b/Cargo.toml index f790b1d..e7703b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ travis-ci = { repository = "sile/hls_m3u8" } codecov = { repository = "sile/hls_m3u8" } [dependencies] -failure = "0.1.5" +thiserror = "1.0" derive_builder = "0.8.0" chrono = "0.4.9" strum = { version = "0.16.0", features = ["derive"] } diff --git a/src/error.rs b/src/error.rs index c64e031..1333ce5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,39 +1,33 @@ use std::fmt; -use failure::{Backtrace, Context, Fail}; +use thiserror::Error; + +use crate::types::ProtocolVersion; /// This crate specific `Result` type. pub type Result = std::result::Result; /// The [`ErrorKind`]. -#[derive(Debug, Fail, Clone, PartialEq, Eq)] -pub enum ErrorKind { - #[fail(display = "ChronoParseError: {}", _0)] - /// An error from the [Chrono](chrono) crate. - ChronoParseError(String), - - #[fail(display = "UnknownError: {}", _0)] - /// An unknown error occured. - UnknownError(String), - - #[fail(display = "A value is missing for the attribute {}", _0)] +#[derive(Debug, Error, Clone, PartialEq)] +enum ErrorKind { /// A required value is missing. + #[error("A value is missing for the attribute {}", _0)] MissingValue(String), - #[fail(display = "Invalid Input")] /// Error for anything. + #[error("Invalid Input")] InvalidInput, - #[fail(display = "ParseIntError: {}", _0)] + #[error("{}", _0)] /// Failed to parse a String to int. - ParseIntError(String), + ParseIntError(::std::num::ParseIntError), - #[fail(display = "ParseFloatError: {}", _0)] + #[error("{}", _0)] /// Failed to parse a String to float. - ParseFloatError(String), + ParseFloatError(::std::num::ParseFloatError), - #[fail(display = "MissingTag: Expected {} at the start of {:?}", tag, input)] /// A tag is missing, that is required at the start of the input. + #[error("Expected `{}` at the start of {:?}", tag, input)] MissingTag { /// The required tag. tag: String, @@ -41,100 +35,98 @@ pub enum ErrorKind { input: String, }, - #[fail(display = "CustomError: {}", _0)] + #[error("{}", _0)] /// A custom error. Custom(String), - #[fail(display = "Unmatched Group: {:?}", _0)] /// Unmatched Group + #[error("Unmatched Group: {:?}", _0)] UnmatchedGroup(String), - #[fail(display = "Unknown Protocol version: {:?}", _0)] /// Unknown m3u8 version. This library supports up to ProtocolVersion 7. + #[error("Unknown protocol version {:?}", _0)] UnknownProtocolVersion(String), - #[fail(display = "IoError: {}", _0)] /// Some io error + #[error("{}", _0)] Io(String), - #[fail( - display = "VersionError: required_version: {:?}, specified_version: {:?}", - _0, _1 - )] /// This error occurs, if there is a ProtocolVersion mismatch. - VersionError(String, String), + #[error("required_version: {:?}, specified_version: {:?}", _0, _1)] + VersionError(ProtocolVersion, ProtocolVersion), - #[fail(display = "BuilderError: {}", _0)] - /// An Error from a Builder. - BuilderError(String), - - #[fail(display = "Missing Attribute: {}", _0)] /// An attribute is missing. + #[error("Missing Attribute: {}", _0)] MissingAttribute(String), - #[fail(display = "Unexpected Attribute: {:?}", _0)] /// An unexpected value. + #[error("Unexpected Attribute: {:?}", _0)] UnexpectedAttribute(String), - #[fail(display = "Unexpected Tag: {:?}", _0)] /// An unexpected tag. + #[error("Unexpected Tag: {:?}", _0)] UnexpectedTag(String), + /// An error from the [`chrono`] crate. + #[error("{}", _0)] + ChronoParseError(chrono::ParseError), + + /// An error from a Builder. + #[error("BuilderError: {}", _0)] + Builder(String), + + #[error("{}", _0)] + Hex(hex::FromHexError), + /// 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")] + #[error("Invalid error")] __Nonexhaustive, } -#[derive(Debug)] /// The Error type of this library. +#[derive(Debug)] pub struct Error { - inner: Context, + inner: ErrorKind, } -impl Fail for Error { - fn cause(&self) -> Option<&dyn Fail> { self.inner.cause() } - - fn backtrace(&self) -> Option<&Backtrace> { self.inner.backtrace() } -} +impl std::error::Error for Error {} 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) -> Self { Self::from(Context::new(kind)) } -} - -impl From> for Error { - fn from(inner: Context) -> Self { Self { inner } } -} - impl Error { + fn new(inner: ErrorKind) -> Self { Self { inner } } + + pub(crate) fn custom(value: T) -> Self { + Self::new(ErrorKind::Custom(value.to_string())) + } + pub(crate) fn missing_value(value: T) -> Self { - Self::from(ErrorKind::MissingValue(value.to_string())) + Self::new(ErrorKind::MissingValue(value.to_string())) } pub(crate) fn unexpected_attribute(value: T) -> Self { - Self::from(ErrorKind::UnexpectedAttribute(value.to_string())) + Self::new(ErrorKind::UnexpectedAttribute(value.to_string())) } pub(crate) fn unexpected_tag(value: T) -> Self { - Self::from(ErrorKind::UnexpectedTag(value.to_string())) + Self::new(ErrorKind::UnexpectedTag(value.to_string())) } - pub(crate) fn invalid_input() -> Self { Self::from(ErrorKind::InvalidInput) } + pub(crate) fn invalid_input() -> Self { Self::new(ErrorKind::InvalidInput) } - pub(crate) fn parse_int_error(value: T) -> Self { - Self::from(ErrorKind::ParseIntError(value.to_string())) + pub(crate) fn parse_int(value: ::std::num::ParseIntError) -> Self { + Self::new(ErrorKind::ParseIntError(value)) } - pub(crate) fn parse_float_error(value: T) -> Self { - Self::from(ErrorKind::ParseFloatError(value.to_string())) + pub(crate) fn parse_float(value: ::std::num::ParseFloatError) -> Self { + Self::new(ErrorKind::ParseFloatError(value)) } pub(crate) fn missing_tag(tag: T, input: U) -> Self @@ -142,76 +134,53 @@ impl Error { T: ToString, U: ToString, { - Self::from(ErrorKind::MissingTag { + Self::new(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())) + Self::new(ErrorKind::UnmatchedGroup(value.to_string())) } pub(crate) fn unknown_protocol_version(value: T) -> Self { - Self::from(ErrorKind::UnknownProtocolVersion(value.to_string())) + Self::new(ErrorKind::UnknownProtocolVersion(value.to_string())) } - pub(crate) fn io(value: T) -> Self { Self::from(ErrorKind::Io(value.to_string())) } + pub(crate) fn io(value: T) -> Self { Self::new(ErrorKind::Io(value.to_string())) } - pub(crate) fn builder_error(value: T) -> Self { - Self::from(ErrorKind::BuilderError(value.to_string())) - } - - pub(crate) fn chrono(value: T) -> Self { - Self::from(ErrorKind::ChronoParseError(value.to_string())) + pub(crate) fn builder(value: T) -> Self { + Self::new(ErrorKind::Builder(value.to_string())) } pub(crate) fn missing_attribute(value: T) -> Self { - Self::from(ErrorKind::MissingAttribute(value.to_string())) + Self::new(ErrorKind::MissingAttribute(value.to_string())) + } + + // third party crates: + pub(crate) fn chrono(value: chrono::format::ParseError) -> Self { + Self::new(ErrorKind::ChronoParseError(value)) + } + + pub(crate) fn hex(value: hex::FromHexError) -> Self { Self::new(ErrorKind::Hex(value)) } + + pub(crate) fn strum(value: strum::ParseError) -> Self { + Self::new(ErrorKind::Custom(value.to_string())) } } +#[doc(hidden)] impl From<::std::num::ParseIntError> for Error { - fn from(value: ::std::num::ParseIntError) -> Self { Self::parse_int_error(value) } + fn from(value: ::std::num::ParseIntError) -> Self { Self::parse_int(value) } } +#[doc(hidden)] impl From<::std::num::ParseFloatError> for Error { - fn from(value: ::std::num::ParseFloatError) -> Self { Self::parse_float_error(value) } -} - -impl From<::std::io::Error> for Error { - fn from(value: ::std::io::Error) -> Self { Self::io(value) } -} - -impl From<::chrono::ParseError> for Error { - fn from(value: ::chrono::ParseError) -> Self { Self::chrono(value) } + fn from(value: ::std::num::ParseFloatError) -> Self { Self::parse_float(value) } } +#[doc(hidden)] impl From<::strum::ParseError> for Error { - fn from(value: ::strum::ParseError) -> Self { - Self::custom(value) // TODO! - } -} - -impl From for Error { - fn from(value: String) -> Self { Self::custom(value) } -} - -impl From<::core::convert::Infallible> for Error { - fn from(_: ::core::convert::Infallible) -> Self { - Self::custom("An Infallible error has been returned! (this should never happen!)") - } -} - -impl From<::hex::FromHexError> for Error { - fn from(value: ::hex::FromHexError) -> Self { - Self::custom(value) // TODO! - } + fn from(value: ::strum::ParseError) -> Self { Self::strum(value) } } diff --git a/src/master_playlist.rs b/src/master_playlist.rs index 04b2d99..513658d 100644 --- a/src/master_playlist.rs +++ b/src/master_playlist.rs @@ -65,16 +65,15 @@ impl MasterPlaylist { /// Returns a Builder for a [`MasterPlaylist`]. /// /// # Example + /// /// ``` /// use hls_m3u8::tags::ExtXStart; /// use hls_m3u8::MasterPlaylist; /// - /// # fn main() -> Result<(), hls_m3u8::Error> { /// MasterPlaylist::builder() /// .start_tag(ExtXStart::new(20.123456)) /// .build()?; - /// # Ok(()) - /// # } + /// # Ok::<(), Box>(()) /// ``` pub fn builder() -> MasterPlaylistBuilder { MasterPlaylistBuilder::default() } @@ -438,7 +437,7 @@ impl FromStr for MasterPlaylist { builder.session_data_tags(session_data_tags); builder.session_key_tags(session_key_tags); - builder.build().map_err(Error::builder_error) + builder.build().map_err(Error::builder) } } diff --git a/src/media_playlist.rs b/src/media_playlist.rs index a334650..4fb97f4 100644 --- a/src/media_playlist.rs +++ b/src/media_playlist.rs @@ -353,7 +353,7 @@ fn parse_media_playlist( Line::Uri(uri) => { segment.uri(uri); segment.keys(available_key_tags.clone()); - segments.push(segment.build().map_err(Error::builder_error)?); + segments.push(segment.build().map_err(Error::builder)?); segment = MediaSegment::builder(); has_partial_segment = false; } @@ -365,7 +365,7 @@ fn parse_media_playlist( } builder.segments(segments); - builder.build().map_err(Error::builder_error) + builder.build().map_err(Error::builder) } impl FromStr for MediaPlaylist { diff --git a/src/tags/basic/m3u.rs b/src/tags/basic/m3u.rs index b1f8592..f930ddc 100644 --- a/src/tags/basic/m3u.rs +++ b/src/tags/basic/m3u.rs @@ -12,18 +12,18 @@ use crate::{Error, RequiredVersion}; /// It is the at the start of every [`Media Playlist`] and [`Master Playlist`]. /// /// # Examples +/// /// Parsing from a [`str`]: +/// /// ``` -/// # use failure::Error; /// # use hls_m3u8::tags::ExtM3u; /// # -/// # fn main() -> Result<(), Error> { /// assert_eq!("#EXTM3U".parse::()?, ExtM3u); -/// # -/// # Ok(()) -/// # } +/// # Ok::<(), Box>(()) /// ``` +/// /// Converting to a [`str`]: +/// /// ``` /// # use hls_m3u8::tags::ExtM3u; /// # diff --git a/src/tags/basic/version.rs b/src/tags/basic/version.rs index e39f079..be37b45 100644 --- a/src/tags/basic/version.rs +++ b/src/tags/basic/version.rs @@ -12,21 +12,18 @@ use crate::{Error, RequiredVersion}; /// It applies to the entire Playlist. /// /// # Examples +/// /// Parsing from a [`str`]: /// ``` -/// # use failure::Error; /// # use hls_m3u8::tags::ExtXVersion; /// # -/// # fn main() -> Result<(), Error> { /// use hls_m3u8::types::ProtocolVersion; /// /// assert_eq!( /// "#EXT-X-VERSION:5".parse::()?, /// ExtXVersion::new(ProtocolVersion::V5) /// ); -/// # -/// # Ok(()) -/// # } +/// # Ok::<(), Box>(()) /// ``` /// Converting to a [`str`]: /// ``` diff --git a/src/tags/master_playlist/i_frame_stream_inf.rs b/src/tags/master_playlist/i_frame_stream_inf.rs index 2e9c9f5..1e2ddec 100644 --- a/src/tags/master_playlist/i_frame_stream_inf.rs +++ b/src/tags/master_playlist/i_frame_stream_inf.rs @@ -85,7 +85,7 @@ impl ExtXIFrameStreamInfBuilder { .uri .clone() .ok_or_else(|| Error::missing_value("frame rate"))?, - stream_inf: self.stream_inf.build().map_err(Error::builder_error)?, + stream_inf: self.stream_inf.build().map_err(Error::builder)?, }) } } diff --git a/src/tags/master_playlist/media.rs b/src/tags/master_playlist/media.rs index f43e73f..f8f65ac 100644 --- a/src/tags/master_playlist/media.rs +++ b/src/tags/master_playlist/media.rs @@ -735,7 +735,7 @@ impl FromStr for ExtXMedia { } } - builder.build().map_err(Error::builder_error) + builder.build().map_err(Error::builder) } } diff --git a/src/tags/master_playlist/stream_inf.rs b/src/tags/master_playlist/stream_inf.rs index d8cc87d..bf6aced 100644 --- a/src/tags/master_playlist/stream_inf.rs +++ b/src/tags/master_playlist/stream_inf.rs @@ -124,7 +124,7 @@ impl ExtXStreamInfBuilder { audio: self.audio.clone(), subtitles: self.subtitles.clone(), closed_captions: self.closed_captions.clone(), - stream_inf: self.stream_inf.build().map_err(Error::builder_error)?, + stream_inf: self.stream_inf.build().map_err(Error::builder)?, }) } } @@ -347,7 +347,7 @@ impl FromStr for ExtXStreamInf { "FRAME-RATE" => frame_rate = Some((value.parse())?), "AUDIO" => audio = Some(unquote(value)), "SUBTITLES" => subtitles = Some(unquote(value)), - "CLOSED-CAPTIONS" => closed_captions = Some(value.parse()?), + "CLOSED-CAPTIONS" => closed_captions = Some(value.parse().unwrap()), _ => {} } } diff --git a/src/tags/media_segment/date_range.rs b/src/tags/media_segment/date_range.rs index 6586f8a..87bca24 100644 --- a/src/tags/media_segment/date_range.rs +++ b/src/tags/media_segment/date_range.rs @@ -719,7 +719,7 @@ impl FromStr for ExtXDateRange { "ID" => id = Some(unquote(value)), "CLASS" => class = Some(unquote(value)), "START-DATE" => start_date = Some(unquote(value)), - "END-DATE" => end_date = Some(unquote(value).parse()?), + "END-DATE" => end_date = Some(unquote(value).parse().map_err(Error::chrono)?), "DURATION" => { duration = Some(Duration::from_secs_f64(value.parse()?)); } @@ -750,7 +750,8 @@ impl FromStr for ExtXDateRange { let id = id.ok_or_else(|| Error::missing_value("ID"))?; let start_date = start_date .ok_or_else(|| Error::missing_value("START-DATE"))? - .parse()?; + .parse() + .map_err(Error::chrono)?; if end_on_next && class.is_none() { return Err(Error::invalid_input()); diff --git a/src/tags/media_segment/program_date_time.rs b/src/tags/media_segment/program_date_time.rs index 5ea34ca..23ccc0b 100644 --- a/src/tags/media_segment/program_date_time.rs +++ b/src/tags/media_segment/program_date_time.rs @@ -114,7 +114,7 @@ impl FromStr for ExtXProgramDateTime { fn from_str(input: &str) -> Result { let input = tag(input, Self::PREFIX)?; - let date_time = DateTime::parse_from_rfc3339(input)?; + let date_time = DateTime::parse_from_rfc3339(input).map_err(Error::chrono)?; Ok(Self::new(date_time)) } } diff --git a/src/types/channels.rs b/src/types/channels.rs index effec06..d56fd01 100644 --- a/src/types/channels.rs +++ b/src/types/channels.rs @@ -80,7 +80,8 @@ impl FromStr for Channels { let channel_number = parameters .first() .ok_or_else(|| Error::missing_attribute("First parameter of channels!"))? - .parse()?; + .parse() + .map_err(Error::parse_int)?; Ok(Self { channel_number, diff --git a/src/types/decimal_floating_point.rs b/src/types/decimal_floating_point.rs index 5e8c2de..af0ac36 100644 --- a/src/types/decimal_floating_point.rs +++ b/src/types/decimal_floating_point.rs @@ -40,7 +40,9 @@ impl DecimalFloatingPoint { impl FromStr for DecimalFloatingPoint { type Err = Error; - fn from_str(input: &str) -> Result { Self::new(input.parse()?) } + fn from_str(input: &str) -> Result { + Self::new(input.parse().map_err(Error::parse_float)?) + } } impl Deref for DecimalFloatingPoint { diff --git a/src/types/decimal_resolution.rs b/src/types/decimal_resolution.rs index 2a4b4ae..50e44c1 100644 --- a/src/types/decimal_resolution.rs +++ b/src/types/decimal_resolution.rs @@ -59,8 +59,8 @@ impl FromStr for DecimalResolution { } Ok(Self { - width: tokens[0].parse()?, - height: tokens[1].parse()?, + width: tokens[0].parse().map_err(Error::parse_int)?, + height: tokens[1].parse().map_err(Error::parse_int)?, }) } } diff --git a/src/types/decryption_key.rs b/src/types/decryption_key.rs index 77d85b6..9a57fa4 100644 --- a/src/types/decryption_key.rs +++ b/src/types/decryption_key.rs @@ -295,11 +295,11 @@ impl FromStr for DecryptionKey { for (key, value) in input.parse::()? { match key.as_str() { - "METHOD" => method = Some(value.parse()?), + "METHOD" => method = Some(value.parse().map_err(Error::strum)?), "URI" => uri = Some(unquote(value)), "IV" => iv = Some(value.parse()?), "KEYFORMAT" => key_format = Some(value.parse()?), - "KEYFORMATVERSIONS" => key_format_versions = Some(value.parse()?), + "KEYFORMATVERSIONS" => key_format_versions = Some(value.parse().unwrap()), _ => { // [6.3.1. General Client Responsibilities] // > ignore any attribute/value pair with an unrecognized diff --git a/src/types/stream_inf.rs b/src/types/stream_inf.rs index 3f5b4b1..4886249 100644 --- a/src/types/stream_inf.rs +++ b/src/types/stream_inf.rs @@ -270,11 +270,15 @@ impl FromStr for StreamInf { for (key, value) in input.parse::()? { match key.as_str() { - "BANDWIDTH" => bandwidth = Some(value.parse::()?), - "AVERAGE-BANDWIDTH" => average_bandwidth = Some(value.parse::()?), + "BANDWIDTH" => bandwidth = Some(value.parse::().map_err(Error::parse_int)?), + "AVERAGE-BANDWIDTH" => { + average_bandwidth = Some(value.parse::().map_err(Error::parse_int)?) + } "CODECS" => codecs = Some(unquote(value)), "RESOLUTION" => resolution = Some(value.parse()?), - "HDCP-LEVEL" => hdcp_level = Some(value.parse()?), + "HDCP-LEVEL" => { + hdcp_level = Some(value.parse::().map_err(Error::strum)?) + } "VIDEO" => video = Some(unquote(value)), _ => { // [6.3.1. General Client Responsibilities] diff --git a/src/types/value.rs b/src/types/value.rs index 8c0d961..98eebe0 100644 --- a/src/types/value.rs +++ b/src/types/value.rs @@ -32,9 +32,10 @@ impl FromStr for Value { fn from_str(input: &str) -> Result { if input.starts_with("0x") || input.starts_with("0X") { - Ok(Self::Hex(hex::decode( - input.trim_start_matches("0x").trim_start_matches("0X"), - )?)) + Ok(Self::Hex( + hex::decode(input.trim_start_matches("0x").trim_start_matches("0X")) + .map_err(Error::hex)?, + )) } else { match input.parse() { Ok(value) => Ok(Self::Float(value)),