diff --git a/Cargo.lock b/Cargo.lock index 3a883f6b3..77a105e55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,6 +47,28 @@ version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "async-trait" version = "0.1.79" @@ -1977,6 +1999,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-test" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" +dependencies = [ + "async-stream", + "bytes", + "futures-core", + "tokio", + "tokio-stream", +] + [[package]] name = "tokio-util" version = "0.7.10" @@ -2712,9 +2747,12 @@ dependencies = [ "serde_urlencoded", "tokio", "tokio-postgres", + "tokio-test", + "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-bindgen-test", "wasm-streams", "web-sys", "worker-kv", @@ -2738,6 +2776,7 @@ dependencies = [ name = "worker-kv" version = "0.7.0" dependencies = [ + "axum", "fs_extra", "js-sys", "psutil", diff --git a/worker-kv/Cargo.toml b/worker-kv/Cargo.toml index ac0a2ca61..2932bd667 100644 --- a/worker-kv/Cargo.toml +++ b/worker-kv/Cargo.toml @@ -10,6 +10,7 @@ license = "MIT OR Apache-2.0" [package.metadata.release] release = false + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] @@ -32,3 +33,11 @@ tokio = { version = "1.5.0", features = [ "test-util", "time", ] } + +[dependencies.axum] +version = "0.7" +optional = true +default-features = false + +[features] +axum = ["dep:axum"] \ No newline at end of file diff --git a/worker-kv/src/lib.rs b/worker-kv/src/lib.rs index d51257864..86ef6c17c 100644 --- a/worker-kv/src/lib.rs +++ b/worker-kv/src/lib.rs @@ -232,3 +232,13 @@ impl ToRawKvValue for T { fn get(target: &JsValue, name: &str) -> Result { Reflect::get(target, &JsValue::from(name)) } + +#[cfg(feature = "axum")] +impl axum::response::IntoResponse for KvError { + fn into_response(self) -> axum::response::Response { + axum::response::Response::builder() + .status(500) + .body("INTERNAL SERVER ERROR".into()) + .unwrap() + } +} diff --git a/worker/Cargo.toml b/worker/Cargo.toml index 594b2bd80..e04558c50 100644 --- a/worker/Cargo.toml +++ b/worker/Cargo.toml @@ -14,6 +14,18 @@ readme = "../README.md" [package.metadata.docs.rs] all-features = true +[dev-dependencies] +tokio-test = "0.4" +wasm-bindgen-test = "0.3.0" + +[dev-dependencies.axum] +version="0.7" +default-features = false + +[dev-dependencies.tower-service] +version="0.3" +default-features = false + [dependencies] async-trait.workspace = true bytes = "1.5" @@ -67,5 +79,5 @@ default-features = false queue = ["worker-macros/queue", "worker-sys/queue"] d1 = ["worker-sys/d1"] http = ["worker-macros/http"] -axum = ["dep:axum"] +axum = ["dep:axum", "worker-kv/axum"] timezone = ["dep:chrono-tz"] diff --git a/worker/src/context.rs b/worker/src/context.rs index e96bd06f9..92c1dfe60 100644 --- a/worker/src/context.rs +++ b/worker/src/context.rs @@ -24,7 +24,7 @@ impl Context { /// until the given future has been completed. The future is executed before the handler /// terminates but does not block the response. For example, this is ideal for caching /// responses or handling logging. - /// ```no_run + /// ```ignore /// context.wait_until(async move { /// let _ = cache.put(request, response).await; /// }); diff --git a/worker/src/date.rs b/worker/src/date.rs index 3580aeb27..c747175d9 100644 --- a/worker/src/date.rs +++ b/worker/src/date.rs @@ -5,6 +5,8 @@ use wasm_bindgen::JsValue; /// The equivalent to a JavaScript `Date` Object. /// ```no_run +/// # use worker::Date; +/// # use worker::DateInit; /// let now = Date::now(); /// let millis = now.as_millis(); /// // or use a specific point in time: @@ -24,6 +26,8 @@ impl PartialEq for Date { /// Initialize a `Date` by constructing this enum. /// ```no_run +/// # use worker::DateInit; +/// # use worker::Date; /// let t1: Date = DateInit::Millis(1630611511000).into(); /// let t2: Date = DateInit::String("Thu, 02 Sep 2021 19:38:31 GMT".to_string()).into(); /// ``` diff --git a/worker/src/durable.rs b/worker/src/durable.rs index fc43d4235..29c38448e 100644 --- a/worker/src/durable.rs +++ b/worker/src/durable.rs @@ -295,12 +295,12 @@ impl Storage { /// /// ```no_run /// # use worker::Storage; - /// use worker::JsValue; + /// use wasm_bindgen::JsValue; /// /// # let storage: Storage = todo!(); /// /// let obj = js_sys::Object::new(); - /// js_sys::Reflect::set(&obj, &JsValue::from_str("foo"), JsValue::from_u64(1)); + /// js_sys::Reflect::set(&obj, &JsValue::from_str("foo"), &JsValue::from_f64(1.0)); /// /// storage.put_multiple_raw(obj); /// ``` @@ -744,6 +744,10 @@ macro to both the impl block and the struct type definition. ```no_run use worker::*; +struct Message; + +struct User; + #[durable_object] pub struct Chatroom { users: Vec, diff --git a/worker/src/dynamic_dispatch.rs b/worker/src/dynamic_dispatch.rs index 7ad76f89b..b4c906c2e 100644 --- a/worker/src/dynamic_dispatch.rs +++ b/worker/src/dynamic_dispatch.rs @@ -10,9 +10,19 @@ use crate::{env::EnvBinding, Fetcher, Result}; /// # Example: /// /// ```no_run +/// # use js_sys::Object; +/// # use wasm_bindgen::JsCast; +/// # let env: worker::Env = Object::new().unchecked_into(); +/// # tokio_test::block_on(async { +/// # #[cfg(feature="http")] +/// # let req = http::Request::get("http://localhost:8080").body(worker::Body::empty())?; +/// # #[cfg(not(feature="http"))] +/// # let req = worker::Request::new("http://localhost:8080", worker::Method::Get)?; /// let dispatcher = env.dynamic_dispatcher("DISPATCHER")?; /// let fetcher = dispatcher.get("namespaced-worker-name")?; /// let resp = fetcher.fetch_request(req).await?; +/// # Ok::<(), worker::Error>(()) +/// # }); /// ``` #[derive(Debug, Clone)] pub struct DynamicDispatcher(DynamicDispatcherSys); diff --git a/worker/src/error.rs b/worker/src/error.rs index b7287e8dd..3a131cead 100644 --- a/worker/src/error.rs +++ b/worker/src/error.rs @@ -190,3 +190,13 @@ impl From for Error { Error::SerdeJsonError(e) } } + +#[cfg(feature = "axum")] +impl axum::response::IntoResponse for Error { + fn into_response(self) -> axum::response::Response { + axum::response::Response::builder() + .status(500) + .body("INTERNAL SERVER ERROR".into()) + .unwrap() + } +} diff --git a/worker/src/lib.rs b/worker/src/lib.rs index 18347ae13..fcfc18164 100644 --- a/worker/src/lib.rs +++ b/worker/src/lib.rs @@ -10,12 +10,18 @@ //! //! Enables `queue` event type in [`[event]`](worker_macros::event) macro. //! -//! ``` +//! ```no_run +//! # mod scope { //! // Consume messages from a queue +//! # use worker::{Env, Context, Result, MessageBatch, event}; +//! +//! struct MyType; +//! //! #[event(queue)] -//! pub async fn main(message_batch: MessageBatch, env: Env, _ctx: Context) -> Result<()> { +//! pub async fn handle(message_batch: MessageBatch, env: Env, _ctx: Context) -> Result<()> { //! Ok(()) //! } +//! # } //! ``` //! //! ## `http` @@ -33,7 +39,13 @@ //! //! The end result is being able to use frameworks like `axum` directly (see [example](./examples/axum)): //! -//! ```rust +//! ```no_run +//! # mod scope { +//! # use worker::{Context, Env, HttpRequest, Result}; +//! # use axum::routing::get; +//! # use axum::Router; +//! # use worker::event; +//! # use tower_service::Service; //! pub async fn root() -> &'static str { //! "Hello Axum!" //! } @@ -41,7 +53,6 @@ //! fn router() -> Router { //! Router::new().route("/", get(root)) //! } -//! //! #[event(fetch)] //! async fn fetch( //! req: HttpRequest, @@ -50,6 +61,7 @@ //! ) -> Result> { //! Ok(router().call(req).await?) //! } +//! # } //! ``` //! //! We also implement `try_from` between `worker::Request` and `http::Request`, and between `worker::Response` and `http::Response`. @@ -64,8 +76,11 @@ //! //! 1. [`send::SendFuture`] - wraps any `Future` and marks it as `Send`: //! -//! ```rust +//! ```no_run //! // `fut` is `Send` +//! # use worker::send; +//! # use wasm_bindgen_futures::JsFuture; +//! # let promise = js_sys::Promise::new(&mut |_, _| {}); //! let fut = send::SendFuture::new(async move { //! // `JsFuture` is not `Send` //! JsFuture::from(promise).await @@ -75,31 +90,43 @@ //! 2. [`send::SendWrapper`] - Marks an arbitrary object as `Send` and implements `Deref` and `DerefMut`, as well as `Clone`, `Debug`, and `Display` if the //! inner type does. This is useful for attaching types as state to an `axum` `Router`: //! -//! ```rust +//! ```no_run +//! # use axum::Extension; +//! # use worker::send; +//! # use worker_kv::KvStore; //! // `KvStore` is not `Send` -//! let store = env.kv("FOO")?; +//! let store = KvStore::create("FOO")?; //! // `state` is `Send` //! let state = send::SendWrapper::new(store); -//! let router = axum::Router::new() +//! let router = axum::Router::<()>::new() //! .layer(Extension(state)); +//! +//! # Ok::<(), worker::Error>(()) //! ``` //! //! 3. [`[worker::send]`](macro@crate::send) - Macro to make any `async` function `Send`. This can be a little tricky to identify as the problem, but //! `axum`'s `[debug_handler]` macro can help, and looking for warnings that a function or object cannot safely be sent //! between threads. //! -//! ```rust +//! ```no_run +//! # #[cfg(feature="http")] +//! # { +//! # use axum::routing::get; +//! # use axum::Extension; +//! # use axum::response::Result; +//! # use worker::Env; //! // This macro makes the whole function (i.e. the `Future` it returns) `Send`. //! #[worker::send] -//! async fn handler(Extension(env): Extension) -> Response { -//! let kv = env.kv("FOO").unwrap()?; +//! async fn handler(Extension(env): Extension) -> Result { +//! let kv = env.kv("FOO")?; //! // Holding `kv`, which is not `Send` across `await` boundary would mark this function as `!Send` //! let value = kv.get("foo").text().await?; -//! Ok(format!("Got value: {:?}", value)); +//! Ok(format!("Got value: {:?}", value)) //! } //! -//! let router = axum::Router::new() -//! .route("/", get(handler)) +//! let router = axum::Router::<()>::new() +//! .route("/", get(handler)); +//! # } //! ``` #[doc(hidden)] diff --git a/worker/src/queue.rs b/worker/src/queue.rs index 48fff682a..fb986484d 100644 --- a/worker/src/queue.rs +++ b/worker/src/queue.rs @@ -576,12 +576,19 @@ impl Queue { /// /// ## Example /// ```no_run + /// # use serde::Serialize; + /// # use js_sys::Object; + /// # use wasm_bindgen::JsCast; /// #[derive(Serialize)] /// pub struct MyMessage { /// my_data: u32, /// } - /// + /// # let env: worker::Env = Object::new().unchecked_into(); + /// # tokio_test::block_on(async { + /// # let queue = env.queue("FOO")?; /// queue.send(MyMessage{ my_data: 1}).await?; + /// # Ok::<(), worker::Error>(()) + /// # }); /// ``` pub async fn send>>(&self, message: U) -> Result<()> where @@ -615,12 +622,19 @@ impl Queue { /// /// ## Example /// ```no_run + /// # use serde::Serialize; + /// # use wasm_bindgen::JsCast; + /// # use js_sys::Object; /// #[derive(Serialize)] /// pub struct MyMessage { /// my_data: u32, /// } - /// + /// # let env: worker::Env = Object::new().unchecked_into(); + /// # tokio_test::block_on(async { + /// # let queue = env.queue("FOO")?; /// queue.send_batch(vec![MyMessage{ my_data: 1}]).await?; + /// # Ok::<(), worker::Error>(()) + /// # }); /// ``` pub async fn send_batch>>( &self, diff --git a/worker/src/r2/builder.rs b/worker/src/r2/builder.rs index 2df099b09..0f1cb17cd 100644 --- a/worker/src/r2/builder.rs +++ b/worker/src/r2/builder.rs @@ -291,7 +291,7 @@ impl<'bucket> CreateMultipartUploadOptionsBuilder<'bucket> { } /// Metadata that's automatically rendered into R2 HTTP API endpoints. -/// ``` +/// ```ignore /// * contentType -> content-type /// * contentLanguage -> content-language /// etc... @@ -378,7 +378,8 @@ impl<'bucket> ListOptionsBuilder<'bucket> { /// you are sending into one bucket. Make sure to look at `truncated` for the result /// rather than having logic like /// - /// ```no_run + /// ```ignore + /// # let limit = 10; /// while listed.len() < limit { /// listed = bucket.list() /// .limit(limit), diff --git a/worker/src/send.rs b/worker/src/send.rs index 5c6d16c7b..0c785b3d3 100644 --- a/worker/src/send.rs +++ b/worker/src/send.rs @@ -11,13 +11,18 @@ use std::pin::Pin; use std::task::Context; use std::task::Poll; -#[pin_project] /// Wrap any future to make it `Send`. /// -/// ```rust +/// ```no_run +/// # use wasm_bindgen_futures::JsFuture; +/// # use worker::send::SendFuture; +/// # tokio_test::block_on(async { +/// # let promise = js_sys::Promise::new(&mut |_, _| {}); /// let fut = SendFuture::new(JsFuture::from(promise)); -/// fut.await +/// fut.await; +/// # }) /// ``` +#[pin_project] pub struct SendFuture { #[pin] inner: F, @@ -42,8 +47,10 @@ impl Future for SendFuture { /// Wrap any type to make it `Send`. /// -/// ```rust -/// // js_sys::Promise is !Send +/// ```no_run +/// # use worker::send::SendWrapper; +/// # let promise = js_sys::Promise::new(&mut |_, _| {}); +/// /// js_sys::Promise is !Send /// let send_promise = SendWrapper::new(promise); /// ``` pub struct SendWrapper(pub T);