diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bcdfe4e..330ac62 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,33 +10,29 @@ jobs: build: name: Build runs-on: ubuntu-latest + strategy: + matrix: + include: + - target: wasm32-unknown-unknown + - target: wasm32-wasip2 steps: - name: Checkout sources uses: actions/checkout@v3 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - target: wasm32-unknown-unknown + - name: Install Rust from rust-toolchain.toml + uses: actions-rust-lang/setup-rust-toolchain@v1 - name: Build uses: actions-rs/cargo@v1 with: command: build - args: --target wasm32-unknown-unknown + args: --target ${{ matrix.target }} fmt: name: Format Check runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v3 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - components: rustfmt - target: wasm32-unknown-unknown + - name: Install Rust from rust-toolchain.toml + uses: actions-rust-lang/setup-rust-toolchain@v1 - name: Run cargo fmt uses: actions-rs/cargo@v1 with: @@ -45,18 +41,18 @@ jobs: clippy: name: Clippy Check runs-on: ubuntu-latest + strategy: + matrix: + include: + - target: wasm32-unknown-unknown + - target: wasm32-wasip2 steps: - name: Checkout sources uses: actions/checkout@v3 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - components: clippy - target: wasm32-unknown-unknown + - name: Install Rust from rust-toolchain.toml + uses: actions-rust-lang/setup-rust-toolchain@v1 - name: Run cargo clippy uses: actions-rs/cargo@v1 with: command: clippy - args: --target wasm32-unknown-unknown -- -D warnings + args: --target ${{ matrix.target }} -- -D warnings diff --git a/.github/workflows/gzip.yml b/.github/workflows/gzip.yml index 78cc64c..8a4f896 100644 --- a/.github/workflows/gzip.yml +++ b/.github/workflows/gzip.yml @@ -10,17 +10,26 @@ jobs: test: name: Integration Tests with gzip compression runs-on: ubuntu-latest + strategy: + matrix: + include: + - target: wasm32-unknown-unknown + recipe: test-gzip-headless + - target: wasm32-wasip2 + recipe: test-gzip-wasip2 steps: - name: Checkout sources uses: actions/checkout@v3 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - target: wasm32-unknown-unknown + - name: Install Rust from rust-toolchain.toml + uses: actions-rust-lang/setup-rust-toolchain@v1 - name: Install wasm-pack run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + if: ${{ matrix.target == 'wasm32-unknown-unknown' }} + - name: Install wasmtime + run: | + curl https://wasmtime.dev/install.sh -sSf | bash + echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH + if: ${{ matrix.target == 'wasm32-wasip2' }} - name: Install just uses: extractions/setup-just@v1 - name: Install Protoc @@ -29,5 +38,5 @@ jobs: run: just build-gzip-test-server - name: Run test `tonic-web` server run: just start-gzip-test-server & - - name: Run headless browser test - run: just test-gzip-headless + - name: Run test ${{ matrix.recipe }} + run: just ${{ matrix.recipe }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 030da68..5f8931c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,17 +10,26 @@ jobs: test: name: Integration Tests runs-on: ubuntu-latest + strategy: + matrix: + include: + - target: wasm32-unknown-unknown + recipe: test-headless + - target: wasm32-wasip2 + recipe: test-wasip2 steps: - name: Checkout sources uses: actions/checkout@v3 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - target: wasm32-unknown-unknown + - name: Install Rust from rust-toolchain.toml + uses: actions-rust-lang/setup-rust-toolchain@v1 - name: Install wasm-pack run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + if: ${{ matrix.target == 'wasm32-unknown-unknown' }} + - name: Install wasmtime + run: | + curl https://wasmtime.dev/install.sh -sSf | bash + echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH + if: ${{ matrix.target == 'wasm32-wasip2' }} - name: Install just uses: extractions/setup-just@v1 - name: Install Protoc @@ -29,5 +38,5 @@ jobs: run: just build-test-server - name: Run test `tonic-web` server run: just start-test-server & - - name: Run headless browser test - run: just test-headless + - name: Run test ${{ matrix.recipe }} + run: just ${{ matrix.recipe }} diff --git a/.gitignore b/.gitignore index 6056843..aec26f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ **/target **/*.rs.bk Cargo.lock -.cargo +/.cargo .config diff --git a/Cargo.toml b/Cargo.toml index 9b281ca..910169b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ readme = "README.md" categories = ["web-programming", "network-programming", "asynchronous"] keywords = ["grpc", "grpc-web", "tonic", "wasm"] edition = "2021" +rust-version = "1.86" [dependencies] base64 = "0.22" @@ -20,11 +21,14 @@ http = "1" http-body = "1" http-body-util = "0.1" httparse = "1" -js-sys = "0.3" pin-project = "1" thiserror = "2" tonic = { version = "0.14", default-features = false } tower-service = "0.3" + +# Browser-specific dependencies +[target.wasm32-unknown-unknown.dependencies] +js-sys = "0.3" wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" wasm-streams = "0.4" @@ -42,3 +46,6 @@ web-sys = { version = "0.3", features = [ "Response", "ServiceWorkerGlobalScope", ] } + +[target.wasm32-wasip2.dependencies] +wstd = "0.5" diff --git a/Justfile b/Justfile index 9fcf0bf..e4bf312 100644 --- a/Justfile +++ b/Justfile @@ -1,9 +1,14 @@ -# Builds `tonic-web-wasm-client` +# Builds `tonic-web-wasm-client` for `wasm32-unknown-unknown` target build: @echo 'Building...' cargo build --target wasm32-unknown-unknown -# Builds test `tonic-web` server +# Builds `tonic-web-wasm-client` for `wasm32-wasip2` target +build-wasip2: + @echo 'Building...' + cargo build --target wasm32-wasip2 + +# Builds test `tonic-web` server natively build-test-server: @echo 'Building test server...' cd test-suite/simple/server && cargo build @@ -23,12 +28,29 @@ test-headless: @echo 'Testing...' cd test-suite/simple/client && wasm-pack test --headless --chrome -# Builds test `tonic-web` server (with compression enabled: gzip) +# Checks wasmtime version, needed only for -wasip2 recipes +check-wasmtime-version: + #!/usr/bin/env bash + set -euo pipefail + version_output=$(wasmtime --version) + current_version=$(echo "${version_output}" | awk '{print $2}') + required_version="35.0.0" + if ! printf '%s\n' "${required_version}" "${current_version}" | sort --check=silent --version-sort; then + echo "Error: wasmtime version ${current_version} is installed." + echo "Version 35.0.0 or greater is required." + exit 1 + fi + +# Runs wasip2 tests in `wasmtime` +test-wasip2: check-wasmtime-version + cd test-suite/simple/client && cargo test --target wasm32-wasip2 + +# Builds test `tonic-web` server (with compression enabled: gzip) for `wasm32-unknown-unknown` target build-gzip-test-server: @echo 'Building test server...' cd test-suite/gzip/server && cargo build -# Starts test `tonic-web` server (with compression enabled: gzip) +# Starts test `tonic-web` server (with compression enabled: gzip) natively start-gzip-test-server: @echo 'Starting test server...' cd test-suite/gzip/server && cargo run @@ -42,3 +64,9 @@ test-gzip: test-gzip-headless: @echo 'Testing...' cd test-suite/gzip/client && wasm-pack test --headless --chrome + +# Runs wasip2 tests (with compression enabled: gzip) in `wasmtime` +test-gzip-wasip2: check-wasmtime-version + @echo 'Testing...' + cd test-suite/gzip/client && cargo test --target wasm32-wasip2 + diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..eba4c4d --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.86" +targets = ["wasm32-unknown-unknown", "wasm32-wasip2"] +components = ["rustfmt", "clippy", "rust-src", "rust-analyzer", "cargo"] diff --git a/src/body_stream.rs b/src/body_stream.rs index b3052e7..e41825f 100644 --- a/src/body_stream.rs +++ b/src/body_stream.rs @@ -4,33 +4,20 @@ use std::{ }; use bytes::Bytes; -use futures_util::{stream::empty, Stream, TryStreamExt}; +use futures_util::{stream::empty, Stream}; use http_body::{Body, Frame}; -use js_sys::Uint8Array; -use wasm_streams::readable::IntoStream; use crate::Error; +type BodyStreamPinned = Pin>>>; + pub struct BodyStream { - body_stream: Pin>>>, + body_stream: BodyStreamPinned, } impl BodyStream { - pub fn new(body_stream: IntoStream<'static>) -> Self { - let body_stream = body_stream - .map_ok(|js_value| { - let buffer = Uint8Array::new(&js_value); - - let mut bytes_vec = vec![0; buffer.length() as usize]; - buffer.copy_to(&mut bytes_vec); - - bytes_vec.into() - }) - .map_err(Error::js_error); - - Self { - body_stream: Box::pin(body_stream), - } + pub fn new(body_stream: BodyStreamPinned) -> Self { + Self { body_stream } } pub fn empty() -> Self { diff --git a/src/call.rs b/src/call.rs index 052aa4c..5414ed8 100644 --- a/src/call.rs +++ b/src/call.rs @@ -6,12 +6,12 @@ use http::{ use http_body_util::BodyExt; use js_sys::{Array, Uint8Array}; use tonic::body::Body; -use wasm_bindgen::JsValue; +use wasm_bindgen::{JsCast as _, JsValue}; use web_sys::{Headers, RequestCredentials, RequestInit}; -use crate::{fetch::fetch, options::FetchOptions, Error, ResponseBody}; +use crate::{body_stream::BodyStream, fetch::fetch, options::FetchOptions, Error, ResponseBody}; -pub async fn call( +pub(crate) async fn call( mut base_url: String, request: Request, options: Option, @@ -28,13 +28,33 @@ pub async fn call( let (result, content_type) = set_response_headers(result, &response)?; let content_type = content_type.ok_or(Error::MissingContentTypeHeader)?; + let body_stream = response.body().ok_or(Error::MissingResponseBody)?; + let body_stream = prepare_body_stream( + wasm_streams::ReadableStream::from_raw(body_stream.unchecked_into()).into_stream(), + ); let body = ResponseBody::new(body_stream, &content_type)?; result.body(body).map_err(Into::into) } +fn prepare_body_stream(body_stream: wasm_streams::readable::IntoStream<'static>) -> BodyStream { + use futures_util::TryStreamExt as _; + let body_stream = body_stream + .map_ok(|js_value| { + let buffer = js_sys::Uint8Array::new(&js_value); + + let mut bytes_vec = vec![0; buffer.length() as usize]; + buffer.copy_to(&mut bytes_vec); + + bytes_vec.into() + }) + .map_err(Error::js_error); + + BodyStream::new(Box::pin(body_stream)) +} + fn prepare_headers(header_map: &HeaderMap) -> Result { // Construct default headers. let headers = Headers::new().map_err(Error::js_error)?; diff --git a/src/error.rs b/src/error.rs index f0426c2..ed2712c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,5 @@ use http::header::{InvalidHeaderName, InvalidHeaderValue, ToStrError}; -use js_sys::Object; use thiserror::Error; -use wasm_bindgen::{JsCast, JsValue}; /// Error type for `tonic-web-wasm-client` #[derive(Debug, Error)] @@ -28,6 +26,7 @@ pub enum Error { #[error("invalid header value")] InvalidHeaderValue(#[from] InvalidHeaderValue), /// JS API error + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] #[error("js api error: {0}")] JsError(String), /// Malformed response @@ -42,17 +41,23 @@ pub enum Error { /// gRPC error #[error("grpc error")] TonicStatusError(#[from] tonic::Status), + #[cfg(all(target_arch = "wasm32", target_os = "wasi", target_env = "p2"))] + #[error(transparent)] + WstdHttp(wstd::http::Error), } impl Error { /// Initialize js error from js value - pub(crate) fn js_error(value: JsValue) -> Self { + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] + pub(crate) fn js_error(value: wasm_bindgen::JsValue) -> Self { let message = js_object_display(&value); Self::JsError(message) } } -fn js_object_display(option: &JsValue) -> String { - let object: &Object = option.unchecked_ref(); +#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] +fn js_object_display(option: &wasm_bindgen::JsValue) -> String { + use wasm_bindgen::JsCast; + let object: &js_sys::Object = option.unchecked_ref(); ToString::to_string(&object.to_string()) } diff --git a/src/lib.rs b/src/lib.rs index 7f2d356..3c62d06 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,13 +44,27 @@ //! This library allows you to set a custom `Accept` header for the requests. This can be useful if you need to specify //! a different content type for the responses. But, be aware that if you set a custom `Accept` header, the client may //! not be able to handle the response correctly. + mod body_stream; +#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] mod call; mod client; mod content_type; mod error; +#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] mod fetch; +#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] pub mod options; + mod response_body; -pub use self::{client::Client, error::Error, response_body::ResponseBody}; +#[cfg(all(target_arch = "wasm32", target_os = "wasi", target_env = "p2"))] +mod wasip2; +#[cfg(all(target_arch = "wasm32", target_os = "wasi", target_env = "p2"))] +pub(crate) use self::wasip2::call; +#[cfg(all(target_arch = "wasm32", target_os = "wasi", target_env = "p2"))] +pub use self::wasip2::options; + +pub use self::response_body::ResponseBody; + +pub use self::{client::Client, error::Error}; diff --git a/src/response_body.rs b/src/response_body.rs index 08e4448..a78868d 100644 --- a/src/response_body.rs +++ b/src/response_body.rs @@ -11,8 +11,6 @@ use http::{header::HeaderName, HeaderMap, HeaderValue}; use http_body::Body; use httparse::{Status, EMPTY_HEADER}; use pin_project::pin_project; -use wasm_bindgen::JsCast; -use web_sys::ReadableStream; use crate::{body_stream::BodyStream, content_type::Encoding, Error}; @@ -118,12 +116,9 @@ pub struct ResponseBody { } impl ResponseBody { - pub(crate) fn new(body_stream: ReadableStream, content_type: &str) -> Result { - let body_stream = - wasm_streams::ReadableStream::from_raw(body_stream.unchecked_into()).into_stream(); - + pub(crate) fn new(body_stream: BodyStream, content_type: &str) -> Result { Ok(Self { - body_stream: BodyStream::new(body_stream), + body_stream, buf: EncodedBytes::new(content_type)?, incomplete_data: BytesMut::new(), data: None, diff --git a/src/wasip2/call.rs b/src/wasip2/call.rs new file mode 100644 index 0000000..69a2696 --- /dev/null +++ b/src/wasip2/call.rs @@ -0,0 +1,164 @@ +use crate::{body_stream::BodyStream, options::FetchOptions, Error, ResponseBody}; +use bytes::Bytes; +use futures_util::stream::Stream; +use http::{ + header::{ACCEPT, CONTENT_TYPE}, + Request, Response, +}; +use http_body_util::BodyExt; +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; +use tonic::body::Body; +use wstd::{ + http::{body::IncomingBody, IntoBody as _}, + io::AsyncRead as _, +}; + +/// A stream that reads from a `wstd::http::IncomingBody` and yields chunks of bytes. +/// +/// This is necessary to adapt the `wstd` response body, which is an asynchronous reader, +/// into a `Stream` that can be used with `http_body_util::StreamBody` to create a `tonic`-compatible +/// response body. +pub struct WstdBodyStream { + // None is invalid state. + state: Option, +} + +type ReadFuture = + Pin, wstd::io::Result)>>>; + +// The state machine of the stream +enum WstdBodyStreamState { + /// Ready to start a new read. Owns the body and the buffer. + Idle { + body: IncomingBody, + buf: Box<[u8; 4096]>, + }, + /// Actively reading. The future owns the body and buffer. + Reading { fut: ReadFuture }, + /// The stream is complete and should not be polled again. + Finished, +} + +impl WstdBodyStream { + pub fn new(body: IncomingBody) -> Self { + Self { + state: Some(WstdBodyStreamState::Idle { + body, + buf: Box::new([0; 4096]), + }), + } + } +} + +impl Stream for WstdBodyStream { + type Item = Result; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + // Temporarily replace self.state with None to satisfy the borrow checker. + // We will always replace it with a valid state before returning. + let mut state = + std::mem::take(&mut self.state).expect("this function never leaves state == None"); + + loop { + match state { + WstdBodyStreamState::Idle { mut body, mut buf } => { + // Create a future that takes ownership of the body and buffer. + let fut = Box::pin(async move { + let result = body.read(buf.as_mut()).await; + (body, buf, result) + }); + + // Transition to the Reading state and immediately poll the new future. + state = WstdBodyStreamState::Reading { fut }; + // Continue loop to poll the new future right away. + } + + WstdBodyStreamState::Reading { mut fut } => { + match fut.as_mut().poll(cx) { + Poll::Pending => { + // The read is not complete. Put the state back and return. + self.state = Some(WstdBodyStreamState::Reading { fut }); + return Poll::Pending; + } + Poll::Ready((body, buf, result)) => { + match result { + Ok(0) => { + // EOF. Mark stream as finished. + self.state = Some(WstdBodyStreamState::Finished); + return Poll::Ready(None); + } + Ok(n) => { + // Successfully read n bytes. + let bytes = Bytes::copy_from_slice(&buf[..n]); + // The read finished. Transition back to Idle for the next read. + self.state = Some(WstdBodyStreamState::Idle { body, buf }); + return Poll::Ready(Some(Ok(bytes))); + } + Err(err) => { + // An error occurred. Mark stream as finished. + self.state = Some(WstdBodyStreamState::Finished); + return Poll::Ready(Some(Err(Error::WstdHttp(err.into())))); + } + } + } + } + } + WstdBodyStreamState::Finished => { + return Poll::Ready(None); + } + } + } + } +} + +// The core async function that handles the request/response logic using wstd. +pub async fn call( + base_url: String, + request: Request, + _options: Option, +) -> Result, Error> { + let request = { + let url = format!("{}{}", base_url, request.uri().path()); + let (parts, body) = request.into_parts(); + + // Build the wstd HTTP request + let request = wstd::http::Request::builder() + .uri(url) + .method(wstd::http::Method::POST); + + let mut request = request + .header(CONTENT_TYPE.as_str(), "application/grpc-web+proto") + .header(ACCEPT.as_str(), "application/grpc-web+proto") + .header("x-grpc-web", "1"); + + // Copy request headers + for (key, value) in &parts.headers { + request = request.header(key.as_str(), value.as_bytes()); + } + // Aggregate the entire request body. This does not work with request message stream. + let body_bytes = body.collect().await?.to_bytes(); + request.body(body_bytes.into_body())? + }; + + let wstd_client = wstd::http::Client::new(); + let (parts, body) = wstd_client + .send(request) + .await + .map_err(Error::WstdHttp)? + .into_parts(); + + let content_type = parts + .headers + .get(CONTENT_TYPE) + .and_then(|v| v.to_str().ok()) + .ok_or(Error::MissingContentTypeHeader)?; + + let body_stream = Box::pin(WstdBodyStream::new(body)); + let response_body = ResponseBody::new(BodyStream::new(body_stream), content_type)?; + + Ok(Response::from_parts(parts, response_body)) +} diff --git a/src/wasip2/mod.rs b/src/wasip2/mod.rs new file mode 100644 index 0000000..9d7a9dd --- /dev/null +++ b/src/wasip2/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod call; +pub mod options; diff --git a/src/wasip2/options.rs b/src/wasip2/options.rs new file mode 100644 index 0000000..e1cedd1 --- /dev/null +++ b/src/wasip2/options.rs @@ -0,0 +1,2 @@ +#[derive(Debug, Clone)] +pub struct FetchOptions {} diff --git a/test-suite/gzip/client/.cargo/config.toml b/test-suite/gzip/client/.cargo/config.toml new file mode 100644 index 0000000..5b022a9 --- /dev/null +++ b/test-suite/gzip/client/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.wasm32-wasip2] +runner = "wasmtime run -Scli -Shttp" # Runs wasip2 tests in wasmtime diff --git a/test-suite/gzip/client/Cargo.toml b/test-suite/gzip/client/Cargo.toml index b549186..fc86b97 100644 --- a/test-suite/gzip/client/Cargo.toml +++ b/test-suite/gzip/client/Cargo.toml @@ -15,4 +15,10 @@ tonic-prost-build = { version = "0.14", default-features = false } [dev-dependencies] tonic-web-wasm-client = { path = "../../.." } + +# Browser-specific dependencies +[target.wasm32-unknown-unknown.dev-dependencies] wasm-bindgen-test = "0.3" + +[target.wasm32-wasip2.dependencies] +wstd = "0.5" diff --git a/test-suite/gzip/client/tests/web.rs b/test-suite/gzip/client/tests/web.rs index f89a66d..080e055 100644 --- a/test-suite/gzip/client/tests/web.rs +++ b/test-suite/gzip/client/tests/web.rs @@ -1,8 +1,10 @@ use client::proto::{echo_client::EchoClient, EchoRequest}; use tonic::codegen::CompressionEncoding; use tonic_web_wasm_client::Client; +#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; +#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] wasm_bindgen_test_configure!(run_in_browser); fn build_client() -> EchoClient { @@ -12,7 +14,11 @@ fn build_client() -> EchoClient { EchoClient::new(wasm_client).accept_compressed(CompressionEncoding::Gzip) } -#[wasm_bindgen_test] +#[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), wasm_bindgen_test)] +#[cfg_attr( + all(target_arch = "wasm32", target_os = "wasi", target_env = "p2"), + wstd::test +)] async fn test_echo() { let mut client = build_client(); @@ -27,7 +33,11 @@ async fn test_echo() { assert_eq!(response.message, "echo(John)"); } -#[wasm_bindgen_test] +#[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), wasm_bindgen_test)] +#[cfg_attr( + all(target_arch = "wasm32", target_os = "wasi", target_env = "p2"), + wstd::test +)] async fn test_echo_stream() { let mut client = build_client(); @@ -51,7 +61,11 @@ async fn test_echo_stream() { assert!(response.is_none()); } -#[wasm_bindgen_test] +#[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), wasm_bindgen_test)] +#[cfg_attr( + all(target_arch = "wasm32", target_os = "wasi", target_env = "p2"), + wstd::test +)] async fn test_infinite_echo_stream() { let mut client = build_client(); diff --git a/test-suite/simple/client/.cargo/config.toml b/test-suite/simple/client/.cargo/config.toml new file mode 100644 index 0000000..5b022a9 --- /dev/null +++ b/test-suite/simple/client/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.wasm32-wasip2] +runner = "wasmtime run -Scli -Shttp" # Runs wasip2 tests in wasmtime diff --git a/test-suite/simple/client/Cargo.toml b/test-suite/simple/client/Cargo.toml index b9716d9..d8a10a0 100644 --- a/test-suite/simple/client/Cargo.toml +++ b/test-suite/simple/client/Cargo.toml @@ -15,4 +15,10 @@ tonic-prost-build = { version = "0.14", default-features = false } [dev-dependencies] tonic-web-wasm-client = { path = "../../.." } + +# Browser-specific dependencies +[target.wasm32-unknown-unknown.dev-dependencies] wasm-bindgen-test = "0.3" + +[target.wasm32-wasip2.dependencies] +wstd = "0.5" diff --git a/test-suite/simple/client/tests/web.rs b/test-suite/simple/client/tests/web.rs index f54c81d..d14ca06 100644 --- a/test-suite/simple/client/tests/web.rs +++ b/test-suite/simple/client/tests/web.rs @@ -1,8 +1,10 @@ use client::proto::{echo_client::EchoClient, EchoRequest}; use tonic::Code; use tonic_web_wasm_client::Client; +#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; +#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] wasm_bindgen_test_configure!(run_in_browser); fn build_client() -> EchoClient { @@ -12,7 +14,11 @@ fn build_client() -> EchoClient { EchoClient::new(wasm_client) } -#[wasm_bindgen_test] +#[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), wasm_bindgen_test)] +#[cfg_attr( + all(target_arch = "wasm32", target_os = "wasi", target_env = "p2"), + wstd::test +)] async fn test_echo() { let mut client = build_client(); @@ -27,7 +33,11 @@ async fn test_echo() { assert_eq!(response.message, "echo(John)"); } -#[wasm_bindgen_test] +#[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), wasm_bindgen_test)] +#[cfg_attr( + all(target_arch = "wasm32", target_os = "wasi", target_env = "p2"), + wstd::test +)] async fn test_echo_stream() { let mut client = build_client(); @@ -51,7 +61,11 @@ async fn test_echo_stream() { assert!(response.is_none()); } -#[wasm_bindgen_test] +#[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), wasm_bindgen_test)] +#[cfg_attr( + all(target_arch = "wasm32", target_os = "wasi", target_env = "p2"), + wstd::test +)] async fn test_infinite_echo_stream() { let mut client = build_client(); @@ -75,7 +89,11 @@ async fn test_infinite_echo_stream() { assert!(response.is_some()); } -#[wasm_bindgen_test] +#[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), wasm_bindgen_test)] +#[cfg_attr( + all(target_arch = "wasm32", target_os = "wasi", target_env = "p2"), + wstd::test +)] async fn test_error_response() { let mut client = build_client();