#![doc(html_favicon_url = "https://www.ruma.io/favicon.ico")]
#![doc(html_logo_url = "https://www.ruma.io/images/logo.png")]
#![warn(missing_docs)]
#[cfg(not(all(feature = "client", feature = "server")))]
compile_error!("ruma_api's Cargo features only exist as a workaround are not meant to be disabled");
use std::{convert::TryInto as _, error::Error as StdError, fmt};
use bytes::BufMut;
use ruma_identifiers::UserId;
pub use ruma_api_macros::ruma_api;
pub mod error;
#[doc(hidden)]
pub mod exports {
pub use bytes;
pub use http;
pub use percent_encoding;
pub use ruma_api_macros;
pub use ruma_serde;
pub use serde;
pub use serde_json;
}
mod metadata;
pub use metadata::{MatrixVersion, Metadata};
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 {
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 = ruma_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> {
let greater_or_equal_any =
|version: MatrixVersion| versions.iter().any(|v| v.is_superset_of(version));
let greater_or_equal_all =
|version: MatrixVersion| versions.iter().all(|v| v.is_superset_of(version));
let is_stable_any = metadata.added.map(greater_or_equal_any).unwrap_or(false);
let is_removed_all = metadata.removed.map(greater_or_equal_all).unwrap_or(false);
if is_stable_any && is_removed_all {
return Err(IntoHttpError::EndpointRemoved(metadata.removed.unwrap()));
}
if is_stable_any {
let is_deprecated_any = metadata.deprecated.map(greater_or_equal_any).unwrap_or(false);
if is_deprecated_any {
let is_removed_any = metadata.removed.map(greater_or_equal_any).unwrap_or(false);
if is_removed_any {
tracing::warn!("endpoint {} is deprecated, but also removed in one of more server-passed versions: {:?}", metadata.name, versions)
} else {
tracing::warn!(
"endpoint {} is deprecated in one of more server-passed versions: {:?}",
metadata.name,
versions
)
}
}
if let Some(r0) = r0 {
if versions.iter().all(|&v| v == MatrixVersion::V1_0) {
return Ok(r0);
}
}
return Ok(stable.expect("metadata.added enforces the stable path to exist"));
}
unstable.ok_or(IntoHttpError::NoUnstablePath)
}