Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 = ["formatting", "parsing", "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
185 changes: 185 additions & 0 deletions sdk/core/src/date.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
//! Azure date and time parsing and formatting

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

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

/// RFC 2822: Internet Message Format
///
/// https://www.rfc-editor.org/rfc/rfc2822#section-3.3
/// https://www.rfc-editor.org/rfc/rfc2822#section-4.3
/// A "GMT" zone is obsolete, instead "+0000" is used.
///
/// Date: Fri, 21 Nov 1997 10:01:10 -0600
pub fn parse_rfc2822(s: &str) -> crate::Result<OffsetDateTime> {
OffsetDateTime::parse(s, &Rfc2822).with_context(ErrorKind::DataConversion, || {
format!("unable to parse smtp date '{s}")
Comment thread
cataggar marked this conversation as resolved.
Outdated
})
}

/// RFC 2822: Internet Message Format
///
/// https://www.rfc-editor.org/rfc/rfc2822#section-3.3
/// https://www.rfc-editor.org/rfc/rfc2822#section-4.3
/// A "GMT" zone is obsolete, instead "+0000" is used.
///
/// Date: Fri, 21 Nov 1997 10:01:10 -0600
pub fn to_rfc2822(date: &OffsetDateTime) -> String {
// known format does not panic
date.format(&Rfc2822).unwrap()
}

/// RFC 3339: Date and Time on the Internet: Timestamps
///
/// https://www.rfc-editor.org/rfc/rfc3339
///
/// 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 internet date '{s}")
Comment thread
cataggar marked this conversation as resolved.
Outdated
})
}

/// Date format for the Internet
///
/// https://www.rfc-editor.org/rfc/rfc3339
///
/// 1985-04-12T23:20:50.52Z
pub fn to_rfc3339(date: &OffsetDateTime) -> String {
// known format does not panic
date.format(&Rfc3339).unwrap()
}

/// Preferred HTTP date format
///
/// https://httpwg.org/specs/rfc9110.html#http.date
///
/// In Azure REST API specification it is specified as `"format": "date-time-rfc1123"`. In .NET it is:
///
/// https://docs.microsoft.com/dotnet/api/system.globalization.datetimeformatinfo.rfc1123pattern
///
/// Sun, 06 Nov 1994 08:49:37 GMT
pub fn parse_http_date(s: &str) -> crate::Result<OffsetDateTime> {
Comment thread
cataggar marked this conversation as resolved.
Outdated
Ok(PrimitiveDateTime::parse(s, HTTP_DATE_FORMAT)
.with_context(ErrorKind::DataConversion, || {
format!("unable to parse http date '{s}")
})?
.assume_utc())
}

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

/// Preferred HTTP date format
///
/// https://httpwg.org/specs/rfc9110.html#http.date
///
/// In Azure REST API specification it is specified as `"format": "date-time-rfc1123"`. In .NET it is:
///
/// https://docs.microsoft.com/dotnet/api/system.globalization.datetimeformatinfo.rfc1123pattern
///
/// Sun, 06 Nov 1994 08:49:37 GMT
pub fn to_http_date(date: &OffsetDateTime) -> String {
date.to_offset(UtcOffset::UTC);
// known format does not panic
date.format(&HTTP_DATE_FORMAT).unwrap()
}

/// 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 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))
}

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

#[test]
fn test_roundtrip_internet_date() -> crate::Result<()> {
let s = "2019-10-12T07:20:50.52Z";
let dt = OffsetDateTime::parse(s, &Rfc3339)?;
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_http_date() -> crate::Result<()> {
let dt = datetime!(1994-11-06 08:49:37 UTC);
assert_eq!("Sun, 06 Nov 1994 08:49:37 GMT", to_http_date(&dt));
Ok(())
}

#[test]
fn test_parse_http_date() -> crate::Result<()> {
let dt = datetime!(1994-11-06 08:49:37 UTC);
assert_eq!(parse_http_date("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_http_date(creation_time)?
);
Ok(())
}
}
6 changes: 6 additions & 0 deletions sdk/core/src/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,12 @@ impl From<url::ParseError> for Error {
}
}

impl From<time::error::Parse> for Error {
Comment thread
cataggar marked this conversation as resolved.
Outdated
fn from(error: time::error::Parse) -> Self {
Self::new(ErrorKind::DataConversion, error)
}
}

impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.context {
Expand Down
55 changes: 15 additions & 40 deletions sdk/core/src/headers/utilities.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,9 @@
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::format_description::well_known::Rfc2822;
use time::OffsetDateTime;

pub fn lease_id_from_headers(headers: &Headers) -> crate::Result<LeaseId> {
headers.get_as(&LEASE_ID)
Expand All @@ -42,29 +19,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>> {
Ok(headers
.get_optional_str(&LAST_MODIFIED)
.map(|date| OffsetDateTime::parse(date, &Rfc2822))
.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> {
http_date_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> {
http_date_from_headers_mandatory(headers, &LAST_MODIFIED)
}

pub fn rfc2822_from_headers_mandatory(
pub fn http_date_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_http_date(date)
}

pub fn continuation_token_from_headers_optional(
Expand Down
2 changes: 2 additions & 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 All @@ -32,6 +33,7 @@ pub mod mock;
pub mod parsing;
pub mod prelude;
pub mod request_options;
pub mod serde;
pub mod sleep;
pub mod util;

Expand Down
6 changes: 3 additions & 3 deletions sdk/core/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::http_client;
use crate::policies::{ExponentialRetryPolicy, FixedRetryPolicy, NoRetryPolicy, Policy};
use crate::HttpClient;
use std::sync::Arc;
use std::time::Duration;
use time::Duration;

/// Client options allow customization of policies, retry options, and more.
///
Expand Down Expand Up @@ -134,9 +134,9 @@ impl Default for RetryOptions {
fn default() -> Self {
RetryOptions {
mode: RetryMode::default(),
delay: Duration::from_millis(800),
delay: Duration::milliseconds(800),
max_retries: 3,
max_delay: Duration::from_secs(60),
max_delay: Duration::seconds(60),
}
}
}
Expand Down
Loading