Skip to content

Commit

Permalink
Avoid panicking on invalid formatting string
Browse files Browse the repository at this point in the history
An additional error type has been created indicating the type does not
have sufficient information to be able to format/parse
  • Loading branch information
jhpratt committed Sep 11, 2020
1 parent d0cc8d2 commit 42250fb
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 23 deletions.
51 changes: 51 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub enum Error {
ComponentRange(Box<ComponentRange>),
Parse(Parse),
IndeterminateOffset(IndeterminateOffset),
Format(Format),
#[cfg(not(__time_02_supports_non_exhaustive))]
#[doc(hidden)]
__NonExhaustive,
Expand All @@ -28,6 +29,7 @@ impl fmt::Display for Error {
Error::ComponentRange(e) => e.fmt(f),
Error::Parse(e) => e.fmt(f),
Error::IndeterminateOffset(e) => e.fmt(f),
Error::Format(e) => e.fmt(f),
#[cfg(not(__time_02_supports_non_exhaustive))]
Error::__NonExhaustive => unreachable!(),
}
Expand All @@ -42,6 +44,7 @@ impl std::error::Error for Error {
Error::ComponentRange(box_err) => Some(box_err.as_ref()),
Error::Parse(err) => Some(err),
Error::IndeterminateOffset(err) => Some(err),
Error::Format(err) => Some(err),
#[cfg(not(__time_02_supports_non_exhaustive))]
Error::__NonExhaustive => unreachable!(),
}
Expand Down Expand Up @@ -160,3 +163,51 @@ impl From<IndeterminateOffset> for Error {
Error::IndeterminateOffset(original)
}
}

/// An error occurred while formatting.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Format {
/// The format provided requires more information than the type provides.
InsufficientTypeInformation,
/// An error occurred while formatting into the provided stream.
StdFmtError,
#[cfg(not(__time_02_supports_non_exhaustive))]
#[doc(hidden)]
__NonExhaustive,
}

impl fmt::Display for Format {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Format::InsufficientTypeInformation => {
f.write_str("The format provided requires more information than the type provides.")
}
Format::StdFmtError => fmt::Error.fmt(f),
#[cfg(not(__time_02_supports_non_exhaustive))]
Format::__NonExhaustive => unreachable!(),
}
}
}

#[cfg(feature = "std")]
impl std::error::Error for Format {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Format::StdFmtError => Some(&fmt::Error),
_ => None,
}
}
}

// This is necessary to be able to use `?` with various formatters.
impl From<fmt::Error> for Format {
fn from(_: fmt::Error) -> Self {
Format::StdFmtError
}
}

impl From<Format> for Error {
fn from(error: Format) -> Self {
Error::Format(error)
}
}
5 changes: 3 additions & 2 deletions src/format/deferred_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,15 @@ impl Display for DeferredFormat {
match item {
FormatItem::Literal(value) => f.write_str(value)?,
FormatItem::Specifier(specifier) => {
format_specifier(f, self.date, self.time, self.offset, specifier)?
format_specifier(f, self.date, self.time, self.offset, specifier)
.map_err(|_| fmt::Error)?
}
}
}

Ok(())
}
Format::Rfc3339 => well_known::rfc3339::fmt(self, f),
Format::Rfc3339 => well_known::rfc3339::fmt(self, f).map_err(|_| fmt::Error),
#[cfg(not(__time_02_supports_non_exhaustive))]
Format::__NonExhaustive => unreachable!(),
}
Expand Down
17 changes: 7 additions & 10 deletions src/format/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ pub(crate) mod parse_items;
pub(crate) mod time;
pub(crate) mod well_known;

use crate::{Date, Time, UtcOffset};
use core::fmt::{self, Formatter};
use crate::{error, Date, Time, UtcOffset};
use core::fmt::Formatter;
pub(crate) use deferred_format::DeferredFormat;
#[allow(unreachable_pub)] // rust-lang/rust#64762
pub use format::Format;
Expand Down Expand Up @@ -92,19 +92,16 @@ fn format_specifier(
time: Option<Time>,
offset: Option<UtcOffset>,
specifier: Specifier,
) -> fmt::Result {
) -> Result<(), error::Format> {
/// Push the provided specifier to the list of items.
macro_rules! specifier {
($type:ident :: $specifier_fn:ident ( $specifier:ident $(, $param:expr)? )) => {
$type::$specifier_fn(
f,
$type.expect(concat!(
"Specifier `%",
stringify!($specifier),
"` requires a ",
stringify!($type),
" to be present."
)),
match $type {
Some(v) => v,
None => return Err(error::Format::InsufficientTypeInformation),
},
$($param)?
)?
};
Expand Down
17 changes: 6 additions & 11 deletions src/format/well_known.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::{
};
#[cfg(not(feature = "std"))]
use alloc::string::String;
use core::fmt::{self, Formatter};
use core::fmt::Formatter;
#[allow(unused_imports)]
use standback::prelude::*;

Expand All @@ -23,16 +23,11 @@ pub(crate) mod rfc3339 {
use crate::{error, UtcOffset};

/// Format `df` according to the RFC3339 specification.
pub(crate) fn fmt(df: &DeferredFormat, f: &mut Formatter<'_>) -> fmt::Result {
// If we're using RFC3339, all three components must be present.
// This will be enforced with typestate when Rust gains sufficient
// capabilities (namely proper sealed traits and/or function overloading).
#[allow(clippy::unwrap_used)]
let date = df.date().unwrap();
#[allow(clippy::unwrap_used)]
let time = df.time().unwrap();
#[allow(clippy::unwrap_used)]
let offset = df.offset().unwrap();
pub(crate) fn fmt(df: &DeferredFormat, f: &mut Formatter<'_>) -> Result<(), error::Format> {
let (date, time, offset) = match (df.date(), df.time(), df.offset()) {
(Some(date), Some(time), Some(offset)) => (date, time, offset),
_ => return Err(error::Format::InsufficientTypeInformation),
};

date::fmt_Y(f, date, Padding::Zero)?;
f.write_str("-")?;
Expand Down

0 comments on commit 42250fb

Please sign in to comment.