Skip to content

Commit

Permalink
try fit wasm partially (#225)
Browse files Browse the repository at this point in the history
* try fit wasm partially

* Refactor AsyncApi struct in client_reqwest.rs not swapping the whole AsyncApi struct anymore

* try make wasm target has minimum affection of other codes.

* only use target to difference logic.

* fix clippy format error.

* ensure min change at lib.rs

* ci(rust): test wasm32

* typo fix and try keep code remain.
 add some statement.

* use more grace default macro

* Update src/client_reqwest.rs

accept suggestion

Co-authored-by: EdJoPaTo <[email protected]>

* follow code style

---------

Co-authored-by: EdJoPaTo <rfc-conform-git-commit-email@funny-long-domain-label-everyone-hates-as-it-is-too-long.edjopato.de>
Co-authored-by: EdJoPaTo <[email protected]>
  • Loading branch information
3 people authored Nov 26, 2024
1 parent 6929d1e commit 2c1a4ec
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 50 deletions.
14 changes: 13 additions & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,24 +116,36 @@ jobs:
matrix:
include:
- triple: x86_64-unknown-linux-gnu
features: --all-features
os: ubuntu-latest
- triple: aarch64-unknown-linux-gnu
features: --all-features
os: ubuntu-latest
- triple: armv7-unknown-linux-gnueabihf
features: --all-features
os: ubuntu-latest
- triple: arm-unknown-linux-gnueabihf
features: --all-features
os: ubuntu-latest
- triple: riscv64gc-unknown-linux-gnu
features: --all-features
os: ubuntu-latest
- triple: wasm32-unknown-unknown
features: --no-default-features --features=async-http-client
os: ubuntu-latest

- triple: x86_64-apple-darwin
features: --all-features
os: macos-latest
- triple: aarch64-apple-darwin
features: --all-features
os: macos-latest

- triple: x86_64-pc-windows-msvc
features: --all-features
os: windows-latest
- triple: aarch64-pc-windows-msvc
features: --all-features
os: windows-latest
env:
RUSTFLAGS: --deny warnings
Expand All @@ -156,4 +168,4 @@ jobs:
key: release-${{ matrix.triple }}-${{ steps.rust.outputs.cachekey }}-${{ hashFiles('**/Cargo.*') }}
path: target/

- run: ${{ runner.os == 'Linux' && 'cross' || 'cargo' }} build --release --offline --all-features --target ${{ matrix.triple }}
- run: ${{ runner.os == 'Linux' && 'cross' || 'cargo' }} build --release --offline ${{ matrix.features }} --target ${{ matrix.triple }}
10 changes: 8 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,19 @@ default-features = false
features = ["client"]
optional = true

[dependencies.reqwest]
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.reqwest]
version = "0.12"
default-features = false
features = ["multipart", "stream", "rustls-tls"]
optional = true

[dependencies.tokio]
[target.'cfg(target_arch = "wasm32")'.dependencies.reqwest]
version = "0.12"
default-features = false
features = ["multipart", "stream"]
optional = true

[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio]
version = "1"
features = ["fs"]
optional = true
Expand Down
109 changes: 63 additions & 46 deletions src/client_reqwest.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
use std::path::PathBuf;
use std::time::Duration;

use async_trait::async_trait;
use bon::Builder;
use reqwest::multipart;
use serde_json::Value;
use tokio::fs::File;

use crate::trait_async::AsyncTelegramApi;
use crate::Error;
Expand All @@ -17,16 +13,21 @@ pub struct AsyncApi {
#[builder(into)]
pub api_url: String,

#[builder(
default = reqwest::ClientBuilder::new()
.connect_timeout(Duration::from_secs(10))
.timeout(Duration::from_secs(500))
.build()
.unwrap()
)]
#[builder(default = default_client())]
pub client: reqwest::Client,
}

fn default_client() -> reqwest::Client {
let client_builder = reqwest::ClientBuilder::new();

#[cfg(not(target_arch = "wasm32"))]
let client_builder = client_builder
.connect_timeout(std::time::Duration::from_secs(10))
.timeout(std::time::Duration::from_secs(500));

client_builder.build().unwrap()
}

impl AsyncApi {
/// Create a new `AsyncApi`. You can use [`AsyncApi::new_url`] or [`AsyncApi::builder`] for more options.
pub fn new(api_key: &str) -> Self {
Expand Down Expand Up @@ -66,7 +67,9 @@ impl From<reqwest::Error> for Error {
}
}

#[async_trait]
// Wasm target need not be `Send` because it is single-threaded
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl AsyncTelegramApi for AsyncApi {
type Error = Error;

Expand Down Expand Up @@ -102,44 +105,58 @@ impl AsyncTelegramApi for AsyncApi {
Params: serde::ser::Serialize + std::fmt::Debug + std::marker::Send,
Output: serde::de::DeserializeOwned,
{
let json_string = crate::json::encode(&params)?;
let json_struct: Value = serde_json::from_str(&json_string).unwrap();
let file_keys: Vec<&str> = files.iter().map(|(key, _)| *key).collect();
let files_with_paths: Vec<(String, &str, String)> = files
.iter()
.map(|(key, path)| {
(
(*key).to_string(),
path.to_str().unwrap(),
path.file_name().unwrap().to_str().unwrap().to_string(),
)
})
.collect();

let mut form = multipart::Form::new();
for (key, val) in json_struct.as_object().unwrap() {
if !file_keys.contains(&key.as_str()) {
let val = match val {
Value::String(val) => val.to_string(),
other => other.to_string(),
};

form = form.text(key.clone(), val);
#[cfg(not(target_arch = "wasm32"))]
{
use reqwest::multipart;
use serde_json::Value;

let json_string = crate::json::encode(&params)?;
let json_struct: Value = serde_json::from_str(&json_string).unwrap();

let file_keys: Vec<&str> = files.iter().map(|(key, _)| *key).collect();
let files_with_paths: Vec<(String, &str, String)> = files
.iter()
.map(|(key, path)| {
(
(*key).to_string(),
path.to_str().unwrap(),
path.file_name().unwrap().to_str().unwrap().to_string(),
)
})
.collect();

let mut form = multipart::Form::new();
for (key, val) in json_struct.as_object().unwrap() {
if !file_keys.contains(&key.as_str()) {
let val = match val {
Value::String(val) => val.to_string(),
other => other.to_string(),
};

form = form.text(key.clone(), val);
}
}
}

for (parameter_name, file_path, file_name) in files_with_paths {
let file = File::open(file_path)
.await
.map_err(|error| Error::Encode(error.to_string()))?;
let part = multipart::Part::stream(file).file_name(file_name);
form = form.part(parameter_name, part);
}
for (parameter_name, file_path, file_name) in files_with_paths {
let file = tokio::fs::File::open(file_path)
.await
.map_err(|error| Error::Encode(error.to_string()))?;
let part = multipart::Part::stream(file).file_name(file_name);
form = form.part(parameter_name, part);
}

let url = format!("{}/{method}", self.api_url);
let url = format!("{}/{method}", self.api_url);

let response = self.client.post(url).multipart(form).send().await?;
Self::decode_response(response).await
let response = self.client.post(url).multipart(form).send().await?;
Self::decode_response(response).await
}

#[cfg(target_arch = "wasm32")]
{
Err(Error::Encode(format!(
"calling {method:?} with files is currently unsupported in WASM due to missing form_data / attachment support. Was called with params {params:?} and files {files:?}",
)))
}
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/trait_async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ macro_rules! request_nb {
}
}

#[async_trait::async_trait]
// Wasm target need not be `Send` because it is single-threaded
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
pub trait AsyncTelegramApi
where
Self: Sync,
Expand Down

0 comments on commit 2c1a4ec

Please sign in to comment.