From 25f9691c750d3fcaf5ac0fdb94f2f0efca1659ca Mon Sep 17 00:00:00 2001 From: Luro02 <24826124+Luro02@users.noreply.github.com> Date: Fri, 14 Feb 2020 13:01:42 +0100 Subject: [PATCH] improve error --- Cargo.toml | 1 + src/error.rs | 159 ++++++++++++------ .../media_playlist/discontinuity_sequence.rs | 12 +- src/tags/media_playlist/media_sequence.rs | 4 +- src/tags/media_playlist/target_duration.rs | 4 +- src/tags/media_segment/date_range.rs | 8 +- src/tags/media_segment/inf.rs | 8 +- src/types/byte_range.rs | 9 +- src/types/channels.rs | 8 +- src/types/float.rs | 2 +- src/types/resolution.rs | 4 +- src/types/stream_data.rs | 14 +- src/types/ufloat.rs | 2 +- 13 files changed, 168 insertions(+), 67 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 135db77..ff1ef16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ hex = "0.4" shorthand = "0.1" strum = { version = "0.17", features = ["derive"] } thiserror = "1.0" +backtrace = { version = "0.3", features = ["std"], optional = true } [dev-dependencies] pretty_assertions = "0.6" diff --git a/src/error.rs b/src/error.rs index f86caf4..bba33d1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,9 @@ use std::fmt; +#[cfg(feature = "backtrace")] +use backtrace::Backtrace; use thiserror::Error; + //use crate::types::ProtocolVersion; /// This crate specific `Result` type. @@ -9,19 +12,25 @@ pub type Result = std::result::Result; #[derive(Debug, Error, Clone, PartialEq)] #[non_exhaustive] enum ErrorKind { - #[error("a value is missing for the attribute {}", _0)] - MissingValue(String), + #[error("a value is missing for the attribute {value}")] + MissingValue { value: String }, #[error("invalid input")] InvalidInput, - #[error("{}", _0)] - ParseIntError(::std::num::ParseIntError), + #[error("{source}: {input:?}")] + ParseIntError { + input: String, + source: ::std::num::ParseIntError, + }, - #[error("{}", _0)] - ParseFloatError(::std::num::ParseFloatError), + #[error("{source}: {input:?}")] + ParseFloatError { + input: String, + source: ::std::num::ParseFloatError, + }, - #[error("expected `{}` at the start of {:?}", tag, input)] + #[error("expected `{tag}` at the start of {input:?}")] MissingTag { /// The required tag. tag: String, @@ -29,41 +38,46 @@ enum ErrorKind { input: String, }, - #[error("{}", _0)] + #[error("{0}")] Custom(String), - #[error("unmatched group: {:?}", _0)] + #[error("unmatched group: {0:?}")] UnmatchedGroup(String), - #[error("unknown protocol version {:?}", _0)] + #[error("unknown protocol version {0:?}")] UnknownProtocolVersion(String), // #[error("required_version: {:?}, specified_version: {:?}", _0, _1)] // VersionError(ProtocolVersion, ProtocolVersion), - #[error("missing attribute: {}", _0)] - MissingAttribute(String), + #[error("missing attribute: {attribute:?}")] + MissingAttribute { attribute: String }, - #[error("unexpected attribute: {:?}", _0)] - UnexpectedAttribute(String), + #[error("unexpected attribute: {attribute:?}")] + UnexpectedAttribute { attribute: String }, - #[error("unexpected tag: {:?}", _0)] - UnexpectedTag(String), + #[error("unexpected tag: {tag:?}")] + UnexpectedTag { tag: String }, - #[error("{}", _0)] - ChronoParseError(chrono::ParseError), + #[error("{source}")] + Chrono { source: chrono::ParseError }, - #[error("builder error: {}", _0)] - Builder(String), + #[error("builder error: {message}")] + Builder { message: String }, - #[doc(hidden)] - #[error("{}", _0)] - Hex(hex::FromHexError), + #[error("{source}")] + Hex { source: hex::FromHexError }, } /// The Error type of this library. #[derive(Debug)] pub struct Error { inner: ErrorKind, + #[cfg(feature = "backtrace")] + backtrace: Backtrace, +} + +impl PartialEq for Error { + fn eq(&self, other: &Self) -> bool { self.inner == other.inner } } impl std::error::Error for Error {} @@ -74,32 +88,53 @@ impl fmt::Display for Error { #[allow(clippy::needless_pass_by_value)] impl Error { - const fn new(inner: ErrorKind) -> Self { Self { inner } } + fn new(inner: ErrorKind) -> Self { + Self { + inner, + #[cfg(feature = "backtrace")] + backtrace: Backtrace::new(), + } + } pub(crate) fn custom(value: T) -> Self { Self::new(ErrorKind::Custom(value.to_string())) } pub(crate) fn missing_value(value: T) -> Self { - Self::new(ErrorKind::MissingValue(value.to_string())) + Self::new(ErrorKind::MissingValue { + value: value.to_string(), + }) } pub(crate) fn unexpected_attribute(value: T) -> Self { - Self::new(ErrorKind::UnexpectedAttribute(value.to_string())) + Self::new(ErrorKind::UnexpectedAttribute { + attribute: value.to_string(), + }) } pub(crate) fn unexpected_tag(value: T) -> Self { - Self::new(ErrorKind::UnexpectedTag(value.to_string())) + Self::new(ErrorKind::UnexpectedTag { + tag: value.to_string(), + }) } - pub(crate) const fn invalid_input() -> Self { Self::new(ErrorKind::InvalidInput) } + pub(crate) fn invalid_input() -> Self { Self::new(ErrorKind::InvalidInput) } - pub(crate) fn parse_int(value: ::std::num::ParseIntError) -> Self { - Self::new(ErrorKind::ParseIntError(value)) + pub(crate) fn parse_int(input: T, source: ::std::num::ParseIntError) -> Self { + Self::new(ErrorKind::ParseIntError { + input: input.to_string(), + source, + }) } - pub(crate) fn parse_float(value: ::std::num::ParseFloatError) -> Self { - Self::new(ErrorKind::ParseFloatError(value)) + pub(crate) fn parse_float( + input: T, + source: ::std::num::ParseFloatError, + ) -> Self { + Self::new(ErrorKind::ParseFloatError { + input: input.to_string(), + source, + }) } pub(crate) fn missing_tag(tag: T, input: U) -> Self @@ -122,36 +157,66 @@ impl Error { } pub(crate) fn builder(value: T) -> Self { - Self::new(ErrorKind::Builder(value.to_string())) + Self::new(ErrorKind::Builder { + message: value.to_string(), + }) } pub(crate) fn missing_attribute(value: T) -> Self { - Self::new(ErrorKind::MissingAttribute(value.to_string())) + Self::new(ErrorKind::MissingAttribute { + attribute: value.to_string(), + }) } // third party crates: - pub(crate) fn chrono(value: chrono::format::ParseError) -> Self { - Self::new(ErrorKind::ChronoParseError(value)) + pub(crate) fn chrono(source: chrono::format::ParseError) -> Self { + Self::new(ErrorKind::Chrono { source }) } - pub(crate) fn hex(value: hex::FromHexError) -> Self { Self::new(ErrorKind::Hex(value)) } + pub(crate) fn hex(source: hex::FromHexError) -> Self { + // + Self::new(ErrorKind::Hex { source }) + } 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(value) } -} - -#[doc(hidden)] -impl From<::std::num::ParseFloatError> for Error { - 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::strum(value) } } + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn test_parse_float_error() { + assert_eq!( + Error::parse_float( + "1.x234", + "1.x234" + .parse::() + .expect_err("this should not parse as a float!") + ) + .to_string(), + "invalid float literal: \"1.x234\"".to_string() + ); + } + + #[test] + fn test_parse_int_error() { + assert_eq!( + Error::parse_int( + "1x", + "1x".parse::() + .expect_err("this should not parse as an usize!") + ) + .to_string(), + "invalid digit found in string: \"1x\"".to_string() + ); + } +} diff --git a/src/tags/media_playlist/discontinuity_sequence.rs b/src/tags/media_playlist/discontinuity_sequence.rs index cf71520..b1f777b 100644 --- a/src/tags/media_playlist/discontinuity_sequence.rs +++ b/src/tags/media_playlist/discontinuity_sequence.rs @@ -5,6 +5,7 @@ use shorthand::ShortHand; use crate::types::ProtocolVersion; use crate::utils::tag; +use crate::Error; use crate::RequiredVersion; /// # [4.4.3.3. EXT-X-DISCONTINUITY-SEQUENCE] @@ -62,10 +63,12 @@ impl fmt::Display for ExtXDiscontinuitySequence { } impl FromStr for ExtXDiscontinuitySequence { - type Err = crate::Error; + type Err = Error; fn from_str(input: &str) -> Result { - let seq_num = tag(input, Self::PREFIX)?.parse()?; + let input = tag(input, Self::PREFIX)?; + let seq_num = input.parse().map_err(|e| Error::parse_int(input, e))?; + Ok(Self::new(seq_num)) } } @@ -97,6 +100,11 @@ mod test { ExtXDiscontinuitySequence::new(123), "#EXT-X-DISCONTINUITY-SEQUENCE:123".parse().unwrap() ); + + assert_eq!( + ExtXDiscontinuitySequence::from_str("#EXT-X-DISCONTINUITY-SEQUENCE:12A"), + Err(Error::parse_int("12A", "12A".parse::().expect_err(""))) + ); } #[test] diff --git a/src/tags/media_playlist/media_sequence.rs b/src/tags/media_playlist/media_sequence.rs index dfac8b7..0536af6 100644 --- a/src/tags/media_playlist/media_sequence.rs +++ b/src/tags/media_playlist/media_sequence.rs @@ -72,7 +72,9 @@ impl FromStr for ExtXMediaSequence { type Err = Error; fn from_str(input: &str) -> Result { - let seq_num = tag(input, Self::PREFIX)?.parse()?; + let input = tag(input, Self::PREFIX)?; + let seq_num = input.parse().map_err(|e| Error::parse_int(input, e))?; + Ok(Self::new(seq_num)) } } diff --git a/src/tags/media_playlist/target_duration.rs b/src/tags/media_playlist/target_duration.rs index 16c631b..4c548ee 100644 --- a/src/tags/media_playlist/target_duration.rs +++ b/src/tags/media_playlist/target_duration.rs @@ -68,7 +68,9 @@ impl FromStr for ExtXTargetDuration { type Err = Error; fn from_str(input: &str) -> Result { - let input = tag(input, Self::PREFIX)?.parse()?; + let input = tag(input, Self::PREFIX)?; + let input = input.parse().map_err(|e| Error::parse_int(input, e))?; + Ok(Self::new(Duration::from_secs(input))) } } diff --git a/src/tags/media_segment/date_range.rs b/src/tags/media_segment/date_range.rs index f357c9a..3d0f129 100644 --- a/src/tags/media_segment/date_range.rs +++ b/src/tags/media_segment/date_range.rs @@ -211,10 +211,14 @@ impl FromStr for ExtXDateRange { "START-DATE" => start_date = Some(unquote(value)), "END-DATE" => end_date = Some(unquote(value).parse().map_err(Error::chrono)?), "DURATION" => { - duration = Some(Duration::from_secs_f64(value.parse()?)); + duration = Some(Duration::from_secs_f64( + value.parse().map_err(|e| Error::parse_float(value, e))?, + )); } "PLANNED-DURATION" => { - planned_duration = Some(Duration::from_secs_f64(value.parse()?)); + planned_duration = Some(Duration::from_secs_f64( + value.parse().map_err(|e| Error::parse_float(value, e))?, + )); } "SCTE35-CMD" => scte35_cmd = Some(unquote(value)), "SCTE35-OUT" => scte35_out = Some(unquote(value)), diff --git a/src/tags/media_segment/inf.rs b/src/tags/media_segment/inf.rs index 8d47376..89eb958 100644 --- a/src/tags/media_segment/inf.rs +++ b/src/tags/media_segment/inf.rs @@ -153,7 +153,13 @@ impl FromStr for ExtInf { fn from_str(input: &str) -> Result { let mut input = tag(input, Self::PREFIX)?.splitn(2, ','); - let duration = Duration::from_secs_f64(input.next().unwrap().parse()?); + let duration = input.next().unwrap(); + let duration = Duration::from_secs_f64( + duration + .parse() + .map_err(|e| Error::parse_float(duration, e))?, + ); + let title = input .next() .map(str::trim) diff --git a/src/types/byte_range.rs b/src/types/byte_range.rs index 7d07045..d5018e2 100644 --- a/src/types/byte_range.rs +++ b/src/types/byte_range.rs @@ -79,10 +79,13 @@ impl FromStr for ByteRange { let length = input .next() - .ok_or_else(|| Error::custom("missing length for #EXT-X-BYTERANGE")) - .and_then(|s| s.parse().map_err(Error::parse_int))?; + .ok_or_else(|| Error::custom("missing length"))?; + let length = length.parse().map_err(|e| Error::parse_int(length, e))?; - let start = input.next().map(str::parse).transpose()?; + let start = input + .next() + .map(|v| v.parse().map_err(|e| Error::parse_int(v, e))) + .transpose()?; Ok(Self::new(length, start)) } diff --git a/src/types/channels.rs b/src/types/channels.rs index 56c1520..2f1630c 100644 --- a/src/types/channels.rs +++ b/src/types/channels.rs @@ -85,11 +85,11 @@ impl FromStr for Channels { fn from_str(input: &str) -> Result { let mut parameters = input.split('/'); - let channel_number = parameters + let param_1 = parameters .next() - .ok_or_else(|| Error::missing_attribute("first parameter of channels"))? - .parse() - .map_err(Error::parse_int)?; + .ok_or_else(|| Error::missing_attribute("first parameter of channels"))?; + + let channel_number = param_1.parse().map_err(|e| Error::parse_int(param_1, e))?; Ok(Self { channel_number, diff --git a/src/types/float.rs b/src/types/float.rs index 48d1e26..d8c8ed9 100644 --- a/src/types/float.rs +++ b/src/types/float.rs @@ -59,7 +59,7 @@ impl FromStr for Float { type Err = Error; fn from_str(input: &str) -> Result { - let float = f32::from_str(input).map_err(Error::parse_float)?; + let float = f32::from_str(input).map_err(|e| Error::parse_float(input, e))?; Self::try_from(float) } } diff --git a/src/types/resolution.rs b/src/types/resolution.rs index 26ad26e..2e8cb67 100644 --- a/src/types/resolution.rs +++ b/src/types/resolution.rs @@ -41,12 +41,12 @@ impl FromStr for Resolution { let width = input .next() .ok_or_else(|| Error::custom("missing width for `Resolution` or an invalid input")) - .and_then(|v| v.parse().map_err(Error::parse_int))?; + .and_then(|v| v.parse().map_err(|e| Error::parse_int(v, e)))?; let height = input .next() .ok_or_else(|| Error::custom("missing height for `Resolution` or an invalid input")) - .and_then(|v| v.parse().map_err(Error::parse_int))?; + .and_then(|v| v.parse().map_err(|e| Error::parse_int(v, e)))?; Ok(Self { width, height }) } diff --git a/src/types/stream_data.rs b/src/types/stream_data.rs index cb4e7bd..8aad849 100644 --- a/src/types/stream_data.rs +++ b/src/types/stream_data.rs @@ -283,9 +283,19 @@ impl FromStr for StreamData { for (key, value) in AttributePairs::new(input) { match key { - "BANDWIDTH" => bandwidth = Some(value.parse::().map_err(Error::parse_int)?), + "BANDWIDTH" => { + bandwidth = Some( + value + .parse::() + .map_err(|e| Error::parse_int(value, e))?, + ); + } "AVERAGE-BANDWIDTH" => { - average_bandwidth = Some(value.parse::().map_err(Error::parse_int)?) + average_bandwidth = Some( + value + .parse::() + .map_err(|e| Error::parse_int(value, e))?, + ) } "CODECS" => codecs = Some(unquote(value)), "RESOLUTION" => resolution = Some(value.parse()?), diff --git a/src/types/ufloat.rs b/src/types/ufloat.rs index 1313fcc..3eb2f44 100644 --- a/src/types/ufloat.rs +++ b/src/types/ufloat.rs @@ -63,7 +63,7 @@ impl FromStr for UFloat { type Err = Error; fn from_str(input: &str) -> Result { - let float = f32::from_str(input).map_err(Error::parse_float)?; + let float = f32::from_str(input).map_err(|e| Error::parse_float(input, e))?; Self::try_from(float) } }