1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-06-16 20:10:33 +00:00

switch error implementation #23

closes #23
This commit is contained in:
Luro02 2020-01-23 19:13:26 +01:00
parent 448c331447
commit ac80ac5c9d
No known key found for this signature in database
GPG key ID: B66FD4F74501A9CF
17 changed files with 115 additions and 141 deletions

View file

@ -16,7 +16,7 @@ travis-ci = { repository = "sile/hls_m3u8" }
codecov = { repository = "sile/hls_m3u8" } codecov = { repository = "sile/hls_m3u8" }
[dependencies] [dependencies]
failure = "0.1.5" thiserror = "1.0"
derive_builder = "0.8.0" derive_builder = "0.8.0"
chrono = "0.4.9" chrono = "0.4.9"
strum = { version = "0.16.0", features = ["derive"] } strum = { version = "0.16.0", features = ["derive"] }

View file

@ -1,39 +1,33 @@
use std::fmt; use std::fmt;
use failure::{Backtrace, Context, Fail}; use thiserror::Error;
use crate::types::ProtocolVersion;
/// This crate specific `Result` type. /// This crate specific `Result` type.
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
/// The [`ErrorKind`]. /// The [`ErrorKind`].
#[derive(Debug, Fail, Clone, PartialEq, Eq)] #[derive(Debug, Error, Clone, PartialEq)]
pub enum ErrorKind { 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)]
/// A required value is missing. /// A required value is missing.
#[error("A value is missing for the attribute {}", _0)]
MissingValue(String), MissingValue(String),
#[fail(display = "Invalid Input")]
/// Error for anything. /// Error for anything.
#[error("Invalid Input")]
InvalidInput, InvalidInput,
#[fail(display = "ParseIntError: {}", _0)] #[error("{}", _0)]
/// Failed to parse a String to int. /// 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. /// 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. /// A tag is missing, that is required at the start of the input.
#[error("Expected `{}` at the start of {:?}", tag, input)]
MissingTag { MissingTag {
/// The required tag. /// The required tag.
tag: String, tag: String,
@ -41,100 +35,98 @@ pub enum ErrorKind {
input: String, input: String,
}, },
#[fail(display = "CustomError: {}", _0)] #[error("{}", _0)]
/// A custom error. /// A custom error.
Custom(String), Custom(String),
#[fail(display = "Unmatched Group: {:?}", _0)]
/// Unmatched Group /// Unmatched Group
#[error("Unmatched Group: {:?}", _0)]
UnmatchedGroup(String), UnmatchedGroup(String),
#[fail(display = "Unknown Protocol version: {:?}", _0)]
/// Unknown m3u8 version. This library supports up to ProtocolVersion 7. /// Unknown m3u8 version. This library supports up to ProtocolVersion 7.
#[error("Unknown protocol version {:?}", _0)]
UnknownProtocolVersion(String), UnknownProtocolVersion(String),
#[fail(display = "IoError: {}", _0)]
/// Some io error /// Some io error
#[error("{}", _0)]
Io(String), Io(String),
#[fail(
display = "VersionError: required_version: {:?}, specified_version: {:?}",
_0, _1
)]
/// This error occurs, if there is a ProtocolVersion mismatch. /// 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. /// An attribute is missing.
#[error("Missing Attribute: {}", _0)]
MissingAttribute(String), MissingAttribute(String),
#[fail(display = "Unexpected Attribute: {:?}", _0)]
/// An unexpected value. /// An unexpected value.
#[error("Unexpected Attribute: {:?}", _0)]
UnexpectedAttribute(String), UnexpectedAttribute(String),
#[fail(display = "Unexpected Tag: {:?}", _0)]
/// An unexpected tag. /// An unexpected tag.
#[error("Unexpected Tag: {:?}", _0)]
UnexpectedTag(String), 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. /// Hints that destructuring should not be exhaustive.
/// ///
/// This enum may grow additional variants, so this makes sure clients /// This enum may grow additional variants, so this makes sure clients
/// don't count on exhaustive matching. (Otherwise, adding a new variant /// don't count on exhaustive matching. (Otherwise, adding a new variant
/// could break existing code.) /// could break existing code.)
#[doc(hidden)] #[doc(hidden)]
#[fail(display = "Invalid error")] #[error("Invalid error")]
__Nonexhaustive, __Nonexhaustive,
} }
#[derive(Debug)]
/// The Error type of this library. /// The Error type of this library.
#[derive(Debug)]
pub struct Error { pub struct Error {
inner: Context<ErrorKind>, inner: ErrorKind,
} }
impl Fail for Error { impl std::error::Error for Error {}
fn cause(&self) -> Option<&dyn Fail> { self.inner.cause() }
fn backtrace(&self) -> Option<&Backtrace> { self.inner.backtrace() }
}
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.inner.fmt(f) } fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.inner.fmt(f) }
} }
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Self { Self::from(Context::new(kind)) }
}
impl From<Context<ErrorKind>> for Error {
fn from(inner: Context<ErrorKind>) -> Self { Self { inner } }
}
impl Error { impl Error {
fn new(inner: ErrorKind) -> Self { Self { inner } }
pub(crate) fn custom<T: fmt::Display>(value: T) -> Self {
Self::new(ErrorKind::Custom(value.to_string()))
}
pub(crate) fn missing_value<T: ToString>(value: T) -> Self { pub(crate) fn missing_value<T: ToString>(value: T) -> Self {
Self::from(ErrorKind::MissingValue(value.to_string())) Self::new(ErrorKind::MissingValue(value.to_string()))
} }
pub(crate) fn unexpected_attribute<T: ToString>(value: T) -> Self { pub(crate) fn unexpected_attribute<T: ToString>(value: T) -> Self {
Self::from(ErrorKind::UnexpectedAttribute(value.to_string())) Self::new(ErrorKind::UnexpectedAttribute(value.to_string()))
} }
pub(crate) fn unexpected_tag<T: ToString>(value: T) -> Self { pub(crate) fn unexpected_tag<T: ToString>(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<T: ToString>(value: T) -> Self { pub(crate) fn parse_int(value: ::std::num::ParseIntError) -> Self {
Self::from(ErrorKind::ParseIntError(value.to_string())) Self::new(ErrorKind::ParseIntError(value))
} }
pub(crate) fn parse_float_error<T: ToString>(value: T) -> Self { pub(crate) fn parse_float(value: ::std::num::ParseFloatError) -> Self {
Self::from(ErrorKind::ParseFloatError(value.to_string())) Self::new(ErrorKind::ParseFloatError(value))
} }
pub(crate) fn missing_tag<T, U>(tag: T, input: U) -> Self pub(crate) fn missing_tag<T, U>(tag: T, input: U) -> Self
@ -142,76 +134,53 @@ impl Error {
T: ToString, T: ToString,
U: ToString, U: ToString,
{ {
Self::from(ErrorKind::MissingTag { Self::new(ErrorKind::MissingTag {
tag: tag.to_string(), tag: tag.to_string(),
input: input.to_string(), input: input.to_string(),
}) })
} }
pub(crate) fn unmatched_group<T: ToString>(value: T) -> Self { pub(crate) fn unmatched_group<T: ToString>(value: T) -> Self {
Self::from(ErrorKind::UnmatchedGroup(value.to_string())) Self::new(ErrorKind::UnmatchedGroup(value.to_string()))
}
pub(crate) fn custom<T>(value: T) -> Self
where
T: fmt::Display,
{
Self::from(ErrorKind::Custom(value.to_string()))
} }
pub(crate) fn unknown_protocol_version<T: ToString>(value: T) -> Self { pub(crate) fn unknown_protocol_version<T: ToString>(value: T) -> Self {
Self::from(ErrorKind::UnknownProtocolVersion(value.to_string())) Self::new(ErrorKind::UnknownProtocolVersion(value.to_string()))
} }
pub(crate) fn io<T: ToString>(value: T) -> Self { Self::from(ErrorKind::Io(value.to_string())) } pub(crate) fn io<T: ToString>(value: T) -> Self { Self::new(ErrorKind::Io(value.to_string())) }
pub(crate) fn builder_error<T: ToString>(value: T) -> Self { pub(crate) fn builder<T: ToString>(value: T) -> Self {
Self::from(ErrorKind::BuilderError(value.to_string())) Self::new(ErrorKind::Builder(value.to_string()))
}
pub(crate) fn chrono<T: ToString>(value: T) -> Self {
Self::from(ErrorKind::ChronoParseError(value.to_string()))
} }
pub(crate) fn missing_attribute<T: ToString>(value: T) -> Self { pub(crate) fn missing_attribute<T: ToString>(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 { 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 { impl From<::std::num::ParseFloatError> for Error {
fn from(value: ::std::num::ParseFloatError) -> Self { Self::parse_float_error(value) } fn from(value: ::std::num::ParseFloatError) -> Self { Self::parse_float(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) }
} }
#[doc(hidden)]
impl From<::strum::ParseError> for Error { impl From<::strum::ParseError> for Error {
fn from(value: ::strum::ParseError) -> Self { fn from(value: ::strum::ParseError) -> Self { Self::strum(value) }
Self::custom(value) // TODO!
}
}
impl From<String> 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!
}
} }

View file

@ -65,16 +65,15 @@ impl MasterPlaylist {
/// Returns a Builder for a [`MasterPlaylist`]. /// Returns a Builder for a [`MasterPlaylist`].
/// ///
/// # Example /// # Example
///
/// ``` /// ```
/// use hls_m3u8::tags::ExtXStart; /// use hls_m3u8::tags::ExtXStart;
/// use hls_m3u8::MasterPlaylist; /// use hls_m3u8::MasterPlaylist;
/// ///
/// # fn main() -> Result<(), hls_m3u8::Error> {
/// MasterPlaylist::builder() /// MasterPlaylist::builder()
/// .start_tag(ExtXStart::new(20.123456)) /// .start_tag(ExtXStart::new(20.123456))
/// .build()?; /// .build()?;
/// # Ok(()) /// # Ok::<(), Box<dyn ::std::error::Error>>(())
/// # }
/// ``` /// ```
pub fn builder() -> MasterPlaylistBuilder { MasterPlaylistBuilder::default() } pub fn builder() -> MasterPlaylistBuilder { MasterPlaylistBuilder::default() }
@ -438,7 +437,7 @@ impl FromStr for MasterPlaylist {
builder.session_data_tags(session_data_tags); builder.session_data_tags(session_data_tags);
builder.session_key_tags(session_key_tags); builder.session_key_tags(session_key_tags);
builder.build().map_err(Error::builder_error) builder.build().map_err(Error::builder)
} }
} }

View file

@ -353,7 +353,7 @@ fn parse_media_playlist(
Line::Uri(uri) => { Line::Uri(uri) => {
segment.uri(uri); segment.uri(uri);
segment.keys(available_key_tags.clone()); 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(); segment = MediaSegment::builder();
has_partial_segment = false; has_partial_segment = false;
} }
@ -365,7 +365,7 @@ fn parse_media_playlist(
} }
builder.segments(segments); builder.segments(segments);
builder.build().map_err(Error::builder_error) builder.build().map_err(Error::builder)
} }
impl FromStr for MediaPlaylist { impl FromStr for MediaPlaylist {

View file

@ -12,18 +12,18 @@ use crate::{Error, RequiredVersion};
/// It is the at the start of every [`Media Playlist`] and [`Master Playlist`]. /// It is the at the start of every [`Media Playlist`] and [`Master Playlist`].
/// ///
/// # Examples /// # Examples
///
/// Parsing from a [`str`]: /// Parsing from a [`str`]:
///
/// ``` /// ```
/// # use failure::Error;
/// # use hls_m3u8::tags::ExtM3u; /// # use hls_m3u8::tags::ExtM3u;
/// # /// #
/// # fn main() -> Result<(), Error> {
/// assert_eq!("#EXTM3U".parse::<ExtM3u>()?, ExtM3u); /// assert_eq!("#EXTM3U".parse::<ExtM3u>()?, ExtM3u);
/// # /// # Ok::<(), Box<dyn ::std::error::Error>>(())
/// # Ok(())
/// # }
/// ``` /// ```
///
/// Converting to a [`str`]: /// Converting to a [`str`]:
///
/// ``` /// ```
/// # use hls_m3u8::tags::ExtM3u; /// # use hls_m3u8::tags::ExtM3u;
/// # /// #

View file

@ -12,21 +12,18 @@ use crate::{Error, RequiredVersion};
/// It applies to the entire Playlist. /// It applies to the entire Playlist.
/// ///
/// # Examples /// # Examples
///
/// Parsing from a [`str`]: /// Parsing from a [`str`]:
/// ``` /// ```
/// # use failure::Error;
/// # use hls_m3u8::tags::ExtXVersion; /// # use hls_m3u8::tags::ExtXVersion;
/// # /// #
/// # fn main() -> Result<(), Error> {
/// use hls_m3u8::types::ProtocolVersion; /// use hls_m3u8::types::ProtocolVersion;
/// ///
/// assert_eq!( /// assert_eq!(
/// "#EXT-X-VERSION:5".parse::<ExtXVersion>()?, /// "#EXT-X-VERSION:5".parse::<ExtXVersion>()?,
/// ExtXVersion::new(ProtocolVersion::V5) /// ExtXVersion::new(ProtocolVersion::V5)
/// ); /// );
/// # /// # Ok::<(), Box<dyn ::std::error::Error>>(())
/// # Ok(())
/// # }
/// ``` /// ```
/// Converting to a [`str`]: /// Converting to a [`str`]:
/// ``` /// ```

View file

@ -85,7 +85,7 @@ impl ExtXIFrameStreamInfBuilder {
.uri .uri
.clone() .clone()
.ok_or_else(|| Error::missing_value("frame rate"))?, .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)?,
}) })
} }
} }

View file

@ -735,7 +735,7 @@ impl FromStr for ExtXMedia {
} }
} }
builder.build().map_err(Error::builder_error) builder.build().map_err(Error::builder)
} }
} }

View file

@ -124,7 +124,7 @@ impl ExtXStreamInfBuilder {
audio: self.audio.clone(), audio: self.audio.clone(),
subtitles: self.subtitles.clone(), subtitles: self.subtitles.clone(),
closed_captions: self.closed_captions.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())?), "FRAME-RATE" => frame_rate = Some((value.parse())?),
"AUDIO" => audio = Some(unquote(value)), "AUDIO" => audio = Some(unquote(value)),
"SUBTITLES" => subtitles = Some(unquote(value)), "SUBTITLES" => subtitles = Some(unquote(value)),
"CLOSED-CAPTIONS" => closed_captions = Some(value.parse()?), "CLOSED-CAPTIONS" => closed_captions = Some(value.parse().unwrap()),
_ => {} _ => {}
} }
} }

View file

@ -719,7 +719,7 @@ impl FromStr for ExtXDateRange {
"ID" => id = Some(unquote(value)), "ID" => id = Some(unquote(value)),
"CLASS" => class = Some(unquote(value)), "CLASS" => class = Some(unquote(value)),
"START-DATE" => start_date = 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" => {
duration = Some(Duration::from_secs_f64(value.parse()?)); 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 id = id.ok_or_else(|| Error::missing_value("ID"))?;
let start_date = start_date let start_date = start_date
.ok_or_else(|| Error::missing_value("START-DATE"))? .ok_or_else(|| Error::missing_value("START-DATE"))?
.parse()?; .parse()
.map_err(Error::chrono)?;
if end_on_next && class.is_none() { if end_on_next && class.is_none() {
return Err(Error::invalid_input()); return Err(Error::invalid_input());

View file

@ -114,7 +114,7 @@ impl FromStr for ExtXProgramDateTime {
fn from_str(input: &str) -> Result<Self, Self::Err> { fn from_str(input: &str) -> Result<Self, Self::Err> {
let input = tag(input, Self::PREFIX)?; 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)) Ok(Self::new(date_time))
} }
} }

View file

@ -80,7 +80,8 @@ impl FromStr for Channels {
let channel_number = parameters let channel_number = parameters
.first() .first()
.ok_or_else(|| Error::missing_attribute("First parameter of channels!"))? .ok_or_else(|| Error::missing_attribute("First parameter of channels!"))?
.parse()?; .parse()
.map_err(Error::parse_int)?;
Ok(Self { Ok(Self {
channel_number, channel_number,

View file

@ -40,7 +40,9 @@ impl DecimalFloatingPoint {
impl FromStr for DecimalFloatingPoint { impl FromStr for DecimalFloatingPoint {
type Err = Error; type Err = Error;
fn from_str(input: &str) -> Result<Self, Self::Err> { Self::new(input.parse()?) } fn from_str(input: &str) -> Result<Self, Self::Err> {
Self::new(input.parse().map_err(Error::parse_float)?)
}
} }
impl Deref for DecimalFloatingPoint { impl Deref for DecimalFloatingPoint {

View file

@ -59,8 +59,8 @@ impl FromStr for DecimalResolution {
} }
Ok(Self { Ok(Self {
width: tokens[0].parse()?, width: tokens[0].parse().map_err(Error::parse_int)?,
height: tokens[1].parse()?, height: tokens[1].parse().map_err(Error::parse_int)?,
}) })
} }
} }

View file

@ -295,11 +295,11 @@ impl FromStr for DecryptionKey {
for (key, value) in input.parse::<AttributePairs>()? { for (key, value) in input.parse::<AttributePairs>()? {
match key.as_str() { match key.as_str() {
"METHOD" => method = Some(value.parse()?), "METHOD" => method = Some(value.parse().map_err(Error::strum)?),
"URI" => uri = Some(unquote(value)), "URI" => uri = Some(unquote(value)),
"IV" => iv = Some(value.parse()?), "IV" => iv = Some(value.parse()?),
"KEYFORMAT" => key_format = 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] // [6.3.1. General Client Responsibilities]
// > ignore any attribute/value pair with an unrecognized // > ignore any attribute/value pair with an unrecognized

View file

@ -270,11 +270,15 @@ impl FromStr for StreamInf {
for (key, value) in input.parse::<AttributePairs>()? { for (key, value) in input.parse::<AttributePairs>()? {
match key.as_str() { match key.as_str() {
"BANDWIDTH" => bandwidth = Some(value.parse::<u64>()?), "BANDWIDTH" => bandwidth = Some(value.parse::<u64>().map_err(Error::parse_int)?),
"AVERAGE-BANDWIDTH" => average_bandwidth = Some(value.parse::<u64>()?), "AVERAGE-BANDWIDTH" => {
average_bandwidth = Some(value.parse::<u64>().map_err(Error::parse_int)?)
}
"CODECS" => codecs = Some(unquote(value)), "CODECS" => codecs = Some(unquote(value)),
"RESOLUTION" => resolution = Some(value.parse()?), "RESOLUTION" => resolution = Some(value.parse()?),
"HDCP-LEVEL" => hdcp_level = Some(value.parse()?), "HDCP-LEVEL" => {
hdcp_level = Some(value.parse::<HdcpLevel>().map_err(Error::strum)?)
}
"VIDEO" => video = Some(unquote(value)), "VIDEO" => video = Some(unquote(value)),
_ => { _ => {
// [6.3.1. General Client Responsibilities] // [6.3.1. General Client Responsibilities]

View file

@ -32,9 +32,10 @@ impl FromStr for Value {
fn from_str(input: &str) -> Result<Self, Self::Err> { fn from_str(input: &str) -> Result<Self, Self::Err> {
if input.starts_with("0x") || input.starts_with("0X") { if input.starts_with("0x") || input.starts_with("0X") {
Ok(Self::Hex(hex::decode( Ok(Self::Hex(
input.trim_start_matches("0x").trim_start_matches("0X"), hex::decode(input.trim_start_matches("0x").trim_start_matches("0X"))
)?)) .map_err(Error::hex)?,
))
} else { } else {
match input.parse() { match input.parse() {
Ok(value) => Ok(Self::Float(value)), Ok(value) => Ok(Self::Float(value)),