Add 'ruma-client/' from commit 'fab0ef566f143c1926bff4af4a0a4751379eff1f'

git-subtree-dir: ruma-client
git-subtree-mainline: c4558774be98863015c51e0ec8f26b3fe3d4a103
git-subtree-split: fab0ef566f143c1926bff4af4a0a4751379eff1f
This commit is contained in:
Jonas Platte 2020-08-10 23:49:55 +02:00
commit 3a6bdd63bd
12 changed files with 883 additions and 0 deletions

View File

@ -0,0 +1,27 @@
image: archlinux
packages:
- rustup
sources:
- https://github.com/ruma/ruma-client
tasks:
- rustup: |
# We specify --profile minimal because we'd otherwise download docs
rustup toolchain install beta --profile minimal -c rustfmt -c clippy
rustup default beta
- test: |
cd ruma-client
# We don't want the build to stop on individual failure of independent
# tools, so capture tool exit codes and set the task exit code manually
set +e
cargo fmt -- --check
fmt_exit=$?
cargo clippy --all-targets --all-features -- -D warnings
clippy_exit=$?
cargo test --verbose
test_exit=$?
exit $(( $fmt_exit || $clippy_exit || $test_exit ))

View File

@ -0,0 +1,32 @@
image: archlinux
packages:
- rustup
sources:
- https://github.com/ruma/ruma-client
tasks:
- rustup: |
rustup toolchain install nightly --profile minimal
rustup default nightly
# Try installing rustfmt & clippy for nightly, but don't fail the build
# if they are not available
rustup component add rustfmt || true
rustup component add clippy || true
- test: |
cd ruma-client
# We don't want the build to stop on individual failure of independent
# tools, so capture tool exit codes and set the task exit code manually
set +e
if ( rustup component list | grep -q rustfmt ); then
cargo fmt -- --check
fi
fmt_exit=$?
if ( rustup component list | grep -q clippy ); then
cargo clippy --all-targets --all-features -- -D warnings
fi
clippy_exit=$?
exit $(( $fmt_exit || $clippy_exit ))

View File

@ -0,0 +1,29 @@
image: archlinux
packages:
- rustup
sources:
- https://github.com/ruma/ruma-client
tasks:
- rustup: |
# We specify --profile minimal because we'd otherwise download docs
rustup toolchain install stable --profile minimal -c rustfmt -c clippy
rustup default stable
- test: |
cd ruma-client
# We don't want the build to stop on individual failure of independent
# tools, so capture tool exit codes and set the task exit code manually
set +e
cargo fmt -- --check
fmt_exit=$?
cargo clippy --all-targets --all-features -- -D warnings
clippy_exit=$?
cargo test --verbose
test_exit=$?
exit $(( $fmt_exit || $clippy_exit || $test_exit ))
# TODO: Add audit task once cargo-audit binary releases are available.
# See https://github.com/RustSec/cargo-audit/issues/66

2
ruma-client/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/Cargo.lock
/target

40
ruma-client/Cargo.toml Normal file
View File

@ -0,0 +1,40 @@
[package]
authors = [
"Jimmy Cuadra <jimmy@jimmycuadra.com>",
"Jonas Platte <jplatte@posteo.de>",
]
categories = ["api-bindings", "web-programming"]
description = "A Matrix client library."
documentation = "https://docs.rs/ruma-client"
edition = "2018"
homepage = "https://github.com/ruma/ruma-client"
keywords = ["matrix", "chat", "messaging", "ruma"]
license = "MIT"
name = "ruma-client"
readme = "README.md"
repository = "https://github.com/ruma/ruma-client"
version = "0.4.0"
[dependencies]
futures-core = "0.3.5"
futures-util = "0.3.5"
http = "0.2.1"
hyper = "0.13.5"
hyper-tls = { version = "0.4.1", optional = true }
ruma-api = "=0.17.0-alpha.1"
ruma-client-api = "=0.10.0-alpha.1"
ruma-common = "0.2.0"
ruma-events = "=0.22.0-alpha.1"
ruma-identifiers = "0.17.1"
serde = { version = "1.0.110", features = ["derive"] }
serde_json = "1.0.53"
serde_urlencoded = "0.6.1"
url = "2.1.1"
[dev-dependencies]
anyhow = "1.0.31"
tokio = { version = "0.2.21", features = ["macros"] }
[features]
default = ["tls"]
tls = ["hyper-tls"]

20
ruma-client/LICENSE Normal file
View File

@ -0,0 +1,20 @@
Copyright (c) 2016 Jimmy Cuadra
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

38
ruma-client/README.md Normal file
View File

@ -0,0 +1,38 @@
# ruma-client
[![crates.io page](https://img.shields.io/crates/v/ruma-client.svg)](https://crates.io/crates/ruma-client)
[![docs.rs page](https://docs.rs/ruma-client/badge.svg)](https://docs.rs/ruma-client/)
[![build status](https://travis-ci.org/ruma/ruma-client.svg?branch=master)](https://travis-ci.org/ruma/ruma-client)
![license: MIT](https://img.shields.io/crates/l/ruma-client.svg)
**ruma-client** is a [Matrix][] client library for [Rust][].
[Matrix]: https://matrix.org/
[Rust]: https://www.rust-lang.org/
## Status
This project is a work in progress and not ready for production usage yet. Most endpoints that are
available in this crate are usable with an up-to-date synapse server, but no comprehensive testing
has been done so far.
As long as the matrix client-server API is still at version 0.x, only the latest API revision is
considered supported. However, due to the low amount of available manpower, it can take a long time
for all changes from a new API revision to arrive in ruma-client (at the time of writing only few
endpoints have received an update for r0.4.0).
## Contributing
If you want to help out, have a look at the issues here and on the other [ruma-\*][gh-org]
repositories (ruma-client-api and ruma-events in particular contain much of the code that powers
ruma-client).
There is also a [room for ruma on matrix.org][#ruma:matrix.org], which can be used for questions
and discussion related to any of the crates in this project.
[gh-org]: https://github.com/ruma
[#ruma:matrix.org]: https://matrix.to/#/#ruma:matrix.org
## Minimum Rust version
ruma-client requires Rust 1.39.0 or later.

View File

@ -0,0 +1,65 @@
use std::{convert::TryFrom, env, process::exit};
use ruma_client::{
self,
api::r0,
events::{
room::message::{MessageEventContent, TextMessageEventContent},
EventType,
},
identifiers::RoomAliasId,
Client,
};
use serde_json::value::to_raw_value as to_raw_json_value;
use url::Url;
async fn hello_world(homeserver_url: Url, room: String) -> anyhow::Result<()> {
let client = Client::new(homeserver_url, None);
client.register_guest().await?;
let response = client
.request(r0::alias::get_alias::Request {
room_alias: RoomAliasId::try_from(&room[..]).unwrap(),
})
.await?;
let room_id = response.room_id;
client
.request(r0::membership::join_room_by_id::Request {
room_id: room_id.clone(),
third_party_signed: None,
})
.await?;
client
.request(r0::message::create_message_event::Request {
room_id,
event_type: EventType::RoomMessage,
txn_id: "1".to_owned(),
data: to_raw_json_value(&MessageEventContent::Text(TextMessageEventContent {
body: "Hello World!".to_owned(),
formatted: None,
relates_to: None,
}))?,
})
.await?;
Ok(())
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let (homeserver_url, room) = match (env::args().nth(1), env::args().nth(2)) {
(Some(a), Some(b)) => (a, b),
_ => {
eprintln!(
"Usage: {} <homeserver_url> <room>",
env::args().next().unwrap()
);
exit(1)
}
};
hello_world(homeserver_url.parse().unwrap(), room).await
}

View File

@ -0,0 +1,81 @@
use std::{env, process::exit, time::Duration};
use futures_util::stream::{StreamExt as _, TryStreamExt as _};
use ruma_client::{
self,
events::room::message::{MessageEventContent, TextMessageEventContent},
HttpClient,
};
use ruma_common::presence::PresenceState;
use ruma_events::{AnySyncMessageEvent, AnySyncRoomEvent, SyncMessageEvent};
use url::Url;
async fn log_messages(
homeserver_url: Url,
username: String,
password: String,
) -> anyhow::Result<()> {
let client = HttpClient::new(homeserver_url, None);
client.log_in(username, password, None, None).await?;
let mut sync_stream = Box::pin(
client
.sync(
None,
None,
PresenceState::Online,
Some(Duration::from_secs(30)),
)
// TODO: This is a horrible way to obtain an initial next_batch token that generates way
// too much server load and network traffic. Fix this!
.skip(1),
);
while let Some(res) = sync_stream.try_next().await? {
// Only look at rooms the user hasn't left yet
for (room_id, room) in res.rooms.join {
for event in room
.timeline
.events
.into_iter()
.flat_map(|r| r.deserialize())
{
// Filter out the text messages
if let AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomMessage(
SyncMessageEvent {
content:
MessageEventContent::Text(TextMessageEventContent {
body: msg_body, ..
}),
sender,
..
},
)) = event
{
println!("{:?} in {:?}: {}", sender, room_id, msg_body);
}
}
}
}
Ok(())
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let (homeserver_url, username, password) =
match (env::args().nth(1), env::args().nth(2), env::args().nth(3)) {
(Some(a), Some(b), Some(c)) => (a, b, c),
_ => {
eprintln!(
"Usage: {} <homeserver_url> <username> <password>",
env::args().next().unwrap()
);
exit(1)
}
};
let server = Url::parse(&homeserver_url).unwrap();
log_messages(server, username, password).await
}

69
ruma-client/src/error.rs Normal file
View File

@ -0,0 +1,69 @@
//! Error conditions.
use std::fmt::{self, Debug, Display, Formatter};
use ruma_api::error::{FromHttpResponseError, IntoHttpError};
/// An error that can occur during client operations.
#[derive(Debug)]
#[non_exhaustive]
pub enum Error<E> {
/// Queried endpoint requires authentication but was called on an anonymous client.
AuthenticationRequired,
/// Construction of the HTTP request failed (this should never happen).
IntoHttp(IntoHttpError),
/// The request's URL is invalid (this should never happen).
Url(UrlError),
/// Couldn't obtain an HTTP response (e.g. due to network or DNS issues).
Response(ResponseError),
/// Converting the HTTP response to one of ruma's types failed.
FromHttpResponse(FromHttpResponseError<E>),
}
impl<E: Display> Display for Error<E> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::AuthenticationRequired => {
write!(f, "The queried endpoint requires authentication but was called with an anonymous client.")
}
Self::IntoHttp(err) => write!(f, "HTTP request construction failed: {}", err),
Self::Url(UrlError(err)) => write!(f, "Invalid URL: {}", err),
Self::Response(ResponseError(err)) => write!(f, "Couldn't obtain a response: {}", err),
Self::FromHttpResponse(err) => write!(f, "HTTP response conversion failed: {}", err),
}
}
}
impl<E> From<IntoHttpError> for Error<E> {
fn from(err: IntoHttpError) -> Self {
Error::IntoHttp(err)
}
}
#[doc(hidden)]
impl<E> From<http::uri::InvalidUri> for Error<E> {
fn from(err: http::uri::InvalidUri) -> Self {
Error::Url(UrlError(err))
}
}
#[doc(hidden)]
impl<E> From<hyper::Error> for Error<E> {
fn from(err: hyper::Error) -> Self {
Error::Response(ResponseError(err))
}
}
impl<E> From<FromHttpResponseError<E>> for Error<E> {
fn from(err: FromHttpResponseError<E>) -> Self {
Error::FromHttpResponse(err)
}
}
impl<E: Debug + Display> std::error::Error for Error<E> {}
#[derive(Debug)]
pub struct UrlError(http::uri::InvalidUri);
#[derive(Debug)]
pub struct ResponseError(hyper::Error);

423
ruma-client/src/lib.rs Normal file
View File

@ -0,0 +1,423 @@
//! Crate `ruma_client` is a [Matrix](https://matrix.org/) client library.
//!
//! # Usage
//!
//! Begin by creating a `Client` type, usually using the `https` method for a client that supports
//! secure connections, and then logging in:
//!
//! ```no_run
//! use ruma_client::Client;
//!
//! let work = async {
//! let homeserver_url = "https://example.com".parse().unwrap();
//! let client = Client::https(homeserver_url, None);
//!
//! let session = client
//! .log_in("@alice:example.com".to_string(), "secret".to_string(), None, None)
//! .await?;
//!
//! // You're now logged in! Write the session to a file if you want to restore it later.
//! // Then start using the API!
//! # Result::<(), ruma_client::Error<_>>::Ok(())
//! };
//! ```
//!
//! You can also pass an existing session to the `Client` constructor to restore a previous session
//! rather than calling `log_in`. This can also be used to create a session for an application service
//! that does not need to log in, but uses the access_token directly:
//!
//! ```no_run
//! use ruma_client::{Client, Session};
//!
//! let work = async {
//! let homeserver_url = "https://example.com".parse().unwrap();
//! let session = Session{access_token: "as_access_token".to_string(), identification: None};
//! let client = Client::https(homeserver_url, Some(session));
//!
//! // make calls to the API
//! };
//! ```
//!
//! For the standard use case of synchronizing with the homeserver (i.e. getting all the latest
//! events), use the `Client::sync`:
//!
//! ```no_run
//! use std::time::Duration;
//!
//! # use futures_util::stream::{StreamExt as _, TryStreamExt as _};
//! # use ruma_client::Client;
//! # use ruma_common::presence::PresenceState;
//! # let homeserver_url = "https://example.com".parse().unwrap();
//! # let client = Client::https(homeserver_url, None);
//! # let next_batch_token = String::new();
//! # async {
//! let mut sync_stream = Box::pin(client.sync(
//! None,
//! Some(next_batch_token),
//! PresenceState::Online,
//! Some(Duration::from_secs(30)),
//! ));
//! while let Some(response) = sync_stream.try_next().await? {
//! // Do something with the data in the response...
//! }
//! # Result::<(), ruma_client::Error<_>>::Ok(())
//! # };
//! ```
//!
//! The `Client` type also provides methods for registering a new account if you don't already have
//! one with the given homeserver.
//!
//! Beyond these basic convenience methods, `ruma-client` gives you access to the entire Matrix
//! client-server API via the `api` module. Each leaf module under this tree of modules contains
//! the necessary types for one API endpoint. Simply call the module's `call` method, passing it
//! the logged in `Client` and the relevant `Request` type. `call` will return a future that will
//! resolve to the relevant `Response` type.
//!
//! For example:
//!
//! ```no_run
//! # use ruma_client::Client;
//! # let homeserver_url = "https://example.com".parse().unwrap();
//! # let client = Client::https(homeserver_url, None);
//! use std::convert::TryFrom;
//!
//! use ruma_client::api::r0::alias::get_alias;
//! use ruma_identifiers::{RoomAliasId, RoomId};
//!
//! async {
//! let response = client
//! .request(get_alias::Request {
//! room_alias: RoomAliasId::try_from("#example_room:example.com").unwrap(),
//! })
//! .await?;
//!
//! assert_eq!(response.room_id, RoomId::try_from("!n8f893n9:example.com").unwrap());
//! # Result::<(), ruma_client::Error<_>>::Ok(())
//! }
//! # ;
//! ```
#![warn(rust_2018_idioms)]
#![deny(
missing_copy_implementations,
missing_debug_implementations,
missing_docs
)]
use std::{
convert::TryFrom,
str::FromStr,
sync::{Arc, Mutex},
time::Duration,
};
use futures_core::{
future::Future,
stream::{Stream, TryStream},
};
use futures_util::stream;
use http::Response as HttpResponse;
use hyper::{client::HttpConnector, Client as HyperClient, Uri};
#[cfg(feature = "hyper-tls")]
use hyper_tls::HttpsConnector;
use ruma_api::Endpoint;
use ruma_identifiers::DeviceId;
use std::collections::BTreeMap;
use url::Url;
pub use ruma_client_api as api;
pub use ruma_events as events;
pub use ruma_identifiers as identifiers;
mod error;
mod session;
pub use self::{error::Error, session::Identification, session::Session};
/// A client for the Matrix client-server API.
#[derive(Debug)]
pub struct Client<C>(Arc<ClientData<C>>);
/// Data contained in Client's Rc
#[derive(Debug)]
struct ClientData<C> {
/// The URL of the homeserver to connect to.
homeserver_url: Url,
/// The underlying HTTP client.
hyper: HyperClient<C>,
/// User session data.
session: Mutex<Option<Session>>,
}
/// Non-secured variant of the client (using plain HTTP requests)
pub type HttpClient = Client<HttpConnector>;
impl HttpClient {
/// Creates a new client for making HTTP requests to the given homeserver.
pub fn new(homeserver_url: Url, session: Option<Session>) -> Self {
Self(Arc::new(ClientData {
homeserver_url,
hyper: HyperClient::builder().build_http(),
session: Mutex::new(session),
}))
}
}
/// Secured variant of the client (using HTTPS requests)
#[cfg(feature = "tls")]
pub type HttpsClient = Client<HttpsConnector<HttpConnector>>;
#[cfg(feature = "tls")]
impl HttpsClient {
/// Creates a new client for making HTTPS requests to the given homeserver.
pub fn https(homeserver_url: Url, session: Option<Session>) -> Self {
let connector = HttpsConnector::new();
Self(Arc::new(ClientData {
homeserver_url,
hyper: HyperClient::builder().build(connector),
session: Mutex::new(session),
}))
}
}
impl<C> Client<C>
where
C: hyper::client::connect::Connect + Clone + Send + Sync + 'static,
{
/// Creates a new client using the given `hyper::Client`.
///
/// This allows the user to configure the details of HTTP as desired.
pub fn custom(
hyper_client: HyperClient<C>,
homeserver_url: Url,
session: Option<Session>,
) -> Self {
Self(Arc::new(ClientData {
homeserver_url,
hyper: hyper_client,
session: Mutex::new(session),
}))
}
/// Get a copy of the current `Session`, if any.
///
/// Useful for serializing and persisting the session to be restored later.
pub fn session(&self) -> Option<Session> {
self.0
.session
.lock()
.expect("session mutex was poisoned")
.clone()
}
/// Log in with a username and password.
///
/// In contrast to `api::r0::session::login::call()`, this method stores the
/// session data returned by the endpoint in this client, instead of
/// returning it.
pub async fn log_in(
&self,
user: String,
password: String,
device_id: Option<Box<DeviceId>>,
initial_device_display_name: Option<String>,
) -> Result<Session, Error<api::Error>> {
use api::r0::session::login;
let response = self
.request(login::Request {
user: login::UserInfo::MatrixId(user),
login_info: login::LoginInfo::Password { password },
device_id,
initial_device_display_name,
})
.await?;
let session = Session {
access_token: response.access_token,
identification: Some(Identification {
device_id: response.device_id,
user_id: response.user_id,
}),
};
*self.0.session.lock().unwrap() = Some(session.clone());
Ok(session)
}
/// Register as a guest. In contrast to `api::r0::account::register::call()`,
/// this method stores the session data returned by the endpoint in this
/// client, instead of returning it.
pub async fn register_guest(&self) -> Result<Session, Error<api::r0::uiaa::UiaaResponse>> {
use api::r0::account::register;
let response = self
.request(register::Request {
auth: None,
device_id: None,
inhibit_login: false,
initial_device_display_name: None,
kind: Some(register::RegistrationKind::Guest),
password: None,
username: None,
})
.await?;
let session = Session {
// since we supply inhibit_login: false above, the access token needs to be there
// TODO: maybe unwrap is not the best solution though
access_token: response.access_token.unwrap(),
identification: Some(Identification {
// same as access_token
device_id: response.device_id.unwrap(),
user_id: response.user_id,
}),
};
*self.0.session.lock().unwrap() = Some(session.clone());
Ok(session)
}
/// Register as a new user on this server.
///
/// In contrast to `api::r0::account::register::call()`, this method stores
/// the session data returned by the endpoint in this client, instead of
/// returning it.
///
/// The username is the local part of the returned user_id. If it is
/// omitted from this request, the server will generate one.
pub async fn register_user(
&self,
username: Option<String>,
password: String,
) -> Result<Session, Error<api::r0::uiaa::UiaaResponse>> {
use api::r0::account::register;
let response = self
.request(register::Request {
auth: None,
device_id: None,
inhibit_login: false,
initial_device_display_name: None,
kind: Some(register::RegistrationKind::User),
password: Some(password),
username,
})
.await?;
let session = Session {
// since we supply inhibit_login: false above, the access token needs to be there
// TODO: maybe unwrap is not the best solution though
access_token: response.access_token.unwrap(),
identification: Some(Identification {
// same as access_token
device_id: response.device_id.unwrap(),
user_id: response.user_id,
}),
};
*self.0.session.lock().unwrap() = Some(session.clone());
Ok(session)
}
/// Convenience method that represents repeated calls to the sync_events endpoint as a stream.
///
/// If the since parameter is None, the first Item might take a significant time to arrive and
/// be deserialized, because it contains all events that have occurred in the whole lifetime of
/// the logged-in users account and are visible to them.
pub fn sync(
&self,
filter: Option<api::r0::sync::sync_events::Filter>,
since: Option<String>,
set_presence: ruma_common::presence::PresenceState,
timeout: Option<Duration>,
) -> impl Stream<Item = Result<api::r0::sync::sync_events::Response, Error<api::Error>>>
+ TryStream<Ok = api::r0::sync::sync_events::Response, Error = Error<api::Error>> {
use api::r0::sync::sync_events;
let client = self.clone();
stream::try_unfold(since, move |since| {
let client = client.clone();
let filter = filter.clone();
async move {
let response = client
.request(sync_events::Request {
filter,
since,
full_state: false,
set_presence,
timeout,
})
.await?;
let next_batch_clone = response.next_batch.clone();
Ok(Some((response, Some(next_batch_clone))))
}
})
}
/// Makes a request to a Matrix API endpoint.
pub fn request<Request: Endpoint>(
&self,
request: Request,
) -> impl Future<Output = Result<Request::Response, Error<Request::ResponseError>>> {
self.request_with_url_params(request, None)
}
/// Makes a request to a Matrix API endpoint including additional URL parameters.
pub fn request_with_url_params<Request: Endpoint>(
&self,
request: Request,
params: Option<BTreeMap<String, String>>,
) -> impl Future<Output = Result<Request::Response, Error<Request::ResponseError>>> {
let client = self.0.clone();
let mut url = client.homeserver_url.clone();
async move {
let mut hyper_request = request.try_into()?.map(hyper::Body::from);
{
let uri = hyper_request.uri();
url.set_path(uri.path());
url.set_query(uri.query());
if let Some(params) = params {
for (key, value) in params {
url.query_pairs_mut().append_pair(&key, &value);
}
}
if Request::METADATA.requires_authentication {
if let Some(ref session) = *client.session.lock().unwrap() {
url.query_pairs_mut()
.append_pair("access_token", &session.access_token);
} else {
return Err(Error::AuthenticationRequired);
}
}
}
*hyper_request.uri_mut() = Uri::from_str(url.as_ref())?;
let hyper_response = client.hyper.request(hyper_request).await?;
let (head, body) = hyper_response.into_parts();
// FIXME: We read the response into a contiguous buffer here (not actually required for
// deserialization) and then copy the whole thing to convert from Bytes to Vec<u8>.
let full_body = hyper::body::to_bytes(body).await?;
let full_response = HttpResponse::from_parts(head, full_body.as_ref().to_owned());
Ok(Request::Response::try_from(full_response)?)
}
}
}
impl<C> Clone for Client<C> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}

View File

@ -0,0 +1,57 @@
//! User sessions.
use ruma_identifiers::{DeviceId, UserId};
/// A user session, containing an access token and information about the associated user account.
#[derive(Clone, Debug, serde::Deserialize, Eq, Hash, PartialEq, serde::Serialize)]
pub struct Session {
/// The access token used for this session.
pub access_token: String,
/// Identification information for a user
pub identification: Option<Identification>,
}
/// The identification information about the associated user account if the session is associated with
/// a single user account.
#[derive(Clone, Debug, serde::Deserialize, Eq, Hash, PartialEq, serde::Serialize)]
pub struct Identification {
/// The user the access token was issued for.
pub user_id: UserId,
/// The ID of the client device
pub device_id: Box<DeviceId>,
}
impl Session {
/// Create a new user session from an access token and a user ID.
#[deprecated]
pub fn new(access_token: String, user_id: UserId, device_id: Box<DeviceId>) -> Self {
Self {
access_token,
identification: Some(Identification { user_id, device_id }),
}
}
/// Get the access token associated with this session.
#[deprecated]
pub fn access_token(&self) -> &str {
&self.access_token
}
/// Get the ID of the user the session belongs to.
#[deprecated]
pub fn user_id(&self) -> Option<&UserId> {
if let Some(identification) = &self.identification {
return Some(&identification.user_id);
}
None
}
/// Get ID of the device the session belongs to.
#[deprecated]
pub fn device_id(&self) -> Option<&DeviceId> {
if let Some(identification) = &self.identification {
return Some(&identification.device_id);
}
None
}
}