use std::{convert::TryInto as _, error::Error as StdError, fmt};
use bytes::BufMut;
use tracing::warn;
use crate::UserId;
pub use ruma_macros::ruma_api;
pub mod error;
mod metadata;
pub use metadata::{MatrixVersion, Metadata, VersioningDecision};
use error::{FromHttpRequestError, FromHttpResponseError, IntoHttpError};
#[derive(Clone, Copy, Debug)]
#[allow(clippy::exhaustive_enums)]
pub enum SendAccessToken<'a> {
IfRequired(&'a str),
Always(&'a str),
None,
}
impl<'a> SendAccessToken<'a> {
pub fn get_required_for_endpoint(self) -> Option<&'a str> {
match self {
Self::IfRequired(tok) | Self::Always(tok) => Some(tok),
Self::None => None,
}
}
pub fn get_not_required_for_endpoint(self) -> Option<&'a str> {
match self {
Self::Always(tok) => Some(tok),
Self::IfRequired(_) | Self::None => None,
}
}
}
pub trait OutgoingRequest: Sized + Clone {
type EndpointError: EndpointError;
type IncomingResponse: IncomingResponse<EndpointError = Self::EndpointError>;
const METADATA: Metadata;
fn try_into_http_request<T: Default + BufMut>(
self,
base_url: &str,
access_token: SendAccessToken<'_>,
considering_versions: &'_ [MatrixVersion],
) -> Result<http::Request<T>, IntoHttpError>;
}
pub trait IncomingResponse: Sized {
type EndpointError: EndpointError;
fn try_from_http_response<T: AsRef<[u8]>>(
response: http::Response<T>,
) -> Result<Self, FromHttpResponseError<Self::EndpointError>>;
}
pub trait OutgoingRequestAppserviceExt: OutgoingRequest {
fn try_into_http_request_with_user_id<T: Default + BufMut>(
self,
base_url: &str,
access_token: SendAccessToken<'_>,
user_id: &UserId,
considering_versions: &'_ [MatrixVersion],
) -> Result<http::Request<T>, IntoHttpError> {
let mut http_request =
self.try_into_http_request(base_url, access_token, considering_versions)?;
let user_id_query = crate::serde::urlencoded::to_string([("user_id", user_id)])?;
let uri = http_request.uri().to_owned();
let mut parts = uri.into_parts();
let path_and_query_with_user_id = match &parts.path_and_query {
Some(path_and_query) => match path_and_query.query() {
Some(_) => format!("{path_and_query}&{user_id_query}"),
None => format!("{path_and_query}?{user_id_query}"),
},
None => format!("/?{user_id_query}"),
};
parts.path_and_query =
Some(path_and_query_with_user_id.try_into().map_err(http::Error::from)?);
*http_request.uri_mut() = parts.try_into().map_err(http::Error::from)?;
Ok(http_request)
}
}
impl<T: OutgoingRequest> OutgoingRequestAppserviceExt for T {}
pub trait IncomingRequest: Sized {
type EndpointError: EndpointError;
type OutgoingResponse: OutgoingResponse;
const METADATA: Metadata;
fn try_from_http_request<B, S>(
req: http::Request<B>,
path_args: &[S],
) -> Result<Self, FromHttpRequestError>
where
B: AsRef<[u8]>,
S: AsRef<str>;
}
pub trait OutgoingResponse {
fn try_into_http_response<T: Default + BufMut>(
self,
) -> Result<http::Response<T>, IntoHttpError>;
}
pub trait EndpointError: OutgoingResponse + StdError + Sized + Send + 'static {
fn try_from_http_response<T: AsRef<[u8]>>(
response: http::Response<T>,
) -> Result<Self, error::DeserializationError>;
}
pub trait OutgoingNonAuthRequest: OutgoingRequest {}
pub trait IncomingNonAuthRequest: IncomingRequest {}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[allow(clippy::exhaustive_enums)]
pub enum AuthScheme {
None,
AccessToken,
ServerSignatures,
QueryOnlyAccessToken,
}
#[doc(hidden)]
pub fn select_path<'a>(
versions: &'_ [MatrixVersion],
metadata: &'_ Metadata,
unstable: Option<fmt::Arguments<'a>>,
r0: Option<fmt::Arguments<'a>>,
stable: Option<fmt::Arguments<'a>>,
) -> Result<fmt::Arguments<'a>, IntoHttpError> {
match metadata.versioning_decision_for(versions) {
VersioningDecision::Removed => Err(IntoHttpError::EndpointRemoved(
metadata.removed.expect("VersioningDecision::Removed implies metadata.removed"),
)),
VersioningDecision::Stable { any_deprecated, all_deprecated, any_removed } => {
if any_removed {
if all_deprecated {
warn!(
"endpoint {} is removed in some (and deprecated in ALL) of the following versions: {:?}",
metadata.name,
versions
);
} else if any_deprecated {
warn!(
"endpoint {} is removed (and deprecated) in some of the following versions: {:?}",
metadata.name,
versions
);
} else {
unreachable!("any_removed implies *_deprecated");
}
} else if all_deprecated {
warn!(
"endpoint {} is deprecated in ALL of the following versions: {:?}",
metadata.name, versions
);
} else if any_deprecated {
warn!(
"endpoint {} is deprecated in some of the following versions: {:?}",
metadata.name, versions
);
}
if let Some(r0) = r0 {
if versions.iter().all(|&v| v == MatrixVersion::V1_0) {
return Ok(r0);
}
}
Ok(stable.expect("metadata.added enforces the stable path to exist"))
}
VersioningDecision::Unstable => unstable.ok_or(IntoHttpError::NoUnstablePath),
}
}