Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
453 changes: 379 additions & 74 deletions Cargo.lock

Large diffs are not rendered by default.

22 changes: 17 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,24 @@ The Spin Rust SDK makes it easy to build Spin components in Rust.
name = "spin_sdk"

[dependencies]
anyhow = "1"
anyhow = { workspace = true }
async-trait = "0.1.74"
chrono = "0.4.38"
form_urlencoded = "1.0"
postgres_range = { version = "0.11.1", optional = true }
rust_decimal = { version = "1.37.2", default-features = false, optional = true }
spin-executor = { version = "5.0.0", path = "crates/executor" }
spin-macro = { version = "5.0.0", path = "crates/macro" }
thiserror = "1.0.37"
spin-wasip3-http = { version = "5.0.0", path = "crates/spin-wasip3-http", optional = true }
spin-wasip3-http-macro = { version = "5.0.0", path = "crates/spin-wasip3-http-macro", optional = true }
thiserror = { workspace = true }
uuid = { version = "1.18.0", optional = true }
wit-bindgen = { workspace = true }
routefinder = "0.5.3"
once_cell = { workspace = true }
futures = { workspace = true }
bytes = "1"
hyperium = { package = "http", version = "1.0.0" }
bytes = { workspace = true }
hyperium = { workspace = true }
serde_json = { version = "1.0.96", optional = true }
serde = { version = "1.0.163", optional = true }
wasi = { workspace = true }
Expand All @@ -42,6 +44,7 @@ default = ["export-sdk-language", "json", "postgres4-types"]
export-sdk-language = []
json = ["dep:serde", "dep:serde_json"]
postgres4-types = ["dep:rust_decimal", "dep:uuid", "dep:postgres_range", "json"]
wasip3-unstable = ["dep:spin-wasip3-http", "dep:spin-wasip3-http-macro"]

[workspace]
resolver = "2"
Expand All @@ -65,6 +68,9 @@ members = [
"examples/variables",
"examples/wasi-http-streaming-outgoing-body",
"examples/wasi-http-streaming-file",
"examples/wasip3-http-axum-router",
"examples/wasip3-http-hello-world",
"examples/wasip3-http-send-request",
"test-cases/simple-http",
"test-cases/simple-redis",
"crates/*",
Expand Down Expand Up @@ -92,12 +98,18 @@ authors = ["Spin Framework Maintainers <[email protected]>"]
edition = "2021"
license = "Apache-2.0 WITH LLVM-exception"
repository = "https://github.com/spinframework/spin-rust-sdk"
rust-version = "1.78"
rust-version = "1.86"
homepage = "https://spinframework.dev/rust-components"

[workspace.dependencies]
anyhow = "1"
hyperium = { package = "http", version = "1.3.1" }
http-body = "1.0.1"
http-body-util = "0.1.3"
bytes = "1.10.1"
wit-bindgen = "0.43.0"
futures = "0.3.28"
once_cell = "1.18.0"
thiserror = "2.0.17"
# Pin to the last version that targeted WASI 0.2.0
wasi = "=0.13.1"
21 changes: 21 additions & 0 deletions crates/spin-wasip3-http-macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "spin-wasip3-http-macro"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
homepage.workspace = true
description = """
Rust procedural macros for Spin and WASIp3
"""

[lib]
name = "spin_wasip3_http_macro"
proc-macro = true

[dependencies]
proc-macro2 = "1"
quote = "1.0"
syn = { version = "1.0", features = [ "full" ]}
63 changes: 63 additions & 0 deletions crates/spin-wasip3-http-macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use proc_macro::TokenStream;
use quote::quote;

/// Marks an `async fn` as an HTTP component entrypoint for Spin.
///
/// The `#[http_component]` attribute designates an asynchronous function as the
/// handler for incoming HTTP requests in a Spin component using the WASI Preview 3
/// (`wasip3`) HTTP ABI.
///
/// When applied, this macro generates the necessary boilerplate to export the
/// function to the Spin runtime as a valid HTTP handler. The function must be
/// declared `async` and take a single argument implementing
/// [`FromRequest`](::spin_sdk::http_wasip3::FromRequest), typically
/// [`Request`](::spin_sdk::http_wasip3::Request), and must return a type that
/// implements [`IntoResponse`](::spin_sdk::http_wasip3::IntoResponse).
///
/// # Requirements
///
/// - The annotated function **must** be `async`.
/// - The function’s parameter type must implement [`FromRequest`].
/// - The return type must implement [`IntoResponse`].
///
/// If the function is not asynchronous, the macro emits a compile-time error.
///
/// # Generated Code
///
/// The macro expands into a module containing a `Spin` struct that implements the
/// WASI `http.handler/Guest` interface, wiring the annotated function as the
/// handler’s entrypoint. This allows the function to be invoked automatically
/// by the Spin runtime when HTTP requests are received.
#[proc_macro_attribute]
pub fn http_component(_attr: TokenStream, item: TokenStream) -> TokenStream {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is http_component the right name? http_handler?

Copy link
Contributor Author

@fibonacci1729 fibonacci1729 Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the person who introduced http_component way back in the day, I've never loved it. Im not opposed to making this switch but I do have FUD around deviating from the norm and consistency with other sdk exports (e.g. redis, cron, mqtt, etc.).

I'd be curious to hear @itowlson's thoughts here from a dx/docs perspective.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@lann lann Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...and would also lead the way toward a #[http_middleware] macro. 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah. That would be my vote.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we switch to service or handler or whatever, I'd consider retaining component (even in P3) as a transitional thing - prevalence in existing docs/blog posts, other trigger types, etc. And I am wary of rushing this decision in the last few days/hours before 3.5. But yeah I love http_component about as much as Brian does.

I'm not sure why middleware needs a different signature with a MiddlewareThingWrappingImportHandler. Why would the component not just call the global next() (or handle() or however the SDK surfaces the import) function in the way we normally do for host functions? To me it feels awkward for this one thing, one amongst all our other globals, to be injected. Or is your ... in the signature meant to imply that other things will be injected too? Anyway not an issue for now.

Copy link
Contributor

@lann lann Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why middleware needs a different signature with a MiddlewareThingWrappingImportHandler.

It doesn't, but my (piping hot) take is that we should pretend like it does because otherwise middleware signatures will look exactly like plain old service signatures and that seems more confusing.

Copy link
Contributor

@lann lann Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually we'd probably really like to not even generate the bindings for this handler import outside of #[http_middleware]. A "fake" argument seems to me like it could be the least-bad way to present that.

Copy link
Contributor Author

@fibonacci1729 fibonacci1729 Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should let this simmer until tomorrow to let EU folks pipe up so i'll plan to merge this PR tomorrow afternoon.

My vote would be to rename to #[http_service] because this entrypoint feels more analogous to a tower_service::Service than to what Axum refers to as a "handler". BUT whatever the people want.

I suppose to @itowlson's transitioning point, I could add:

pub use spin_wasip3_http_macro::{
        http_service, 
        http_service as http_component,
};

... to where we export all the things in lib.rs

Copy link
Contributor Author

@fibonacci1729 fibonacci1729 Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to go ahead and make the change to #[http_service]. However, this PR will remain open until Monday to leave an opportunity to object!

let func = syn::parse_macro_input!(item as syn::ItemFn);

if func.sig.asyncness.is_none() {
return syn::Error::new_spanned(
func.sig.fn_token,
"the `#[http_component]` function must be `async`",
)
.to_compile_error()
.into();
}

let func_name = &func.sig.ident;

quote!(
#func
mod __spin_wasip3_http {
use ::spin_sdk::http_wasip3::IntoResponse;

struct Spin;
::spin_sdk::http_wasip3::wasip3::http::proxy::export!(Spin);

impl ::spin_sdk::http_wasip3::wasip3::exports::http::handler::Guest for self::Spin {
async fn handle(request: ::spin_sdk::http_wasip3::wasip3::http::types::Request) -> Result<::spin_sdk::http_wasip3::wasip3::http::types::Response, ::spin_sdk::http_wasip3::wasip3::http::types::ErrorCode> {
let request = <::spin_sdk::http_wasip3::Request as ::spin_sdk::http_wasip3::FromRequest>::from_request(request)?;
::spin_sdk::http_wasip3::IntoResponse::into_response(super::#func_name(request).await)
}
}
}
)
.into()
}
17 changes: 17 additions & 0 deletions crates/spin-wasip3-http/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "spin-wasip3-http"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
homepage.workspace = true

[dependencies]
anyhow = { workspace = true }
bytes = { workspace = true }
http-body = { workspace = true }
http-body-util = { workspace = true }
hyperium = { workspace = true }
wasip3-http-ext = { version = "5.0.0", path = "../wasip3-http-ext" }
Loading