appservice: Improve API

master
Johannes Becker 2021-06-01 10:48:01 +02:00
parent ee40d917d1
commit c0b30cadc9
3 changed files with 45 additions and 30 deletions

View File

@ -37,7 +37,7 @@ impl EventHandler for AppserviceEventHandler {
let mut appservice = self.appservice.clone(); let mut appservice = self.appservice.clone();
appservice.register(user_id.localpart()).await.unwrap(); appservice.register(user_id.localpart()).await.unwrap();
let client = appservice.client(Some(user_id.localpart())).await.unwrap(); let client = appservice.virtual_user(user_id.localpart()).await.unwrap();
client.join_room_by_id(room.room_id()).await.unwrap(); client.join_room_by_id(room.room_id()).await.unwrap();
} }

View File

@ -177,12 +177,20 @@ impl Deref for AppserviceRegistration {
type Localpart = String; type Localpart = String;
/// The main appservice user is the `sender_localpart` from the given /// The `localpart` of the user associated with the application service via
/// [`AppserviceRegistration`] /// `sender_localpart` in [`AppserviceRegistration`].
/// ///
/// Dummy type for shared documentation /// Dummy type for shared documentation
#[allow(dead_code)] #[allow(dead_code)]
pub type MainAppserviceUser = (); pub type MainUser = ();
/// The application service may specify the virtual user to act as through use
/// of a user_id query string parameter on the request. The user specified in
/// the query string must be covered by one of the [`AppserviceRegistration`]'s
/// `users` namespaces.
///
/// Dummy type for shared documentation
pub type VirtualUser = ();
/// Appservice /// Appservice
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -196,9 +204,9 @@ pub struct Appservice {
impl Appservice { impl Appservice {
/// Create new Appservice /// Create new Appservice
/// ///
/// Also creates and caches a [`Client`] with the [`MainAppserviceUser`]. /// Also creates and caches a [`Client`] for the [`MainUser`].
/// The default [`ClientConfig`] is used, if you want to customize it /// The default [`ClientConfig`] is used, if you want to customize it
/// use [`Self::new_with_client_config()`] instead. /// use [`Self::new_with_config()`] instead.
/// ///
/// # Arguments /// # Arguments
/// ///
@ -214,7 +222,7 @@ impl Appservice {
server_name: impl TryInto<ServerNameBox, Error = identifiers::Error>, server_name: impl TryInto<ServerNameBox, Error = identifiers::Error>,
registration: AppserviceRegistration, registration: AppserviceRegistration,
) -> Result<Self> { ) -> Result<Self> {
let appservice = Self::new_with_client_config( let appservice = Self::new_with_config(
homeserver_url, homeserver_url,
server_name, server_name,
registration, registration,
@ -227,7 +235,7 @@ impl Appservice {
/// Same as [`Self::new()`] but lets you provide a [`ClientConfig`] for the /// Same as [`Self::new()`] but lets you provide a [`ClientConfig`] for the
/// [`Client`] /// [`Client`]
pub async fn new_with_client_config( pub async fn new_with_config(
homeserver_url: impl TryInto<Url, Error = url::ParseError>, homeserver_url: impl TryInto<Url, Error = url::ParseError>,
server_name: impl TryInto<ServerNameBox, Error = identifiers::Error>, server_name: impl TryInto<ServerNameBox, Error = identifiers::Error>,
registration: AppserviceRegistration, registration: AppserviceRegistration,
@ -237,23 +245,26 @@ impl Appservice {
let server_name = server_name.try_into()?; let server_name = server_name.try_into()?;
let registration = Arc::new(registration); let registration = Arc::new(registration);
let clients = Arc::new(DashMap::new()); let clients = Arc::new(DashMap::new());
let sender_localpart = registration.sender_localpart.clone();
let appservice = Appservice { homeserver_url, server_name, registration, clients }; let appservice = Appservice { homeserver_url, server_name, registration, clients };
// we cache the [`MainAppserviceUser`] by default // we cache the [`MainUser`] by default
appservice.client_with_config(None, client_config).await?; appservice.virtual_user_with_config(sender_localpart, client_config).await?;
Ok(appservice) Ok(appservice)
} }
/// Create a [`Client`] /// Create a [`Client`] for the given [`VirtualUser`]'s `localpart`
/// ///
/// Will create and return a [`Client`] that's configured to [assert the /// Will create and return a [`Client`] that's configured to [assert the
/// identity] on all outgoing homeserver requests if `localpart` is /// identity] on all outgoing homeserver requests if `localpart` is
/// given. If not given the [`Client`] will use the [`MainAppserviceUser`]. /// given.
/// ///
/// This method is a singleton that saves the client internally for re-use /// This method is a singleton that saves the client internally for re-use
/// based on the `localpart`. /// based on the `localpart`. The cached [`Client`] can be retrieved either
/// by calling this method again or by calling [`Self::get_cached_client()`]
/// which is non-async convenience wrapper.
/// ///
/// # Arguments /// # Arguments
/// ///
@ -261,30 +272,31 @@ impl Appservice {
/// ///
/// [registration]: https://matrix.org/docs/spec/application_service/r0.1.2#registration /// [registration]: https://matrix.org/docs/spec/application_service/r0.1.2#registration
/// [assert the identity]: https://matrix.org/docs/spec/application_service/r0.1.2#identity-assertion /// [assert the identity]: https://matrix.org/docs/spec/application_service/r0.1.2#identity-assertion
pub async fn client(&self, localpart: Option<&str>) -> Result<Client> { pub async fn virtual_user(&self, localpart: impl AsRef<str>) -> Result<Client> {
let client = self.client_with_config(localpart, ClientConfig::default()).await?; let client = self.virtual_user_with_config(localpart, ClientConfig::default()).await?;
Ok(client) Ok(client)
} }
/// Same as [`Self::client`] but with the ability to pass in a /// Same as [`Self::virtual_user()`] but with the ability to pass in a
/// [`ClientConfig`] /// [`ClientConfig`]
/// ///
/// Since this method is a singleton follow-up calls with different /// Since this method is a singleton follow-up calls with different
/// [`ClientConfig`]s will be ignored. /// [`ClientConfig`]s will be ignored.
pub async fn client_with_config( pub async fn virtual_user_with_config(
&self, &self,
localpart: Option<&str>, localpart: impl AsRef<str>,
config: ClientConfig, config: ClientConfig,
) -> Result<Client> { ) -> Result<Client> {
let localpart = localpart.unwrap_or_else(|| self.registration.sender_localpart.as_ref()); // TODO: check if localpart is covered by namespace?
let localpart = localpart.as_ref();
let client = if let Some(client) = self.clients.get(localpart) { let client = if let Some(client) = self.clients.get(localpart) {
client.clone() client.clone()
} else { } else {
let user_id = UserId::parse_with_server_name(localpart, &self.server_name)?; let user_id = UserId::parse_with_server_name(localpart, &self.server_name)?;
// The `as_token` in the `Session` maps to the [`MainAppserviceUser`] // The `as_token` in the `Session` maps to the [`MainUser`]
// (`sender_localpart`) by default, so we don't need to assert identity // (`sender_localpart`) by default, so we don't need to assert identity
// in that case // in that case
if localpart != self.registration.sender_localpart { if localpart != self.registration.sender_localpart {
@ -312,8 +324,11 @@ impl Appservice {
/// Get cached [`Client`] /// Get cached [`Client`]
/// ///
/// Will return the client for the given `localpart` if previously /// Will return the client for the given `localpart` if previously
/// constructed with [`Self::client()`] or [`Self::client_with_config()`]. /// constructed with [`Self::virtual_user()`] or
/// If no client for the `localpart` is found it will return an Error. /// [`Self::virtual_user_with_config()`].
///
/// If no `localpart` is given it assumes the [`MainUser`]'s `localpart`. If
/// no client for `localpart` is found it will return an Error.
pub fn get_cached_client(&self, localpart: Option<&str>) -> Result<Client> { pub fn get_cached_client(&self, localpart: Option<&str>) -> Result<Client> {
let localpart = localpart.unwrap_or_else(|| self.registration.sender_localpart.as_ref()); let localpart = localpart.unwrap_or_else(|| self.registration.sender_localpart.as_ref());
@ -324,10 +339,9 @@ impl Appservice {
/// Convenience wrapper around [`Client::set_event_handler()`] /// Convenience wrapper around [`Client::set_event_handler()`]
/// ///
/// Attaches the event handler to [`Self::client()`] with `None` as /// Attaches the event handler to the [`MainUser`]'s [`Client`]
/// `localpart`
pub async fn set_event_handler(&mut self, handler: Box<dyn EventHandler>) -> Result<()> { pub async fn set_event_handler(&mut self, handler: Box<dyn EventHandler>) -> Result<()> {
let client = self.client(None).await?; let client = self.get_cached_client(None)?;
client.set_event_handler(handler).await; client.set_event_handler(handler).await;
@ -347,7 +361,7 @@ impl Appservice {
login_type: Some(&LoginType::ApplicationService), login_type: Some(&LoginType::ApplicationService),
}); });
let client = self.client(None).await?; let client = self.get_cached_client(None)?;
match client.register(request).await { match client.register(request).await {
Ok(_) => (), Ok(_) => (),
Err(error) => match error { Err(error) => match error {
@ -383,7 +397,8 @@ impl Appservice {
self.registration.hs_token == hs_token.as_ref() self.registration.hs_token == hs_token.as_ref()
} }
/// Check if given `user_id` is in any of the registration user namespaces /// Check if given `user_id` is in any of the [`AppserviceRegistration`]'s
/// `users` namespaces
pub fn user_id_is_in_namespace(&self, user_id: impl AsRef<str>) -> Result<bool> { pub fn user_id_is_in_namespace(&self, user_id: impl AsRef<str>) -> Result<bool> {
for user in &self.registration.namespaces.users { for user in &self.registration.namespaces.users {
// TODO: precompile on Appservice construction // TODO: precompile on Appservice construction

View File

@ -87,14 +87,14 @@ async fn test_event_handler() -> Result<()> {
events, events,
); );
appservice.client(None).await?.receive_transaction(incoming).await?; appservice.get_cached_client(None)?.receive_transaction(incoming).await?;
Ok(()) Ok(())
} }
#[async_test] #[async_test]
async fn test_transaction() -> Result<()> { async fn test_transaction() -> Result<()> {
let mut appservice = appservice(None).await?; let appservice = appservice(None).await?;
let event = serde_json::from_value::<AnyStateEvent>(member_json()).unwrap(); let event = serde_json::from_value::<AnyStateEvent>(member_json()).unwrap();
let event: Raw<AnyRoomEvent> = AnyRoomEvent::State(event).into(); let event: Raw<AnyRoomEvent> = AnyRoomEvent::State(event).into();
@ -105,7 +105,7 @@ async fn test_transaction() -> Result<()> {
events, events,
); );
appservice.client(None).await?.receive_transaction(incoming).await?; appservice.get_cached_client(None)?.receive_transaction(incoming).await?;
Ok(()) Ok(())
} }