Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clients/rust/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ via [`ClientBuilder::token`]. It accepts either:
and scope being accessed.
- A **`String` / `&str`** — a pre-signed JWT, used as-is for every request.
Use this for external services that receive a token from another source.
- An `Option` of any of the above — useful for chained builder calls.

```rust,ignore
use objectstore_client::{Client, SecretKey, TokenGenerator, Usecase};
Expand Down
48 changes: 36 additions & 12 deletions clients/rust/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,44 @@ impl std::fmt::Debug for TokenProvider {
}
}

impl From<TokenGenerator> for TokenProvider {
fn from(generator: TokenGenerator) -> Self {
TokenProvider::Generator(generator)
/// Conversion into an optional [`TokenProvider`] for [`ClientBuilder::token`].
///
/// This is implemented for [`TokenGenerator`], `String`, and `&str`, each of which yields a
/// configured provider. It is also implemented for any `Option<T>` where `T: IntoTokenProvider`,
/// so a `None` resolves to no authentication and a `Some(value)` to the inner provider. This lets
/// callers pass optional auth configuration to [`ClientBuilder::token`] without an explicit
/// conditional.
///
/// [`ClientBuilder::token`]: crate::ClientBuilder::token
pub trait IntoTokenProvider {
/// Converts `self` into an optional [`TokenProvider`].
fn into_token_provider(self) -> Option<TokenProvider>;
}

impl<T> IntoTokenProvider for Option<T>
where
T: IntoTokenProvider,
{
fn into_token_provider(self) -> Option<TokenProvider> {
self.and_then(|t| t.into_token_provider())
}
}

impl IntoTokenProvider for TokenGenerator {
fn into_token_provider(self) -> Option<TokenProvider> {
Some(TokenProvider::Generator(self))
}
}

impl From<String> for TokenProvider {
fn from(token: String) -> Self {
TokenProvider::Static(token)
impl IntoTokenProvider for String {
fn into_token_provider(self) -> Option<TokenProvider> {
Some(TokenProvider::Static(self))
}
}

impl From<&str> for TokenProvider {
fn from(token: &str) -> Self {
TokenProvider::Static(token.to_owned())
impl IntoTokenProvider for &str {
fn into_token_provider(self) -> Option<TokenProvider> {
Some(TokenProvider::Static(self.to_owned()))
}
}

Expand All @@ -78,9 +101,10 @@ impl From<&str> for TokenProvider {
/// Tokens are signed with an EdDSA private key and have certain permissions and expiry timeouts
/// applied.
///
/// Use this for internal services that have access to an EdDSA keypair. You can pass a
/// `TokenGenerator` directly to [`ClientBuilder::token`](crate::ClientBuilder::token),
/// and it will be automatically converted into a [`TokenProvider::Generator`].
/// Use this for internal services that have access to an EdDSA keypair. A `TokenGenerator`
/// implements [`IntoTokenProvider`], so it can be passed directly to
/// [`ClientBuilder::token`](crate::ClientBuilder::token), where it becomes a
/// [`TokenProvider::Generator`].
#[derive(Debug)]
pub struct TokenGenerator {
kid: String,
Expand Down
26 changes: 23 additions & 3 deletions clients/rust/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use objectstore_types::scope;
use reqwest::RequestBuilder;
use url::Url;

use crate::IntoTokenProvider;
use crate::auth::TokenProvider;

const USER_AGENT: &str = concat!("objectstore-client/", env!("CARGO_PKG_VERSION"));
Expand Down Expand Up @@ -110,13 +111,15 @@ impl ClientBuilder {

/// Sets the authentication token to use for requests to Objectstore.
///
/// Accepts anything that implements `Into<TokenProvider>`:
/// Accepts anything that implements [`IntoTokenProvider`]:
/// - A [`TokenGenerator`](crate::TokenGenerator) — for internal services that have access to
/// an EdDSA keypair. The generator signs a fresh JWT for each request.
/// - A `String` or `&str` — a pre-signed JWT, used as-is for every request.
pub fn token(self, token: impl Into<TokenProvider>) -> Self {
/// - An `Option` of any of the above — a `None` leaves the client unauthenticated, which is
/// convenient when authentication is configured conditionally.
pub fn token(self, token: impl IntoTokenProvider) -> Self {
let Ok(mut inner) = self.0 else { return self };
inner.token = Some(token.into());
inner.token = token.into_token_provider();
Self(Ok(inner))
}

Expand Down Expand Up @@ -365,6 +368,23 @@ pub(crate) struct ClientInner {
/// # Ok(())
/// # }
/// ```
///
/// Optional authentication — pass an `Option` straight through, leaving the client
/// unauthenticated when it is `None`:
///
/// ```no_run
/// use objectstore_client::Client;
///
/// # fn example() -> objectstore_client::Result<()> {
/// // Authenticate only if a token is present in the environment.
/// let token_opt = std::env::var("OBJECTSTORE_TOKEN").ok();
///
/// let client = Client::builder("http://localhost:8888/")
/// .token(token_opt)
/// .build()?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone)]
pub struct Client {
inner: Arc<ClientInner>,
Expand Down
12 changes: 5 additions & 7 deletions stresstest/src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,12 @@ pub struct HttpRemote {
impl HttpRemote {
/// Creates a new `HttpRemote` instance with the given remote URL and optional token generator.
pub fn new(remote: &str, token: Option<TokenGenerator>) -> Self {
let mut builder = Client::builder(remote).configure_reqwest(|r| r.no_hickory_dns());
if let Some(t) = token {
builder = builder.token(t);
}
Self {
// INVARIANT: builder is always valid — remote is a caller-supplied URL and no
// fallible configuration is applied.
client: builder.build().unwrap(),
client: Client::builder(remote)
.configure_reqwest(|r| r.no_hickory_dns())
.token(token)
.build()
.unwrap(),
}
}

Expand Down
Loading