Skip to main content

rp_supabase_client/
client.rs

1use futures::{Stream, StreamExt as _};
2use rp_postgrest::{Postgrest, reqwest};
3use rp_supabase_auth::error::AuthError;
4use rp_supabase_auth::jwt_stream::SupabaseAuthConfig;
5use rp_supabase_auth::types::{AccessTokenResponseSchema, LoginCredentials};
6use rp_supabase_auth::url;
7
8pub const SUPABASE_KEY: &str = "apikey";
9
10/// Create an authenticated client stream using a default HTTP transport.
11///
12/// # Errors
13/// Returns transport construction, configuration or sign-in setup failures.
14pub fn new_authenticated(
15    config: SupabaseAuthConfig,
16    login_info: LoginCredentials,
17) -> Result<
18    impl Stream<Item = Result<(Postgrest, AccessTokenResponseSchema), SupabaseClientError>>,
19    SupabaseClientError,
20> {
21    new_authenticated_with_client(config, login_info, default_client()?)
22}
23
24/// Create an authenticated stream sharing the supplied HTTP transport for REST,
25/// login and every token refresh. Request credentials do not alter its defaults.
26///
27/// # Errors
28/// Returns configuration or sign-in setup failures.
29pub fn new_authenticated_with_client(
30    config: SupabaseAuthConfig,
31    login_info: LoginCredentials,
32    http: reqwest::Client,
33) -> Result<
34    impl Stream<Item = Result<(Postgrest, AccessTokenResponseSchema), SupabaseClientError>>,
35    SupabaseClientError,
36> {
37    let base = anonymous_client_with_client(config.api_key.clone(), &config.url, http.clone())?;
38    let auth_stream = rp_supabase_auth::jwt_stream::JwtStream::new(config)
39        .sign_in_with_client(login_info, http)?;
40    Ok(auth_stream.map(move |item| {
41        let item = item?;
42        let client = match item.access_token.as_ref() {
43            Some(token) => base.clone().auth(token)?,
44            None => base.clone(),
45        };
46        Ok((client, item))
47    }))
48}
49
50/// Create an anonymous client using a default HTTP transport.
51///
52/// # Errors
53/// Returns transport construction or configuration failures.
54pub fn anonymous_client(api_key: String, url: &url::Url) -> Result<Postgrest, SupabaseClientError> {
55    anonymous_client_with_client(api_key, url, default_client()?)
56}
57
58/// Create an anonymous client retaining the supplied HTTP pool and policies.
59///
60/// # Errors
61/// Returns URL or header configuration failures.
62pub fn anonymous_client_with_client(
63    api_key: String,
64    url: &url::Url,
65    http: reqwest::Client,
66) -> Result<Postgrest, SupabaseClientError> {
67    let url = url.join("rest/v1/")?;
68    Ok(Postgrest::new_with_client(url.as_str(), http)?.insert_header(SUPABASE_KEY, api_key)?)
69}
70
71/// Builds the default transport. Redirects are followed only within the same origin.
72fn default_client() -> Result<reqwest::Client, reqwest::Error> {
73    const MAX_REDIRECTS: usize = 10;
74    reqwest::Client::builder()
75        .redirect(reqwest::redirect::Policy::custom(|attempt| {
76            let same_origin = attempt
77                .previous()
78                .last()
79                .is_some_and(|previous| previous.origin() == attempt.url().origin());
80            if !same_origin {
81                attempt.stop()
82            } else if attempt.previous().len() > MAX_REDIRECTS {
83                attempt.error("too many redirects")
84            } else {
85                attempt.follow()
86            }
87        }))
88        .build()
89}
90
91#[derive(thiserror::Error, Debug)]
92pub enum SupabaseClientError {
93    #[error("Jwt Stream closed unexpectedly")]
94    JwtStreamClosedUnexpectedly,
95    #[error("Refresh stream error")]
96    RefreshStreamError(#[from] rp_supabase_auth::jwt_stream::RefreshStreamError),
97    #[error("Auth sign in error")]
98    AuthSignInError(#[from] rp_supabase_auth::jwt_stream::SignInError),
99    #[error("Url parse error {0}")]
100    UrlParseError(#[from] url::ParseError),
101    #[error("Auth error {0}")]
102    AuthError(#[from] AuthError),
103    #[error(transparent)]
104    Postgrest(#[from] rp_postgrest::Error),
105    #[error(transparent)]
106    Transport(#[from] reqwest::Error),
107}