diff --git a/.woodpecker/audit.yml b/.woodpecker/audit.yml new file mode 100644 index 0000000..a128f7b --- /dev/null +++ b/.woodpecker/audit.yml @@ -0,0 +1,15 @@ +when: + - event: [push, pull_request, manual, cron] + path: + include: + - "**/Cargo.toml" + - "**/Cargo.lock" + - ".woodpecker/audit.yml" + on_empty: true + +steps: + - name: audit + image: rust:1.90 + commands: + - cargo install cargo-audit + - cargo audit diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml index 400f9d1..506d8e9 100644 --- a/.woodpecker/lint.yml +++ b/.woodpecker/lint.yml @@ -2,14 +2,28 @@ when: - event: [push, pull_request, manual] steps: - - name: fmt + - name: build + image: rust:1.90 + commands: + - cargo build --workspace --verbose + depends_on: [] + + - name: build-all-features image: rust:1.90 + commands: + - cargo build --workspace --verbose --all-features + depends_on: [] + + - name: fmt + image: rustlang/rust:nightly commands: - rustup component add rustfmt - - cargo fmt -- --check + - cargo fmt --all -- --check + depends_on: [] - name: clippy image: rust:1.90 commands: - rustup component add clippy - cargo clippy --workspace --all-targets --all-features -- -D warnings + depends_on: [build] diff --git a/.woodpecker/spellcheck.yml b/.woodpecker/spellcheck.yml new file mode 100644 index 0000000..0e55bb6 --- /dev/null +++ b/.woodpecker/spellcheck.yml @@ -0,0 +1,16 @@ +when: + - event: [push, pull_request, manual] + path: + include: + - "**/*.md" + - "**/*.rs" + - ".woodpecker/spellcheck.yml" + - "_typos.toml" + on_empty: true + +steps: + - name: typos + image: rust:1.90 + commands: + - cargo install typos-cli + - typos diff --git a/.woodpecker/test.yml b/.woodpecker/test.yml index f1754d7..69c6cbc 100644 --- a/.woodpecker/test.yml +++ b/.woodpecker/test.yml @@ -6,3 +6,23 @@ steps: image: rust:1.90 commands: - cargo test --workspace + depends_on: [] + + - name: test-all-features + image: rust:1.90 + commands: + - cargo test --workspace --all-features + depends_on: [] + + - name: no-std-check + image: rust:1.90 + commands: + - cargo check --package future_form --no-default-features + depends_on: [] + + - name: wasm32-check + image: rust:1.90 + commands: + - rustup target add wasm32-unknown-unknown + - cargo check --package future_form --target wasm32-unknown-unknown + depends_on: [] diff --git a/_typos.toml b/_typos.toml new file mode 100644 index 0000000..edc4c54 --- /dev/null +++ b/_typos.toml @@ -0,0 +1,10 @@ +[default.extend-words] +# Rust/crate identifiers +ident = "ident" + +[files] +extend-exclude = [ + "*.lock", + "target/", + ".ignore/", +] diff --git a/future_form/src/lib.rs b/future_form/src/lib.rs index 4612715..87eac33 100644 --- a/future_form/src/lib.rs +++ b/future_form/src/lib.rs @@ -136,6 +136,30 @@ pub trait FutureForm { { FromFuture::from_future(f) } + + /// Create this form's future type from an already-computed value. + /// + /// This is useful for sync operations that need to return a future type, + /// avoiding the need to capture values in an async block. + /// + /// # Example + /// + /// ```rust + /// use future_form::{FutureForm, Sendable, Local}; + /// use futures::future::{BoxFuture, LocalBoxFuture}; + /// + /// fn sendable_ready() -> BoxFuture<'static, u32> { + /// Sendable::ready(42) + /// } + /// + /// fn local_ready() -> LocalBoxFuture<'static, u32> { + /// Local::ready(42) + /// } + /// ``` + fn ready<'a, T>(value: T) -> Self::Future<'a, T> + where + T: 'a, + Self::Future<'a, T>: FromReady<'a, T>; } /// Abstraction over [`Send`] futures. @@ -169,6 +193,14 @@ pub trait FutureForm { pub enum Sendable {} impl FutureForm for Sendable { type Future<'a, T: 'a> = BoxFuture<'a, T>; + + fn ready<'a, T>(value: T) -> Self::Future<'a, T> + where + T: 'a, + Self::Future<'a, T>: FromReady<'a, T>, + { + FromReady::from_ready(value) + } } /// Abstraction over [`!Send`][Send] futures. @@ -203,6 +235,14 @@ impl FutureForm for Sendable { pub enum Local {} impl FutureForm for Local { type Future<'a, T: 'a> = LocalBoxFuture<'a, T>; + + fn ready<'a, T>(value: T) -> Self::Future<'a, T> + where + T: 'a, + Self::Future<'a, T>: FromReady<'a, T>, + { + FromReady::from_ready(value) + } } /// A trait for constructing [`FutureForm`]-specific types from raw futures. @@ -266,3 +306,35 @@ where Box::pin(f) } } + +/// A trait for constructing [`FutureForm`]-specific types from ready values. +/// +/// This abstracts over the wrapping operation for values that are already computed, +/// avoiding the overhead and complexity of capturing values in async blocks. +/// +/// Typically you'll use [`FutureForm::ready`] instead of calling this directly. +pub trait FromReady<'a, T> +where + T: 'a, +{ + /// Create this type from a ready value. + fn from_ready(value: T) -> Self; +} + +impl<'a, T> FromReady<'a, T> for BoxFuture<'a, T> +where + T: Send + 'a, +{ + fn from_ready(value: T) -> Self { + Box::pin(core::future::ready(value)) + } +} + +impl<'a, T> FromReady<'a, T> for LocalBoxFuture<'a, T> +where + T: 'a, +{ + fn from_ready(value: T) -> Self { + Box::pin(core::future::ready(value)) + } +}