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
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,20 @@ jobs:
rustup target add wasm32-unknown-unknown
cargo build --target wasm32-unknown-unknown --no-default-features --features=aws,azure,aliyun,tencent

# Keep wasm compatibility checks focused on core traits and supported reqsign feature sets.
wasm_compat:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Check
run: |
rustup target add wasm32-unknown-unknown
cargo check -p reqsign-core --target wasm32-unknown-unknown
cargo build --manifest-path reqsign/Cargo.toml --target wasm32-unknown-unknown --no-default-features --features=aws,azure,aliyun,tencent
cargo build --manifest-path reqsign/Cargo.toml --target wasm32-unknown-unknown --no-default-features --features=default-context,aws,azure,aliyun,tencent

unit:
runs-on: ubuntu-latest
permissions:
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,13 @@ reqsign-volcengine-tos = { version = "2.0.2", path = "services/volcengine-tos" }

# Crates.io dependencies
anyhow = "1"
async-trait = "0.1"
base64 = "0.22"
bytes = "1"
criterion = { version = "0.8.1", features = ["async_tokio", "html_reports"] }
dotenvy = "0.15"
env_logger = "0.11"
form_urlencoded = "1"
futures = "0.3"
hex = "0.4"
hmac = "0.12"
http = "1"
Expand Down
5 changes: 3 additions & 2 deletions context/command-execute-tokio/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ repository.workspace = true
rust-version.workspace = true

[dependencies]
async-trait = { workspace = true }
reqsign-core = { workspace = true }
tokio = { version = "1", features = ["process", "io-util"] }

[target.'cfg(not(target_family = "wasm"))'.dependencies]
tokio = { version = "1", default-features = false, features = ["process", "io-util"] }

[dev-dependencies]
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
17 changes: 13 additions & 4 deletions context/command-execute-tokio/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@
//! # Ok(())
//! # }
//! ```

use async_trait::async_trait;
use reqsign_core::{CommandExecute, CommandOutput, Error, Result};
#[cfg(not(target_family = "wasm"))]
use std::process::Stdio;
#[cfg(not(target_family = "wasm"))]
use tokio::process::Command;

/// Tokio-based implementation of the `CommandExecute` trait.
Expand All @@ -90,7 +90,7 @@ use tokio::process::Command;
#[derive(Debug, Clone, Copy, Default)]
pub struct TokioCommandExecute;

#[async_trait]
#[cfg(not(target_family = "wasm"))]
impl CommandExecute for TokioCommandExecute {
async fn command_execute(&self, program: &str, args: &[&str]) -> Result<CommandOutput> {
let output = Command::new(program)
Expand All @@ -111,7 +111,16 @@ impl CommandExecute for TokioCommandExecute {
}
}

#[cfg(test)]
#[cfg(target_family = "wasm")]
impl CommandExecute for TokioCommandExecute {
async fn command_execute(&self, _program: &str, _args: &[&str]) -> Result<CommandOutput> {
Err(Error::unexpected(
"TokioCommandExecute is unsupported on wasm targets",
))
}
}

#[cfg(all(test, not(target_family = "wasm")))]
mod tests {
use super::*;

Expand Down
1 change: 0 additions & 1 deletion context/file-read-tokio/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ rust-version.workspace = true

[dependencies]
anyhow = { workspace = true }
async-trait = { workspace = true }
reqsign-core = { workspace = true }

[target.'cfg(not(target_family = "wasm"))'.dependencies]
Expand Down
4 changes: 0 additions & 4 deletions context/file-read-tokio/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@
//! # Ok(())
//! # }
//! ```

use async_trait::async_trait;
use reqsign_core::{Error, FileRead, Result};

/// Tokio-based implementation of the `FileRead` trait.
Expand All @@ -75,7 +73,6 @@ use reqsign_core::{Error, FileRead, Result};
pub struct TokioFileRead;

#[cfg(not(target_family = "wasm"))]
#[async_trait]
impl FileRead for TokioFileRead {
async fn file_read(&self, path: &str) -> Result<Vec<u8>> {
tokio::fs::read(path)
Expand All @@ -85,7 +82,6 @@ impl FileRead for TokioFileRead {
}

#[cfg(target_family = "wasm")]
#[async_trait]
impl FileRead for TokioFileRead {
async fn file_read(&self, _path: &str) -> Result<Vec<u8>> {
Err(Error::unexpected(
Expand Down
1 change: 0 additions & 1 deletion context/http-send-reqwest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ rust-version.workspace = true

[dependencies]
anyhow = { workspace = true }
async-trait = { workspace = true }
bytes = { workspace = true }
http = { workspace = true }
http-body-util = { version = "0.1.3" }
Expand Down
4 changes: 0 additions & 4 deletions context/http-send-reqwest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,6 @@
//! // Use the custom client
//! let http_send = ReqwestHttpSend::new(client);
//! ```

use async_trait::async_trait;
use bytes::Bytes;
#[cfg(target_arch = "wasm32")]
use futures_channel::oneshot;
Expand Down Expand Up @@ -136,8 +134,6 @@ impl ReqwestHttpSend {
Self { client }
}
}

#[async_trait]
impl HttpSend for ReqwestHttpSend {
async fn http_send(&self, req: http::Request<Bytes>) -> Result<http::Response<Bytes>> {
let req = Request::try_from(req)
Expand Down
2 changes: 1 addition & 1 deletion core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ rust-version.workspace = true

[dependencies]
anyhow = { workspace = true }
async-trait = { workspace = true }
base64 = { workspace = true }
bytes = { workspace = true }
form_urlencoded = { workspace = true }
futures = { workspace = true }
hex = { workspace = true }
hmac = { workspace = true }
http = { workspace = true }
Expand Down
110 changes: 99 additions & 11 deletions core/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
// specific language governing permissions and limitations
// under the License.

use crate::{Context, Result};
use crate::{BoxedFuture, Context, MaybeSend, Result};
use std::fmt::Debug;
use std::future::Future;
use std::ops::Deref;
use std::time::Duration;

/// SigningCredential is the trait used by signer as the signing credential.
Expand All @@ -39,19 +41,57 @@ impl<T: SigningCredential> SigningCredential for Option<T> {
///`
/// Service may require different credential to sign the request, for example, AWS require
/// access key and secret key, while Google Cloud Storage require token.
#[async_trait::async_trait]
pub trait ProvideCredential: Debug + Send + Sync + Unpin + 'static {
/// Credential returned by this loader.
///
/// Typically, it will be a credential.
type Credential: Send + Sync + Unpin + 'static;

/// Load signing credential from current env.
async fn provide_credential(&self, ctx: &Context) -> Result<Option<Self::Credential>>;
fn provide_credential(
&self,
ctx: &Context,
) -> impl Future<Output = Result<Option<Self::Credential>>> + MaybeSend;
}

/// ProvideCredentialDyn is the dyn version of [`ProvideCredential`].
pub trait ProvideCredentialDyn: Debug + Send + Sync + Unpin + 'static {
/// Credential returned by this loader.
type Credential: Send + Sync + Unpin + 'static;

/// Dyn version of [`ProvideCredential::provide_credential`].
fn provide_credential_dyn<'a>(
&'a self,
ctx: &'a Context,
) -> BoxedFuture<'a, Result<Option<Self::Credential>>>;
}

impl<T> ProvideCredentialDyn for T
where
T: ProvideCredential + ?Sized,
{
type Credential = T::Credential;

fn provide_credential_dyn<'a>(
&'a self,
ctx: &'a Context,
) -> BoxedFuture<'a, Result<Option<Self::Credential>>> {
Box::pin(self.provide_credential(ctx))
}
}

impl<T> ProvideCredential for std::sync::Arc<T>
where
T: ProvideCredentialDyn + ?Sized,
{
type Credential = T::Credential;

async fn provide_credential(&self, ctx: &Context) -> Result<Option<Self::Credential>> {
self.deref().provide_credential_dyn(ctx).await
}
}

/// SignRequest is the trait used by signer to build the signing request.
#[async_trait::async_trait]
pub trait SignRequest: Debug + Send + Sync + Unpin + 'static {
/// Credential used by this builder.
///
Expand All @@ -71,13 +111,64 @@ pub trait SignRequest: Debug + Send + Sync + Unpin + 'static {
///
/// Implementation details determine how to handle the expiration logic. For instance,
/// AWS uses a query string that includes an `Expires` parameter.
fn sign_request<'a>(
&'a self,
ctx: &'a Context,
req: &'a mut http::request::Parts,
credential: Option<&'a Self::Credential>,
expires_in: Option<Duration>,
) -> impl Future<Output = Result<()>> + MaybeSend + 'a;
}

/// SignRequestDyn is the dyn version of [`SignRequest`].
pub trait SignRequestDyn: Debug + Send + Sync + Unpin + 'static {
/// Credential used by this builder.
type Credential: Send + Sync + Unpin + 'static;

/// Dyn version of [`SignRequest::sign_request`].
fn sign_request_dyn<'a>(
&'a self,
ctx: &'a Context,
req: &'a mut http::request::Parts,
credential: Option<&'a Self::Credential>,
expires_in: Option<Duration>,
) -> BoxedFuture<'a, Result<()>>;
}

impl<T> SignRequestDyn for T
where
T: SignRequest + ?Sized,
{
type Credential = T::Credential;

fn sign_request_dyn<'a>(
&'a self,
ctx: &'a Context,
req: &'a mut http::request::Parts,
credential: Option<&'a Self::Credential>,
expires_in: Option<Duration>,
) -> BoxedFuture<'a, Result<()>> {
Box::pin(self.sign_request(ctx, req, credential, expires_in))
}
}

impl<T> SignRequest for std::sync::Arc<T>
where
T: SignRequestDyn + ?Sized,
{
type Credential = T::Credential;

async fn sign_request(
&self,
ctx: &Context,
req: &mut http::request::Parts,
credential: Option<&Self::Credential>,
expires_in: Option<Duration>,
) -> Result<()>;
) -> Result<()> {
self.deref()
.sign_request_dyn(ctx, req, credential, expires_in)
.await
}
}

/// A chain of credential providers that will be tried in order.
Expand All @@ -90,7 +181,6 @@ pub trait SignRequest: Debug + Send + Sync + Unpin + 'static {
///
/// ```no_run
/// use reqsign_core::{ProvideCredentialChain, Context, ProvideCredential, Result};
/// use async_trait::async_trait;
///
/// #[derive(Debug)]
/// struct MyCredential {
Expand All @@ -100,7 +190,6 @@ pub trait SignRequest: Debug + Send + Sync + Unpin + 'static {
/// #[derive(Debug)]
/// struct EnvironmentProvider;
///
/// #[async_trait]
/// impl ProvideCredential for EnvironmentProvider {
/// type Credential = MyCredential;
///
Expand All @@ -118,7 +207,7 @@ pub trait SignRequest: Debug + Send + Sync + Unpin + 'static {
/// # }
/// ```
pub struct ProvideCredentialChain<C> {
providers: Vec<Box<dyn ProvideCredential<Credential = C>>>,
providers: Vec<Box<dyn ProvideCredentialDyn<Credential = C>>>,
}

impl<C> ProvideCredentialChain<C>
Expand Down Expand Up @@ -150,7 +239,7 @@ where
}

/// Create a credential provider chain from a vector of providers.
pub fn from_vec(providers: Vec<Box<dyn ProvideCredential<Credential = C>>>) -> Self {
pub fn from_vec(providers: Vec<Box<dyn ProvideCredentialDyn<Credential = C>>>) -> Self {
Self { providers }
}

Expand Down Expand Up @@ -185,7 +274,6 @@ where
}
}

#[async_trait::async_trait]
impl<C> ProvideCredential for ProvideCredentialChain<C>
where
C: Send + Sync + Unpin + 'static,
Expand All @@ -196,7 +284,7 @@ where
for provider in &self.providers {
log::debug!("Trying credential provider: {provider:?}");

match provider.provide_credential(ctx).await {
match provider.provide_credential_dyn(ctx).await {
Ok(Some(cred)) => {
log::debug!("Successfully loaded credential from provider: {provider:?}");
return Ok(Some(cred));
Expand Down
Loading
Loading