Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sdk/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ edition = "2021"
async-trait = "0.1"
base64 = "0.13"
bytes = "1.0"
chrono = "0.4"
time = { version = "0.3", features = ["serde-well-known", "macros", "local-offset"] }
dyn-clone = "1.0"
futures = "0.3"
http-types = { version = "2.12", default-features = false }
Expand Down
6 changes: 3 additions & 3 deletions sdk/core/src/auth.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! Azure authentication and authorization.

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::{borrow::Cow, fmt::Debug};
use time::OffsetDateTime;

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AccessToken(Cow<'static, str>);
Expand All @@ -26,12 +26,12 @@ pub struct TokenResponse {
/// Get the access token value.
pub token: AccessToken,
/// Gets the time when the provided token expires.
pub expires_on: DateTime<Utc>,
pub expires_on: OffsetDateTime,
}

impl TokenResponse {
/// Create a new `TokenResponse`.
pub fn new(token: AccessToken, expires_on: DateTime<Utc>) -> Self {
pub fn new(token: AccessToken, expires_on: OffsetDateTime) -> Self {
Self { token, expires_on }
}
}
Expand Down
195 changes: 195 additions & 0 deletions sdk/core/src/date/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
//! Azure date and time parsing and formatting

// RFC 3339 vs ISO 8601
// https://ijmacd.github.io/rfc3339-iso8601/

use crate::error::{ErrorKind, ResultExt};
use std::time::Duration;
use time::{
format_description::{well_known::Rfc3339, FormatItem},
macros::format_description,
OffsetDateTime, PrimitiveDateTime, UtcOffset,
};

// Serde modules
pub use time::serde::rfc3339;
pub use time::serde::timestamp;
pub mod rfc1123;

/// RFC 3339: Date and Time on the Internet: Timestamps
///
/// https://www.rfc-editor.org/rfc/rfc3339
///
/// In Azure REST API specifications it is specified as `"format": "date-time"`.
///
/// 1985-04-12T23:20:50.52Z
pub fn parse_rfc3339(s: &str) -> crate::Result<OffsetDateTime> {
OffsetDateTime::parse(s, &Rfc3339).with_context(ErrorKind::DataConversion, || {
format!("unable to parse rfc3339 date '{s}")
})
}

/// RFC 3339: Date and Time on the Internet: Timestamps
///
/// https://www.rfc-editor.org/rfc/rfc3339
///
/// In Azure REST API specifications it is specified as `"format": "date-time"`.
///
/// 1985-04-12T23:20:50.52Z
pub fn to_rfc3339(date: &OffsetDateTime) -> String {
// known format does not panic
date.format(&Rfc3339).unwrap()
}

/// RFC 1123: Requirements for Internet Hosts - Application and Support
///
/// https://www.rfc-editor.org/rfc/rfc1123
///
/// In Azure REST API specifications it is specified as `"format": "date-time-rfc1123"`.
///
/// In .NET it is the `rfc1123pattern`.
/// https://docs.microsoft.com/dotnet/api/system.globalization.datetimeformatinfo.rfc1123pattern
///
/// This format is also the preferred HTTP date format.
/// https://httpwg.org/specs/rfc9110.html#http.date
///
/// Sun, 06 Nov 1994 08:49:37 GMT
pub fn parse_rfc1123(s: &str) -> crate::Result<OffsetDateTime> {
Ok(PrimitiveDateTime::parse(s, RFC1123_FORMAT)
.with_context(ErrorKind::DataConversion, || {
format!("unable to parse rfc1123 date '{s}")
})?
.assume_utc())
}

const RFC1123_FORMAT: &[FormatItem] = format_description!(
"[weekday repr:short], [day] [month repr:short] [year] [hour]:[minute]:[second] GMT"
);

/// RFC 1123: Requirements for Internet Hosts - Application and Support
///
/// https://www.rfc-editor.org/rfc/rfc1123
///
/// In Azure REST API specifications it is specified as `"format": "date-time-rfc1123"`.
///
/// In .NET it is the `rfc1123pattern`.
/// https://docs.microsoft.com/dotnet/api/system.globalization.datetimeformatinfo.rfc1123pattern
///
/// This format is also the preferred HTTP date format.
/// https://httpwg.org/specs/rfc9110.html#http.date
///
/// Sun, 06 Nov 1994 08:49:37 GMT
pub fn to_rfc1123(date: &OffsetDateTime) -> String {
date.to_offset(UtcOffset::UTC);
// known format does not panic
date.format(&RFC1123_FORMAT).unwrap()
}

/// Similar to RFC 1123, but includes milliseconds.
///
/// https://docs.microsoft.com/rest/api/cosmos-db/patch-a-document
///
/// x-ms-last-state-change-utc: Fri, 25 Mar 2016 21:27:20.035 GMT
pub fn parse_last_state_change(s: &str) -> crate::Result<OffsetDateTime> {
Ok(PrimitiveDateTime::parse(s, LAST_STATE_CHANGE_FORMAT)
.with_context(ErrorKind::DataConversion, || {
format!("unable to parse last state change date '{s}")
})?
.assume_utc())
}

const LAST_STATE_CHANGE_FORMAT: &[FormatItem] = format_description!(
"[weekday repr:short], [day] [month repr:short] [year] [hour]:[minute]:[second].[subsecond digits:3] GMT"
);

/// Similar to preferred HTTP date format, but includes milliseconds
///
/// https://docs.microsoft.com/rest/api/cosmos-db/patch-a-document
///
/// x-ms-last-state-change-utc: Fri, 25 Mar 2016 21:27:20.035 GMT
pub fn to_last_state_change(date: &OffsetDateTime) -> String {
date.to_offset(UtcOffset::UTC);
// known format does not panic
date.format(LAST_STATE_CHANGE_FORMAT).unwrap()
}

/// Assumes the local offset. Default to UTC if unable to get local offset.
pub fn assume_local(date: &PrimitiveDateTime) -> OffsetDateTime {
date.assume_offset(UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC))
}

// Create a duration from the number of minutes.
pub fn duration_from_minutes(minutes: u64) -> Duration {
Duration::from_secs(minutes * 60)
}

// Create a duration from the number of hours.
pub fn duration_from_hours(hours: u64) -> Duration {
Duration::from_secs(hours * 3_600)
}

// Create a duration from the number of days.
pub fn duration_from_days(days: u64) -> Duration {
Duration::from_secs(days * 86_400)
}

/// Get the difference between two dates.
pub fn diff(first: OffsetDateTime, second: OffsetDateTime) -> Duration {
(first - second).unsigned_abs()
}

#[cfg(test)]
mod tests {
use super::*;
use time::macros::datetime;

#[test]
fn test_roundtrip_rfc3339() -> crate::Result<()> {
let s = "2019-10-12T07:20:50.52Z";
let dt = parse_rfc3339(s)?;
assert_eq!(s, to_rfc3339(&dt));
Ok(())
}

#[test]
fn test_device_update_dates() -> crate::Result<()> {
let created = parse_rfc3339("1999-09-10T21:59:22Z")?;
let last_action = parse_rfc3339("1999-09-10T03:05:07.3845533+01:00")?;
assert_eq!(created, datetime!(1999-09-10 21:59:22 UTC));
assert_eq!(last_action, datetime!(1999-09-10 03:05:07.3845533 +01));
Ok(())
}

#[test]
fn test_to_rfc1123() -> crate::Result<()> {
let dt = datetime!(1994-11-06 08:49:37 UTC);
assert_eq!("Sun, 06 Nov 1994 08:49:37 GMT", to_rfc1123(&dt));
Ok(())
}

#[test]
fn test_parse_rfc1123() -> crate::Result<()> {
let dt = datetime!(1994-11-06 08:49:37 UTC);
assert_eq!(parse_rfc1123("Sun, 06 Nov 1994 08:49:37 GMT")?, dt);
Ok(())
}

#[test]
fn test_parse_last_state_change() -> crate::Result<()> {
assert_eq!(
datetime!(2020-01-15 23:39:44.369 UTC),
parse_last_state_change("Wed, 15 Jan 2020 23:39:44.369 GMT")?
);
Ok(())
}

#[test]
fn test_list_blob_creation_time() -> crate::Result<()> {
let creation_time = "Thu, 01 Jul 2021 10:45:02 GMT";
assert_eq!(
datetime!(2021-07-01 10:45:02 UTC),
parse_rfc1123(creation_time)?
);
Ok(())
}
}
46 changes: 46 additions & 0 deletions sdk/core/src/date/rfc1123.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use serde::{self, Deserialize, Deserializer, Serializer};
use time::OffsetDateTime;

use crate::date::*;
use serde::de;

pub fn deserialize<'de, D>(deserializer: D) -> Result<OffsetDateTime, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
parse_rfc1123(&s).map_err(de::Error::custom)
}

pub fn serialize<S>(date: &OffsetDateTime, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&to_rfc1123(date))
}

pub mod option {
use crate::date::*;
use serde::{Deserialize, Deserializer, Serializer};
use time::OffsetDateTime;

pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<OffsetDateTime>, D::Error>
where
D: Deserializer<'de>,
{
let s: Option<String> = Option::deserialize(deserializer)?;
s.map(|s| parse_rfc1123(&s).map_err(serde::de::Error::custom))
.transpose()
}

pub fn serialize<S>(date: &Option<OffsetDateTime>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if let Some(date) = date {
serializer.serialize_str(&to_rfc1123(date))
} else {
serializer.serialize_none()
}
}
}
54 changes: 14 additions & 40 deletions sdk/core/src/headers/utilities.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,8 @@
use super::*;
use crate::error::{Error, ErrorKind};
use crate::prelude::Continuation;
use crate::request_options::LeaseId;
use crate::{RequestId, SessionToken};
use chrono::{DateTime, FixedOffset, Utc};

pub fn parse_date_from_str(date: &str, fmt: &str) -> crate::Result<DateTime<FixedOffset>> {
DateTime::parse_from_str(date, fmt).map_err(|e| {
Error::full(
ErrorKind::DataConversion,
e,
format!(
"failed to parse date '{}' with format string {:?}",
date, fmt
),
)
})
}

pub fn parse_date_from_rfc2822(date: &str) -> crate::Result<DateTime<FixedOffset>> {
DateTime::parse_from_rfc2822(date).map_err(|e| {
Error::full(
ErrorKind::DataConversion,
e,
format!("failed to parse date '{}' with as rfc2822", date),
)
})
}
use crate::{date, RequestId, SessionToken};
use time::OffsetDateTime;

pub fn lease_id_from_headers(headers: &Headers) -> crate::Result<LeaseId> {
headers.get_as(&LEASE_ID)
Expand All @@ -42,29 +18,27 @@ pub fn client_request_id_from_headers_optional(headers: &Headers) -> Option<Stri

pub fn last_modified_from_headers_optional(
headers: &Headers,
) -> crate::Result<Option<DateTime<Utc>>> {
headers.get_optional_as(&LAST_MODIFIED)
) -> crate::Result<Option<OffsetDateTime>> {
headers
.get_optional_str(&LAST_MODIFIED)
.map(date::parse_rfc1123)
.transpose()
}

pub fn date_from_headers(headers: &Headers) -> crate::Result<DateTime<Utc>> {
rfc2822_from_headers_mandatory(headers, &DATE)
pub fn date_from_headers(headers: &Headers) -> crate::Result<OffsetDateTime> {
rfc1123_from_headers_mandatory(headers, &DATE)
}

pub fn last_modified_from_headers(headers: &Headers) -> crate::Result<DateTime<Utc>> {
rfc2822_from_headers_mandatory(headers, &LAST_MODIFIED)
pub fn last_modified_from_headers(headers: &Headers) -> crate::Result<OffsetDateTime> {
rfc1123_from_headers_mandatory(headers, &LAST_MODIFIED)
}

pub fn rfc2822_from_headers_mandatory(
pub fn rfc1123_from_headers_mandatory(
headers: &Headers,
header_name: &HeaderName,
) -> crate::Result<DateTime<Utc>> {
) -> crate::Result<OffsetDateTime> {
let date = headers.get_str(header_name)?;
utc_date_from_rfc2822(date)
}

pub fn utc_date_from_rfc2822(date: &str) -> crate::Result<DateTime<Utc>> {
let date = parse_date_from_rfc2822(date)?;
Ok(DateTime::from_utc(date.naive_utc(), Utc))
date::parse_rfc1123(date)
}

pub fn continuation_token_from_headers_optional(
Expand Down
1 change: 1 addition & 0 deletions sdk/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod macros;
mod bytes_stream;
mod constants;
mod context;
pub mod date;
pub mod error;
mod http_client;
mod models;
Expand Down
Loading