diff --git a/examples/command_bot.rs b/examples/command_bot.rs index 220b0ea1..0cf607ba 100644 --- a/examples/command_bot.rs +++ b/examples/command_bot.rs @@ -12,11 +12,14 @@ use url::Url; struct CommandBot { /// This clone of the `AsyncClient` will send requests to the server, /// while the other keeps us in sync with the server using `sync_forever`. - client: AsyncClient, + /// + /// The two type parameters are for the `StateStore` trait and specify the `Store` + /// type and `IoError` type to use, here we don't care. + client: AsyncClient<(), ()>, } impl CommandBot { - pub fn new(client: AsyncClient) -> Self { + pub fn new(client: AsyncClient<(), ()>) -> Self { Self { client } } } diff --git a/examples/login.rs b/examples/login.rs index 150684c7..4521332c 100644 --- a/examples/login.rs +++ b/examples/login.rs @@ -45,7 +45,8 @@ async fn login( .proxy("http://localhost:8080")? .disable_ssl_verification(); let homeserver_url = Url::parse(&homeserver_url)?; - let mut client = AsyncClient::new_with_config(homeserver_url, None, client_config).unwrap(); + let mut client = + AsyncClient::<(), ()>::new_with_config(homeserver_url, None, client_config).unwrap(); client.add_event_emitter(Box::new(EventCallback)).await; diff --git a/src/async_client.rs b/src/async_client.rs index f206b9e9..cdd12870 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -55,16 +55,16 @@ const DEFAULT_SYNC_TIMEOUT: Duration = Duration::from_secs(30); /// An async/await enabled Matrix client. /// /// All of the state is held in an `Arc` so the `AsyncClient` can be cloned freely. -pub struct AsyncClient { +pub struct AsyncClient { /// The URL of the homeserver to connect to. homeserver: Url, /// The underlying HTTP client. http_client: reqwest::Client, /// User session data. - pub(crate) base_client: Arc>, + pub(crate) base_client: Arc>>, } -impl std::fmt::Debug for AsyncClient { +impl std::fmt::Debug for AsyncClient { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> StdResult<(), std::fmt::Error> { write!(fmt, "AsyncClient {{ homeserver: {} }}", self.homeserver) } @@ -197,7 +197,7 @@ use api::r0::room::create_room; use api::r0::session::login; use api::r0::sync::sync_events; -impl AsyncClient { +impl AsyncClient { /// Creates a new client for making HTTP requests to the given homeserver. /// /// # Arguments @@ -485,7 +485,7 @@ impl AsyncClient { /// .name("name") /// .room_version("v1.0"); /// - /// let mut cli = AsyncClient::new(homeserver, None).unwrap(); + /// let mut cli = AsyncClient::<(), ()>::new(homeserver, None).unwrap(); /// # use futures::executor::block_on; /// # block_on(async { /// assert!(cli.create_room(builder).await.is_ok()); @@ -529,7 +529,7 @@ impl AsyncClient { /// .direction(Direction::Backward) /// .limit(UInt::new(10).unwrap()); /// - /// let mut cli = AsyncClient::new(homeserver, None).unwrap(); + /// let mut cli = AsyncClient::<(), ()>::new(homeserver, None).unwrap(); /// # use futures::executor::block_on; /// # block_on(async { /// assert!(cli.room_messages(builder).await.is_ok()); @@ -673,7 +673,7 @@ impl AsyncClient { /// # use futures::executor::block_on; /// # block_on(async { /// # let homeserver = Url::parse("http://localhost:8080").unwrap(); - /// # let mut client = AsyncClient::new(homeserver, None).unwrap(); + /// # let mut client = AsyncClient::<(), ()>::new(homeserver, None).unwrap(); /// /// use async_std::sync::channel; /// @@ -863,7 +863,7 @@ impl AsyncClient { /// use matrix_sdk::events::room::message::{MessageEventContent, TextMessageEventContent}; /// # block_on(async { /// # let homeserver = Url::parse("http://localhost:8080").unwrap(); - /// # let mut client = AsyncClient::new(homeserver, None).unwrap(); + /// # let mut client = AsyncClient::<(), ()>::new(homeserver, None).unwrap(); /// # let room_id = RoomId::try_from("!test:localhost").unwrap(); /// use uuid::Uuid; /// @@ -1120,7 +1120,7 @@ mod test { device_id: "DEVICEID".to_owned(), }; let homeserver = url::Url::parse(&mockito::server_url()).unwrap(); - let client = AsyncClient::new(homeserver, Some(session)).unwrap(); + let client = AsyncClient::<(), ()>::new(homeserver, Some(session)).unwrap(); let rid = RoomId::try_from("!roomid:room.com").unwrap(); let uid = UserId::try_from("@example:localhost").unwrap(); @@ -1152,7 +1152,7 @@ mod test { }; let homeserver = url::Url::parse(&mockito::server_url()).unwrap(); - let client = AsyncClient::new(homeserver, Some(session)).unwrap(); + let client = AsyncClient::<(), ()>::new(homeserver, Some(session)).unwrap(); let mut bld = EventBuilder::default() .add_room_event_from_file("./tests/data/events/member.json", RoomEvent::RoomMember) @@ -1182,7 +1182,7 @@ mod test { .with_body_from_file("tests/data/login_response_error.json") .create(); - let client = AsyncClient::new(homeserver, None).unwrap(); + let client = AsyncClient::<(), ()>::new(homeserver, None).unwrap(); if let Err(err) = client.login("example", "wordpass", None, None).await { if let crate::Error::RumaResponse(ruma_api::error::FromHttpResponseError::Http( diff --git a/src/base_client.rs b/src/base_client.rs index 30cefc79..1655fe86 100644 --- a/src/base_client.rs +++ b/src/base_client.rs @@ -34,6 +34,7 @@ use crate::events::EventResult; use crate::identifiers::{RoomId, UserId}; use crate::models::Room; use crate::session::Session; +use crate::state::StateStore; use crate::EventEmitter; #[cfg(feature = "encryption")] @@ -60,7 +61,7 @@ pub type Token = String; /// /// This Client is a state machine that receives responses and events and /// accordingly updates it's state. -pub struct Client { +pub struct Client { /// The current client session containing our user id, device id and access /// token. pub session: Option, @@ -75,12 +76,14 @@ pub struct Client { /// Any implementor of EventEmitter will act as the callbacks for various /// events. pub event_emitter: Option>, + /// + pub state_store: Option>>, #[cfg(feature = "encryption")] olm: Arc>>, } -impl fmt::Debug for Client { +impl fmt::Debug for Client { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Client") .field("session", &self.session) @@ -93,7 +96,7 @@ impl fmt::Debug for Client { } } -impl Client { +impl Client { /// Create a new client. /// /// # Arguments @@ -114,6 +117,7 @@ impl Client { ignored_users: Vec::new(), push_ruleset: None, event_emitter: None, + state_store: None, #[cfg(feature = "encryption")] olm: Arc::new(Mutex::new(olm)), }) @@ -811,7 +815,7 @@ mod test { .with_body_from_file("tests/data/sync.json") .create(); - let client = AsyncClient::new(homeserver, Some(session)).unwrap(); + let client = AsyncClient::<(), ()>::new(homeserver, Some(session)).unwrap(); let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); diff --git a/src/event_emitter/mod.rs b/src/event_emitter/mod.rs index 2d675521..369f73be 100644 --- a/src/event_emitter/mod.rs +++ b/src/event_emitter/mod.rs @@ -238,7 +238,7 @@ mod test { let vec = Arc::new(Mutex::new(Vec::new())); let test_vec = Arc::clone(&vec); let emitter = Box::new(EvEmitterTest(vec)) as Box<(dyn EventEmitter)>; - let mut client = AsyncClient::new(homeserver, Some(session)).unwrap(); + let mut client = AsyncClient::<(), ()>::new(homeserver, Some(session)).unwrap(); client.add_event_emitter(emitter).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); diff --git a/src/models/event_deser.rs b/src/models/event_deser.rs new file mode 100644 index 00000000..7c0a6252 --- /dev/null +++ b/src/models/event_deser.rs @@ -0,0 +1,56 @@ +//! De-/serialization functions to and from json strings, allows the type to be used as a query string. + +use serde::de::{Deserialize, Deserializer, Error as _}; + +use crate::events::collections::all::Event; +use crate::events::presence::PresenceEvent; +use crate::events::EventResult; + +pub fn deserialize_events<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let mut events = vec![]; + let ev = Vec::>::deserialize(deserializer)?; + for event in ev { + events.push(event.into_result().map_err(D::Error::custom)?); + } + + Ok(events) +} + +pub fn deserialize_presence<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let mut events = vec![]; + let ev = Vec::>::deserialize(deserializer)?; + for event in ev { + events.push(event.into_result().map_err(D::Error::custom)?); + } + + Ok(events) +} + +#[cfg(test)] +mod test { + use std::fs; + + use crate::events::room::member::MemberEvent; + use crate::events::EventResult; + use crate::models::RoomMember; + + #[test] + fn events_and_presence_deserialization() { + let ev_json = fs::read_to_string("./tests/data/events/member.json").unwrap(); + let ev = serde_json::from_str::>(&ev_json) + .unwrap() + .into_result() + .unwrap(); + let member = RoomMember::new(&ev); + + let member_json = serde_json::to_string(&member).unwrap(); + let mem = serde_json::from_str::(&member_json).unwrap(); + assert_eq!(member, mem); + } +} diff --git a/src/models/mod.rs b/src/models/mod.rs index 07fcb00e..5e461a32 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,3 +1,4 @@ +mod event_deser; mod room; mod room_member; diff --git a/src/models/room.rs b/src/models/room.rs index f6353583..a87bb792 100644 --- a/src/models/room.rs +++ b/src/models/room.rs @@ -439,7 +439,7 @@ mod test { .with_body_from_file("tests/data/sync.json") .create(); - let client = AsyncClient::new(homeserver, Some(session)).unwrap(); + let client = AsyncClient::<(), ()>::new(homeserver, Some(session)).unwrap(); let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); diff --git a/src/models/room_member.rs b/src/models/room_member.rs index fb784653..b53d660c 100644 --- a/src/models/room_member.rs +++ b/src/models/room_member.rs @@ -58,16 +58,16 @@ pub struct RoomMember { /// The human readable name of this room member. pub name: String, /// The events that created the state of this room member. - #[serde(skip)] + #[serde(deserialize_with = "super::event_deser::deserialize_events")] pub events: Vec, /// The `PresenceEvent`s connected to this user. - #[serde(skip)] + #[serde(deserialize_with = "super::event_deser::deserialize_presence")] pub presence_events: Vec, } impl PartialEq for RoomMember { fn eq(&self, other: &RoomMember) -> bool { - // TODO check everything but events and presence_events they dont impl PartialEq + // TODO check everything but events and presence_events they don;t impl PartialEq self.room_id == other.room_id && self.user_id == other.user_id && self.name == other.name diff --git a/src/request_builder.rs b/src/request_builder.rs index 795e8534..d76a5179 100644 --- a/src/request_builder.rs +++ b/src/request_builder.rs @@ -29,7 +29,7 @@ use js_int::UInt; /// .visibility(Visibility::Public) /// .name("name") /// .room_version("v1.0"); -/// let mut cli = AsyncClient::new(homeserver, None).unwrap(); +/// let mut cli = AsyncClient::<(), ()>::new(homeserver, None).unwrap(); /// cli.create_room(builder).await; /// # }) /// ``` @@ -186,7 +186,7 @@ impl Into for RoomBuilder { /// # rt.block_on(async { /// # let room_id = RoomId::new(homeserver.as_str()).unwrap(); /// # let last_sync_token = "".to_string();; -/// let mut cli = AsyncClient::new(homeserver, None).unwrap(); +/// let mut cli = AsyncClient::<(), ()>::new(homeserver, None).unwrap(); /// /// let mut builder = MessagesRequestBuilder::new(); /// builder.room_id(room_id) @@ -341,7 +341,7 @@ mod test { .room_alias_name("room_alias") .topic("room topic") .visibility(Visibility::Private); - let cli = AsyncClient::new(homeserver, Some(session)).unwrap(); + let cli = AsyncClient::<(), ()>::new(homeserver, Some(session)).unwrap(); assert!(cli.create_room(builder).await.is_ok()); } @@ -373,7 +373,7 @@ mod test { // TODO this makes ruma error `Err(IntoHttp(IntoHttpError(Query(Custom("unsupported value")))))`?? // .filter(RoomEventFilter::default()); - let cli = AsyncClient::new(homeserver, Some(session)).unwrap(); + let cli = AsyncClient::<(), ()>::new(homeserver, Some(session)).unwrap(); assert!(cli.room_messages(builder).await.is_ok()); } } diff --git a/src/state/mod.rs b/src/state/mod.rs index ae81fc82..0df00c12 100644 --- a/src/state/mod.rs +++ b/src/state/mod.rs @@ -13,16 +13,18 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::path::Path; + pub mod state_store; pub use state_store::JsonStore; use serde::{Deserialize, Serialize}; +use crate::base_client::Token; use crate::events::push_rules::Ruleset; use crate::identifiers::{RoomId, UserId}; use crate::models::Room; use crate::session::Session; -use crate::{base_client::Token, Result}; #[derive(Debug, Default, PartialEq, Serialize, Deserialize)] pub struct ClientState { @@ -38,15 +40,24 @@ pub struct ClientState { } /// Abstraction around the data store to avoid unnecessary request on client initialization. -pub trait StateStore { +pub trait StateStore: Send + Sync { + /// The type of store to create. The default `JsonStore` uses `ClientState` as the store + /// to serialize and deserialize state to JSON files. + type Store; + + /// The error type to return. + type IoError; + + /// Set up connections or open files to load/save state. + fn open(&self, path: &Path) -> Result<(), Self::IoError>; /// - fn load_client_state(&self) -> Result; + fn load_client_state(&self) -> Result; /// - fn load_room_state(&self, room_id: &RoomId) -> Result; + fn load_room_state(&self, room_id: &RoomId) -> Result; /// - fn store_client_state(&self, _: ClientState) -> Result<()>; + fn store_client_state(&self, _: Self::Store) -> Result<(), Self::IoError>; /// - fn store_room_state(&self, _: &Room) -> Result<()>; + fn store_room_state(&self, _: &Room) -> Result<(), Self::IoError>; } #[cfg(test)] @@ -85,7 +96,10 @@ mod test { "room_name": { "name": null, "canonical_alias": null, - "aliases": [] + "aliases": [], + "heroes": [], + "joined_member_count": null, + "invited_member_count": null }, "own_user_id": "@example:example.com", "creator": null, diff --git a/src/state/state_store.rs b/src/state/state_store.rs index ea78cee1..65e3b7ee 100644 --- a/src/state/state_store.rs +++ b/src/state/state_store.rs @@ -10,6 +10,15 @@ use crate::{Error, Result, Room}; pub struct JsonStore; impl StateStore for JsonStore { + type IoError = Error; + type Store = ClientState; + + fn open(&self, path: &Path) -> Result<()> { + if !path.exists() { + std::fs::create_dir_all(path)?; + } + Ok(()) + } fn load_client_state(&self) -> Result { if let Some(mut path) = dirs::home_dir() { path.push(".matrix_store/client.json"); diff --git a/src/test_builder.rs b/src/test_builder.rs index 4849cfb1..edcff255 100644 --- a/src/test_builder.rs +++ b/src/test_builder.rs @@ -49,9 +49,9 @@ pub struct RoomTestRunner { state_events: Vec, } -pub struct ClientTestRunner { +pub struct ClientTestRunner { /// Used when testing the whole client - client: Option, + client: Option>, /// RoomId and UserId to use for the events. /// /// The RoomId must match the RoomId of the events to track. @@ -69,9 +69,9 @@ pub struct ClientTestRunner { } #[allow(dead_code)] -pub struct MockTestRunner { +pub struct MockTestRunner { /// Used when testing the whole client - client: Option, + client: Option>, /// The ephemeral room events that determine the state of a `Room`. ephemeral: Vec, /// The account data events that determine the state of a `Room`. @@ -169,11 +169,11 @@ impl EventBuilder { /// /// The `TestRunner` streams the events to the client and holds methods to make assertions /// about the state of the client. - pub fn build_mock_runner>( + pub fn build_mock_runner>( mut self, method: &str, path: P, - ) -> MockTestRunner { + ) -> MockTestRunner { let body = serde_json::json! { { "device_one_time_keys_count": {}, @@ -238,7 +238,11 @@ impl EventBuilder { /// /// The `TestRunner` streams the events to the `AsyncClient` and holds methods to make assertions /// about the state of the `AsyncClient`. - pub fn build_client_runner(self, room_id: RoomId, user_id: UserId) -> ClientTestRunner { + pub fn build_client_runner( + self, + room_id: RoomId, + user_id: UserId, + ) -> ClientTestRunner { ClientTestRunner { client: None, room_user_id: (room_id, user_id), @@ -313,8 +317,8 @@ impl RoomTestRunner { } } -impl ClientTestRunner { - pub fn set_client(&mut self, client: AsyncClient) -> &mut Self { +impl ClientTestRunner { + pub fn set_client(&mut self, client: AsyncClient) -> &mut Self { self.client = Some(client); self } @@ -355,14 +359,14 @@ impl ClientTestRunner { } } - pub async fn to_client(&mut self) -> &mut AsyncClient { + pub async fn to_client(&mut self) -> &mut AsyncClient { self.stream_client_events().await; self.client.as_mut().unwrap() } } -impl MockTestRunner { - pub fn set_client(&mut self, client: AsyncClient) -> &mut Self { +impl MockTestRunner { + pub fn set_client(&mut self, client: AsyncClient) -> &mut Self { self.client = Some(client); self } @@ -372,7 +376,7 @@ impl MockTestRunner { self } - pub async fn to_client(&mut self) -> Result<&mut AsyncClient, crate::Error> { + pub async fn to_client(&mut self) -> Result<&mut AsyncClient, crate::Error> { self.client .as_mut() .unwrap() diff --git a/tests/async_client_tests.rs b/tests/async_client_tests.rs index 3c9dacdc..fe68f9ae 100644 --- a/tests/async_client_tests.rs +++ b/tests/async_client_tests.rs @@ -17,7 +17,7 @@ async fn login() { .with_body_from_file("tests/data/login_response.json") .create(); - let client = AsyncClient::new(homeserver, None).unwrap(); + let client = AsyncClient::<(), ()>::new(homeserver, None).unwrap(); client .login("example", "wordpass", None, None) @@ -46,7 +46,7 @@ async fn sync() { .with_body_from_file("tests/data/sync.json") .create(); - let client = AsyncClient::new(homeserver, Some(session)).unwrap(); + let client = AsyncClient::<(), ()>::new(homeserver, Some(session)).unwrap(); let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -75,7 +75,7 @@ async fn room_names() { .with_body_from_file("tests/data/sync.json") .create(); - let client = AsyncClient::new(homeserver, Some(session)).unwrap(); + let client = AsyncClient::<(), ()>::new(homeserver, Some(session)).unwrap(); let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));