client-api: Add helper methods to convert SystemTime from/to a HTTP date
This commit is contained in:
parent
b4d0ab42a3
commit
10c7e59c57
@ -6,17 +6,22 @@ use as_variant::as_variant;
|
||||
use bytes::{BufMut, Bytes};
|
||||
use ruma_common::{
|
||||
api::{
|
||||
error::{FromHttpResponseError, IntoHttpError, MatrixErrorBody},
|
||||
error::{
|
||||
FromHttpResponseError, HeaderDeserializationError, HeaderSerializationError,
|
||||
IntoHttpError, MatrixErrorBody,
|
||||
},
|
||||
EndpointError, OutgoingResponse,
|
||||
},
|
||||
RoomVersionId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{from_slice as from_json_slice, Value as JsonValue};
|
||||
use thiserror::Error;
|
||||
use web_time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
use web_time::{Duration, SystemTime};
|
||||
|
||||
use crate::PrivOwnedStr;
|
||||
use crate::{
|
||||
http_headers::{http_date_to_system_time, system_time_to_http_date},
|
||||
PrivOwnedStr,
|
||||
};
|
||||
|
||||
/// Deserialize and Serialize implementations for ErrorKind.
|
||||
/// Separate module because it's a lot of code.
|
||||
@ -372,9 +377,8 @@ impl EndpointError for Error {
|
||||
ErrorKind::LimitExceeded { retry_after } => {
|
||||
// The Retry-After header takes precedence over the retry_after_ms field in
|
||||
// the body.
|
||||
if let Some(retry_after_header) = headers
|
||||
.get(http::header::RETRY_AFTER)
|
||||
.and_then(RetryAfter::from_header_value)
|
||||
if let Some(Ok(retry_after_header)) =
|
||||
headers.get(http::header::RETRY_AFTER).map(RetryAfter::try_from)
|
||||
{
|
||||
*retry_after = Some(retry_after_header);
|
||||
}
|
||||
@ -567,59 +571,31 @@ pub enum RetryAfter {
|
||||
DateTime(SystemTime),
|
||||
}
|
||||
|
||||
impl RetryAfter {
|
||||
fn from_header_value(value: &http::HeaderValue) -> Option<Self> {
|
||||
let bytes = value.as_bytes();
|
||||
impl TryFrom<&http::HeaderValue> for RetryAfter {
|
||||
type Error = HeaderDeserializationError;
|
||||
|
||||
if bytes.iter().all(|b| b.is_ascii_digit()) {
|
||||
fn try_from(value: &http::HeaderValue) -> Result<Self, Self::Error> {
|
||||
if value.as_bytes().iter().all(|b| b.is_ascii_digit()) {
|
||||
// It should be a duration.
|
||||
Some(Self::Delay(Duration::from_secs(u64::from_str(value.to_str().ok()?).ok()?)))
|
||||
Ok(Self::Delay(Duration::from_secs(u64::from_str(value.to_str()?)?)))
|
||||
} else {
|
||||
// It should be a date.
|
||||
let ts = date_header::parse(bytes).ok()?;
|
||||
Some(Self::DateTime(UNIX_EPOCH.checked_add(Duration::from_secs(ts))?))
|
||||
Ok(Self::DateTime(http_date_to_system_time(value)?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&RetryAfter> for http::HeaderValue {
|
||||
type Error = RetryAfterInvalidDateTime;
|
||||
type Error = HeaderSerializationError;
|
||||
|
||||
fn try_from(value: &RetryAfter) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
RetryAfter::Delay(duration) => Ok(duration.as_secs().into()),
|
||||
RetryAfter::DateTime(time) => {
|
||||
let mut buffer = [0; 29];
|
||||
let duration =
|
||||
time.duration_since(UNIX_EPOCH).map_err(|_| RetryAfterInvalidDateTime)?;
|
||||
date_header::format(duration.as_secs(), &mut buffer)
|
||||
.map_err(|_| RetryAfterInvalidDateTime)?;
|
||||
let value = http::HeaderValue::from_bytes(&buffer)
|
||||
.expect("date_header should produce a valid header value");
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
RetryAfter::DateTime(time) => system_time_to_http_date(time),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An error when converting a [`RetryAfter`] to a [`http::HeaderValue`].
|
||||
///
|
||||
/// Happens when the `DateTime` is too far in the past (before the Unix epoch) or the
|
||||
/// future (after the year 9999).
|
||||
#[derive(Debug, Error)]
|
||||
#[allow(clippy::exhaustive_structs)]
|
||||
#[error(
|
||||
"Retry-After header serialization failed: the datetime is too far in the past or the future"
|
||||
)]
|
||||
pub struct RetryAfterInvalidDateTime;
|
||||
|
||||
impl From<RetryAfterInvalidDateTime> for IntoHttpError {
|
||||
fn from(_value: RetryAfterInvalidDateTime) -> Self {
|
||||
IntoHttpError::RetryAfterInvalidDatetime
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension trait for `FromHttpResponseError<ruma_client_api::Error>`.
|
||||
pub trait FromHttpResponseErrorExt {
|
||||
/// If `self` is a server error in the `errcode` + `error` format expected
|
||||
|
@ -1,10 +1,40 @@
|
||||
//! Custom HTTP headers not defined in the `http` crate.
|
||||
//! Helpers for HTTP headers with the `http` crate.
|
||||
#![allow(clippy::declare_interior_mutable_const)]
|
||||
|
||||
use http::header::HeaderName;
|
||||
use http::{header::HeaderName, HeaderValue};
|
||||
use ruma_common::api::error::{HeaderDeserializationError, HeaderSerializationError};
|
||||
use web_time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
/// The [`Cross-Origin-Resource-Policy`] HTTP response header.
|
||||
///
|
||||
/// [`Cross-Origin-Resource-Policy`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy
|
||||
pub const CROSS_ORIGIN_RESOURCE_POLICY: HeaderName =
|
||||
HeaderName::from_static("cross-origin-resource-policy");
|
||||
|
||||
/// Convert as `SystemTime` to a HTTP date header value.
|
||||
pub fn system_time_to_http_date(
|
||||
time: &SystemTime,
|
||||
) -> Result<HeaderValue, HeaderSerializationError> {
|
||||
let mut buffer = [0; 29];
|
||||
|
||||
let duration =
|
||||
time.duration_since(UNIX_EPOCH).map_err(|_| HeaderSerializationError::InvalidHttpDate)?;
|
||||
date_header::format(duration.as_secs(), &mut buffer)
|
||||
.map_err(|_| HeaderSerializationError::InvalidHttpDate)?;
|
||||
|
||||
Ok(http::HeaderValue::from_bytes(&buffer)
|
||||
.expect("date_header should produce a valid header value"))
|
||||
}
|
||||
|
||||
/// Convert a header value representing a HTTP date to a `SystemTime`.
|
||||
pub fn http_date_to_system_time(
|
||||
value: &HeaderValue,
|
||||
) -> Result<SystemTime, HeaderDeserializationError> {
|
||||
let bytes = value.as_bytes();
|
||||
|
||||
let ts = date_header::parse(bytes).map_err(|_| HeaderDeserializationError::InvalidHttpDate)?;
|
||||
|
||||
UNIX_EPOCH
|
||||
.checked_add(Duration::from_secs(ts))
|
||||
.ok_or(HeaderDeserializationError::InvalidHttpDate)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ Bug fixes:
|
||||
- Allow to deserialize `Ruleset` with missing fields.
|
||||
|
||||
Breaking changes:
|
||||
|
||||
- The power levels fields in `PushConditionRoomCtx` are grouped in an optional `power_levels` field.
|
||||
If the field is missing, push rules that depend on it will never match. However, this allows to
|
||||
match the `.m.rule.invite_for_me` push rule because usually the `invite_state` doesn't include
|
||||
@ -14,6 +15,7 @@ Breaking changes:
|
||||
- `deserialize_as_f64_or_string` has been extended to also support parsing integers, and renamed to
|
||||
`deserialize_as_number_or_string` to reflect that.
|
||||
- The http crate had a major version bump to version 1.1
|
||||
- `IntoHttpError::Header` now contains a `HeaderSerializationError`
|
||||
|
||||
Improvements:
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
//! converting between http requests / responses and ruma's representation of
|
||||
//! matrix API requests / responses.
|
||||
|
||||
use std::{error::Error as StdError, fmt, sync::Arc};
|
||||
use std::{error::Error as StdError, fmt, num::ParseIntError, sync::Arc};
|
||||
|
||||
use bytes::{BufMut, Bytes};
|
||||
use serde_json::{from_slice as from_json_slice, Value as JsonValue};
|
||||
@ -127,20 +127,19 @@ pub enum IntoHttpError {
|
||||
|
||||
/// Header serialization failed.
|
||||
#[error("header serialization failed: {0}")]
|
||||
Header(#[from] http::header::InvalidHeaderValue),
|
||||
|
||||
/// Retry-After header serialization failed because the datetime provided is after the year
|
||||
/// 9999.
|
||||
#[error(
|
||||
"Retry-After header serialization failed: the year of the datetime is bigger than 9999"
|
||||
)]
|
||||
RetryAfterInvalidDatetime,
|
||||
Header(#[from] HeaderSerializationError),
|
||||
|
||||
/// HTTP request construction failed.
|
||||
#[error("HTTP request construction failed: {0}")]
|
||||
Http(#[from] http::Error),
|
||||
}
|
||||
|
||||
impl From<http::header::InvalidHeaderValue> for IntoHttpError {
|
||||
fn from(value: http::header::InvalidHeaderValue) -> Self {
|
||||
Self::Header(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// An error when converting a http request to one of ruma's endpoint-specific request types.
|
||||
#[derive(Debug, Error)]
|
||||
#[non_exhaustive]
|
||||
@ -258,13 +257,21 @@ impl From<http::header::ToStrError> for DeserializationError {
|
||||
}
|
||||
}
|
||||
|
||||
/// An error with the http headers.
|
||||
/// An error when deserializing the HTTP headers.
|
||||
#[derive(Debug, Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum HeaderDeserializationError {
|
||||
/// Failed to convert `http::header::HeaderValue` to `str`.
|
||||
#[error("{0}")]
|
||||
ToStrError(http::header::ToStrError),
|
||||
ToStrError(#[from] http::header::ToStrError),
|
||||
|
||||
/// Failed to convert `http::header::HeaderValue` to an integer.
|
||||
#[error("{0}")]
|
||||
ParseIntError(#[from] ParseIntError),
|
||||
|
||||
/// Failed to parse a HTTP date from a `http::header::Value`.
|
||||
#[error("failed to parse HTTP date")]
|
||||
InvalidHttpDate,
|
||||
|
||||
/// The given required header is missing.
|
||||
#[error("missing header `{0}`")]
|
||||
@ -303,3 +310,19 @@ impl fmt::Display for IncorrectArgumentCount {
|
||||
}
|
||||
|
||||
impl StdError for IncorrectArgumentCount {}
|
||||
|
||||
/// An error when serializing the HTTP headers.
|
||||
#[derive(Debug, Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum HeaderSerializationError {
|
||||
/// Failed to convert a header value to `http::header::HeaderValue`.
|
||||
#[error(transparent)]
|
||||
ToHeaderValue(#[from] http::header::InvalidHeaderValue),
|
||||
|
||||
/// The `SystemTime` could not be converted to a HTTP date.
|
||||
///
|
||||
/// This only happens if the `SystemTime` provided is too far in the past (before the Unix
|
||||
/// epoch) or the future (after the year 9999).
|
||||
#[error("invalid HTTP date")]
|
||||
InvalidHttpDate,
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user