atagen

matrix-rust-sdk

public
Star0
Watch0
ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk.git

Blame: bindings/matrix-sdk-ffi/src/authentication.rs

7ff8adafe9e4atagen2026-10-22 12:30 UTC1
// Copyright 2025 The Matrix.org Foundation C.I.C.
7ff8adafe9e4atagen2026-10-22 12:30 UTC2
//
7ff8adafe9e4atagen2026-10-22 12:30 UTC3
// Licensed under the Apache License, Version 2.0 (the "License");
7ff8adafe9e4atagen2026-10-22 12:30 UTC4
// you may not use this file except in compliance with the License.
7ff8adafe9e4atagen2026-10-22 12:30 UTC5
// You may obtain a copy of the License at
7ff8adafe9e4atagen2026-10-22 12:30 UTC6
//
7ff8adafe9e4atagen2026-10-22 12:30 UTC7
//     http://www.apache.org/licenses/LICENSE-2.0
7ff8adafe9e4atagen2026-10-22 12:30 UTC8
//
7ff8adafe9e4atagen2026-10-22 12:30 UTC9
// Unless required by applicable law or agreed to in writing, software
7ff8adafe9e4atagen2026-10-22 12:30 UTC10
// distributed under the License is distributed on an "AS IS" BASIS,
7ff8adafe9e4atagen2026-10-22 12:30 UTC11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7ff8adafe9e4atagen2026-10-22 12:30 UTC12
// See the License for that specific language governing permissions and
7ff8adafe9e4atagen2026-10-22 12:30 UTC13
// limitations under the License.
7ff8adafe9e4atagen2026-10-22 12:30 UTC14
7ff8adafe9e4atagen2026-10-22 12:30 UTC15
use std::{
7ff8adafe9e4atagen2026-10-22 12:30 UTC16
    collections::HashMap,
7ff8adafe9e4atagen2026-10-22 12:30 UTC17
    fmt::{self, Debug},
7ff8adafe9e4atagen2026-10-22 12:30 UTC18
    sync::Arc,
7ff8adafe9e4atagen2026-10-22 12:30 UTC19
};
7ff8adafe9e4atagen2026-10-22 12:30 UTC20
7ff8adafe9e4atagen2026-10-22 12:30 UTC21
use matrix_sdk::{
7ff8adafe9e4atagen2026-10-22 12:30 UTC22
    Error,
7ff8adafe9e4atagen2026-10-22 12:30 UTC23
    authentication::oauth::{
7ff8adafe9e4atagen2026-10-22 12:30 UTC24
        ClientId, ClientRegistrationData, OAuthError as SdkOAuthError,
7ff8adafe9e4atagen2026-10-22 12:30 UTC25
        error::OAuthAuthorizationCodeError,
7ff8adafe9e4atagen2026-10-22 12:30 UTC26
        registration::{ApplicationType, ClientMetadata, Localized, OAuthGrantType},
7ff8adafe9e4atagen2026-10-22 12:30 UTC27
    },
7ff8adafe9e4atagen2026-10-22 12:30 UTC28
};
7ff8adafe9e4atagen2026-10-22 12:30 UTC29
use ruma::serde::Raw;
7ff8adafe9e4atagen2026-10-22 12:30 UTC30
use url::Url;
7ff8adafe9e4atagen2026-10-22 12:30 UTC31
7ff8adafe9e4atagen2026-10-22 12:30 UTC32
use crate::client::{Client, OAuthPrompt, SlidingSyncVersion};
7ff8adafe9e4atagen2026-10-22 12:30 UTC33
7ff8adafe9e4atagen2026-10-22 12:30 UTC34
#[derive(uniffi::Object)]
7ff8adafe9e4atagen2026-10-22 12:30 UTC35
pub struct HomeserverLoginDetails {
7ff8adafe9e4atagen2026-10-22 12:30 UTC36
    pub(crate) url: String,
7ff8adafe9e4atagen2026-10-22 12:30 UTC37
    pub(crate) sliding_sync_version: SlidingSyncVersion,
7ff8adafe9e4atagen2026-10-22 12:30 UTC38
    pub(crate) supports_oauth_login: bool,
7ff8adafe9e4atagen2026-10-22 12:30 UTC39
    pub(crate) supported_oauth_prompts: Vec<OAuthPrompt>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC40
    pub(crate) supports_sso_login: bool,
7ff8adafe9e4atagen2026-10-22 12:30 UTC41
    pub(crate) supports_password_login: bool,
7ff8adafe9e4atagen2026-10-22 12:30 UTC42
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC43
7ff8adafe9e4atagen2026-10-22 12:30 UTC44
#[matrix_sdk_ffi_macros::export]
7ff8adafe9e4atagen2026-10-22 12:30 UTC45
impl HomeserverLoginDetails {
7ff8adafe9e4atagen2026-10-22 12:30 UTC46
    /// The URL of the currently configured homeserver.
7ff8adafe9e4atagen2026-10-22 12:30 UTC47
    pub fn url(&self) -> String {
7ff8adafe9e4atagen2026-10-22 12:30 UTC48
        self.url.clone()
7ff8adafe9e4atagen2026-10-22 12:30 UTC49
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC50
7ff8adafe9e4atagen2026-10-22 12:30 UTC51
    /// The sliding sync version.
7ff8adafe9e4atagen2026-10-22 12:30 UTC52
    pub fn sliding_sync_version(&self) -> SlidingSyncVersion {
7ff8adafe9e4atagen2026-10-22 12:30 UTC53
        self.sliding_sync_version.clone()
7ff8adafe9e4atagen2026-10-22 12:30 UTC54
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC55
7ff8adafe9e4atagen2026-10-22 12:30 UTC56
    /// Whether the current homeserver supports login using OAuth.
7ff8adafe9e4atagen2026-10-22 12:30 UTC57
    pub fn supports_oauth_login(&self) -> bool {
7ff8adafe9e4atagen2026-10-22 12:30 UTC58
        self.supports_oauth_login
7ff8adafe9e4atagen2026-10-22 12:30 UTC59
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC60
7ff8adafe9e4atagen2026-10-22 12:30 UTC61
    /// The prompts advertised by the authentication issuer for use in the login
7ff8adafe9e4atagen2026-10-22 12:30 UTC62
    /// URL.
7ff8adafe9e4atagen2026-10-22 12:30 UTC63
    pub fn supported_oauth_prompts(&self) -> Vec<OAuthPrompt> {
7ff8adafe9e4atagen2026-10-22 12:30 UTC64
        self.supported_oauth_prompts.clone()
7ff8adafe9e4atagen2026-10-22 12:30 UTC65
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC66
7ff8adafe9e4atagen2026-10-22 12:30 UTC67
    /// Whether the current homeserver supports login using legacy SSO.
7ff8adafe9e4atagen2026-10-22 12:30 UTC68
    pub fn supports_sso_login(&self) -> bool {
7ff8adafe9e4atagen2026-10-22 12:30 UTC69
        self.supports_sso_login
7ff8adafe9e4atagen2026-10-22 12:30 UTC70
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC71
7ff8adafe9e4atagen2026-10-22 12:30 UTC72
    /// Whether the current homeserver supports the password login flow.
7ff8adafe9e4atagen2026-10-22 12:30 UTC73
    pub fn supports_password_login(&self) -> bool {
7ff8adafe9e4atagen2026-10-22 12:30 UTC74
        self.supports_password_login
7ff8adafe9e4atagen2026-10-22 12:30 UTC75
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC76
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC77
7ff8adafe9e4atagen2026-10-22 12:30 UTC78
/// An object encapsulating the SSO login flow
7ff8adafe9e4atagen2026-10-22 12:30 UTC79
#[derive(uniffi::Object)]
7ff8adafe9e4atagen2026-10-22 12:30 UTC80
pub struct SsoHandler {
7ff8adafe9e4atagen2026-10-22 12:30 UTC81
    /// The wrapped Client.
7ff8adafe9e4atagen2026-10-22 12:30 UTC82
    pub(crate) client: Arc<Client>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC83
7ff8adafe9e4atagen2026-10-22 12:30 UTC84
    /// The underlying URL for authentication.
7ff8adafe9e4atagen2026-10-22 12:30 UTC85
    pub(crate) url: String,
7ff8adafe9e4atagen2026-10-22 12:30 UTC86
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC87
7ff8adafe9e4atagen2026-10-22 12:30 UTC88
#[matrix_sdk_ffi_macros::export]
7ff8adafe9e4atagen2026-10-22 12:30 UTC89
impl SsoHandler {
7ff8adafe9e4atagen2026-10-22 12:30 UTC90
    /// Returns the URL for starting SSO authentication. The URL should be
7ff8adafe9e4atagen2026-10-22 12:30 UTC91
    /// opened in a web view. Once the web view succeeds, call `finish` with
7ff8adafe9e4atagen2026-10-22 12:30 UTC92
    /// the callback URL.
7ff8adafe9e4atagen2026-10-22 12:30 UTC93
    pub fn url(&self) -> String {
7ff8adafe9e4atagen2026-10-22 12:30 UTC94
        self.url.clone()
7ff8adafe9e4atagen2026-10-22 12:30 UTC95
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC96
7ff8adafe9e4atagen2026-10-22 12:30 UTC97
    /// Completes the SSO login process.
7ff8adafe9e4atagen2026-10-22 12:30 UTC98
    pub async fn finish(&self, callback_url: String) -> Result<(), SsoError> {
7ff8adafe9e4atagen2026-10-22 12:30 UTC99
        let auth = self.client.inner.matrix_auth();
7ff8adafe9e4atagen2026-10-22 12:30 UTC100
        let url = Url::parse(&callback_url).map_err(|_| SsoError::CallbackUrlInvalid)?;
7ff8adafe9e4atagen2026-10-22 12:30 UTC101
        let builder =
7ff8adafe9e4atagen2026-10-22 12:30 UTC102
            auth.login_with_sso_callback(url.into()).map_err(|_| SsoError::CallbackUrlInvalid)?;
7ff8adafe9e4atagen2026-10-22 12:30 UTC103
        builder.await.map_err(|_| SsoError::LoginWithTokenFailed)?;
7ff8adafe9e4atagen2026-10-22 12:30 UTC104
7ff8adafe9e4atagen2026-10-22 12:30 UTC105
        Ok(())
7ff8adafe9e4atagen2026-10-22 12:30 UTC106
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC107
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC108
7ff8adafe9e4atagen2026-10-22 12:30 UTC109
impl Debug for SsoHandler {
7ff8adafe9e4atagen2026-10-22 12:30 UTC110
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
7ff8adafe9e4atagen2026-10-22 12:30 UTC111
        fmt.debug_struct("SsoHandler").field("url", &self.url).finish_non_exhaustive()
7ff8adafe9e4atagen2026-10-22 12:30 UTC112
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC113
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC114
7ff8adafe9e4atagen2026-10-22 12:30 UTC115
#[derive(Debug, thiserror::Error, uniffi::Error)]
7ff8adafe9e4atagen2026-10-22 12:30 UTC116
#[uniffi(flat_error)]
7ff8adafe9e4atagen2026-10-22 12:30 UTC117
pub enum SsoError {
7ff8adafe9e4atagen2026-10-22 12:30 UTC118
    #[error("The supplied callback URL used to complete SSO is invalid.")]
7ff8adafe9e4atagen2026-10-22 12:30 UTC119
    CallbackUrlInvalid,
7ff8adafe9e4atagen2026-10-22 12:30 UTC120
    #[error("Logging in with the token from the supplied callback URL failed.")]
7ff8adafe9e4atagen2026-10-22 12:30 UTC121
    LoginWithTokenFailed,
7ff8adafe9e4atagen2026-10-22 12:30 UTC122
7ff8adafe9e4atagen2026-10-22 12:30 UTC123
    #[error("An error occurred: {message}")]
7ff8adafe9e4atagen2026-10-22 12:30 UTC124
    Generic { message: String },
7ff8adafe9e4atagen2026-10-22 12:30 UTC125
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC126
7ff8adafe9e4atagen2026-10-22 12:30 UTC127
/// The configuration to use when authenticating with OAuth.
7ff8adafe9e4atagen2026-10-22 12:30 UTC128
#[derive(uniffi::Record)]
7ff8adafe9e4atagen2026-10-22 12:30 UTC129
pub struct OAuthConfiguration {
7ff8adafe9e4atagen2026-10-22 12:30 UTC130
    /// The name of the client that will be shown during OAuth authentication.
7ff8adafe9e4atagen2026-10-22 12:30 UTC131
    pub client_name: Option<String>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC132
    /// The redirect URI that will be used when OAuth authentication is
7ff8adafe9e4atagen2026-10-22 12:30 UTC133
    /// successful.
7ff8adafe9e4atagen2026-10-22 12:30 UTC134
    pub redirect_uri: String,
7ff8adafe9e4atagen2026-10-22 12:30 UTC135
    /// A URI that contains information about the client.
7ff8adafe9e4atagen2026-10-22 12:30 UTC136
    pub client_uri: String,
7ff8adafe9e4atagen2026-10-22 12:30 UTC137
    /// A URI that contains the client's logo.
7ff8adafe9e4atagen2026-10-22 12:30 UTC138
    pub logo_uri: Option<String>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC139
    /// A URI that contains the client's terms of service.
7ff8adafe9e4atagen2026-10-22 12:30 UTC140
    pub tos_uri: Option<String>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC141
    /// A URI that contains the client's privacy policy.
7ff8adafe9e4atagen2026-10-22 12:30 UTC142
    pub policy_uri: Option<String>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC143
7ff8adafe9e4atagen2026-10-22 12:30 UTC144
    /// Pre-configured registrations for use with homeservers that don't support
7ff8adafe9e4atagen2026-10-22 12:30 UTC145
    /// dynamic client registration.
7ff8adafe9e4atagen2026-10-22 12:30 UTC146
    ///
7ff8adafe9e4atagen2026-10-22 12:30 UTC147
    /// The keys of the map should be the URLs of the homeservers, but keys
7ff8adafe9e4atagen2026-10-22 12:30 UTC148
    /// using `issuer` URLs are also supported.
7ff8adafe9e4atagen2026-10-22 12:30 UTC149
    pub static_registrations: HashMap<String, String>,
7ff8adafe9e4atagen2026-10-22 12:30 UTC150
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC151
7ff8adafe9e4atagen2026-10-22 12:30 UTC152
impl OAuthConfiguration {
7ff8adafe9e4atagen2026-10-22 12:30 UTC153
    pub(crate) fn redirect_uri(&self) -> Result<Url, OAuthError> {
7ff8adafe9e4atagen2026-10-22 12:30 UTC154
        Url::parse(&self.redirect_uri).map_err(|_| OAuthError::CallbackUrlInvalid)
7ff8adafe9e4atagen2026-10-22 12:30 UTC155
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC156
7ff8adafe9e4atagen2026-10-22 12:30 UTC157
    pub(crate) fn client_metadata(&self) -> Result<Raw<ClientMetadata>, OAuthError> {
7ff8adafe9e4atagen2026-10-22 12:30 UTC158
        let redirect_uri = self.redirect_uri()?;
7ff8adafe9e4atagen2026-10-22 12:30 UTC159
        let client_name = self.client_name.as_ref().map(|n| Localized::new(n.to_owned(), []));
7ff8adafe9e4atagen2026-10-22 12:30 UTC160
        let client_uri = self.client_uri.localized_url()?;
7ff8adafe9e4atagen2026-10-22 12:30 UTC161
        let logo_uri = self.logo_uri.localized_url()?;
7ff8adafe9e4atagen2026-10-22 12:30 UTC162
        let policy_uri = self.policy_uri.localized_url()?;
7ff8adafe9e4atagen2026-10-22 12:30 UTC163
        let tos_uri = self.tos_uri.localized_url()?;
7ff8adafe9e4atagen2026-10-22 12:30 UTC164
7ff8adafe9e4atagen2026-10-22 12:30 UTC165
        let metadata = ClientMetadata {
7ff8adafe9e4atagen2026-10-22 12:30 UTC166
            // The server should display the following fields when getting the user's consent.
7ff8adafe9e4atagen2026-10-22 12:30 UTC167
            client_name,
7ff8adafe9e4atagen2026-10-22 12:30 UTC168
            logo_uri,
7ff8adafe9e4atagen2026-10-22 12:30 UTC169
            policy_uri,
7ff8adafe9e4atagen2026-10-22 12:30 UTC170
            tos_uri,
7ff8adafe9e4atagen2026-10-22 12:30 UTC171
            ..ClientMetadata::new(
7ff8adafe9e4atagen2026-10-22 12:30 UTC172
                ApplicationType::Native,
7ff8adafe9e4atagen2026-10-22 12:30 UTC173
                vec![
7ff8adafe9e4atagen2026-10-22 12:30 UTC174
                    OAuthGrantType::AuthorizationCode { redirect_uris: vec![redirect_uri] },
7ff8adafe9e4atagen2026-10-22 12:30 UTC175
                    OAuthGrantType::DeviceCode,
7ff8adafe9e4atagen2026-10-22 12:30 UTC176
                ],
7ff8adafe9e4atagen2026-10-22 12:30 UTC177
                client_uri,
7ff8adafe9e4atagen2026-10-22 12:30 UTC178
            )
7ff8adafe9e4atagen2026-10-22 12:30 UTC179
        };
7ff8adafe9e4atagen2026-10-22 12:30 UTC180
7ff8adafe9e4atagen2026-10-22 12:30 UTC181
        Raw::new(&metadata).map_err(|_| OAuthError::MetadataInvalid)
7ff8adafe9e4atagen2026-10-22 12:30 UTC182
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC183
7ff8adafe9e4atagen2026-10-22 12:30 UTC184
    pub(crate) fn registration_data(&self) -> Result<ClientRegistrationData, OAuthError> {
7ff8adafe9e4atagen2026-10-22 12:30 UTC185
        let client_metadata = self.client_metadata()?;
7ff8adafe9e4atagen2026-10-22 12:30 UTC186
7ff8adafe9e4atagen2026-10-22 12:30 UTC187
        let mut registration_data = ClientRegistrationData::new(client_metadata);
7ff8adafe9e4atagen2026-10-22 12:30 UTC188
7ff8adafe9e4atagen2026-10-22 12:30 UTC189
        if !self.static_registrations.is_empty() {
7ff8adafe9e4atagen2026-10-22 12:30 UTC190
            let static_registrations = self
7ff8adafe9e4atagen2026-10-22 12:30 UTC191
                .static_registrations
7ff8adafe9e4atagen2026-10-22 12:30 UTC192
                .iter()
7ff8adafe9e4atagen2026-10-22 12:30 UTC193
                .filter_map(|(issuer, client_id)| {
7ff8adafe9e4atagen2026-10-22 12:30 UTC194
                    let Ok(issuer) = Url::parse(issuer) else {
7ff8adafe9e4atagen2026-10-22 12:30 UTC195
                        tracing::error!("Failed to parse {issuer:?}");
7ff8adafe9e4atagen2026-10-22 12:30 UTC196
                        return None;
7ff8adafe9e4atagen2026-10-22 12:30 UTC197
                    };
7ff8adafe9e4atagen2026-10-22 12:30 UTC198
                    Some((issuer, ClientId::new(client_id.clone())))
7ff8adafe9e4atagen2026-10-22 12:30 UTC199
                })
7ff8adafe9e4atagen2026-10-22 12:30 UTC200
                .collect();
7ff8adafe9e4atagen2026-10-22 12:30 UTC201
7ff8adafe9e4atagen2026-10-22 12:30 UTC202
            registration_data.static_registrations = Some(static_registrations);
7ff8adafe9e4atagen2026-10-22 12:30 UTC203
        }
7ff8adafe9e4atagen2026-10-22 12:30 UTC204
7ff8adafe9e4atagen2026-10-22 12:30 UTC205
        Ok(registration_data)
7ff8adafe9e4atagen2026-10-22 12:30 UTC206
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC207
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC208
7ff8adafe9e4atagen2026-10-22 12:30 UTC209
#[derive(Debug, thiserror::Error, uniffi::Error)]
7ff8adafe9e4atagen2026-10-22 12:30 UTC210
#[uniffi(flat_error)]
7ff8adafe9e4atagen2026-10-22 12:30 UTC211
pub enum OAuthError {
7ff8adafe9e4atagen2026-10-22 12:30 UTC212
    #[error(
7ff8adafe9e4atagen2026-10-22 12:30 UTC213
        "The homeserver doesn't provide an authentication issuer in its well-known configuration."
7ff8adafe9e4atagen2026-10-22 12:30 UTC214
    )]
7ff8adafe9e4atagen2026-10-22 12:30 UTC215
    NotSupported,
7ff8adafe9e4atagen2026-10-22 12:30 UTC216
    #[error("Unable to use OAuth as the supplied client metadata is invalid.")]
7ff8adafe9e4atagen2026-10-22 12:30 UTC217
    MetadataInvalid,
7ff8adafe9e4atagen2026-10-22 12:30 UTC218
    #[error("The supplied callback URL used to complete OAuth is invalid.")]
7ff8adafe9e4atagen2026-10-22 12:30 UTC219
    CallbackUrlInvalid,
7ff8adafe9e4atagen2026-10-22 12:30 UTC220
    #[error("The OAuth login was cancelled by the user.")]
7ff8adafe9e4atagen2026-10-22 12:30 UTC221
    Cancelled,
7ff8adafe9e4atagen2026-10-22 12:30 UTC222
7ff8adafe9e4atagen2026-10-22 12:30 UTC223
    #[error("An error occurred: {message}")]
7ff8adafe9e4atagen2026-10-22 12:30 UTC224
    Generic { message: String },
7ff8adafe9e4atagen2026-10-22 12:30 UTC225
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC226
7ff8adafe9e4atagen2026-10-22 12:30 UTC227
impl From<SdkOAuthError> for OAuthError {
7ff8adafe9e4atagen2026-10-22 12:30 UTC228
    fn from(e: SdkOAuthError) -> OAuthError {
7ff8adafe9e4atagen2026-10-22 12:30 UTC229
        match e {
7ff8adafe9e4atagen2026-10-22 12:30 UTC230
            SdkOAuthError::Discovery(error) if error.is_not_supported() => OAuthError::NotSupported,
7ff8adafe9e4atagen2026-10-22 12:30 UTC231
            SdkOAuthError::AuthorizationCode(OAuthAuthorizationCodeError::RedirectUri(_))
7ff8adafe9e4atagen2026-10-22 12:30 UTC232
            | SdkOAuthError::AuthorizationCode(OAuthAuthorizationCodeError::InvalidState) => {
7ff8adafe9e4atagen2026-10-22 12:30 UTC233
                OAuthError::CallbackUrlInvalid
7ff8adafe9e4atagen2026-10-22 12:30 UTC234
            }
7ff8adafe9e4atagen2026-10-22 12:30 UTC235
            SdkOAuthError::AuthorizationCode(OAuthAuthorizationCodeError::Cancelled) => {
7ff8adafe9e4atagen2026-10-22 12:30 UTC236
                OAuthError::Cancelled
7ff8adafe9e4atagen2026-10-22 12:30 UTC237
            }
7ff8adafe9e4atagen2026-10-22 12:30 UTC238
            _ => OAuthError::Generic { message: e.to_string() },
7ff8adafe9e4atagen2026-10-22 12:30 UTC239
        }
7ff8adafe9e4atagen2026-10-22 12:30 UTC240
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC241
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC242
7ff8adafe9e4atagen2026-10-22 12:30 UTC243
impl From<Error> for OAuthError {
7ff8adafe9e4atagen2026-10-22 12:30 UTC244
    fn from(e: Error) -> OAuthError {
7ff8adafe9e4atagen2026-10-22 12:30 UTC245
        match e {
7ff8adafe9e4atagen2026-10-22 12:30 UTC246
            Error::OAuth(e) => (*e).into(),
7ff8adafe9e4atagen2026-10-22 12:30 UTC247
            _ => OAuthError::Generic { message: e.to_string() },
7ff8adafe9e4atagen2026-10-22 12:30 UTC248
        }
7ff8adafe9e4atagen2026-10-22 12:30 UTC249
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC250
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC251
7ff8adafe9e4atagen2026-10-22 12:30 UTC252
/* Helpers */
7ff8adafe9e4atagen2026-10-22 12:30 UTC253
7ff8adafe9e4atagen2026-10-22 12:30 UTC254
trait OptionExt {
7ff8adafe9e4atagen2026-10-22 12:30 UTC255
    /// Convenience method to convert an `Option<String>` to a URL and returns
7ff8adafe9e4atagen2026-10-22 12:30 UTC256
    /// it as a Localized URL. No localization is actually performed.
7ff8adafe9e4atagen2026-10-22 12:30 UTC257
    fn localized_url(&self) -> Result<Option<Localized<Url>>, OAuthError>;
7ff8adafe9e4atagen2026-10-22 12:30 UTC258
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC259
7ff8adafe9e4atagen2026-10-22 12:30 UTC260
impl OptionExt for Option<String> {
7ff8adafe9e4atagen2026-10-22 12:30 UTC261
    fn localized_url(&self) -> Result<Option<Localized<Url>>, OAuthError> {
7ff8adafe9e4atagen2026-10-22 12:30 UTC262
        self.as_deref().map(StrExt::localized_url).transpose()
7ff8adafe9e4atagen2026-10-22 12:30 UTC263
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC264
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC265
7ff8adafe9e4atagen2026-10-22 12:30 UTC266
trait StrExt {
7ff8adafe9e4atagen2026-10-22 12:30 UTC267
    /// Convenience method to convert a string to a URL and returns it as a
7ff8adafe9e4atagen2026-10-22 12:30 UTC268
    /// Localized URL. No localization is actually performed.
7ff8adafe9e4atagen2026-10-22 12:30 UTC269
    fn localized_url(&self) -> Result<Localized<Url>, OAuthError>;
7ff8adafe9e4atagen2026-10-22 12:30 UTC270
}
7ff8adafe9e4atagen2026-10-22 12:30 UTC271
7ff8adafe9e4atagen2026-10-22 12:30 UTC272
impl StrExt for str {
7ff8adafe9e4atagen2026-10-22 12:30 UTC273
    fn localized_url(&self) -> Result<Localized<Url>, OAuthError> {
7ff8adafe9e4atagen2026-10-22 12:30 UTC274
        Ok(Localized::new(Url::parse(self).map_err(|_| OAuthError::MetadataInvalid)?, []))
7ff8adafe9e4atagen2026-10-22 12:30 UTC275
    }
7ff8adafe9e4atagen2026-10-22 12:30 UTC276
}