-
Notifications
You must be signed in to change notification settings - Fork 268
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore(room_preview): add RoomListItem::preview_room
#4152
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #4152 +/- ##
=======================================
Coverage 84.85% 84.86%
=======================================
Files 269 269
Lines 28916 28924 +8
=======================================
+ Hits 24536 24545 +9
+ Misses 4380 4379 -1 ☔ View full report in Codecov by Sentry. |
/// | ||
/// ⚠️ Holding on to this room instance after it has been joined is not | ||
/// safe. Use `full_room` instead. | ||
fn room_without_timeline(&self) -> Result<Arc<Room>, RoomListError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really not fond of this name, I see one of three ways to go from here (there can be more!):
- Either rename this
preview_room()
orstripped_room()
, as it's really a very basic stripped view of a room. - Rename
fn room()
tofn room_with_timeline()
, and this one can befn room()
later. - (is kind of orthogonal, and could be in addition to (1) or (2)) It might be nice to return a
StrippedRoom
orPreviewRoom
type instead ofRoom
, from this function, to make it clear which functions are available and which are not. In fact, this might even get rid of the unused internal timeline instance.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
preview_room
sounds fine to me. As for having a separate struct backing those, maybe we can create one, once we know all the values we need to expose through the FFI. I can try to get this working.
bindings/matrix-sdk-ffi/src/lib.rs
Outdated
#[allow(deprecated)] | ||
mod room_list; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How many uses of the deprecated functions are there in this module? Can we add the guard on them one by one instead, or would that be too many? If too many, can we put the allow guard in the module itself, instead of here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I tried using the allow guard in the function it didn't take effect (I still had the same compiler warning message, which clippy turned into an error), I had to place it in the module for it to work. I suspect it has something to do with the FFI generated code, but I'm not sure:
error: use of deprecated method `room_list::RoomListItem::invited_room`: Please use `room_without_timeline` instead.
--> bindings/matrix-sdk-ffi/src/room_list.rs:594:8
|
594 | fn invited_room(&self) -> Result<Arc<Room>, RoomListError> {
| ^^^^^^^^^^^^
|
= note: `-D deprecated` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(deprecated)]`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah, expanding the macro could tell us whether it's possible to put it on a slightly more specialized location, e.g. the impl
block itself, or if it's using it in code generated elsewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding it to the impl block fails too. I've only been able to fix it by adding it in the mod
declaration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I add it to the function, the expanded macro looks like this:
#[doc = " Builds a `Room` FFI from an invited room without initializing its"]
#[doc = " internal timeline."]
#[doc = ""]
#[doc = " An error will be returned if the room is a state different than invited."]
#[doc = ""]
#[doc = " ⚠\u{65039} Holding on to this room instance after it has been joined is not"]
#[doc = " safe. Use `full_room` instead."]
#[allow(deprecated)]
#[deprecated(note = "Please use `preview_room` instead.")]
fn invited_room(&self) -> Result<Arc<Room>, RoomListError> {
if !match (self.membership()) {
Membership::Invited => true,
_ => false
} {
return Err(RoomListError::IncorrectRoomMembership {
expected: (<[_]>::into_vec(
#[rustc_box]
::alloc::boxed::Box::new([Membership::Invited])
)),
actual: self.membership(),
});
}
Ok(Arc::new(Room::new(self.inner.inner_room().clone())))
}
And if I add it to the impl block it looks like:
#[uniffi::export(async_runtime = "tokio", )]
#[allow(deprecated)]
impl RoomListItem {
...
But clippy still complains in both cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's the call sites that need the allow(deprecated)
guard, not the function itself; but it might be that it's in code generated by uniffi, that's outside our control, so we might need to resort to the mod-level guard indeed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Forgot to comment here: it's indeed an unmangled free-function generated in the same module, that's calling the invited_room
method, so welp 🤷 Module-level guard it is. I wondered if we could get rid of the entire invited_room
method altogether, but I don't want to add more API changes to the other side of this PR.
if !matches!(membership, Membership::Invited) | ||
&& !matches!(self.membership(), Membership::Knocked) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can be a single matches!
.
if !matches!(membership, Membership::Invited) | |
&& !matches!(self.membership(), Membership::Knocked) | |
if !matches(membership, Membership::Invited | Membership::Knocked) |
… both an invited or a knocked room. This deprecates `RoomListItem::invited_room`.
This allows us to get `RoomPreview` from a room list item when the membership is either invited or knocked. Also, modify `RoomPreview` so it contains both the room info needed for the preview as well as 2 methods, for joining the room or leaving it (rejecting the invite/rescinding the knock).
1127a48
to
26958b5
Compare
I ended up creating a val preview = roomListItem.previewRoom()
if (preview.info.membership == Membership.Invite) {
preview.actions.join()
} Also, the idea with the different |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we don't need all the preview actions data structure at the SDK level; these need to exist at the FFI layer because of… reasons (that I'm detailed below; let me know if that's wrong). In the current shape I am strongly not in favor of merging this, let's identify what's the minimal set of changes that we could have to unblock us, and maybe consider doing some refactoring later.
} | ||
} | ||
|
||
pub enum RoomPreviewActions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this entire file you need to comment each struct, enum, variant, field, function. I'll enable a new lint for this 😈
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was fixed in the following commit if I'm not mistaken.
#[error(transparent)] | ||
Generic(#[from] Box<dyn std::error::Error + Send + Sync>) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really don't like this; having an exceptional generic error type like that should be limited at the strict minimum, i.e. matrix_sdk::Error
only. Let's avoid it entirely by not making a custom error, but instead use an matrix_sdk::Error
instead of this error type, there's already WrongRoomState
which describes the same error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed in 0fcfd84.
/// Builds a `RoomPreview` from a room list item. This is intended for rooms | ||
/// with [`Membership::Invite`] or [`Membership::Knocked`], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this sentence ends with a comma, and it doesn't really translate to proper English IMO: maybe This is intended for invited or knocked rooms.
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
f8110d2 should fix this.
struct | ||
|
||
impl RoomPreview { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like this is not an atomic commit… (It's fine but next time to make the review easier, feel free to squash them altogether, since I know that another commit will now change these three lines.)
#[derive(uniffi::Record)] | ||
pub struct RoomPreview { | ||
pub info: RoomPreviewInfo, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need the intermediary RoomPreviewInfo
data structure?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we didn't need to move everything inside RoomPreview
into a Record
if we made it an Object
(as is the case now since I'm removing the separate actions
part).
@@ -624,7 +622,7 @@ impl RoomListItem { | |||
}; | |||
|
|||
let room_preview = client.get_room_preview(&room_or_alias_id, server_names).await?; | |||
Ok(Arc::new(RoomPreview::try_from_sdk(room_preview, client))) | |||
RoomPreview::try_from_sdk(room_preview, client) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fixes stuff from the previous commit, right? 😔
pub fn new(client: Client, room_preview: RoomPreview) -> Self { | ||
let room_id = room_preview.room_id; | ||
let room_state = room_preview.state; | ||
let is_direct = room_preview.is_direct.unwrap_or_default(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a lie, let's keep an Option<bool>
for is_direct
;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code was removed.
Knocked { leave: LeaveRoomPreviewAction }, | ||
/// A set of actions that can be performed in a [`RoomPreview`]. | ||
#[derive(Debug)] | ||
pub struct RoomPreviewActions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall, I don't think this data structure and the other actions are required at the SDK level; maybe they're required at the FFI layer because we need some kind of "lazy provider"-like object for the Record requirements, but I don't think they make sense for the RoomPreview
of the SDK, considering there are many other methods that already exist providing the same functionality, so removing it would avoid blowing up the number of abstractions, code duplication, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added it to the SDK so they could be tested and didn't have to duplicate code, i.e. the Room::join
contents mentioned below, which could be just put into one of these actions and then reused inside Room::join
instead:
impl Room {
async fn join(&self) -> Result<_, _> {
self.actions.join()
}
But if that's not something we want, we can just remove the actions, leaving a needed RoomPreview::leave
method that's needed (Client
has code for joining and knocking, but it doesn't have one for leaving a room given its id, so a RoomPreview
with invite/knock state can never be left like that).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actions removed in 0fcfd84
match self.client.join_room_by_id(&self.room_id).await { | ||
Ok(room) => { | ||
if room_preview.is_direct.unwrap_or(false) { | ||
if self.is_direct { | ||
if let Some(e) = room.set_is_direct(true).await.err() { | ||
warn!("error when setting room as direct: {e}"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code sequence is duplicated from somewhere else, right? If so, that would be bad, because now we'd need to keep both code locations in sync without each code location knowing about the other :/
|
RoomListItem::room_without_timeline
RoomListItem::preview_room
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, it's still too many unneeded changes for me. Let me try to hack on this, see if I can get it into the shape I'm expecting.
crates/matrix-sdk/src/client/mod.rs
Outdated
@@ -1208,6 +1208,13 @@ impl Client { | |||
Ok(Room::new(self.clone(), base_room)) | |||
} | |||
|
|||
/// Leave a room given its `RoomId`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need this method, it's the same as Room::leave()
, and we can only leave a room we do know about (i.e. we can get it with Client::get_room
first).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we're going in circles... We don't have anything in the ffi::Client
to get a Room
, and even if we had it, it would be a 'not safe to hold onto' Room as we had in RoomListItem::invited_room
(see #4152 (comment)). That's the main reason I split Room and RoomPreview here, and I think it's what we agreed to do.
So now I'm not sure what to do anymore:
- If we're ok with returning a Room with no timeline as we did before then I'll just remove everything from
RoomPreview
and just return that Room inRoomListItem::preview_room
instead. - If we're not ok with that, then we need a way to be able to leave that room (aka decline the invite, cancel the knock).
/// Leave a room using its [`RoomPreview`]. | ||
pub async fn leave(&self) -> Result<(), Error> { | ||
match self.state { | ||
Some(RoomState::Left) | None => { | ||
Err(Error::WrongRoomPreviewState(WrongRoomPreviewState { | ||
expected: "Invited, Knocked or Joined", | ||
got: self.state, | ||
})) | ||
} | ||
Some(_) => self.client.leave_room(&self.room_id).await.map_err(Into::into), | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I still think that RoomPreview
ought to be a purely descriptive data record (i.e. no action methods); can the caller use the room id that's always around, and call leave_room
by themselves? Then, can we get rid of the Client
stored and address the other comment I had on from_room_info
?
// Warning! If new join rules are introduced we may be mapping wrong values | ||
// here, but there's no way to match | ||
// `SpaceRoomJoinRule::_Custom(_)` and have an exhaustive match. | ||
JoinRule::Private |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd do it differently:
- for custom, add a
JoinRule::Custom(String)
variant that's built withjoin_rule.to_string()
(SpaceRoomJoinRule
implementsStringEnum
which derives aDisplay
impl)? - for
_
, log a warning and returnNone
. Yes, let's turn this intoTryInto
😇
@@ -1027,7 +1027,7 @@ impl Client { | |||
&self, | |||
room_id: String, | |||
via_servers: Vec<String>, | |||
) -> Result<RoomPreview, ClientError> { | |||
) -> Result<Arc<RoomPreview>, ClientError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we still need all the Arc
s here? (I'm not very sure about it myself, but I suspect we don't since RoomPreview
is now a uniffi object)
let server_names: Vec<OwnedServerName> = via | ||
.into_iter() | ||
.map(|server| ServerName::parse(server).map_err(ClientError::from)) | ||
.collect::<Result<_, ClientError>>()?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a bit weird that if there's a canonical alias, the via
routes will not be validated 🤔 Should we validate them ahead, even if we're discarding them before?
bindings/matrix-sdk-ffi/src/lib.rs
Outdated
#[allow(deprecated)] | ||
mod room_list; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's the call sites that need the allow(deprecated)
guard, not the function itself; but it might be that it's in code generated by uniffi, that's outside our control, so we might need to resort to the mod-level guard indeed.
7a8668a
to
f59d47f
Compare
// Since we have no way to get the _Custom contents, assume it's private. | ||
// Warning! If new join rules are introduced we may be mapping wrong values | ||
// here, but there's no way to match | ||
// `SpaceRoomJoinRule::_Custom(_)` and have an exhaustive match. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe remove/fix this comment?
05e449d
to
8aaaf23
Compare
8aaaf23
to
c7f5638
Compare
Jorge confirmed this was good enough for the EX apps, so merging. Thanks @jmartinesp for all the work done here, and sorry about the few back-and-forths. |
This method will return a
RoomPreview
for the provided room id.Also added
fn RoomPreview::leave()
action to be able to decline invites or cancel knocks, since there wasn't aClient::leave_room_by_id
counterpart as there is for join.The PR also deprecates
RoomListItem::invited_room
, since we have a better alternative now.Signed-off-by: