diff --git a/Cargo.lock b/Cargo.lock
index b99df352..087cd7ee 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -29,6 +29,12 @@ version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
[[package]]
name = "base64"
version = "0.22.1"
@@ -56,6 +62,22 @@ dependencies = [
"allocator-api2",
]
+[[package]]
+name = "bytes"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
+
+[[package]]
+name = "cc"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
[[package]]
name = "cfg-if"
version = "1.0.4"
@@ -68,6 +90,23 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
+[[package]]
+name = "cfg_aliases"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
+
+[[package]]
+name = "chacha20"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "rand_core",
+]
+
[[package]]
name = "clipboard-win"
version = "5.4.1"
@@ -77,6 +116,15 @@ dependencies = [
"error-code",
]
+[[package]]
+name = "cpufeatures"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "cranelift-assembler-x64"
version = "0.129.1"
@@ -243,6 +291,17 @@ version = "0.129.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d953932541249c91e3fa70a75ff1e52adc62979a2a8132145d4b9b3e6d1a9b6a"
+[[package]]
+name = "displaydoc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
[[package]]
name = "endian-type"
version = "0.1.2"
@@ -282,6 +341,12 @@ dependencies = [
"windows-sys 0.59.0",
]
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+
[[package]]
name = "fnv"
version = "1.0.7"
@@ -294,6 +359,15 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+[[package]]
+name = "form_urlencoded"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
+dependencies = [
+ "percent-encoding",
+]
+
[[package]]
name = "futures-channel"
version = "0.3.32"
@@ -305,9 +379,81 @@ dependencies = [
[[package]]
name = "futures-core"
-version = "0.3.32"
+version = "0.3.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
+
+[[package]]
+name = "futures-io"
+version = "0.3.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307"
+
+[[package]]
+name = "futures-task"
+version = "0.3.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
+
+[[package]]
+name = "futures-util"
+version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
+checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
+dependencies = [
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "r-efi",
+ "rand_core",
+ "wasm-bindgen",
+]
[[package]]
name = "gimli"
@@ -351,6 +497,207 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "http"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
+dependencies = [
+ "bytes",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+
+[[package]]
+name = "hyper"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "http",
+ "http-body",
+ "httparse",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.27.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
+dependencies = [
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+ "webpki-roots",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
+dependencies = [
+ "base64",
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "ipnet",
+ "libc",
+ "percent-encoding",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "icu_collections"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "utf8_iter",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
+dependencies = [
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
+
+[[package]]
+name = "icu_properties"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
+dependencies = [
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
+
+[[package]]
+name = "icu_provider"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "idna"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
[[package]]
name = "indexmap"
version = "2.13.0"
@@ -361,12 +708,29 @@ dependencies = [
"hashbrown 0.16.1",
]
+[[package]]
+name = "ipnet"
+version = "2.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78"
+
[[package]]
name = "itoa"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
+[[package]]
+name = "js-sys"
+version = "0.3.103"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "wasm-bindgen",
+]
+
[[package]]
name = "lazy_static"
version = "1.5.0"
@@ -391,12 +755,24 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
+[[package]]
+name = "litemap"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
+
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+[[package]]
+name = "lru-slab"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
+
[[package]]
name = "mach2"
version = "0.4.3"
@@ -412,6 +788,17 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
+[[package]]
+name = "mio"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "nibble_vec"
version = "0.1.0"
@@ -429,10 +816,16 @@ checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
dependencies = [
"bitflags 2.11.0",
"cfg-if",
- "cfg_aliases",
+ "cfg_aliases 0.1.1",
"libc",
]
+[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
[[package]]
name = "paste"
version = "1.0.15"
@@ -446,7 +839,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9676d58588b220f7af69d7aa86108042d2acaf21dd24c641a6d9ef3c4e193ba"
dependencies = [
"pd-host-function 0.22.2",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -455,7 +848,7 @@ version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -466,7 +859,7 @@ checksum = "d9c941589fbbb839a40f7b80595d7b8f3742a8811268d787218f0c45c274d1f9"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -480,18 +873,21 @@ dependencies = [
"cranelift-module",
"cranelift-native",
"futures-channel",
+ "futures-util",
"libc",
"paste",
"pd-edge-abi",
"pd-host-function 0.1.0",
"regex",
+ "reqwest",
"rt-format",
"rustyline",
"self_cell",
"serde",
"serde_json",
- "syn",
+ "syn 2.0.117",
"tokio",
+ "url",
"windows-sys 0.59.0",
]
@@ -512,12 +908,27 @@ dependencies = [
"serde_json",
]
+[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+[[package]]
+name = "potential_utf"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
+dependencies = [
+ "zerovec",
+]
+
[[package]]
name = "proc-macro2"
version = "1.0.106"
@@ -527,6 +938,62 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "quinn"
+version = "0.11.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8"
+dependencies = [
+ "bytes",
+ "cfg_aliases 0.2.2",
+ "pin-project-lite",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash",
+ "rustls",
+ "socket2",
+ "thiserror",
+ "tokio",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-proto"
+version = "0.11.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560"
+dependencies = [
+ "bytes",
+ "getrandom 0.4.3",
+ "lru-slab",
+ "rand",
+ "rand_pcg",
+ "ring",
+ "rustc-hash",
+ "rustls",
+ "rustls-pki-types",
+ "slab",
+ "thiserror",
+ "tinyvec",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-udp"
+version = "0.5.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
+dependencies = [
+ "cfg_aliases 0.2.2",
+ "libc",
+ "once_cell",
+ "socket2",
+ "tracing",
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "quote"
version = "1.0.44"
@@ -536,6 +1003,12 @@ dependencies = [
"proc-macro2",
]
+[[package]]
+name = "r-efi"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+
[[package]]
name = "radix_trie"
version = "0.2.1"
@@ -546,6 +1019,32 @@ dependencies = [
"nibble_vec",
]
+[[package]]
+name = "rand"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
+dependencies = [
+ "chacha20",
+ "getrandom 0.4.3",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
+
+[[package]]
+name = "rand_pcg"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
+dependencies = [
+ "rand_core",
+]
+
[[package]]
name = "regalloc2"
version = "0.13.5"
@@ -602,15 +1101,70 @@ dependencies = [
]
[[package]]
-name = "rt-format"
-version = "0.3.1"
+name = "reqwest"
+version = "0.12.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45087cee619d316fa4bd1675494acff4a5eaa0892fa53bc364bd246f13e452e2"
+checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
dependencies = [
- "lazy_static",
- "regex",
-]
-
+ "base64",
+ "bytes",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "percent-encoding",
+ "pin-project-lite",
+ "quinn",
+ "rustls",
+ "rustls-pki-types",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tokio-rustls",
+ "tokio-util",
+ "tower",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wasm-streams",
+ "web-sys",
+ "webpki-roots",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.17",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rt-format"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45087cee619d316fa4bd1675494acff4a5eaa0892fa53bc364bd246f13e452e2"
+dependencies = [
+ "lazy_static",
+ "regex",
+]
+
[[package]]
name = "rustc-hash"
version = "2.1.1"
@@ -630,6 +1184,41 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "rustls"
+version = "0.23.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
+dependencies = [
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
+dependencies = [
+ "web-time",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
[[package]]
name = "rustscript"
version = "0.1.0"
@@ -637,6 +1226,12 @@ dependencies = [
"pd-vm",
]
+[[package]]
+name = "rustversion"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
+
[[package]]
name = "rustyline"
version = "14.0.0"
@@ -659,6 +1254,12 @@ dependencies = [
"windows-sys 0.52.0",
]
+[[package]]
+name = "ryu"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
+
[[package]]
name = "self_cell"
version = "1.2.2"
@@ -692,7 +1293,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
]
[[package]]
@@ -708,18 +1309,58 @@ dependencies = [
"zmij",
]
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "shlex"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
+
+[[package]]
+name = "slab"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+[[package]]
+name = "socket2"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
[[package]]
name = "syn"
version = "2.0.117"
@@ -731,20 +1372,101 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "syn"
+version = "3.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "target-lexicon"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
+[[package]]
+name = "thiserror"
+version = "2.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
[[package]]
name = "tokio"
version = "1.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
dependencies = [
+ "bytes",
+ "libc",
+ "mio",
"pin-project-lite",
+ "socket2",
"tokio-macros",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -755,9 +1477,102 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tower"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-http"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
+dependencies = [
+ "bitflags 2.11.0",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "pin-project-lite",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "url",
]
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+dependencies = [
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
[[package]]
name = "unicode-ident"
version = "1.0.24"
@@ -776,12 +1591,119 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "url"
+version = "2.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "wasm-streams"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
+dependencies = [
+ "futures-util",
+ "js-sys",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
[[package]]
name = "wasmtime-internal-core"
version = "42.0.1"
@@ -803,6 +1725,35 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "web-sys"
+version = "0.3.103"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web-time"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
+dependencies = [
+ "rustls-pki-types",
+]
+
[[package]]
name = "windows-link"
version = "0.2.1"
@@ -900,6 +1851,95 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+[[package]]
+name = "writeable"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
+
+[[package]]
+name = "yoke"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
+dependencies = [
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+ "synstructure",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+ "synstructure",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
+
+[[package]]
+name = "zerotrie"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "zmij"
version = "1.0.21"
diff --git a/Cargo.toml b/Cargo.toml
index 5cfc571d..098701b4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -27,6 +27,7 @@ name = "vm"
[features]
default = ["runtime", "cli", "cranelift-jit"]
runtime = []
+http-client = ["dep:reqwest", "dep:url", "dep:tokio", "dep:futures-util"]
edge-abi = [
"dep:edge_abi",
"edge_abi/console",
@@ -60,6 +61,10 @@ cranelift-jit = { version = "0.129.1", optional = true }
cranelift-module = { version = "0.129.1", optional = true }
cranelift-native = { version = "0.129.1", optional = true }
pd-host-function = { path = "./pd-host-function", version = "0.1.0" }
+reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "stream"], optional = true }
+url = { version = "2", optional = true }
+futures-util = { version = "0.3", optional = true }
+tokio = { version = "1", features = ["rt", "net", "time"], optional = true }
edge_abi = { package = "pd-edge-abi", version = "0.1.1", default-features = false, optional = true }
futures-channel = "0.3"
paste = "1"
@@ -85,5 +90,10 @@ name = "host_binding_generation_tests"
path = "tests/host_binding_generation_tests.rs"
required-features = ["cranelift-jit"]
+[[test]]
+name = "http_host_tests"
+path = "tests/vm/http_host_tests.rs"
+required-features = ["http-client"]
+
[build-dependencies]
syn = { version = "2", features = ["full"] }
diff --git a/build.rs b/build.rs
index 09cfa5ac..6a7e1dc0 100644
--- a/build.rs
+++ b/build.rs
@@ -149,11 +149,18 @@ fn main() {
println!("cargo:rerun-if-changed={}", catalog_path.display());
let catalog = parse_catalog(&catalog_path);
- let host_sources = [SourceSpec {
- path: "src/builtins/runtime/host.rs".to_string(),
- module: "host".to_string(),
- category: SourceCategory::DefaultHost,
- }];
+ let host_sources = [
+ SourceSpec {
+ path: "src/builtins/runtime/host.rs".to_string(),
+ module: "host".to_string(),
+ category: SourceCategory::DefaultHost,
+ },
+ SourceSpec {
+ path: "src/builtins/runtime/http.rs".to_string(),
+ module: "http".to_string(),
+ category: SourceCategory::DefaultHost,
+ },
+ ];
let builtin_sources = builtin_source_specs(&namespaces);
let core_sources = [SourceSpec {
path: "src/builtins/runtime/core.rs".to_string(),
diff --git a/pd-host-function/src/edge.rs b/pd-host-function/src/edge.rs
new file mode 100644
index 00000000..4f6c268a
--- /dev/null
+++ b/pd-host-function/src/edge.rs
@@ -0,0 +1,1162 @@
+use quote::{format_ident, quote};
+use syn::{
+ Error, Expr, FnArg, Ident, ItemFn, LitStr, Meta, Pat, PatIdent, ReturnType, Token, Type,
+ punctuated::Punctuated,
+};
+
+pub(crate) fn expand_scoped_pd_host_function(
+ attr: Punctuated,
+ mut item: ItemFn,
+) -> Result {
+ let edge_attr = parse_edge_host_attr(&attr)?;
+ let was_async = item.sig.asyncness.is_some();
+ let docs = doc_string(&item.attrs);
+ if docs.trim().is_empty() {
+ return Err(Error::new_spanned(
+ &item.sig.ident,
+ "#[pd_host_function] requires /// doc comments",
+ ));
+ }
+ if edge_attr.scope.is_some() && !edge_attr.bind_params.is_empty() {
+ return Err(Error::new_spanned(
+ &item.sig.ident,
+ "scoped pd_host_function does not support bind(...); scoped registrations must be self-contained",
+ ));
+ }
+
+ transform_async_edge_function(&mut item, &edge_attr)?;
+ validate_edge_bind_names(&item, &edge_attr.bind_params)?;
+ for input in &item.sig.inputs {
+ validate_edge_param(input, &edge_attr.bind_params)?;
+ }
+ validate_edge_return_type(&item.sig.output)?;
+
+ let (wrapper_name, impl_name) = wrapper_and_impl_names(&item.sig.ident);
+ if item.sig.ident != impl_name {
+ item.sig.ident = impl_name.clone();
+ }
+ let wrapper = generate_edge_host_binder(&item, &wrapper_name, &edge_attr)?;
+ let static_wrapper =
+ generate_scoped_edge_host_static_wrapper(&item, &wrapper_name, &edge_attr, was_async)?;
+ let registration = generate_edge_host_registration(&item, &wrapper_name, &edge_attr, &docs)?;
+ Ok(quote! {
+ #item
+ #wrapper
+ #static_wrapper
+ #registration
+ })
+}
+
+fn doc_string(attrs: &[syn::Attribute]) -> String {
+ attrs
+ .iter()
+ .filter_map(|attr| {
+ if !attr.path().is_ident("doc") {
+ return None;
+ }
+ match &attr.meta {
+ Meta::NameValue(name_value) => match &name_value.value {
+ syn::Expr::Lit(expr_lit) => match &expr_lit.lit {
+ syn::Lit::Str(value) => Some(value.value().trim().to_string()),
+ _ => None,
+ },
+ _ => None,
+ },
+ _ => None,
+ }
+ })
+ .filter(|line| !line.is_empty())
+ .collect::>()
+ .join("\n")
+}
+
+struct EdgeHostAttr {
+ name: Expr,
+ scope: Option,
+ bind_params: Vec,
+}
+
+#[derive(Clone, Copy)]
+enum EdgeHostScopeAttr {
+ Runtime,
+ Http,
+ HttpExtension,
+ Io,
+ Transport,
+ Mqtt,
+ WebSocket,
+ WebRtc,
+ Proxy,
+ Console,
+}
+
+fn parse_edge_host_attr(args: &Punctuated) -> Result {
+ let mut name = None;
+ let mut scope = None;
+ let mut bind_params = Vec::new();
+
+ for meta in args {
+ match meta {
+ Meta::NameValue(name_value) if name_value.path.is_ident("name") => {
+ if name.is_some() {
+ return Err(Error::new_spanned(
+ name_value,
+ "duplicate name argument in #[pd_host_function(...)]",
+ ));
+ }
+ name = Some(name_value.value.clone());
+ }
+ Meta::NameValue(name_value) if name_value.path.is_ident("scope") => {
+ if scope.is_some() {
+ return Err(Error::new_spanned(
+ name_value,
+ "duplicate scope argument in #[pd_host_function(...)]",
+ ));
+ }
+ scope = Some(parse_edge_scope(&name_value.value)?);
+ }
+ Meta::List(list) if list.path.is_ident("bind") => {
+ let idents =
+ list.parse_args_with(Punctuated::::parse_terminated)?;
+ bind_params.extend(idents.into_iter());
+ }
+ other => {
+ return Err(Error::new_spanned(
+ other,
+ "expected #[pd_host_function(name = ..., scope = ..., bind(...))]",
+ ));
+ }
+ }
+ }
+
+ let Some(name) = name else {
+ return Err(Error::new(
+ proc_macro2::Span::call_site(),
+ "expected #[pd_host_function(name = ..., scope = ..., bind(...))]",
+ ));
+ };
+
+ Ok(EdgeHostAttr {
+ name,
+ scope,
+ bind_params,
+ })
+}
+
+fn parse_edge_scope(value: &Expr) -> Result {
+ let scope_name = match value {
+ Expr::Path(path) => {
+ let Some(segment) = path.path.segments.last() else {
+ return Err(Error::new_spanned(
+ value,
+ "scope must be one of runtime, http, http_extension, io, transport, mqtt, websocket, webrtc, proxy, or console",
+ ));
+ };
+ if path.path.segments.len() != 1 {
+ return Err(Error::new_spanned(
+ value,
+ "scope must be one of runtime, http, http_extension, io, transport, mqtt, websocket, webrtc, proxy, or console",
+ ));
+ }
+ segment.ident.to_string()
+ }
+ Expr::Lit(expr_lit) => match &expr_lit.lit {
+ syn::Lit::Str(value) => value.value(),
+ _ => {
+ return Err(Error::new_spanned(
+ value,
+ "scope must be one of runtime, http, http_extension, io, transport, mqtt, websocket, webrtc, proxy, or console",
+ ));
+ }
+ },
+ _ => {
+ return Err(Error::new_spanned(
+ value,
+ "scope must be one of runtime, http, http_extension, io, transport, mqtt, websocket, webrtc, proxy, or console",
+ ));
+ }
+ };
+
+ match scope_name.as_str() {
+ "runtime" => Ok(EdgeHostScopeAttr::Runtime),
+ "http" => Ok(EdgeHostScopeAttr::Http),
+ "http_extension" | "http_extensions" => Ok(EdgeHostScopeAttr::HttpExtension),
+ "io" | "io_override" | "io_overrides" => Ok(EdgeHostScopeAttr::Io),
+ "transport" => Ok(EdgeHostScopeAttr::Transport),
+ "mqtt" => Ok(EdgeHostScopeAttr::Mqtt),
+ "websocket" => Ok(EdgeHostScopeAttr::WebSocket),
+ "webrtc" => Ok(EdgeHostScopeAttr::WebRtc),
+ "proxy" => Ok(EdgeHostScopeAttr::Proxy),
+ "console" => Ok(EdgeHostScopeAttr::Console),
+ _ => Err(Error::new_spanned(
+ value,
+ "scope must be one of runtime, http, http_extension, io, transport, mqtt, websocket, webrtc, proxy, or console",
+ )),
+ }
+}
+
+fn edge_scope_tokens(scope: EdgeHostScopeAttr) -> proc_macro2::TokenStream {
+ match scope {
+ EdgeHostScopeAttr::Runtime => {
+ quote!(crate::abi_impl::registry::EdgeHostScope::Runtime)
+ }
+ EdgeHostScopeAttr::Http => quote!(crate::abi_impl::registry::EdgeHostScope::Http),
+ EdgeHostScopeAttr::HttpExtension => {
+ quote!(crate::abi_impl::registry::EdgeHostScope::HttpExtension)
+ }
+ EdgeHostScopeAttr::Io => quote!(crate::abi_impl::registry::EdgeHostScope::Io),
+ EdgeHostScopeAttr::Transport => {
+ quote!(crate::abi_impl::registry::EdgeHostScope::Transport)
+ }
+ EdgeHostScopeAttr::Mqtt => quote!(crate::abi_impl::registry::EdgeHostScope::Mqtt),
+ EdgeHostScopeAttr::WebSocket => {
+ quote!(crate::abi_impl::registry::EdgeHostScope::WebSocket)
+ }
+ EdgeHostScopeAttr::WebRtc => {
+ quote!(crate::abi_impl::registry::EdgeHostScope::WebRtc)
+ }
+ EdgeHostScopeAttr::Proxy => {
+ quote!(crate::abi_impl::registry::EdgeHostScope::Proxy)
+ }
+ EdgeHostScopeAttr::Console => {
+ quote!(crate::abi_impl::registry::EdgeHostScope::Console)
+ }
+ }
+}
+
+fn find_context_param_ident(item: &ItemFn) -> Option {
+ item.sig.inputs.iter().find_map(|input| {
+ let FnArg::Typed(pat_type) = input else {
+ return None;
+ };
+ if !is_edge_context_type(&pat_type.ty) {
+ return None;
+ }
+ match pat_type.pat.as_ref() {
+ Pat::Ident(PatIdent { ident, .. }) => Some(ident.clone()),
+ _ => None,
+ }
+ })
+}
+
+fn async_scope_prepare_stmt(
+ item: &ItemFn,
+ attr: &EdgeHostAttr,
+) -> Result {
+ let Some(scope) = attr.scope else {
+ return Ok(quote!());
+ };
+ let requires_prepare = matches!(
+ scope,
+ EdgeHostScopeAttr::Http | EdgeHostScopeAttr::HttpExtension
+ );
+ if !requires_prepare {
+ return Ok(quote!());
+ }
+ let Some(context_ident) = find_context_param_ident(item) else {
+ return Err(Error::new_spanned(
+ &item.sig.ident,
+ "async scoped http host functions must accept SharedProxyVmContext",
+ ));
+ };
+ let scope_tokens = edge_scope_tokens(scope);
+ let name_expr = &attr.name;
+ Ok(quote! {
+ crate::abi_impl::prepare_scoped_host_call(
+ #context_ident.clone(),
+ #scope_tokens,
+ #name_expr,
+ )
+ .await?;
+ })
+}
+
+fn transform_async_edge_function(item: &mut ItemFn, attr: &EdgeHostAttr) -> Result<(), Error> {
+ if item.sig.asyncness.is_none() {
+ return Ok(());
+ }
+
+ for input in &item.sig.inputs {
+ let FnArg::Typed(pat_type) = input else {
+ return Err(Error::new_spanned(input, "methods are not supported"));
+ };
+ if is_value_slice_type(&pat_type.ty) {
+ return Err(Error::new_spanned(
+ &pat_type.ty,
+ "async edge host functions do not support raw args; use typed parameters instead",
+ ));
+ }
+ if edge_arg_decoder_kind(&pat_type.ty)
+ .ok()
+ .is_some_and(edge_arg_decoder_is_borrowed)
+ {
+ return Err(Error::new_spanned(
+ &pat_type.ty,
+ "async edge host functions do not support borrowed typed parameters; use owned String, Value, or VmMap inputs",
+ ));
+ }
+ }
+
+ let Some(vm_ident) = find_vm_param_ident(item) else {
+ return Err(Error::new_spanned(
+ &item.sig.ident,
+ "async edge host functions must accept a Vm parameter so the macro can schedule the future",
+ ));
+ };
+
+ match edge_output_kind(&item.sig.output)? {
+ Some(EdgeOutputKind::ResultCallOutcome) => {}
+ Some(EdgeOutputKind::CallOutcome) => {
+ return Err(Error::new_spanned(
+ &item.sig.output,
+ "async edge host functions must return Result",
+ ));
+ }
+ None => {
+ return Err(Error::new_spanned(
+ &item.sig.output,
+ "edge host functions must return CallOutcome or Result",
+ ));
+ }
+ }
+
+ let original_block = item.block.clone();
+ let prepare_stmt = async_scope_prepare_stmt(item, attr)?;
+ item.sig.asyncness = None;
+ *item.block = syn::parse2(quote!({
+ crate::abi_impl::schedule_current_future_call(#vm_ident, async move {
+ #prepare_stmt
+ let __pd_edge_outcome = (async move #original_block).await?;
+ match __pd_edge_outcome {
+ ::vm::CallOutcome::Return(values) => Ok(values),
+ ::vm::CallOutcome::Halt => Err(::vm::VmError::HostError(
+ "async edge host functions must not return Halt".to_string(),
+ )),
+ ::vm::CallOutcome::Yield => Err(::vm::VmError::HostError(
+ "async edge host functions must not return Yield".to_string(),
+ )),
+ ::vm::CallOutcome::Pending(_) => Err(::vm::VmError::HostError(
+ "async edge host functions must not return Pending".to_string(),
+ )),
+ }
+ })
+ }))?;
+ Ok(())
+}
+
+fn validate_edge_bind_names(item: &ItemFn, bind_params: &[Ident]) -> Result<(), Error> {
+ let params = item
+ .sig
+ .inputs
+ .iter()
+ .filter_map(|input| match input {
+ FnArg::Typed(pat_type) => match pat_type.pat.as_ref() {
+ Pat::Ident(ident) => Some(ident.ident.to_string()),
+ _ => None,
+ },
+ FnArg::Receiver(_) => None,
+ })
+ .collect::>();
+
+ for bind in bind_params {
+ if !params.iter().any(|name| name == &bind.to_string()) {
+ return Err(Error::new_spanned(
+ bind,
+ format!(
+ "bind parameter '{}' does not match any function parameter",
+ bind
+ ),
+ ));
+ }
+ }
+
+ Ok(())
+}
+
+fn validate_edge_param(arg: &FnArg, bind_params: &[Ident]) -> Result<(), Error> {
+ let FnArg::Typed(pat_type) = arg else {
+ return Err(Error::new_spanned(arg, "methods are not supported"));
+ };
+ let Pat::Ident(PatIdent { ident, .. }) = pat_type.pat.as_ref() else {
+ return Err(Error::new_spanned(
+ &pat_type.pat,
+ "edge host parameters must use identifier patterns",
+ ));
+ };
+
+ if is_vm_context_type(&pat_type.ty)
+ || is_edge_async_ops_type(&pat_type.ty)
+ || is_edge_context_type(&pat_type.ty)
+ || is_value_slice_type(&pat_type.ty)
+ {
+ if bind_params.iter().any(|candidate| candidate == ident) {
+ return Err(Error::new_spanned(
+ ident,
+ "special edge host parameters must not be listed in bind(...)",
+ ));
+ }
+ return Ok(());
+ }
+
+ if bind_params.iter().any(|candidate| candidate == ident) {
+ return Ok(());
+ }
+
+ edge_arg_decoder_kind(&pat_type.ty).map(|_| ())
+}
+
+fn validate_edge_return_type(output: &ReturnType) -> Result<(), Error> {
+ match edge_output_kind(output)? {
+ Some(_) => Ok(()),
+ None => Err(Error::new_spanned(
+ output,
+ "edge host functions must return CallOutcome or Result",
+ )),
+ }
+}
+
+fn generate_edge_host_binder(
+ item: &ItemFn,
+ wrapper_name: &syn::Ident,
+ attr: &EdgeHostAttr,
+) -> Result {
+ let impl_name = &item.sig.ident;
+ let vis = &item.vis;
+ let name_expr = &attr.name;
+ let mut binder_params = Vec::::new();
+ let mut binder_setup = Vec::::new();
+ let mut closure_setup = Vec::::new();
+ let mut call_args = Vec::::new();
+ let mut extract_stmts = Vec::::new();
+ let mut arg_index = 0usize;
+ let mut raw_args = false;
+
+ binder_params.push(quote!(bind_vm: &mut ::vm::Vm));
+ binder_params.push(quote!(bind_context: &crate::abi_impl::SharedProxyVmContext));
+ binder_params.push(quote!(bind_async_ops: &crate::abi_impl::SharedVmAsyncOps));
+
+ for input in &item.sig.inputs {
+ let FnArg::Typed(pat_type) = input else {
+ return Err(Error::new_spanned(input, "methods are not supported"));
+ };
+ let Pat::Ident(PatIdent { ident, .. }) = pat_type.pat.as_ref() else {
+ return Err(Error::new_spanned(
+ &pat_type.pat,
+ "edge host parameters must use identifier patterns",
+ ));
+ };
+ let ty = &pat_type.ty;
+
+ if is_vm_context_type(ty) {
+ call_args.push(quote!(vm));
+ continue;
+ }
+
+ if is_edge_async_ops_type(ty) {
+ binder_setup.push(quote!(let #ident = bind_async_ops.clone();));
+ closure_setup.push(quote!(let #ident = #ident.clone();));
+ call_args.push(quote!(#ident));
+ continue;
+ }
+
+ if is_edge_context_type(ty) {
+ binder_setup.push(quote!(let #ident = bind_context.clone();));
+ closure_setup.push(quote!(let #ident = #ident.clone();));
+ call_args.push(quote!(#ident));
+ continue;
+ }
+
+ if is_value_slice_type(ty) {
+ raw_args = true;
+ call_args.push(quote!(args));
+ continue;
+ }
+
+ if attr.bind_params.iter().any(|candidate| candidate == ident) {
+ binder_params.push(quote!(#ident: #ty));
+ binder_setup.push(quote!(let #ident = #ident.clone();));
+ closure_setup.push(quote!(let #ident = #ident.clone();));
+ call_args.push(quote!(#ident));
+ continue;
+ }
+
+ let decoder = edge_arg_decoder_kind(ty)?;
+ extract_stmts.push(edge_extract_stmt(ident, decoder, arg_index, wrapper_name));
+ call_args.push(quote!(#ident));
+ arg_index += 1;
+ }
+
+ let arity_check = if raw_args {
+ None
+ } else {
+ Some(quote! {
+ if args.len() != #arg_index {
+ return Err(::vm::VmError::HostError(format!(
+ "expected {} arguments, got {}",
+ #arg_index,
+ args.len()
+ )));
+ }
+ })
+ };
+
+ let call_expr = match edge_output_kind(&item.sig.output)? {
+ Some(EdgeOutputKind::ResultCallOutcome) => quote!(#impl_name(#(#call_args),*)),
+ Some(EdgeOutputKind::CallOutcome) => quote!(Ok(#impl_name(#(#call_args),*))),
+ None => {
+ return Err(Error::new_spanned(
+ &item.sig.output,
+ "edge host functions must return CallOutcome or Result",
+ ));
+ }
+ };
+
+ Ok(quote! {
+ #[allow(dead_code)]
+ #vis fn #wrapper_name(#(#binder_params),*) {
+ #(#binder_setup)*
+ crate::abi_impl::bind_async_host_handler(bind_vm, bind_async_ops, #name_expr, move |vm, args| {
+ #arity_check
+ #(#closure_setup)*
+ #(#extract_stmts)*
+ #call_expr
+ });
+ }
+ })
+}
+
+fn generate_scoped_edge_host_static_wrapper(
+ item: &ItemFn,
+ wrapper_name: &syn::Ident,
+ attr: &EdgeHostAttr,
+ was_async: bool,
+) -> Result {
+ let Some(scope) = attr.scope else {
+ return Ok(quote!());
+ };
+ let impl_name = &item.sig.ident;
+ let static_wrapper_name = format_ident!("__pd_edge_static_{}", wrapper_name);
+ let uses_vm = scoped_wrapper_uses_vm(item);
+ let scope_tokens = edge_scope_tokens(scope);
+ let scope_requires_prepare = matches!(
+ scope,
+ EdgeHostScopeAttr::Http | EdgeHostScopeAttr::HttpExtension
+ );
+ let args_only_sync_fast_path = !uses_vm && !was_async;
+ let prepare_context_ident = format_ident!("__pd_edge_prepare_context");
+ let mut setup_stmts = Vec::::new();
+ let mut call_args = Vec::::new();
+ let mut extract_stmts = Vec::::new();
+ let mut arg_index = 0usize;
+
+ for input in &item.sig.inputs {
+ let FnArg::Typed(pat_type) = input else {
+ return Err(Error::new_spanned(input, "methods are not supported"));
+ };
+ let Pat::Ident(PatIdent { ident, .. }) = pat_type.pat.as_ref() else {
+ return Err(Error::new_spanned(
+ &pat_type.pat,
+ "edge host parameters must use identifier patterns",
+ ));
+ };
+ let ty = &pat_type.ty;
+
+ if is_vm_context_type(ty) {
+ call_args.push(quote!(vm));
+ continue;
+ }
+
+ if is_edge_async_ops_type(ty) {
+ setup_stmts.push(quote!(let #ident = crate::abi_impl::current_async_ops()?;));
+ call_args.push(quote!(#ident));
+ continue;
+ }
+
+ if is_edge_context_type(ty) {
+ if args_only_sync_fast_path && scope_requires_prepare {
+ setup_stmts.push(quote!(let #ident = #prepare_context_ident.clone();));
+ } else {
+ setup_stmts.push(quote!(let #ident = crate::abi_impl::current_vm_context()?;));
+ }
+ call_args.push(quote!(#ident));
+ continue;
+ }
+
+ if is_value_slice_type(ty) {
+ return Err(Error::new_spanned(
+ ty,
+ "scoped pd_host_function does not support raw args",
+ ));
+ }
+
+ if attr.bind_params.iter().any(|candidate| candidate == ident) {
+ return Err(Error::new_spanned(
+ ident,
+ "scoped pd_host_function does not support bind(...)",
+ ));
+ }
+
+ let decoder = edge_arg_decoder_kind(ty)?;
+ extract_stmts.push(edge_extract_stmt(ident, decoder, arg_index, wrapper_name));
+ call_args.push(quote!(#ident));
+ arg_index += 1;
+ }
+
+ let _ = u8::try_from(arg_index).map_err(|_| {
+ Error::new_spanned(
+ &item.sig.ident,
+ "edge host functions must have 255 arguments or fewer",
+ )
+ })?;
+ let call_expr = match edge_output_kind(&item.sig.output)? {
+ Some(EdgeOutputKind::ResultCallOutcome) => quote!(#impl_name(#(#call_args),*)),
+ Some(EdgeOutputKind::CallOutcome) => quote!(Ok(#impl_name(#(#call_args),*))),
+ None => {
+ return Err(Error::new_spanned(
+ &item.sig.output,
+ "edge host functions must return CallOutcome or Result",
+ ));
+ }
+ };
+
+ if uses_vm {
+ Ok(quote! {
+ fn #static_wrapper_name(
+ vm: &mut ::vm::Vm,
+ args: &[::vm::Value],
+ ) -> Result<::vm::CallOutcome, ::vm::VmError> {
+ if args.len() != #arg_index {
+ return Err(::vm::VmError::HostError(format!(
+ "expected {} arguments, got {}",
+ #arg_index,
+ args.len()
+ )));
+ }
+ #(#setup_stmts)*
+ let __pd_edge_outcome = {
+ #(#extract_stmts)*
+ #call_expr
+ }?;
+ Ok(__pd_edge_outcome)
+ }
+ })
+ } else if args_only_sync_fast_path && scope_requires_prepare {
+ let name_expr = &attr.name;
+ Ok(quote! {
+ fn #static_wrapper_name(
+ args: &[::vm::Value],
+ ) -> Result<::vm::CallOutcome, ::vm::VmError> {
+ if args.len() != #arg_index {
+ return Err(::vm::VmError::HostError(format!(
+ "expected {} arguments, got {}",
+ #arg_index,
+ args.len()
+ )));
+ }
+ let #prepare_context_ident = crate::abi_impl::current_vm_context()?;
+ if !crate::abi_impl::scoped_host_call_can_run_synchronously(
+ prepare_context_ident,
+ #scope_tokens,
+ #name_expr,
+ )? {
+ return Err(::vm::VmError::HostError(format!(
+ "synchronous scoped host function {} requires an async signature",
+ #name_expr,
+ )));
+ }
+ #(#setup_stmts)*
+ #(#extract_stmts)*
+ let __pd_edge_outcome = #call_expr?;
+ Ok(__pd_edge_outcome)
+ }
+ })
+ } else {
+ Ok(quote! {
+ fn #static_wrapper_name(
+ args: &[::vm::Value],
+ ) -> Result<::vm::CallOutcome, ::vm::VmError> {
+ if args.len() != #arg_index {
+ return Err(::vm::VmError::HostError(format!(
+ "expected {} arguments, got {}",
+ #arg_index,
+ args.len()
+ )));
+ }
+ #(#setup_stmts)*
+ let __pd_edge_outcome = {
+ #(#extract_stmts)*
+ #call_expr
+ }?;
+ Ok(__pd_edge_outcome)
+ }
+ })
+ }
+}
+
+fn generate_edge_host_registration(
+ item: &ItemFn,
+ wrapper_name: &syn::Ident,
+ attr: &EdgeHostAttr,
+ docs: &str,
+) -> Result {
+ let Some(scope) = attr.scope else {
+ return Ok(quote!());
+ };
+
+ let entry_name = format_ident!("__pd_edge_registration_{}", wrapper_name);
+ let scope_tokens = edge_scope_tokens(scope);
+ let static_wrapper_name = format_ident!("__pd_edge_static_{}", wrapper_name);
+ let function_kind = if scoped_wrapper_uses_vm(item) {
+ quote!(crate::abi_impl::registry::EdgeHostRegistrationFunction::StackStatic(#static_wrapper_name))
+ } else {
+ quote!(crate::abi_impl::registry::EdgeHostRegistrationFunction::ArgsStatic(#static_wrapper_name))
+ };
+ let mut arity = 0usize;
+
+ for input in &item.sig.inputs {
+ let FnArg::Typed(pat_type) = input else {
+ return Err(Error::new_spanned(input, "methods are not supported"));
+ };
+ if is_vm_context_type(&pat_type.ty)
+ || is_edge_async_ops_type(&pat_type.ty)
+ || is_edge_context_type(&pat_type.ty)
+ {
+ continue;
+ }
+ if is_value_slice_type(&pat_type.ty) {
+ return Err(Error::new_spanned(
+ &pat_type.ty,
+ "scoped pd_host_function does not support raw args",
+ ));
+ }
+ arity += 1;
+ }
+
+ let arity = u8::try_from(arity).map_err(|_| {
+ Error::new_spanned(
+ &item.sig.ident,
+ "edge host functions must have 255 arguments or fewer",
+ )
+ })?;
+ let name_expr = &attr.name;
+ let docs = docs.to_string();
+
+ Ok(quote! {
+ #[::linkme::distributed_slice(crate::abi_impl::registry::PD_EDGE_HOST_FUNCTIONS)]
+ #[allow(non_upper_case_globals)]
+ static #entry_name: crate::abi_impl::registry::EdgeHostRegistration =
+ crate::abi_impl::registry::EdgeHostRegistration {
+ scope: #scope_tokens,
+ name: #name_expr,
+ arity: #arity,
+ docs: #docs,
+ function: #function_kind,
+ };
+ })
+}
+
+fn scoped_wrapper_uses_vm(item: &ItemFn) -> bool {
+ item.sig.inputs.iter().any(|input| match input {
+ FnArg::Typed(pat_type) => is_vm_context_type(&pat_type.ty),
+ FnArg::Receiver(_) => false,
+ })
+}
+
+fn find_vm_param_ident(item: &ItemFn) -> Option {
+ item.sig.inputs.iter().find_map(|input| {
+ let FnArg::Typed(pat_type) = input else {
+ return None;
+ };
+ if !is_vm_context_type(&pat_type.ty) {
+ return None;
+ }
+ match pat_type.pat.as_ref() {
+ Pat::Ident(PatIdent { ident, .. }) => Some(ident.clone()),
+ _ => None,
+ }
+ })
+}
+
+fn wrapper_and_impl_names(name: &syn::Ident) -> (syn::Ident, syn::Ident) {
+ let original = name.to_string();
+ match original.strip_suffix("_impl") {
+ Some(prefix) => (
+ syn::Ident::new(prefix, name.span()),
+ syn::Ident::new(&original, name.span()),
+ ),
+ None => (
+ syn::Ident::new(&original, name.span()),
+ syn::Ident::new(&format!("{original}_impl"), name.span()),
+ ),
+ }
+}
+
+#[derive(Clone, Copy)]
+enum EdgeArgDecoderKind {
+ String,
+ StringRef,
+ Int,
+ Bool,
+ Value,
+ ValueRef,
+ Map,
+ MapRef,
+}
+
+#[derive(Clone, Copy)]
+enum EdgeOutputKind {
+ CallOutcome,
+ ResultCallOutcome,
+}
+
+fn edge_arg_decoder_kind(ty: &Type) -> Result {
+ match ty {
+ Type::Group(group) => edge_arg_decoder_kind(&group.elem),
+ Type::Paren(paren) => edge_arg_decoder_kind(&paren.elem),
+ Type::Reference(reference) => {
+ if reference.mutability.is_some() {
+ return Err(Error::new_spanned(
+ ty,
+ "mutable borrowed edge host argument types are not supported",
+ ));
+ }
+ let Some(inner) = type_path_terminal_ident(reference.elem.as_ref()) else {
+ return Err(Error::new_spanned(
+ ty,
+ "unsupported borrowed edge host argument type",
+ ));
+ };
+ match inner.as_str() {
+ "str" => Ok(EdgeArgDecoderKind::StringRef),
+ "Value" => Ok(EdgeArgDecoderKind::ValueRef),
+ "VmMap" => Ok(EdgeArgDecoderKind::MapRef),
+ other => Err(Error::new_spanned(
+ ty,
+ format!("unsupported borrowed edge host argument type '&{other}'"),
+ )),
+ }
+ }
+ Type::Path(path) => {
+ let Some(segment) = path.path.segments.last() else {
+ return Err(Error::new_spanned(
+ ty,
+ "unsupported edge host argument type",
+ ));
+ };
+ match segment.ident.to_string().as_str() {
+ "String" => Ok(EdgeArgDecoderKind::String),
+ "i8" | "i16" | "i32" | "i64" | "isize" | "u8" | "u16" | "u32" | "u64" | "usize" => {
+ Ok(EdgeArgDecoderKind::Int)
+ }
+ "bool" => Ok(EdgeArgDecoderKind::Bool),
+ "Value" => Ok(EdgeArgDecoderKind::Value),
+ "VmMap" => Ok(EdgeArgDecoderKind::Map),
+ other => Err(Error::new_spanned(
+ ty,
+ format!("unsupported edge host argument type '{other}'"),
+ )),
+ }
+ }
+ _ => Err(Error::new_spanned(
+ ty,
+ "unsupported edge host argument type",
+ )),
+ }
+}
+
+fn edge_extract_stmt(
+ ident: &Ident,
+ decoder: EdgeArgDecoderKind,
+ arg_index: usize,
+ wrapper_name: &syn::Ident,
+) -> proc_macro2::TokenStream {
+ let label = LitStr::new(
+ &format!("{} {}", wrapper_name, ident),
+ proc_macro2::Span::call_site(),
+ );
+ let index = syn::Index::from(arg_index);
+ match decoder {
+ EdgeArgDecoderKind::String => quote! {
+ let #ident = match args.get(#index) {
+ Some(::vm::Value::String(value)) => value.to_string(),
+ Some(_) => return Err(::vm::VmError::TypeMismatch("string")),
+ None => {
+ return Err(::vm::VmError::HostError(format!(
+ "missing argument: {}",
+ #label
+ )));
+ }
+ };
+ },
+ EdgeArgDecoderKind::StringRef => quote! {
+ let #ident = match args.get(#index) {
+ Some(::vm::Value::String(value)) => value.as_str(),
+ Some(_) => return Err(::vm::VmError::TypeMismatch("string")),
+ None => {
+ return Err(::vm::VmError::HostError(format!(
+ "missing argument: {}",
+ #label
+ )));
+ }
+ };
+ },
+ EdgeArgDecoderKind::Int => quote! {
+ let #ident = match args.get(#index) {
+ Some(::vm::Value::Int(value)) => *value,
+ Some(_) => return Err(::vm::VmError::TypeMismatch("int")),
+ None => {
+ return Err(::vm::VmError::HostError(format!(
+ "missing argument: {}",
+ #label
+ )));
+ }
+ };
+ },
+ EdgeArgDecoderKind::Bool => quote! {
+ let #ident = match args.get(#index) {
+ Some(::vm::Value::Bool(value)) => *value,
+ Some(_) => return Err(::vm::VmError::TypeMismatch("bool")),
+ None => {
+ return Err(::vm::VmError::HostError(format!(
+ "missing argument: {}",
+ #label
+ )));
+ }
+ };
+ },
+ EdgeArgDecoderKind::Value => quote! {
+ let #ident = match args.get(#index) {
+ Some(value) => value.clone(),
+ None => {
+ return Err(::vm::VmError::HostError(format!(
+ "missing argument: {}",
+ #label
+ )));
+ }
+ };
+ },
+ EdgeArgDecoderKind::ValueRef => quote! {
+ let #ident = match args.get(#index) {
+ Some(value) => value,
+ None => {
+ return Err(::vm::VmError::HostError(format!(
+ "missing argument: {}",
+ #label
+ )));
+ }
+ };
+ },
+ EdgeArgDecoderKind::Map => quote! {
+ let #ident = match args.get(#index) {
+ Some(::vm::Value::Map(entries)) => entries.as_ref().clone(),
+ Some(_) => return Err(::vm::VmError::TypeMismatch("map")),
+ None => {
+ return Err(::vm::VmError::HostError(format!(
+ "missing argument: {}",
+ #label
+ )));
+ }
+ };
+ },
+ EdgeArgDecoderKind::MapRef => quote! {
+ let #ident = match args.get(#index) {
+ Some(::vm::Value::Map(entries)) => entries.as_ref(),
+ Some(_) => return Err(::vm::VmError::TypeMismatch("map")),
+ None => {
+ return Err(::vm::VmError::HostError(format!(
+ "missing argument: {}",
+ #label
+ )));
+ }
+ };
+ },
+ }
+}
+
+fn edge_arg_decoder_is_borrowed(decoder: EdgeArgDecoderKind) -> bool {
+ matches!(
+ decoder,
+ EdgeArgDecoderKind::StringRef | EdgeArgDecoderKind::ValueRef | EdgeArgDecoderKind::MapRef
+ )
+}
+
+fn type_path_terminal_ident(ty: &Type) -> Option {
+ match ty {
+ Type::Group(group) => type_path_terminal_ident(&group.elem),
+ Type::Paren(paren) => type_path_terminal_ident(&paren.elem),
+ Type::Path(path) => path
+ .path
+ .segments
+ .last()
+ .map(|segment| segment.ident.to_string()),
+ _ => None,
+ }
+}
+
+fn edge_output_kind(output: &ReturnType) -> Result