Add Time
module for storing ISO-8601 time and converting to and from
a string
This commit is contained in:
parent
5ab89f74db
commit
0fcfb6db47
7 changed files with 470 additions and 8 deletions
|
@ -1,4 +1,4 @@
|
|||
use super::{Verifier, FingerPrintStore};
|
||||
use super::{FingerPrintStore, Verifier};
|
||||
use rustls::ServerConfig;
|
||||
use std::{net::TcpStream, sync::Arc};
|
||||
|
||||
|
|
|
@ -2,7 +2,11 @@ pub mod builder;
|
|||
pub mod error;
|
||||
pub mod verifier;
|
||||
|
||||
pub use self::{builder::Builder, error::Error, verifier::{FingerPrintStore, Verifier}};
|
||||
pub use self::{
|
||||
builder::Builder,
|
||||
error::Error,
|
||||
verifier::{FingerPrintStore, Verifier},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Connection {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use rustls::server::ClientCertVerifier;
|
||||
|
||||
use crate::{prelude::CertificateStore, mailuser::Mailuser};
|
||||
use crate::{mailuser::Mailuser, prelude::CertificateStore};
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -20,11 +20,11 @@ impl<S: FingerPrintStore> ClientCertVerifier for Verifier<S> {
|
|||
}
|
||||
|
||||
fn verify_client_cert(
|
||||
&self,
|
||||
end_entity: &rustls::Certificate,
|
||||
intermediates: &[rustls::Certificate],
|
||||
now: std::time::SystemTime,
|
||||
) -> Result<rustls::server::ClientCertVerified, rustls::Error> {
|
||||
&self,
|
||||
end_entity: &rustls::Certificate,
|
||||
intermediates: &[rustls::Certificate],
|
||||
now: std::time::SystemTime,
|
||||
) -> Result<rustls::server::ClientCertVerified, rustls::Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,5 +13,6 @@ pub mod request;
|
|||
pub mod response;
|
||||
pub mod sender;
|
||||
pub mod status;
|
||||
pub mod time;
|
||||
|
||||
pub use sender::{Error as SenderError, Sender};
|
||||
|
|
29
src/time/error.rs
Normal file
29
src/time/error.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
use std::{fmt, num::ParseIntError};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
ParseInt,
|
||||
InvalidMonth,
|
||||
InvalidDay,
|
||||
InvalidHour,
|
||||
InvalidMinute,
|
||||
InvalidSecond,
|
||||
InvalidTimezone,
|
||||
InvalidOffset,
|
||||
TrailingGarbage,
|
||||
Truncated,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl From<ParseIntError> for Error {
|
||||
fn from(_value: ParseIntError) -> Self {
|
||||
Self::ParseInt
|
||||
}
|
||||
}
|
254
src/time/mod.rs
Normal file
254
src/time/mod.rs
Normal file
|
@ -0,0 +1,254 @@
|
|||
//! A container representing a moment in ISO-8601 format Time
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
mod error;
|
||||
mod parser;
|
||||
pub use {error::Error, parser::Parser};
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||
pub enum Sign {
|
||||
Positive,
|
||||
Negative,
|
||||
}
|
||||
|
||||
impl fmt::Display for Sign {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Positive => write!(f, "+"),
|
||||
Self::Negative => write!(f, "-"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<char> for Sign {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: char) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
'+' => Ok(Self::Positive),
|
||||
'-' => Ok(Self::Negative),
|
||||
_ => Err(Error::InvalidOffset),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||
pub struct Offset {
|
||||
pub sign: Sign,
|
||||
pub hours: u8,
|
||||
pub minutes: Option<u8>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Offset {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if let Some(m) = self.minutes {
|
||||
write!(f, "{}{:02}:{m:02}", self.sign, self.hours)
|
||||
} else {
|
||||
write!(f, "{}{:02}", self.sign, self.hours)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||
pub enum TimeZone {
|
||||
UTC,
|
||||
Offset(Offset),
|
||||
}
|
||||
|
||||
impl fmt::Display for TimeZone {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::UTC => write!(f, "Z"),
|
||||
Self::Offset(o) => write!(f, "{o}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||
pub struct DateTime {
|
||||
pub year: u32,
|
||||
pub month: Option<u8>,
|
||||
pub day: Option<u8>,
|
||||
pub hour: Option<u8>,
|
||||
pub minute: Option<u8>,
|
||||
pub second: Option<u8>,
|
||||
pub tz: Option<TimeZone>,
|
||||
}
|
||||
|
||||
impl fmt::Display for DateTime {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:04}", self.year)?;
|
||||
'blk: {
|
||||
for field in &[self.month, self.day] {
|
||||
if let Some(field) = field {
|
||||
write!(f, "-{field:02}")?;
|
||||
} else {
|
||||
break 'blk;
|
||||
}
|
||||
}
|
||||
if let Some(h) = self.hour {
|
||||
write!(f, "T{h:02}")?;
|
||||
} else {
|
||||
break 'blk;
|
||||
}
|
||||
for field in &[self.minute, self.second] {
|
||||
if let Some(field) = field {
|
||||
write!(f, ":{field:02}")?;
|
||||
} else {
|
||||
break 'blk;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(ref tz) = self.tz {
|
||||
write!(f, "{tz}")
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn fmt_full() {
|
||||
let dt = DateTime {
|
||||
year: 2023,
|
||||
month: Some(5),
|
||||
day: Some(7),
|
||||
hour: Some(9),
|
||||
minute: Some(3),
|
||||
second: Some(8),
|
||||
tz: Some(TimeZone::Offset(Offset {
|
||||
sign: Sign::Positive,
|
||||
hours: 5,
|
||||
minutes: Some(15),
|
||||
})),
|
||||
};
|
||||
assert_eq!(dt.to_string(), "2023-05-07T09:03:08+05:15");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fmt_partial_offset() {
|
||||
let dt = DateTime {
|
||||
year: 2023,
|
||||
month: Some(5),
|
||||
day: Some(7),
|
||||
hour: Some(9),
|
||||
minute: Some(3),
|
||||
second: Some(8),
|
||||
tz: Some(TimeZone::Offset(Offset {
|
||||
sign: Sign::Negative,
|
||||
hours: 5,
|
||||
minutes: None,
|
||||
})),
|
||||
};
|
||||
assert_eq!(dt.to_string(), "2023-05-07T09:03:08-05");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fmt_utc() {
|
||||
let dt = DateTime {
|
||||
year: 2023,
|
||||
month: Some(5),
|
||||
day: Some(7),
|
||||
hour: Some(9),
|
||||
minute: Some(3),
|
||||
second: Some(8),
|
||||
tz: Some(TimeZone::UTC),
|
||||
};
|
||||
assert_eq!(dt.to_string(), "2023-05-07T09:03:08Z");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fmt_no_tz() {
|
||||
let dt = DateTime {
|
||||
year: 2023,
|
||||
month: Some(5),
|
||||
day: Some(7),
|
||||
hour: Some(9),
|
||||
minute: Some(3),
|
||||
second: Some(8),
|
||||
tz: None,
|
||||
};
|
||||
assert_eq!(dt.to_string(), "2023-05-07T09:03:08");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fmt_no_seconds() {
|
||||
let dt = DateTime {
|
||||
year: 2023,
|
||||
month: Some(5),
|
||||
day: Some(7),
|
||||
hour: Some(9),
|
||||
minute: Some(3),
|
||||
second: None,
|
||||
tz: Some(TimeZone::UTC),
|
||||
};
|
||||
assert_eq!(dt.to_string(), "2023-05-07T09:03Z");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fmt_no_minutes() {
|
||||
let dt = DateTime {
|
||||
year: 2023,
|
||||
month: Some(5),
|
||||
day: Some(7),
|
||||
hour: Some(9),
|
||||
minute: None,
|
||||
second: Some(50),
|
||||
tz: Some(TimeZone::UTC),
|
||||
};
|
||||
assert_eq!(dt.to_string(), "2023-05-07T09Z");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fmt_no_time() {
|
||||
let dt = DateTime {
|
||||
year: 2023,
|
||||
month: Some(5),
|
||||
day: Some(7),
|
||||
hour: None,
|
||||
minute: None,
|
||||
second: Some(50),
|
||||
tz: Some(TimeZone::UTC),
|
||||
};
|
||||
assert_eq!(dt.to_string(), "2023-05-07Z");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fmt_no_day() {
|
||||
let dt = DateTime {
|
||||
year: 2023,
|
||||
month: Some(5),
|
||||
day: None,
|
||||
hour: Some(20),
|
||||
minute: None,
|
||||
second: Some(50),
|
||||
tz: None,
|
||||
};
|
||||
assert_eq!(dt.to_string(), "2023-05");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fmt_no_month() {
|
||||
let dt = DateTime {
|
||||
year: 2023,
|
||||
month: None,
|
||||
day: None,
|
||||
hour: Some(20),
|
||||
minute: None,
|
||||
second: Some(50),
|
||||
tz: None,
|
||||
};
|
||||
assert_eq!(dt.to_string(), "2023");
|
||||
}
|
||||
}
|
174
src/time/parser.rs
Normal file
174
src/time/parser.rs
Normal file
|
@ -0,0 +1,174 @@
|
|||
//! Implements a parser for ISO-8601 format Time
|
||||
use super::{DateTime, Error, TimeZone};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Format {
|
||||
Basic,
|
||||
Extended,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Mode {
|
||||
Year,
|
||||
Month,
|
||||
Day,
|
||||
Hour,
|
||||
Minute,
|
||||
Second,
|
||||
TimeZone,
|
||||
Finish,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Parser<'a> {
|
||||
text: &'a str,
|
||||
format: Format,
|
||||
mode: Mode,
|
||||
year: Option<u32>,
|
||||
month: Option<u8>,
|
||||
day: Option<u8>,
|
||||
hour: Option<u8>,
|
||||
minute: Option<u8>,
|
||||
second: Option<u8>,
|
||||
tz: Option<TimeZone>,
|
||||
}
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
pub fn new(text: &'a str) -> Self {
|
||||
Self {
|
||||
text,
|
||||
format: Format::Extended,
|
||||
mode: Mode::Year,
|
||||
year: None,
|
||||
month: None,
|
||||
day: None,
|
||||
hour: None,
|
||||
minute: None,
|
||||
second: None,
|
||||
tz: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_year(&mut self) -> Result<(), Error> {
|
||||
assert_eq!(self.mode, Mode::Year);
|
||||
let year = self.text.get(..3).ok_or(Error::Truncated)?.parse()?;
|
||||
self.year = Some(year);
|
||||
if let Some(c) = self.text.chars().nth(5) {
|
||||
match c {
|
||||
'-' => self.format = Format::Extended,
|
||||
_ => self.format = Format::Basic,
|
||||
}
|
||||
}
|
||||
self.mode = Mode::Month;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_month(&mut self) -> Result<(), Error> {
|
||||
assert_eq!(self.mode, Mode::Month);
|
||||
let month = match self.format {
|
||||
Format::Basic => self.text.get(4..6),
|
||||
Format::Extended => self.text.get(5..7),
|
||||
};
|
||||
if let Some(m) = month {
|
||||
let month = m.parse()?;
|
||||
if month > 12 {
|
||||
return Err(Error::InvalidMonth);
|
||||
}
|
||||
self.month = Some(month);
|
||||
self.mode = Mode::Day;
|
||||
} else {
|
||||
self.mode = Mode::TimeZone;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_day(&mut self) -> Result<(), Error> {
|
||||
assert_eq!(self.mode, Mode::Day);
|
||||
let day = match self.format {
|
||||
Format::Basic => self.text.get(6..8),
|
||||
Format::Extended => self.text.get(8..10),
|
||||
};
|
||||
if let Some(d) = day {
|
||||
let day = d.parse()?;
|
||||
let max = match self.month {
|
||||
Some(1|3|5|7|10|12) => 31,
|
||||
Some(2) => {
|
||||
if self.year.unwrap() % 4 == 0 {
|
||||
29
|
||||
} else {
|
||||
28
|
||||
}
|
||||
},
|
||||
Some(4|6|9|11) => 30,
|
||||
_ => return Err(Error::InvalidMonth),
|
||||
};
|
||||
if day > max {
|
||||
return Err(Error::InvalidDay);
|
||||
}
|
||||
self.day = Some(day);
|
||||
let tidx = match self.format {
|
||||
Format::Basic => 8,
|
||||
Format::Extended => 10,
|
||||
};
|
||||
if let Some(c) = self.text.chars().nth(tidx) {
|
||||
match c {
|
||||
'T' => self.mode = Mode::Hour,
|
||||
'Z' | '-' | '+' => self.mode = Mode::TimeZone,
|
||||
_ => return Err(Error::InvalidHour),
|
||||
}
|
||||
} else {
|
||||
self.mode = Mode::Finish;
|
||||
}
|
||||
} else {
|
||||
self.mode = Mode::TimeZone;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_hour(&mut self) -> Result<(), Error> {
|
||||
assert_eq!(self.mode, Mode::Hour);
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn parse_minute(&mut self) -> Result<(), Error> {
|
||||
assert_eq!(self.mode, Mode::Minute);
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn parse_second(&mut self) -> Result<(), Error> {
|
||||
assert_eq!(self.mode, Mode::Second);
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn parse_timezone(&mut self) -> Result<(), Error> {
|
||||
assert_eq!(self.mode, Mode::TimeZone);
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn parse(mut self) -> Result<DateTime, Error> {
|
||||
loop {
|
||||
match self.mode {
|
||||
Mode::Year => self.parse_year()?,
|
||||
Mode::Month => self.parse_month()?,
|
||||
Mode::Day => self.parse_day()?,
|
||||
Mode::Hour => self.parse_hour()?,
|
||||
Mode::Minute => self.parse_minute()?,
|
||||
Mode::Second => self.parse_second()?,
|
||||
Mode::TimeZone => {
|
||||
self.parse_timezone()?;
|
||||
break;
|
||||
}
|
||||
Mode::Finish => break,
|
||||
}
|
||||
}
|
||||
Ok(DateTime {
|
||||
year: self.year.unwrap(),
|
||||
month: self.month,
|
||||
day: self.day,
|
||||
hour: self.hour,
|
||||
minute: self.minute,
|
||||
second: self.second,
|
||||
tz: self.tz,
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue