message: cardano: Implement FromStr for BlockDate
  This hooks into the parse method of strings to enable parsing of the block
  date format.
diff: @@ -89,3 +89,103 @@ impl fmt::Display for BlockDate {
  }
  }
  }
  +
  +impl str::FromStr for BlockDate {
  + type Err = BlockDateParseError;
  +
  + fn from_str(s: &str) -> Result<BlockDate, BlockDateParseError> {
  + use self::ParseErrorKind::*;
  +
  + let (ep, opt_sp) = match s.find('.') {
  + None => (s, None),
  + Some(pos) => (&s[..pos], Some(&s[(pos + 1)..])),
  + };
  + let epoch = str::parse::<EpochId>(ep)
  + .map_err(|e| BlockDateParseError(BadEpochId(e)))?;
  + match opt_sp {
  + None => Ok(BlockDate::Boundary(epoch)),
  + Some(sp) => {
  + if sp == "GENESIS" {
  + return Ok(BlockDate::Boundary(epoch));
  + }
  + let slotid = str::parse::<SlotId>(sp)
  + .map_err(|e| BlockDateParseError(BadSlotId(e)))?;
  + Ok(BlockDate::Normal(EpochSlotId { epoch, slotid }))
  + }
  + }
  + }
  +}
  +
  +#[derive(Debug)]
  +pub struct BlockDateParseError(ParseErrorKind);
  +
  +#[derive(Debug)]
  +enum ParseErrorKind {
  + BadEpochId(ParseIntError),
  + BadSlotId(ParseIntError),
  +}
  +
  +const EXPECT_FORMAT_MESSAGE: &'static str = "expected block date format EPOCH[.SLOT]";
  +
  +impl fmt::Display for BlockDateParseError {
  + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  + use self::ParseErrorKind::*;
  + match self.0 {
  + BadEpochId(_) => write!(f, "invalid epoch ID, {}", EXPECT_FORMAT_MESSAGE),
  + BadSlotId(_) => write!(f, "invalid slot ID, {}", EXPECT_FORMAT_MESSAGE),
  + }
  + }
  +}
  +
  +impl Error for BlockDateParseError {
  + fn cause(&self) -> Option<&dyn Error> {
  + use self::ParseErrorKind::*;
  + match self.0 {
  + BadEpochId(ref e) => Some(e),
  + BadSlotId(ref e) => Some(e),
  + }
  + }
  +}
  +
  +#[cfg(test)]
  +mod tests {
  + use super::{BlockDate, BlockDateParseError};
  + use block::EpochSlotId;
  + use std::error::Error;
  +
  + #[test]
  + fn parse_bare_epoch() {
  + let date = "42".parse::<BlockDate>().unwrap();
  + assert_eq!(date, BlockDate::Boundary(42));
  + }
  +
  + #[test]
  + fn parse_epoch_slot_id() {
  + let date = "42.12".parse::<BlockDate>().unwrap();
  + assert_eq!(
  + date,
  + BlockDate::Normal(EpochSlotId {
  + epoch: 42,
  + slotid: 12
  + })
  + );
  + }
  +
  + #[test]
  + fn parse_epoch_genesis() {
  + let date = "42.GENESIS".parse::<BlockDate>().unwrap();
  + assert_eq!(date, BlockDate::Boundary(42));
  + }
  +
  + #[test]
  + fn parse_bad_epoch() {
  + let err = "".parse::<BlockDate>().unwrap_err();
  + println!("{}: {}", err, err.cause().unwrap());
  + }
  +
  + #[test]
  + fn parse_bad_slotid() {
  + let err = "42.INVALID".parse::<BlockDate>().unwrap_err();
  + println!("{}: {}", err, err.cause().unwrap());
  + }
  +}
  