-
Notifications
You must be signed in to change notification settings - Fork 270
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
Use ClientBuilder pattern in SDK FFI #739
Conversation
Define new ClientConfig type which can customize parameters of the client such as homeserver (incl. localhost) or http_proxy.
crates/matrix-sdk-ffi/src/lib.rs
Outdated
let builder = new_client_builder(base_path, session.user_id.to_string())? | ||
.homeserver_url(&homeurl) | ||
.user_id(&session.user_id); | ||
.homeserver_url(&homeurl); |
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.
As mentioned in another comment user_id
was actually setting server name, and that method is mutually exclusive with homeserver_url
(as the docs state, only the last will be used). In effect the homeurl
is always overriden by user_id_server
, and for example localhost
cannot be used properly.
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 understand the notion - this is mainly for allowing developers to work with a local homeserver, right?
But I'd argue this make the API more confusing as we now have an unclear (and un-intuitive) preference of configuration options. Maybe we can make it more explicit by:
a) make the ClientOption
optional itself; and
b) call them config_overwrites
or the homeserver
-param _overwrite_homeserver
To make clear what has precedence and what is optional...
crates/matrix-sdk-ffi/src/lib.rs
Outdated
let user = Box::<UserId>::try_from(username.clone())?; | ||
let builder = new_client_builder_with_config(base_path, username, config, &user)?; |
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 ... isn't really making sense to me. UserId
must contain a homeserver
-address, which we then look up on the rust-side by doing the spec-jumps of .well-known
. Given that we have to pass both config and username, I'd argue that username
should have preference as it is more specific.
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.
Yea I can see this is not ideal. Preferring username if both are available would not work for localhost (without the support of https), but an alternative could be to always set the server from username
and potentially override by homeserver if passed from the client (as you suggest in the other comment)
this needs style fixes. You probably want to run |
Codecov Report
@@ Coverage Diff @@
## main #739 +/- ##
=======================================
Coverage 77.76% 77.76%
=======================================
Files 92 92
Lines 13675 13675
=======================================
Hits 10635 10635
Misses 3040 3040 Continue to review full report at Codecov.
|
I might be late to the party, but why do we have |
@poljar this FFI API predates the client-builder pattern and we never updated it to it ... arguably, we might want to do that though. @Anderas what do you think? exposing The FFI does still have the handy tooling to serialize the homeserver into the token, too, for convenience. But we could keep that and have that reconstruct the data for the client-builder. |
That's interesting, I'd like to give this a go. Are you suggesting the client always needs to pass Regarding the |
The pub async fn build(self) -> Result<Client, ClientBuildError>
Correct, server discovery requires a |
After having experimented a bit, we've found that the client-builder doesn't really work for FFI as the pattern requires |
Thanks for this summary, I was just about to write something along these lines |
Is our builder not |
No and there is good reason for it: as it contains the |
I think we can just put it behind an |
@@ -25,63 +24,6 @@ pub use matrix_sdk::ruma::{api::client::account::register, UserId}; | |||
|
|||
pub use self::{backward_stream::*, client::*, messages::*, room::*}; | |||
|
|||
pub fn guest_client(base_path: String, homeurl: String) -> anyhow::Result<Arc<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.
The guest client login is not used by element-x, only in a unit test of sample project that could be recreated in other ways, so I did not port this method yet. If I missed some other reason why this is important, I can bring it back
@@ -44,6 +44,25 @@ impl Client { | |||
} | |||
} | |||
|
|||
pub fn login(&self, username: String, password: String) -> anyhow::Result<()> { |
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.
These are the methods that used to exist as top-level functions, now methods on the Client
. Internally they pretty much just call the inner SDK methods of the same name
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 method is mutually exclusive with | ||
/// [`homeserver_url()`][Self::homeserver_url], if you set both whatever was | ||
/// set last will be used. | ||
pub fn user_id(self, user_id: &UserId) -> Self { |
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.
Given that user_id
naming was confusing (sets server, not user), and server_name_from_user_id
was too long, it seems we could simply just rely on server_name(user_id.server_name())
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.
Can we please not do this in this PR? I'd rather deprecate it for now, not remove it entirely.
.username(username: "@test:domain") | ||
.build() | ||
|
||
try client.login(username: "@test:domain", password: "test") |
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.
The use of the new ClientBuilder API highlights that username now has to be passed twice, which is not ideal. The client builder needs username to determine file store path, and potentially homeserver, if not set explicitely. The client needs username to construct user to log in with.
Potentially the client could retrieve the username directly from the builder, but not sure if api like client.login(password)
is very good.
After further discussions with @jplatte I refactored the FFI layer to follow the same architecture as the inner SDK, which means exposing a configurable There are some potentially controversal changes in this PR (removing guest login, removing |
Co-authored-by: Jonas Platte <jplatte@element.io>
impl Default for ClientBuilder { | ||
fn default() -> Self { | ||
Self::new() | ||
} | ||
} |
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.
What is this Default
impl used for?
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.
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, that's pretty useless in this context but whatever.
/// This method is mutually exclusive with | ||
/// [`homeserver_url()`][Self::homeserver_url], if you set both whatever was | ||
/// set last will be used. | ||
pub fn user_id(self, user_id: &UserId) -> Self { |
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.
Can we please not do this in this PR? I'd rather deprecate it for now, not remove it entirely.
…rust-sdk into andy/homeserver_login
@poljar @gnunicorn do you want to re-review before this is merged? |
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.
Nice,
Just some minor remarks
|
||
pub fn username(self: Arc<Self>, username: String) -> Arc<Self> { | ||
let mut builder = unwrap_or_clone_arc(self); | ||
builder.username = Some(username); |
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 hold an inner builder but still have all parameters twice? Why not just
builder.username = Some(username); | |
builder.inner = builder.inner.username(username); |
For the homeserver, too...
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 can't currently read those fields from the inner builder, but they're needed in build
. I think there's room for improvement here, but it's not entirely clear how this should be improved. In general I think the FFI layer shouldn't add its own logic like deriving a store path from a base path plus username / homeserver, but doing that immediately would mean extra work for using the FFI compared to before this change, which is also not great.
inner: MatrixClient::builder().user_agent("rust-sdk-ios"), | ||
} | ||
} | ||
|
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.
Docs should probably say that base path must be set. Which let's me wonder if we shouldn't have that as a parameters on new
so it is enforced and never Option
that could fail at runtime (when calling build
) . Wdyt?
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 Andy actually wanted to have it as a new
parameter, and I wanted to have it as a builder method instead. My reasoning for that was that it's the usual pattern for builders, and a new ClientBuilder("/some/path")
invocation wouldn't be quite clear. (and it would be a very easy to catch programmer error when it isn't set)
However, this point only applies to Kotlin since UniFFI's Swift codegen requires callers to re-state argument names (as in new ClientBuilder(base_path: "/some/path")
). So I don't insist on keeping it this way.
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.
Yea I generally favor API that makes invalid states impossible to represent, which would require passing them to new
. This sounds like an example of clash between SDK and client-side consistency. Happy to follow up on this in a new PR.
let builder = unwrap_or_clone_arc(self); | ||
|
||
let base_path = builder.base_path.context("Base path was not set")?; | ||
let username = builder |
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 would fail for guest clients... but as this also drops support fir them, I guess that's fine...
Change the SDK-ffi architecture to use builder pattern, where the client creates and configures
ClientBuilder
before creating a client. Further operations, such as login are then methods on the client rather than top-level functions. This is done to closely follow the architecture of the SDK and only provide convenience wrappers for hidden types, concurrency and other iOS-incompatible features.Additionally expose
homeserver_url
method to configure custom homeserver, such as localhost.