diff --git a/Cargo.lock b/Cargo.lock index bdb604a..f3c8f29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,18 +2,139 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59317f77929f0e679d39364702289274de2f0f0b22cbf50b2b8cff2169a0b27a" +dependencies = [ + "gimli", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "ambient-authority" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b" + [[package]] name = "anyhow" version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +dependencies = [ + "allocator-api2", +] + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cap-primitives" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b5f74729fd2f44701d1a8eb47e906cdb3ccd9ec0f02baad85a744b791940b18" +dependencies = [ + "ambient-authority", + "fs-set-times", + "io-extras", + "io-lifetimes 3.0.1", + "ipnet", + "maybe-owned", + "rustix", + "rustix-linux-procfs", + "windows-sys 0.61.2", + "winx", +] + +[[package]] +name = "cc" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f74872d07caf508b30a21f6836e7d7016a2eaf7d9ff4f48deaa58cd8a0407630" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "rand_core", +] + [[package]] name = "client" version = "0.1.0" @@ -22,6 +143,278 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.21", +] + +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + +[[package]] +name = "cpp_demangle" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0667304c32ea56cb4cd6d2d7c0cfe9a2f8041229db8c033af7f8d69492429def" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "cranelift-assembler-x64" +version = "0.136.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658e8623bea025d142fb11c299b8c6572ffe9fd663b52887c4dc124735cc130b" +dependencies = [ + "cranelift-assembler-x64-meta", +] + +[[package]] +name = "cranelift-assembler-x64-meta" +version = "0.136.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4b12b8c55c31c3d8598c2de9a657c3b978f905f6886c43e02216e0b15fe45a3" +dependencies = [ + "cranelift-srcgen", +] + +[[package]] +name = "cranelift-bforest" +version = "0.136.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "894b984f82c5a36f05fdd4973fbf4d37d69cd5cdf8c16e8ffe3af4a9cdfe193c" +dependencies = [ + "cranelift-entity", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-bitset" +version = "0.136.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a1e4c34abe94193ada09f821f474be87ac8a1fbbacd87058571ef04c1bf4009" +dependencies = [ + "serde", + "serde_derive", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-codegen" +version = "0.136.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63826cef0bf70e61d56cc930aa4ee423638f66dc3cfc10d683e73138a6d177b8" +dependencies = [ + "bumpalo", + "cranelift-assembler-x64", + "cranelift-bforest", + "cranelift-bitset", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-control", + "cranelift-entity", + "cranelift-isle", + "gimli", + "hashbrown 0.17.1", + "libm", + "log", + "postcard", + "pulley-interpreter", + "regalloc2", + "rustc-hash", + "serde", + "serde_derive", + "sha2", + "smallvec", + "target-lexicon", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.136.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26de7849112293681a19f7525c689e1d73dc6eb2687355170fb7458c4da8d353" +dependencies = [ + "cranelift-assembler-x64-meta", + "cranelift-codegen-shared", + "cranelift-srcgen", + "heck", + "pulley-interpreter", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.136.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df315a8b1cfd6e620971e3d4249bd2c4528167717e8f48a6317a87c55891bcb8" + +[[package]] +name = "cranelift-control" +version = "0.136.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3fff7610fb17f228f16598e1a044db79a12806ece7496ff2c69cf1a27a79a5" +dependencies = [ + "arbitrary", +] + +[[package]] +name = "cranelift-entity" +version = "0.136.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6158b1ac381792ad11e670abbcd15db8b1737e84bfa8a7f2bd7eb41388a92530" +dependencies = [ + "cranelift-bitset", + "serde", + "serde_derive", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-frontend" +version = "0.136.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099361a0919067086dcde624a281ce719834cebf8544d6c24646f5e924107234" +dependencies = [ + "cranelift-codegen", + "hashbrown 0.17.1", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.136.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2f30ea3fd5a11f7d3473976d3b62b5b3f2b62fa943c85e0331d88bf505082c" + +[[package]] +name = "cranelift-native" +version = "0.136.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397508a8d817dc74019603ac5a4c4e40b4df627e5aed4f2f8088345d4c01d401" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + +[[package]] +name = "cranelift-srcgen" +version = "0.136.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4944ef05513e1c1a85a945cf8f29d95ddea8bcbb19e0bfd4e71063450967420" + +[[package]] +name = "crc32fast" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "uuid", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "displaydoc" version = "0.2.7" @@ -33,12 +426,78 @@ dependencies = [ "syn 3.0.5", ] +[[package]] +name = "either" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "encoding_rs" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" +dependencies = [ + "cfg-if", + "core_detect", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", +] + [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "find-msvc-tools" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.2.0" @@ -54,6 +513,17 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs-set-times" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a" +dependencies = [ + "io-lifetimes 2.0.4", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "futures" version = "0.3.34" @@ -108,36 +578,192 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ + "futures-channel", "futures-core", + "futures-io", "futures-sink", "futures-task", + "memchr", "pin-project-lite", "slab", ] [[package]] -name = "hashbrown" -version = "0.17.1" +name = "fxprof-processed-profile" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +checksum = "25234f20a3ec0a962a61770cfe39ecf03cb529a6e474ad8cff025ed497eda557" dependencies = [ - "foldhash", + "bitflags", + "debugid", + "rustc-hash", + "serde", + "serde_derive", + "serde_json", ] [[package]] -name = "heck" -version = "0.5.0" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] [[package]] -name = "icu_collections" -version = "2.2.0" +name = "getrandom" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ - "displaydoc", - "potential_utf", + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "rand_core", +] + +[[package]] +name = "gimli" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7f043f89559805f8c7cacc432749b2fa0d0a0a9ee46ce47164ed5ba7f126c" +dependencies = [ + "fnv", + "hashbrown 0.16.1", + "indexmap", + "stable_deref_trait", +] + +[[package]] +name = "h2" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash", + "serde", + "serde_core", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +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 = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[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", @@ -198,314 +824,1694 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] -name = "icu_provider" -version = "2.2.0" +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 = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[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.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "io-extras" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20fd6de4ccfcc187e38bc21cfa543cb5a302cb86a8b114eb7f0bf0dc9f8ac00f" +dependencies = [ + "io-lifetimes 3.0.1", + "windows-sys 0.60.2", +] + +[[package]] +name = "io-lifetimes" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" + +[[package]] +name = "io-lifetimes" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0fb0570afe1fed943c5c3d4102d5358592d8625fda6a0007fdbe65a92fba96" + +[[package]] +name = "ipnet" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "ittapi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b996fe614c41395cdaedf3cf408a9534851090959d90d54a535f675550b64b1" +dependencies = [ + "anyhow", + "ittapi-sys", + "log", +] + +[[package]] +name = "ittapi-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5385394064fa2c886205dba02598013ce83d3e92d33dbdc0c52fe0e7bf4fc" +dependencies = [ + "cc", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "leb128" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83bff1d572d6b9aeef67ddfc8448e4a3737909cb28e81f97c791b9018703e52" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" +dependencies = [ + "libc", +] + +[[package]] +name = "linux-raw-sys" +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.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "mach2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" + +[[package]] +name = "macro-string" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e7b3a5bbb2f63aaa223a671ea1bd0e7bc16bd30ce387324cf63995deaddbbd2" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "maybe-owned" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memfd" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57804b2c9b69967f1536a56f86297e367a33b19e98852ed624b84551cdbc0d90" +dependencies = [ + "rustix", +] + +[[package]] +name = "mio" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + +[[package]] +name = "object" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd229a0361b9d0d4396176e02d65897f487eebeab7caa6d443855ee152ca0b9c" +dependencies = [ + "crc32fast", + "hashbrown 0.17.1", + "indexmap", + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "prettyplease" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bfe0f4c752e450fc2faf62654f1c134747922825d5b04ca717b8874f41a40c0" +dependencies = [ + "proc-macro2", + "syn 3.0.5", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pulley-interpreter" +version = "49.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b66b7448cb92d709f2924de98b5ba5b5ce4ea08e6d6b9c3b403fcd88d122ca2" +dependencies = [ + "cranelift-bitset", + "log", + "pulley-macros", + "wasmtime-internal-core", +] + +[[package]] +name = "pulley-macros" +version = "49.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185b9b4cd91f85110f48f8c0314757dfb2bb48c40f5d91055ea666cbbcbf317b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +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 = "rand" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +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 = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "regalloc2" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757712e8e61590d6d4f5d563483755538b5aa13467837a3b41cd9832509a7f85" +dependencies = [ + "allocator-api2", + "bumpalo", + "hashbrown 0.17.1", + "log", + "rustc-hash", + "serde", + "smallvec", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustix" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustix-linux-procfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc84bf7e9aa16c4f2c758f27412dc9841341e16aa682d9c7ac308fe3ee12056" +dependencies = [ + "once_cell", + "rustix", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +dependencies = [ + "serde", +] + +[[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 = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[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.119", +] + +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "test-harness" +version = "0.1.0" +dependencies = [ + "bytes", + "http", + "http-body-util", + "tokio", + "wasmtime", + "wasmtime-wasi", + "wasmtime-wasi-http", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl 2.0.21", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[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", + "libc", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "1.1.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[package]] +name = "trace" +version = "0.1.0" +dependencies = [ + "test-harness", + "tokio", + "wasmtime", + "wit-bindgen", +] + +[[package]] +name = "trace-client" +version = "0.1.0" +dependencies = [ + "test-harness", + "tokio", + "wasmtime", + "wit-bindgen", +] + +[[package]] +name = "trace-componentized-client" +version = "0.1.0" +dependencies = [ + "test-harness", + "tokio", + "wasmtime", + "wit-bindgen", +] + +[[package]] +name = "trace-handler" +version = "0.1.0" +dependencies = [ + "test-harness", + "tokio", + "wasmtime", + "wit-bindgen", +] + +[[package]] +name = "trace-types" +version = "0.1.0" +dependencies = [ + "test-harness", + "tokio", + "wasmtime", + "wit-bindgen", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[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 = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[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 = "uuid" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[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.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.5", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-compose" +version = "0.258.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27dc71f96d21a8b9153da9005c603f4f20d5781b7e6d2675ea0ca5def8713b75" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "log", + "petgraph", + "smallvec", + "wasm-encoder 0.258.3", + "wasmparser 0.258.3", + "wat", +] + +[[package]] +name = "wasm-encoder" +version = "0.258.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f64765e43c000fae008a48db8b288e175e237fc9ce20a26586836878c1bee89f" +dependencies = [ + "leb128fmt", + "wasmparser 0.258.3", +] + +[[package]] +name = "wasm-encoder" +version = "0.259.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d0246511d901aacf25d2dc9111f0054947de5e093fe662099e709ff7530dc3" +dependencies = [ + "leb128fmt", + "wasmparser 0.259.0", +] + +[[package]] +name = "wasm-encoder" +version = "0.261.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2608e8bb6d67fd68f5a8d0eb1363d6e7bcbc1f8ded5a0bd3a1e382462b876b22" +dependencies = [ + "leb128fmt", + "wasmparser 0.261.0", +] + +[[package]] +name = "wasm-metadata" +version = "0.258.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e10808eb27efde692f1b1706089fe19296728bf872291f5b4e0d50fba88f2f" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder 0.258.3", + "wasmparser 0.258.3", +] + +[[package]] +name = "wasm-metadata" +version = "0.259.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7bf119eb01f4a246864c10bd87c8f26c566350abb25aa4d5273c415bbab338" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder 0.259.0", + "wasmparser 0.259.0", +] + +[[package]] +name = "wasmparser" +version = "0.258.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9269dd817e0e7021bf534ccd9761bc2d64778b6708e41228b8439d75eaa0b1" +dependencies = [ + "bitflags", + "hashbrown 0.17.1", + "indexmap", + "semver", + "serde", +] + +[[package]] +name = "wasmparser" +version = "0.259.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f7c12eac7bb587801590f6a67ff0bc84d0748513864b71108e4b311cc3df694" +dependencies = [ + "bitflags", + "hashbrown 0.17.1", + "indexmap", + "semver", +] + +[[package]] +name = "wasmparser" +version = "0.261.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f20f20e44f7e8aeb6744823ea9d869ede51e51be4fdaedede2852282e54d2d8" +dependencies = [ + "bitflags", + "indexmap", + "semver", +] + +[[package]] +name = "wasmprinter" +version = "0.258.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14a457ba8670a3db3d357c1aeac3ed4fe378a76054a221789f0df0ea507e292b" +dependencies = [ + "anyhow", + "termcolor", + "wasmparser 0.258.3", +] + +[[package]] +name = "wasmtime" +version = "49.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1801b9c9f91e16e352ed77656824e7c4556a7538dbfc1a50b37243f0e12ea045" +dependencies = [ + "addr2line", + "async-trait", + "bitflags", + "bumpalo", + "bytes", + "cc", + "encoding_rs", + "futures", + "fxprof-processed-profile", + "gimli", + "ittapi", + "libc", + "log", + "mach2", + "memfd", + "object", + "once_cell", + "postcard", + "pulley-interpreter", + "rayon", + "rustix", + "semver", + "serde", + "serde_derive", + "serde_json", + "smallvec", + "target-lexicon", + "tempfile", + "wasm-compose", + "wasm-encoder 0.258.3", + "wasmparser 0.258.3", + "wasmtime-environ", + "wasmtime-internal-cache", + "wasmtime-internal-component-macro", + "wasmtime-internal-component-util", + "wasmtime-internal-core", + "wasmtime-internal-cranelift", + "wasmtime-internal-fiber", + "wasmtime-internal-jit-debug", + "wasmtime-internal-jit-icache-coherence", + "wasmtime-internal-unwinder", + "wasmtime-internal-versioned-export-macros", + "wat", + "windows-sys 0.61.2", + "wit-parser 0.258.3", +] + +[[package]] +name = "wasmtime-environ" +version = "49.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f397969b8daf208e45120036be74956e99364ce916d5b5d67f9e9f0fe40e5477" +dependencies = [ + "anyhow", + "cpp_demangle", + "cranelift-bforest", + "cranelift-bitset", + "cranelift-entity", + "gimli", + "hashbrown 0.17.1", + "indexmap", + "log", + "object", + "postcard", + "rustc-demangle", + "semver", + "serde", + "serde_derive", + "sha2", + "smallvec", + "target-lexicon", + "wasm-encoder 0.258.3", + "wasmparser 0.258.3", + "wasmprinter", + "wasmtime-internal-component-util", + "wasmtime-internal-core", +] + +[[package]] +name = "wasmtime-internal-cache" +version = "49.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf564a08f96acec55a335e3185d1df98ba3feee47e178f5bc2429e723d30d0f" +dependencies = [ + "base64", + "directories-next", + "log", + "postcard", + "rustix", + "serde", + "serde_derive", + "sha2", + "toml", + "wasmtime-environ", + "windows-sys 0.61.2", + "zstd", +] + +[[package]] +name = "wasmtime-internal-component-macro" +version = "49.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47c428b868b59d51a9f77e24bff02cc321f8ec0faeba5c40f7e4c1cddd43a64" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasmtime-internal-component-util", + "wasmtime-internal-wit-bindgen", + "wit-parser 0.258.3", +] + +[[package]] +name = "wasmtime-internal-component-util" +version = "49.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fe14bd5ea6444a09f6baa3865559c93c91158930e7fe6d127429a9372f6165" + +[[package]] +name = "wasmtime-internal-core" +version = "49.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2053fd1d90080e502ca4ea39d86c3528587a40220b9572a874c8b742c219a00c" +dependencies = [ + "anyhow", + "hashbrown 0.17.1", + "libm", + "serde", +] + +[[package]] +name = "wasmtime-internal-cranelift" +version = "49.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e96d6c55401e68e40e3f74078ec96f92fe9b8a7e90e885fb87265e69eea559" +dependencies = [ + "cranelift-codegen", + "cranelift-control", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "gimli", + "itertools", + "log", + "object", + "pulley-interpreter", + "smallvec", + "target-lexicon", + "thiserror 2.0.21", + "wasmparser 0.258.3", + "wasmtime-environ", + "wasmtime-internal-core", + "wasmtime-internal-unwinder", + "wasmtime-internal-versioned-export-macros", +] + +[[package]] +name = "wasmtime-internal-fiber" +version = "49.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b913875bab1b78fada489e55d5e0a10b53286fb4bb09f5657212f549c9af36c" +dependencies = [ + "cc", + "libc", + "rustix", + "wasmtime-environ", + "wasmtime-internal-versioned-export-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmtime-internal-jit-debug" +version = "49.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0356a1a09dee1239904e09e962456d9f6edfa5da61c5e3e1b0e4669710f48fba" +dependencies = [ + "cc", + "object", + "rustix", + "wasmtime-internal-versioned-export-macros", +] + +[[package]] +name = "wasmtime-internal-jit-icache-coherence" +version = "49.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dead9a2d7b5d835698a2e1a32006e7d0b5711f4697b24bab1564bb2488e3d7a" +dependencies = [ + "libc", + "wasmtime-internal-core", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmtime-internal-unwinder" +version = "49.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69ffac8fc893de2958b263d9d7274cf4c531ba9ff582558348d4e28d97b8a0f2" +dependencies = [ + "cranelift-codegen", + "log", + "object", + "wasmtime-environ", +] + +[[package]] +name = "wasmtime-internal-versioned-export-macros" +version = "49.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a20f0dcb6c6a006363d3f85b5c633675730487b0dc23726b2d40495872407fb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "wasmtime-internal-wit-bindgen" +version = "49.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80758f73f5f5daacd8a6d1a8513336a4f9a58d26c4f1c73eb80502cb143e2626" +dependencies = [ + "anyhow", + "bitflags", + "heck", + "indexmap", + "wit-component 0.258.3", + "wit-parser 0.258.3", +] + +[[package]] +name = "wasmtime-wasi" +version = "49.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bac947c8478d8ea84ef4120d04f984012a514e493fe16fa5bb72906d7e55addb" +dependencies = [ + "async-trait", + "bitflags", + "bytes", + "cap-primitives", + "futures", + "rand", + "rustix", + "rustix-linux-procfs", + "thiserror 2.0.21", + "tokio", + "tracing", + "url", + "wasmtime", + "wasmtime-wasi-io", + "wiggle", + "windows-sys 0.61.2", + "winx", +] + +[[package]] +name = "wasmtime-wasi-http" +version = "49.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4cd327c70de1309b0c04d49df3633a2ffeae9b29b526d421320b565e198175" +dependencies = [ + "async-trait", + "bytes", + "futures", + "http", + "http-body", + "http-body-util", + "hyper", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", + "wasmtime", + "wasmtime-wasi", + "wasmtime-wasi-io", +] + +[[package]] +name = "wasmtime-wasi-io" +version = "49.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +checksum = "e65d70efbe70631acbb4c6cf46eea543c75908f01e2fd09550e5a89631693304" dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", + "async-trait", + "bytes", + "futures", + "tracing", + "wasmtime", ] [[package]] -name = "id-arena" -version = "2.3.0" +name = "wast" +version = "35.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +checksum = "2ef140f1b49946586078353a453a1d28ba90adfc54dde75710bc1931de204d68" +dependencies = [ + "leb128", +] [[package]] -name = "idna" -version = "1.1.0" +name = "wast" +version = "261.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +checksum = "776443145731a4062e5b0d392892a2005909b6ab72d9fdc3cad53dd1a714e44a" dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", + "bumpalo", + "leb128fmt", + "memchr", + "unicode-width", + "wasm-encoder 0.261.0", ] [[package]] -name = "idna_adapter" -version = "1.2.2" +name = "wat" +version = "1.261.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +checksum = "a7b4d1a49ea73a8f3326e74e3a05db667001b16bd1035ed3356fc1a0ed05ca7f" dependencies = [ - "icu_normalizer", - "icu_properties", + "wast 261.0.0", ] [[package]] -name = "indexmap" -version = "2.14.0" +name = "wiggle" +version = "49.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "7f7c3a2e8ec4789bb82e4bf04b0dba44b02ce05beba6cbc6eb992b8f0b93c9cd" dependencies = [ - "equivalent", - "hashbrown", - "serde", - "serde_core", + "bitflags", + "thiserror 2.0.21", + "tracing", + "wasmtime", + "wasmtime-environ", + "wiggle-macro", ] [[package]] -name = "itoa" -version = "1.0.18" +name = "wiggle-generate" +version = "49.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +checksum = "761bf518bf5972dd0ddb8c3c693b6a52ed826e25d10bfae89b1368327c86030d" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasmtime-environ", + "witx", +] [[package]] -name = "leb128fmt" -version = "0.1.0" +name = "wiggle-macro" +version = "49.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +checksum = "0bdaa435787bc4bb662020aea37538745fc60b4629d7f37864a09195cec0e1eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "wiggle-generate", +] [[package]] -name = "litemap" -version = "0.8.2" +name = "winapi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] [[package]] -name = "log" -version = "0.4.33" +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] -name = "macro-string" -version = "0.3.0" +name = "winapi-util" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e7b3a5bbb2f63aaa223a671ea1bd0e7bc16bd30ce387324cf63995deaddbbd2" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", + "windows-sys 0.61.2", ] [[package]] -name = "memchr" -version = "2.8.3" +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "percent-encoding" -version = "2.3.2" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "pin-project-lite" -version = "0.2.17" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] [[package]] -name = "potential_utf" -version = "0.1.5" +name = "windows-sys" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "zerovec", + "windows-targets 0.53.5", ] [[package]] -name = "prettyplease" -version = "0.3.0" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bfe0f4c752e450fc2faf62654f1c134747922825d5b04ca717b8874f41a40c0" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "proc-macro2", - "syn 3.0.5", + "windows-link", ] [[package]] -name = "proc-macro2" -version = "1.0.107" +name = "windows-targets" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "unicode-ident", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] -name = "quote" -version = "1.0.47" +name = "windows-targets" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "proc-macro2", + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] -name = "semver" -version = "1.0.28" +name = "windows_aarch64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "serde" -version = "1.0.229" +name = "windows_aarch64_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" -dependencies = [ - "serde_core", -] +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] -name = "serde_core" -version = "1.0.229" +name = "windows_aarch64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" -dependencies = [ - "serde_derive", -] +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "serde_derive" -version = "1.0.229" +name = "windows_aarch64_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] -name = "serde_json" -version = "1.0.151" +name = "windows_i686_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] -name = "slab" -version = "0.4.12" +name = "windows_i686_gnu" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] -name = "smallvec" -version = "1.15.2" +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "stable_deref_trait" -version = "1.2.1" +name = "windows_i686_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] -name = "syn" -version = "2.0.119" +name = "windows_i686_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] -name = "syn" -version = "3.0.5" +name = "windows_i686_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] -name = "synstructure" -version = "0.13.2" +name = "windows_x86_64_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] -name = "tinystr" -version = "0.8.3" +name = "windows_x86_64_gnu" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" -dependencies = [ - "displaydoc", - "zerovec", -] +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] -name = "unicode-ident" -version = "1.0.24" +name = "windows_x86_64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "url" -version = "2.5.8" +name = "windows_x86_64_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] -name = "utf8_iter" -version = "1.0.4" +name = "windows_x86_64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "wasm-encoder" -version = "0.259.0" +name = "windows_x86_64_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d0246511d901aacf25d2dc9111f0054947de5e093fe662099e709ff7530dc3" -dependencies = [ - "leb128fmt", - "wasmparser", -] +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] -name = "wasm-metadata" -version = "0.259.0" +name = "winnow" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7bf119eb01f4a246864c10bd87c8f26c566350abb25aa4d5273c415bbab338" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" [[package]] -name = "wasmparser" -version = "0.259.0" +name = "winx" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f7c12eac7bb587801590f6a67ff0bc84d0748513864b71108e4b311cc3df694" +checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d" dependencies = [ "bitflags", - "hashbrown", - "indexmap", - "semver", + "windows-sys 0.59.0", ] [[package]] @@ -527,7 +2533,7 @@ checksum = "72ad22a37ecbc0e1fdca34d2b670ba41368cb0920735643e00fdf39a16ee5431" dependencies = [ "anyhow", "heck", - "wit-parser", + "wit-parser 0.259.0", ] [[package]] @@ -541,9 +2547,9 @@ dependencies = [ "indexmap", "prettyplease", "syn 3.0.5", - "wasm-metadata", + "wasm-metadata 0.259.0", "wit-bindgen-core", - "wit-component", + "wit-component 0.259.0", ] [[package]] @@ -562,6 +2568,25 @@ dependencies = [ "wit-bindgen-rust", ] +[[package]] +name = "wit-component" +version = "0.258.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0fca88950af0c1f42bc77e17315e48798b8d68340171b6d204f62bc19a878a6" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder 0.258.3", + "wasm-metadata 0.258.3", + "wasmparser 0.258.3", + "wit-parser 0.258.3", +] + [[package]] name = "wit-component" version = "0.259.0" @@ -575,10 +2600,29 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", + "wasm-encoder 0.259.0", + "wasm-metadata 0.259.0", + "wasmparser 0.259.0", + "wit-parser 0.259.0", +] + +[[package]] +name = "wit-parser" +version = "0.258.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2127663dcf3ee16614fb0e3c33d5c6c66fec9dad317a8b0fdd3869487a533" +dependencies = [ + "anyhow", + "hashbrown 0.17.1", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-ident", + "wasmparser 0.258.3", ] [[package]] @@ -588,7 +2632,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6390f4f02493ce678d509c859cf1698e38929fb75c8012a6ee2f7d6b6cb66cd7" dependencies = [ "anyhow", - "hashbrown", + "hashbrown 0.17.1", "id-arena", "indexmap", "log", @@ -597,7 +2641,19 @@ dependencies = [ "serde_derive", "serde_json", "unicode-ident", - "wasmparser", + "wasmparser 0.259.0", +] + +[[package]] +name = "witx" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" +dependencies = [ + "anyhow", + "log", + "thiserror 1.0.69", + "wast 35.0.2", ] [[package]] @@ -688,3 +2744,31 @@ name = "zmij" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d80649ab6db9d9f6f9c80a40becd948eda4714a0a5ac8c4d157a32231c7882" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.1.0+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 7603948..791c9db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,9 +2,23 @@ resolver = "2" members = [ "components/client", + "components/trace", + "components/trace-client", + "components/trace-handler", + "components/trace-componentized-client", + "components/trace-types", + "crates/test-harness", ] [workspace.dependencies] +bytes = "1" +http = "1" +http-body-util = "0.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +test-harness = { path = "./crates/test-harness" } +tokio = { version = "1", features = ["macros", "rt-multi-thread", "sync"] } +wasmtime = { version = "49", features = ["component-model-async"] } +wasmtime-wasi = { version = "49", features = ["p3"] } +wasmtime-wasi-http = { version = "49", default-features = false, features = ["p3", "component-model-async"] } wit-bindgen = "0.62.0" diff --git a/Makefile b/Makefile index 9c2a844..2ce997e 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,16 @@ SHELL := /bin/bash -WKG_CONFIG := $(CURDIR)/.config/wasm-pkg/config.toml +# prefer paths are relative to the root of the repository, where make runs, to keep the logs readable + +WKG_CONFIG := .config/wasm-pkg/config.toml export RUST_BACKTRACE ?= 1 export WASMTIME_BACKTRACE_DETAILS ?= 1 -COMPONENTS_DIR := $(abspath target/components) -TOOLS_DIR := $(abspath target/tools) -export PATH := $(TOOLS_DIR)/bin:$(PATH) +COMPONENTS_DIR := target/components +TOOLS_DIR := target/tools +# absolute, tools also run from other directories, e.g. `cd components && wkg fetch` +export PATH := $(abspath $(TOOLS_DIR))/bin:$(PATH) # cargo binstall downloads prebuilt binaries, without it the tools are built with cargo install CARGO_INSTALL := $(if $(shell command -v cargo-binstall 2> /dev/null),cargo binstall --no-confirm --disable-telemetry,cargo install) @@ -125,7 +128,7 @@ wit/deps: wkg.toml $(shell find wit -type f -name "*.wit" -not -path "deps") | $ wkg fetch --config $(WKG_CONFIG) components/wit/deps: wit/deps components/wkg.toml $(shell find components/wit -type f -name "*.wit" -not -path "deps") | $(call tool,wkg) - ( cd components && wkg fetch --config $(WKG_CONFIG) ) + ( cd components && wkg fetch --config ../$(WKG_CONFIG) ) # sign published components with cosign, `SIGN=false` to push without signing, e.g. to a local registry SIGN ?= true diff --git a/README.md b/README.md index 9e52bea..9abb05d 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ A collection of utility components that remix wasi:http types and interfaces. - [Components](#components) + - [Choosing a component variant](#choosing-a-component-variant) - [Build](#build) - [Community](#community) - [Code of Conduct](#code-of-conduct) @@ -15,15 +16,35 @@ A collection of utility components that remix wasi:http types and interfaces. ## Components - [`client`](./components/client/) +- [`trace`](./components/trace/) +- [`trace-client`](./components/trace-client/) +- [`trace-handler`](./components/trace-handler/) +- [`trace-componentized-client`](./components/trace-componentized-client/) +- [`trace-types`](./components/trace-types/) -## Build +### Choosing a component variant + +Due to resource types being unique to the instance that defines them in the Component Model, fine grain composition of the `wasi:http` interfaces can be persnickety. Pick the most specific component that covers the interfaces the target component imports. While the base component is more universal, a larger surface area asks the host for capabilities the target component doesn't use. + +For example, with the `trace-*` components: + +| The target component imports | Use | +| ------------------------------------------------------------- | ------------------------------------------------------------------------ | +| `wasi:http/types` | [`trace-types`](./components/trace-types/) | +| `wasi:http/types` and `wasi:http/client` | [`trace-client`](./components/trace-client/) | +| `wasi:http/types` and `wasi:http/handler` | [`trace-handler`](./components/trace-handler/) | +| `wasi:http/types`, `wasi:http/client` and `wasi:http/handler` | [`trace`](./components/trace/) | +| `componentized:http/client` | [`trace-componentized-client`](./components/trace-componentized-client/) | -A [dev container](https://containers.dev) is available that contains the necessary tools and configuration out of the box. +`trace-client`, `trace-handler` and `trace` also trace `wasi:http/types`, don't combine them with `trace-types`. + +The `wasi:http/handler` exported by `trace-handler` and `trace` takes requests created with their exported `wasi:http/types`. Use them in front of a component that forwards requests with `wasi:http/handler`, a host serving incoming requests can't call them directly. + +## Build Prereqs: - a rust toolchain -- [`wasm-tools`](https://github.com/bytecodealliance/wasm-tools) -- [`wkg`](https://github.com/bytecodealliance/wasm-pkg-tools) +- [`cargo-binstall`](https://github.com/cargo-bins/cargo-binstall), optional, to download prebuilt tools instead of building them ```sh make components @@ -31,15 +52,7 @@ make components The build creates each component in [`components`](./components) into `target/components`, e.g. the client at `target/components/client/client.wasm`, along with `target/components/interface.wasm`, the `componentized:http` WIT package. Each component is also built with debug info, e.g. `target/components/client/client.debug.wasm`. -```sh -make test -``` - -The WIT dependencies in each `wit/deps` directory are fetched rather than committed, pinned by the `wkg.lock` files. The make targets fetch them as needed. To fetch or update them directly, e.g. before building the Rust components with `cargo`, whose bindings are generated from the WIT: - -```sh -make wit -``` +The cli tools the build uses, [`static-config`](https://github.com/componentized/static-config), [`wasm-tools`](https://github.com/bytecodealliance/wasm-tools), [`wac`](https://github.com/bytecodealliance/wac) and [`wkg`](https://github.com/bytecodealliance/wasm-pkg-tools), are pinned in [`tools/Cargo.toml`](./tools/Cargo.toml) and installed into `target/tools` as needed, or ahead of time with `make tools`. Dependabot bumps the pinned versions. ## Community diff --git a/components/trace-client/Cargo.toml b/components/trace-client/Cargo.toml new file mode 100644 index 0000000..1ed8596 --- /dev/null +++ b/components/trace-client/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "trace-client" +version = "0.1.0" +edition = "2024" +license = "Apache-2.0" + +[lib] +crate-type = ["cdylib"] +# shared with the other trace components +path = "../../crates/trace/src/lib.rs" + +[features] +default = ["client"] +client = [] +handler = [] + +[dependencies] +wit-bindgen = { workspace = true, features = ["async-spawn"] } + +[dev-dependencies] +test-harness = { workspace = true } +tokio = { workspace = true } +wasmtime = { workspace = true } diff --git a/components/trace-client/README.md b/components/trace-client/README.md new file mode 100644 index 0000000..db7d739 --- /dev/null +++ b/components/trace-client/README.md @@ -0,0 +1,13 @@ +# `trace-client` + +Virtualizes the wasi:http/types and wasi:http/client interfaces logging all method calls at the TRACE level and 'componentized-trace' component. + +Built from the sources shared with the other trace components in [`crates/trace`](../../crates/trace/src/lib.rs). + +## The `trace-client` World + +- exports `wasi:http/types` +- exports `wasi:http/client` +- imports `wasi:http/types` +- imports `wasi:http/client` +- imports `wasi:logging/logging` diff --git a/components/trace-client/tests/client.rs b/components/trace-client/tests/client.rs new file mode 100644 index 0000000..3445a7f --- /dev/null +++ b/components/trace-client/tests/client.rs @@ -0,0 +1,116 @@ +//! Tests for the `wasi:http/client` operations traced by `trace-client`. + +use test_harness::{ + ErrorCode, Harness, LogEntry, Method, ResourceAny, Scheme, SentRequest, UPSTREAM_HEADER, + UPSTREAM_STATUS, +}; + +fn traced(message: &str) -> LogEntry { + LogEntry::trace("componentized-trace", message) +} + +#[tokio::test(flavor = "multi_thread")] +async fn exports_types_and_client() -> wasmtime::Result<()> { + let trace = Harness::new("trace-client").build().await?; + assert!(trace.exports().exports_types()); + assert!(trace.exports().exports_client()); + assert!(!trace.exports().exports_handler()); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn send_traced() -> wasmtime::Result<()> { + let mut trace = Harness::new("trace-client").build().await?; + let (status, header) = trace + .run(async |accessor, trace| { + let request = trace + .new_request( + accessor, + Method::Get, + Scheme::Https, + "example.com", + "/hello", + ) + .await?; + let response: ResourceAny = trace + .wasi_http_client() + .call_send(accessor, request) + .await? + .expect("send"); + // the response is a traced resource + let types = trace.wasi_http_types(); + let status = types + .response() + .call_get_status_code(accessor, response) + .await?; + let headers = types + .response() + .call_get_headers(accessor, response) + .await?; + let header = types + .fields() + .call_get(accessor, headers, UPSTREAM_HEADER.0.to_string()) + .await?; + Ok((status, header)) + }) + .await?; + // the upstream response is passed through + assert_eq!(status, UPSTREAM_STATUS); + assert_eq!(header, vec![UPSTREAM_HEADER.1.as_bytes().to_vec()]); + assert_eq!( + trace.recorder().requests(), + vec![SentRequest { + method: "GET".to_string(), + uri: "https://example.com/hello".to_string(), + }] + ); + assert_eq!( + trace.recorder().masked_logs(), + vec![ + traced("OPERATION=wasi:http/types#fields.new"), + traced("OPERATION=wasi:http/types#request.new"), + traced("OPERATION=wasi:http/types#request.set-method SELF=&---- METHOD=get"), + traced("OPERATION=wasi:http/types#request.set-scheme SELF=&---- SCHEME=some"), + traced( + "OPERATION=wasi:http/types#request.set-authority SELF=&---- AUTHORITY=some" + ), + traced( + "OPERATION=wasi:http/types#request.set-path-with-query SELF=&---- PATH-WITH-QUERY=some" + ), + traced("OPERATION=wasi:http/client#send REQUEST=&----"), + traced("OPERATION=wasi:http/types#response.get-status-code SELF=&----"), + traced("OPERATION=wasi:http/types#response.get-headers SELF=&----"), + traced("OPERATION=wasi:http/types#fields.get SELF=&---- NAME=x-upstream"), + ] + ); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn send_error_passed_through() -> wasmtime::Result<()> { + let mut trace = Harness::new("trace-client").build().await?; + let result = trace + .run(async |accessor, trace| { + let scheme = Scheme::Other("gopher".to_string()); + let request = trace + .new_request(accessor, Method::Get, scheme, "example.com", "/") + .await?; + let result = trace + .wasi_http_client() + .call_send(accessor, request) + .await?; + Ok(result.map(|_| ())) + }) + .await?; + // the upstream error is passed through, the request never left + assert!( + matches!(result, Err(ErrorCode::HttpProtocolError)), + "unsupported scheme: {result:?}" + ); + assert_eq!(trace.recorder().requests(), vec![]); + assert_eq!( + trace.recorder().masked_logs().last(), + Some(&traced("OPERATION=wasi:http/client#send REQUEST=&----")) + ); + Ok(()) +} diff --git a/components/trace-client/tests/handles.rs b/components/trace-client/tests/handles.rs new file mode 100644 index 0000000..b6629bd --- /dev/null +++ b/components/trace-client/tests/handles.rs @@ -0,0 +1,55 @@ +//! The handle logged for a resource must be the same in every message about it, including when +//! it is passed to `wasi:http/client#send`. + +use test_harness::{ErrorCode, Harness, Method, Scheme, ready}; + +fn same_handle(values: Vec, resource: &str) -> String { + let first = values + .first() + .unwrap_or_else(|| panic!("no handle logged for the {resource}")) + .clone(); + assert!( + values.iter().all(|value| *value == first), + "the {resource} was logged with different handles: {values:?}" + ); + first +} + +#[tokio::test(flavor = "multi_thread")] +async fn send_logs_the_request_handle() -> wasmtime::Result<()> { + let mut trace = Harness::new("trace-client").build().await?; + trace + .run(async |accessor, trace| { + let request = trace + .new_request(accessor, Method::Get, Scheme::Https, "example.com", "/") + .await?; + let response = trace + .wasi_http_client() + .call_send(accessor, request) + .await? + .expect("send"); + let responses = trace.wasi_http_types().response(); + responses.call_get_status_code(accessor, response).await?; + responses.call_get_headers(accessor, response).await?; + let res = ready(accessor, Ok::<(), ErrorCode>(()))?; + responses.call_consume_body(accessor, response, res).await?; + Ok(()) + }) + .await?; + let recorder = trace.recorder(); + let request = same_handle( + recorder.handle_values("wasi:http/types#request."), + "request", + ); + assert_eq!( + recorder.handle_values("wasi:http/client#send"), + vec![request], + "send logs the handle of the request it was passed" + ); + // the response from upstream is a new resource, its handle is stable too + same_handle( + recorder.handle_values("wasi:http/types#response."), + "response", + ); + Ok(()) +} diff --git a/components/trace-componentized-client/Cargo.toml b/components/trace-componentized-client/Cargo.toml new file mode 100644 index 0000000..c5f7503 --- /dev/null +++ b/components/trace-componentized-client/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "trace-componentized-client" +version = "0.1.0" +edition = "2024" +license = "Apache-2.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen = { workspace = true } + +[dev-dependencies] +test-harness = { workspace = true } +tokio = { workspace = true } +wasmtime = { workspace = true } diff --git a/components/trace-componentized-client/README.md b/components/trace-componentized-client/README.md new file mode 100644 index 0000000..82dca1f --- /dev/null +++ b/components/trace-componentized-client/README.md @@ -0,0 +1,9 @@ +# `trace-componentized-client` + +Virtualizes the componentized:http/client interface logging all method calls at the TRACE level and 'componentized-trace' component. + +## The `trace-componentized-client` World + +- exports `componentized:http/client` +- imports `componentized:http/client` +- imports `wasi:logging/logging` diff --git a/components/trace-componentized-client/src/lib.rs b/components/trace-componentized-client/src/lib.rs new file mode 100644 index 0000000..2fec168 --- /dev/null +++ b/components/trace-componentized-client/src/lib.rs @@ -0,0 +1,142 @@ +use std::fmt::Display; + +use crate::{ + componentized::http::client::{self, ErrorCode, HttpResponse, Method, RequestOptions}, + exports::componentized::http::client::Guest, + wasi::logging::logging::{Level, log}, +}; +use wit_bindgen::StreamReader; + +#[macro_export] +macro_rules! trace { + ($dst:expr, $($arg:tt)*) => { + log(Level::Trace, "componentized-trace", &format!($dst, $($arg)*)); + }; + ($dst:expr) => { + log(Level::Trace, "componentized-trace", &format!($dst)); + }; +} + +struct TraceComponentizedClient; + +impl Guest for TraceComponentizedClient { + async fn request( + method: Method, + url: String, + headers: Vec<(String, String)>, + body: Option>, + options: Option, + ) -> Result { + trace!("OPERATION=componentized:http/client#request URL={url}"); + client::request(method, url, headers, body, options).await + } + + async fn get( + url: String, + headers: Vec<(String, String)>, + options: Option, + ) -> Result { + trace!("OPERATION=componentized:http/client#get URL={url}"); + client::get(url, headers, options).await + } + + async fn post( + url: String, + headers: Vec<(String, String)>, + body: StreamReader, + options: Option, + ) -> Result { + trace!("OPERATION=componentized:http/client#post URL={url}"); + client::post(url, headers, body, options).await + } + + async fn put( + url: String, + headers: Vec<(String, String)>, + body: StreamReader, + options: Option, + ) -> Result { + trace!("OPERATION=componentized:http/client#put URL={url}"); + client::put(url, headers, body, options).await + } + + async fn delete( + url: String, + headers: Vec<(String, String)>, + options: Option, + ) -> Result { + trace!("OPERATION=componentized:http/client#delete URL={url}"); + client::delete(url, headers, options).await + } + + async fn patch( + url: String, + headers: Vec<(String, String)>, + body: StreamReader, + options: Option, + ) -> Result { + trace!("OPERATION=componentized:http/client#patch URL={url}"); + client::patch(url, headers, body, options).await + } + + async fn head( + url: String, + headers: Vec<(String, String)>, + options: Option, + ) -> Result { + trace!("OPERATION=componentized:http/client#head URL={url}"); + client::head(url, headers, options).await + } + + async fn options( + url: String, + headers: Vec<(String, String)>, + options: Option, + ) -> Result { + trace!("OPERATION=componentized:http/client#options URL={url}"); + client::options(url, headers, options).await + } + + async fn trace( + url: String, + headers: Vec<(String, String)>, + options: Option, + ) -> Result { + trace!("OPERATION=componentized:http/client#trace URL={url}"); + client::trace(url, headers, options).await + } + + async fn query( + url: String, + headers: Vec<(String, String)>, + body: StreamReader, + options: Option, + ) -> Result { + trace!("OPERATION=componentized:http/client#query URL={url}"); + client::query(url, headers, body, options).await + } +} + +impl Display for Method { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Method::Get => f.write_str("get"), + Method::Post => f.write_str("post"), + Method::Put => f.write_str("put"), + Method::Delete => f.write_str("delete"), + Method::Patch => f.write_str("patch"), + Method::Head => f.write_str("head"), + Method::Options => f.write_str("options"), + Method::Trace => f.write_str("trace"), + Method::Query => f.write_str("query"), + } + } +} +wit_bindgen::generate!({ + path: "../wit", + world: "trace-componentized-client", + merge_structurally_equal_types: true, + generate_all, +}); + +export!(TraceComponentizedClient); diff --git a/components/trace-componentized-client/tests/client.rs b/components/trace-componentized-client/tests/client.rs new file mode 100644 index 0000000..399448e --- /dev/null +++ b/components/trace-componentized-client/tests/client.rs @@ -0,0 +1,110 @@ +//! Tests for the `componentized:http/client` operations traced by `trace-componentized-client`. + +use test_harness::{ + Harness, HttpClientMethod, LogEntry, SentRequest, UPSTREAM_HEADER, UPSTREAM_STATUS, +}; + +fn traced(message: &str) -> LogEntry { + LogEntry::trace("componentized-trace", message) +} + +#[tokio::test(flavor = "multi_thread")] +async fn exports_only_http_client() -> wasmtime::Result<()> { + let trace = Harness::new("trace-componentized-client").build().await?; + assert!(!trace.exports().exports_types()); + assert!(!trace.exports().exports_client()); + assert!(!trace.exports().exports_handler()); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn request_traced() -> wasmtime::Result<()> { + let mut trace = Harness::new("trace-componentized-client").build().await?; + let (status, headers) = trace + .run(async |accessor, trace| { + let response = trace + .componentized_http_client() + .call_request( + accessor, + HttpClientMethod::Delete, + "https://example.com/a".to_string(), + vec![], + None, + None, + ) + .await? + .expect("request"); + Ok((response.status, response.headers)) + }) + .await?; + // the upstream response is passed through + assert_eq!(status, UPSTREAM_STATUS); + assert_eq!( + headers, + vec![(UPSTREAM_HEADER.0.to_string(), UPSTREAM_HEADER.1.to_string())] + ); + assert_eq!( + trace.recorder().requests(), + vec![SentRequest { + method: "DELETE".to_string(), + uri: "https://example.com/a".to_string(), + }] + ); + assert_eq!( + trace.recorder().logs(), + vec![traced( + "OPERATION=componentized:http/client#request URL=https://example.com/a" + )] + ); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn methods_traced() -> wasmtime::Result<()> { + let mut trace = Harness::new("trace-componentized-client").build().await?; + trace + .run(async |accessor, trace| { + let client = trace.componentized_http_client(); + let url = |path: &str| format!("https://example.com/{path}"); + client + .call_get(accessor, url("get"), vec![], None) + .await? + .expect("get"); + client + .call_delete(accessor, url("delete"), vec![], None) + .await? + .expect("delete"); + client + .call_head(accessor, url("head"), vec![], None) + .await? + .expect("head"); + client + .call_options(accessor, url("options"), vec![], None) + .await? + .expect("options"); + client + .call_trace(accessor, url("trace"), vec![], None) + .await? + .expect("trace"); + Ok(()) + }) + .await?; + let methods: Vec = trace + .recorder() + .requests() + .into_iter() + .map(|request| request.method) + .collect(); + assert_eq!(methods, vec!["GET", "DELETE", "HEAD", "OPTIONS", "TRACE"]); + assert_eq!( + trace.recorder().logs(), + vec![ + traced("OPERATION=componentized:http/client#get URL=https://example.com/get"), + traced("OPERATION=componentized:http/client#delete URL=https://example.com/delete"), + traced("OPERATION=componentized:http/client#head URL=https://example.com/head"), + traced("OPERATION=componentized:http/client#options URL=https://example.com/options"), + traced("OPERATION=componentized:http/client#trace URL=https://example.com/trace"), + ] + ); + Ok(()) +} diff --git a/components/trace-handler/Cargo.toml b/components/trace-handler/Cargo.toml new file mode 100644 index 0000000..9c685ea --- /dev/null +++ b/components/trace-handler/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "trace-handler" +version = "0.1.0" +edition = "2024" +license = "Apache-2.0" + +[lib] +crate-type = ["cdylib"] +# shared with the other trace components +path = "../../crates/trace/src/lib.rs" + +[features] +default = ["handler"] +client = [] +handler = [] + +[dependencies] +wit-bindgen = { workspace = true, features = ["async-spawn"] } + +[dev-dependencies] +test-harness = { workspace = true } +tokio = { workspace = true } +wasmtime = { workspace = true } diff --git a/components/trace-handler/README.md b/components/trace-handler/README.md new file mode 100644 index 0000000..ce21052 --- /dev/null +++ b/components/trace-handler/README.md @@ -0,0 +1,13 @@ +# `trace-handler` + +Virtualizes the wasi:http/types and wasi:http/handler interfaces logging all method calls at the TRACE level and 'componentized-trace' component. + +Built from the sources shared with the other trace components in [`crates/trace`](../../crates/trace/src/lib.rs). + +## The `trace-handler` World + +- exports `wasi:http/types` +- exports `wasi:http/handler` +- imports `wasi:http/types` +- imports `wasi:http/handler` +- imports `wasi:logging/logging` diff --git a/components/trace-handler/tests/handler.rs b/components/trace-handler/tests/handler.rs new file mode 100644 index 0000000..5cf2b07 --- /dev/null +++ b/components/trace-handler/tests/handler.rs @@ -0,0 +1,116 @@ +//! Tests for the `wasi:http/handler` operations traced by `trace-handler`. + +use test_harness::{ + ErrorCode, Harness, LogEntry, Method, ResourceAny, Scheme, SentRequest, UPSTREAM_HEADER, + UPSTREAM_STATUS, +}; + +fn traced(message: &str) -> LogEntry { + LogEntry::trace("componentized-trace", message) +} + +#[tokio::test(flavor = "multi_thread")] +async fn exports_types_and_handler() -> wasmtime::Result<()> { + let trace = Harness::new("trace-handler").build().await?; + assert!(trace.exports().exports_types()); + assert!(!trace.exports().exports_client()); + assert!(trace.exports().exports_handler()); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn handle_traced() -> wasmtime::Result<()> { + let mut trace = Harness::new("trace-handler").build().await?; + let (status, header) = trace + .run(async |accessor, trace| { + let request = trace + .new_request( + accessor, + Method::Get, + Scheme::Https, + "example.com", + "/hello", + ) + .await?; + let response: ResourceAny = trace + .wasi_http_handler() + .call_handle(accessor, request) + .await? + .expect("handle"); + // the response is a traced resource + let types = trace.wasi_http_types(); + let status = types + .response() + .call_get_status_code(accessor, response) + .await?; + let headers = types + .response() + .call_get_headers(accessor, response) + .await?; + let header = types + .fields() + .call_get(accessor, headers, UPSTREAM_HEADER.0.to_string()) + .await?; + Ok((status, header)) + }) + .await?; + // the upstream response is passed through + assert_eq!(status, UPSTREAM_STATUS); + assert_eq!(header, vec![UPSTREAM_HEADER.1.as_bytes().to_vec()]); + assert_eq!( + trace.recorder().requests(), + vec![SentRequest { + method: "GET".to_string(), + uri: "https://example.com/hello".to_string(), + }] + ); + assert_eq!( + trace.recorder().masked_logs(), + vec![ + traced("OPERATION=wasi:http/types#fields.new"), + traced("OPERATION=wasi:http/types#request.new"), + traced("OPERATION=wasi:http/types#request.set-method SELF=&---- METHOD=get"), + traced("OPERATION=wasi:http/types#request.set-scheme SELF=&---- SCHEME=some"), + traced( + "OPERATION=wasi:http/types#request.set-authority SELF=&---- AUTHORITY=some" + ), + traced( + "OPERATION=wasi:http/types#request.set-path-with-query SELF=&---- PATH-WITH-QUERY=some" + ), + traced("OPERATION=wasi:http/handler#handle REQUEST=&----"), + traced("OPERATION=wasi:http/types#response.get-status-code SELF=&----"), + traced("OPERATION=wasi:http/types#response.get-headers SELF=&----"), + traced("OPERATION=wasi:http/types#fields.get SELF=&---- NAME=x-upstream"), + ] + ); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn handle_error_passed_through() -> wasmtime::Result<()> { + let mut trace = Harness::new("trace-handler").build().await?; + let result = trace + .run(async |accessor, trace| { + let scheme = Scheme::Other("gopher".to_string()); + let request = trace + .new_request(accessor, Method::Get, scheme, "example.com", "/") + .await?; + let result = trace + .wasi_http_handler() + .call_handle(accessor, request) + .await?; + Ok(result.map(|_| ())) + }) + .await?; + // the upstream error is passed through, the request never left + assert!( + matches!(result, Err(ErrorCode::HttpProtocolError)), + "unsupported scheme: {result:?}" + ); + assert_eq!(trace.recorder().requests(), vec![]); + assert_eq!( + trace.recorder().masked_logs().last(), + Some(&traced("OPERATION=wasi:http/handler#handle REQUEST=&----")) + ); + Ok(()) +} diff --git a/components/trace-handler/tests/handles.rs b/components/trace-handler/tests/handles.rs new file mode 100644 index 0000000..1c84118 --- /dev/null +++ b/components/trace-handler/tests/handles.rs @@ -0,0 +1,55 @@ +//! The handle logged for a resource must be the same in every message about it, including when +//! it is passed to `wasi:http/handler#handle`. + +use test_harness::{ErrorCode, Harness, Method, Scheme, ready}; + +fn same_handle(values: Vec, resource: &str) -> String { + let first = values + .first() + .unwrap_or_else(|| panic!("no handle logged for the {resource}")) + .clone(); + assert!( + values.iter().all(|value| *value == first), + "the {resource} was logged with different handles: {values:?}" + ); + first +} + +#[tokio::test(flavor = "multi_thread")] +async fn handle_logs_the_request_handle() -> wasmtime::Result<()> { + let mut trace = Harness::new("trace-handler").build().await?; + trace + .run(async |accessor, trace| { + let request = trace + .new_request(accessor, Method::Get, Scheme::Https, "example.com", "/") + .await?; + let response = trace + .wasi_http_handler() + .call_handle(accessor, request) + .await? + .expect("handle"); + let responses = trace.wasi_http_types().response(); + responses.call_get_status_code(accessor, response).await?; + responses.call_get_headers(accessor, response).await?; + let res = ready(accessor, Ok::<(), ErrorCode>(()))?; + responses.call_consume_body(accessor, response, res).await?; + Ok(()) + }) + .await?; + let recorder = trace.recorder(); + let request = same_handle( + recorder.handle_values("wasi:http/types#request."), + "request", + ); + assert_eq!( + recorder.handle_values("wasi:http/handler#handle"), + vec![request], + "handle logs the handle of the request it was passed" + ); + // the response from upstream is a new resource, its handle is stable too + same_handle( + recorder.handle_values("wasi:http/types#response."), + "response", + ); + Ok(()) +} diff --git a/components/trace-types/Cargo.toml b/components/trace-types/Cargo.toml new file mode 100644 index 0000000..4cdbbd8 --- /dev/null +++ b/components/trace-types/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "trace-types" +version = "0.1.0" +edition = "2024" +license = "Apache-2.0" + +[lib] +crate-type = ["cdylib"] +# shared with the other trace components +path = "../../crates/trace/src/lib.rs" + +[features] +default = [] +client = [] +handler = [] + +[dependencies] +wit-bindgen = { workspace = true, features = ["async-spawn"] } + +[dev-dependencies] +test-harness = { workspace = true } +tokio = { workspace = true } +wasmtime = { workspace = true } diff --git a/components/trace-types/README.md b/components/trace-types/README.md new file mode 100644 index 0000000..3f00f94 --- /dev/null +++ b/components/trace-types/README.md @@ -0,0 +1,11 @@ +# `trace-types` + +Virtualizes the wasi:http/types interface logging all method calls at the TRACE level and 'componentized-trace' component. + +Built from the sources shared with the other trace components in [`crates/trace`](../../crates/trace/src/lib.rs). + +## The `trace-types` World + +- exports `wasi:http/types` +- imports `wasi:http/types` +- imports `wasi:logging/logging` diff --git a/components/trace-types/tests/handles.rs b/components/trace-types/tests/handles.rs new file mode 100644 index 0000000..3c3dbc9 --- /dev/null +++ b/components/trace-types/tests/handles.rs @@ -0,0 +1,214 @@ +//! The handle logged for a resource identifies it while debugging. The value depends on the +//! runtime, but must be the same in every message about the same resource, whatever name it is +//! logged under, e.g. `SELF` or `THIS`. + +use test_harness::{ErrorCode, Harness, Method, Scheme, ready}; + +/// The single value logged for a resource, panics if it was not logged or logged with different +/// values. +fn same_handle(values: Vec, resource: &str) -> String { + let first = values + .first() + .unwrap_or_else(|| panic!("no handle logged for the {resource}")) + .clone(); + assert!( + values.iter().all(|value| *value == first), + "the {resource} was logged with different handles: {values:?}" + ); + first +} + +#[tokio::test(flavor = "multi_thread")] +async fn fields_handle_is_stable() -> wasmtime::Result<()> { + let mut trace = Harness::new("trace-types").build().await?; + trace + .run(async |accessor, trace| { + let fields = trace.wasi_http_types().fields(); + let a = fields.call_constructor(accessor).await?; + let b = fields.call_constructor(accessor).await?; + fields.call_has(accessor, a, "x".to_string()).await?; + fields.call_has(accessor, b, "x".to_string()).await?; + fields.call_copy_all(accessor, a).await?; + fields.call_get(accessor, a, "x".to_string()).await?; + fields.call_copy_all(accessor, b).await?; + Ok(()) + }) + .await?; + let values = trace.recorder().handle_values("wasi:http/types#fields."); + let a = same_handle( + vec![values[0].clone(), values[2].clone(), values[3].clone()], + "fields", + ); + let b = same_handle(vec![values[1].clone(), values[4].clone()], "fields"); + assert_ne!(a, b, "each fields has its own handle"); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn request_handle_is_stable() -> wasmtime::Result<()> { + let mut trace = Harness::new("trace-types").build().await?; + trace + .run(async |accessor, trace| { + let request = trace + .new_request(accessor, Method::Get, Scheme::Https, "example.com", "/") + .await?; + let requests = trace.wasi_http_types().request(); + requests.call_get_method(accessor, request).await?; + requests.call_get_headers(accessor, request).await?; + requests.call_get_options(accessor, request).await?; + let res = ready(accessor, Ok::<(), ErrorCode>(()))?; + requests.call_consume_body(accessor, request, res).await?; + Ok(()) + }) + .await?; + let handles = trace.recorder().handles(); + // logged as SELF by the methods and THIS by consume-body + assert!(handles.iter().any(|handle| handle.key == "SELF")); + assert!(handles.iter().any(|handle| handle.key == "THIS")); + same_handle( + trace.recorder().handle_values("wasi:http/types#request."), + "request", + ); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn request_options_handle_is_stable() -> wasmtime::Result<()> { + let mut trace = Harness::new("trace-types").build().await?; + trace + .run(async |accessor, trace| { + let options = trace.wasi_http_types().request_options(); + let opts = options.call_constructor(accessor).await?; + options + .call_set_connect_timeout(accessor, opts, Some(1)) + .await? + .expect("set-connect-timeout"); + options.call_get_connect_timeout(accessor, opts).await?; + let clone = options.call_clone(accessor, opts).await?; + options.call_get_first_byte_timeout(accessor, clone).await?; + options + .call_get_between_bytes_timeout(accessor, clone) + .await?; + options + .call_get_between_bytes_timeout(accessor, opts) + .await?; + Ok(()) + }) + .await?; + let values = trace + .recorder() + .handle_values("wasi:http/types#request-options."); + // set, get, clone and the last get are on the original, the others on the clone + let original = same_handle( + vec![ + values[0].clone(), + values[1].clone(), + values[2].clone(), + values[5].clone(), + ], + "request-options", + ); + let clone = same_handle( + vec![values[3].clone(), values[4].clone()], + "request-options clone", + ); + assert_ne!(original, clone, "the clone has its own handle"); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn response_handle_is_stable() -> wasmtime::Result<()> { + let mut trace = Harness::new("trace-types").build().await?; + trace + .run(async |accessor, trace| { + let types = trace.wasi_http_types(); + let headers = types.fields().call_constructor(accessor).await?; + let trailers = ready(accessor, Ok(None))?; + let responses = types.response(); + let (response, _transmitted) = responses + .call_new(accessor, headers, None, trailers) + .await?; + responses.call_get_status_code(accessor, response).await?; + responses + .call_set_status_code(accessor, response, 201) + .await? + .expect("set-status-code"); + responses.call_get_headers(accessor, response).await?; + let res = ready(accessor, Ok::<(), ErrorCode>(()))?; + responses.call_consume_body(accessor, response, res).await?; + Ok(()) + }) + .await?; + let handles = trace.recorder().handles(); + // logged as SELF by the methods and THIS by consume-body + assert!(handles.iter().any(|handle| handle.key == "SELF")); + assert!(handles.iter().any(|handle| handle.key == "THIS")); + same_handle( + trace.recorder().handle_values("wasi:http/types#response."), + "response", + ); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn handles_are_hex() -> wasmtime::Result<()> { + let mut trace = Harness::new("trace-types").build().await?; + trace + .run(async |accessor, trace| { + let fields = trace.wasi_http_types().fields(); + // enough resources for handles past 9 + for _ in 0..32 { + let f = fields.call_constructor(accessor).await?; + fields.call_has(accessor, f, "x".to_string()).await?; + } + Ok(()) + }) + .await?; + let values = trace.recorder().handle_values("wasi:http/types#fields."); + assert_eq!(values.len(), 32); + for value in &values { + let digits = value + .strip_prefix('&') + .unwrap_or_else(|| panic!("prefixed with &: {value}")); + assert_eq!(digits.len(), 4, "{value}"); + assert!( + digits + .bytes() + .all(|b| b.is_ascii_hexdigit() && !b.is_ascii_uppercase()), + "lowercase hex: {value}" + ); + } + assert!( + values + .iter() + .any(|value| value.bytes().any(|b| (b'a'..=b'f').contains(&b))), + "handles past 9 use hex digits: {values:?}" + ); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn values_are_not_mistaken_for_handles() -> wasmtime::Result<()> { + let mut trace = Harness::new("trace-types").build().await?; + trace + .run(async |accessor, trace| { + let fields = trace.wasi_http_types().fields(); + let headers = fields.call_constructor(accessor).await?; + // a name and value with the shape of a handle's digits + fields + .call_append(accessor, headers, "beef".to_string(), b"cafe".to_vec()) + .await? + .expect("append"); + Ok(()) + }) + .await?; + let handles = trace.recorder().handles(); + assert_eq!(handles.len(), 1, "{handles:?}"); + assert_eq!(handles[0].key, "SELF"); + let logs = trace.recorder().masked_logs(); + assert_eq!( + logs.last().map(|log| log.message.as_str()), + Some("OPERATION=wasi:http/types#fields.append SELF=&---- NAME=beef VALUE=cafe") + ); + Ok(()) +} diff --git a/components/trace-types/tests/types.rs b/components/trace-types/tests/types.rs new file mode 100644 index 0000000..44a07dd --- /dev/null +++ b/components/trace-types/tests/types.rs @@ -0,0 +1,339 @@ +//! Tests for the `wasi:http/types` operations traced by every trace component, run against +//! `trace-types`. + +use test_harness::{ErrorCode, Harness, LogEntry, Method, Scheme, ready}; + +fn traced(message: &str) -> LogEntry { + LogEntry::trace("componentized-trace", message) +} + +#[tokio::test(flavor = "multi_thread")] +async fn exports_only_types() -> wasmtime::Result<()> { + let trace = Harness::new("trace-types").build().await?; + assert!(trace.exports().exports_types()); + assert!(!trace.exports().exports_client()); + assert!(!trace.exports().exports_handler()); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn fields_traced() -> wasmtime::Result<()> { + let mut trace = Harness::new("trace-types").build().await?; + let results = trace + .run(async |accessor, trace| { + let fields = trace.wasi_http_types().fields(); + let headers = fields + .call_from_list( + accessor, + vec![("accept".to_string(), b"text/plain".to_vec())], + ) + .await? + .expect("from-list"); + fields + .call_append(accessor, headers, "x-a".to_string(), b"1".to_vec()) + .await? + .expect("append"); + fields + .call_set( + accessor, + headers, + "x-b".to_string(), + vec![b"2".to_vec(), b"3".to_vec()], + ) + .await? + .expect("set"); + let get = fields + .call_get(accessor, headers, "x-b".to_string()) + .await?; + let has = fields + .call_has(accessor, headers, "x-a".to_string()) + .await?; + fields + .call_delete(accessor, headers, "x-a".to_string()) + .await? + .expect("delete"); + let deleted = fields + .call_get_and_delete(accessor, headers, "x-b".to_string()) + .await? + .expect("get-and-delete"); + let copy = fields.call_copy_all(accessor, headers).await?; + let clone = fields.call_clone(accessor, headers).await?; + let cloned = fields.call_copy_all(accessor, clone).await?; + let empty = fields.call_constructor(accessor).await?; + let empty = fields.call_copy_all(accessor, empty).await?; + Ok((get, has, deleted, copy, cloned, empty)) + }) + .await?; + let (get, has, deleted, copy, cloned, empty) = results; + // the upstream values are passed through + assert_eq!(get, vec![b"2".to_vec(), b"3".to_vec()]); + assert!(has); + assert_eq!(deleted, vec![b"2".to_vec(), b"3".to_vec()]); + assert_eq!(copy, vec![("accept".to_string(), b"text/plain".to_vec())]); + assert_eq!(cloned, copy); + assert_eq!(empty, vec![]); + assert_eq!( + trace.recorder().masked_logs(), + vec![ + traced("OPERATION=wasi:http/types#fields.from-list ENTRIES-LEN=1"), + traced("OPERATION=wasi:http/types#fields.append SELF=&---- NAME=x-a VALUE=1"), + traced("OPERATION=wasi:http/types#fields.set SELF=&---- NAME=x-b VALUE-LEN=2"), + traced("OPERATION=wasi:http/types#fields.get SELF=&---- NAME=x-b"), + traced("OPERATION=wasi:http/types#fields.has SELF=&---- NAME=x-a"), + traced("OPERATION=wasi:http/types#fields.delete SELF=&---- NAME=x-a"), + traced("OPERATION=wasi:http/types#fields.get-and-delete SELF=&---- NAME=x-b"), + traced("OPERATION=wasi:http/types#fields.copy-all SELF=&----"), + traced("OPERATION=wasi:http/types#fields.clone SELF=&----"), + traced("OPERATION=wasi:http/types#fields.copy-all SELF=&----"), + traced("OPERATION=wasi:http/types#fields.new"), + traced("OPERATION=wasi:http/types#fields.copy-all SELF=&----"), + ] + ); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn fields_error_passed_through() -> wasmtime::Result<()> { + let mut trace = Harness::new("trace-types").build().await?; + let result = trace + .run(async |accessor, trace| { + let fields = trace.wasi_http_types().fields(); + let headers = fields.call_constructor(accessor).await?; + Ok(fields + .call_append(accessor, headers, "bad name".to_string(), b"1".to_vec()) + .await?) + }) + .await?; + assert!(result.is_err(), "invalid header name: {result:?}"); + assert_eq!( + trace.recorder().masked_logs(), + vec![ + traced("OPERATION=wasi:http/types#fields.new"), + traced("OPERATION=wasi:http/types#fields.append SELF=&---- NAME=bad name VALUE=1"), + ] + ); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn request_traced() -> wasmtime::Result<()> { + let mut trace = Harness::new("trace-types").build().await?; + let results = trace + .run(async |accessor, trace| { + let request = trace + .new_request( + accessor, + Method::Post, + Scheme::Http, + "example.com", + "/a?b=c", + ) + .await?; + let types = trace.wasi_http_types(); + let requests = types.request(); + let method = requests.call_get_method(accessor, request).await?; + let scheme = requests.call_get_scheme(accessor, request).await?; + let authority = requests.call_get_authority(accessor, request).await?; + let path_with_query = requests.call_get_path_with_query(accessor, request).await?; + let options = requests.call_get_options(accessor, request).await?; + let headers = requests.call_get_headers(accessor, request).await?; + let headers = types.fields().call_copy_all(accessor, headers).await?; + let res = ready(accessor, Ok::<(), ErrorCode>(()))?; + let (_contents, _trailers) = requests.call_consume_body(accessor, request, res).await?; + Ok(( + method, + scheme, + authority, + path_with_query, + options.is_some(), + headers, + )) + }) + .await?; + let (method, scheme, authority, path_with_query, has_options, headers) = results; + // the upstream values are passed through + assert!(matches!(method, Method::Post), "{method:?}"); + assert!(matches!(scheme, Some(Scheme::Http)), "{scheme:?}"); + assert_eq!(authority.as_deref(), Some("example.com")); + assert_eq!(path_with_query.as_deref(), Some("/a?b=c")); + assert!(!has_options); + assert_eq!(headers, vec![]); + assert_eq!( + trace.recorder().masked_logs(), + vec![ + traced("OPERATION=wasi:http/types#fields.new"), + traced("OPERATION=wasi:http/types#request.new"), + traced("OPERATION=wasi:http/types#request.set-method SELF=&---- METHOD=post"), + traced("OPERATION=wasi:http/types#request.set-scheme SELF=&---- SCHEME=some"), + traced( + "OPERATION=wasi:http/types#request.set-authority SELF=&---- AUTHORITY=some" + ), + traced( + "OPERATION=wasi:http/types#request.set-path-with-query SELF=&---- PATH-WITH-QUERY=some" + ), + traced("OPERATION=wasi:http/types#request.get-method SELF=&----"), + traced("OPERATION=wasi:http/types#request.get-scheme SELF=&----"), + traced("OPERATION=wasi:http/types#request.get-authority SELF=&----"), + traced("OPERATION=wasi:http/types#request.get-path-with-query SELF=&----"), + traced("OPERATION=wasi:http/types#request.get-options SELF=&----"), + traced("OPERATION=wasi:http/types#request.get-headers SELF=&----"), + traced("OPERATION=wasi:http/types#fields.copy-all SELF=&----"), + traced("OPERATION=wasi:http/types#request.consume-body THIS=&----"), + ] + ); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn request_options_traced() -> wasmtime::Result<()> { + let mut trace = Harness::new("trace-types").build().await?; + let results = trace + .run(async |accessor, trace| { + let options = trace.wasi_http_types().request_options(); + let opts = options.call_constructor(accessor).await?; + options + .call_set_connect_timeout(accessor, opts, Some(1_000)) + .await? + .expect("set-connect-timeout"); + options + .call_set_first_byte_timeout(accessor, opts, Some(2_000)) + .await? + .expect("set-first-byte-timeout"); + options + .call_set_between_bytes_timeout(accessor, opts, None) + .await? + .expect("set-between-bytes-timeout"); + let clone = options.call_clone(accessor, opts).await?; + let connect = options.call_get_connect_timeout(accessor, clone).await?; + let first_byte = options.call_get_first_byte_timeout(accessor, clone).await?; + let between_bytes = options + .call_get_between_bytes_timeout(accessor, clone) + .await?; + Ok((connect, first_byte, between_bytes)) + }) + .await?; + // the upstream values are passed through, including to the clone + assert_eq!(results, (Some(1_000), Some(2_000), None)); + assert_eq!( + trace.recorder().masked_logs(), + vec![ + traced("OPERATION=wasi:http/types#request-options.new"), + traced( + "OPERATION=wasi:http/types#request-options.set-connect-timeout SELF=&---- DURATION=some<1000>" + ), + traced( + "OPERATION=wasi:http/types#request-options.set-first-byte-timeout SELF=&---- DURATION=some<2000>" + ), + traced( + "OPERATION=wasi:http/types#request-options.set-between-bytes-timeout SELF=&---- DURATION=none" + ), + traced("OPERATION=wasi:http/types#request-options.clone SELF=&----"), + traced("OPERATION=wasi:http/types#request-options.get-connect-timeout SELF=&----"), + traced("OPERATION=wasi:http/types#request-options.get-first-byte-timeout SELF=&----"), + traced( + "OPERATION=wasi:http/types#request-options.get-between-bytes-timeout SELF=&----" + ), + ] + ); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn request_with_options_traced() -> wasmtime::Result<()> { + let mut trace = Harness::new("trace-types").build().await?; + let connect = trace + .run(async |accessor, trace| { + let types = trace.wasi_http_types(); + let options = types.request_options(); + let opts = options.call_constructor(accessor).await?; + options + .call_set_connect_timeout(accessor, opts, Some(1_000)) + .await? + .expect("set-connect-timeout"); + let headers = types.fields().call_constructor(accessor).await?; + let trailers = ready(accessor, Ok(None))?; + let (request, _transmitted) = types + .request() + .call_new(accessor, headers, None, trailers, Some(opts)) + .await?; + let opts = types + .request() + .call_get_options(accessor, request) + .await? + .expect("options"); + Ok(options.call_get_connect_timeout(accessor, opts).await?) + }) + .await?; + // the options are unwrapped for the upstream request, and wrapped again when read back + assert_eq!(connect, Some(1_000)); + assert_eq!( + trace.recorder().masked_logs(), + vec![ + traced("OPERATION=wasi:http/types#request-options.new"), + traced( + "OPERATION=wasi:http/types#request-options.set-connect-timeout SELF=&---- DURATION=some<1000>" + ), + traced("OPERATION=wasi:http/types#fields.new"), + traced("OPERATION=wasi:http/types#request.new"), + traced("OPERATION=wasi:http/types#request.get-options SELF=&----"), + traced("OPERATION=wasi:http/types#request-options.get-connect-timeout SELF=&----"), + ] + ); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn response_traced() -> wasmtime::Result<()> { + let mut trace = Harness::new("trace-types").build().await?; + let results = trace + .run(async |accessor, trace| { + let types = trace.wasi_http_types(); + let headers = types + .fields() + .call_from_list(accessor, vec![("x-a".to_string(), b"1".to_vec())]) + .await? + .expect("from-list"); + let trailers = ready(accessor, Ok(None))?; + let responses = types.response(); + let (response, _transmitted) = responses + .call_new(accessor, headers, None, trailers) + .await?; + let default_status = responses.call_get_status_code(accessor, response).await?; + responses + .call_set_status_code(accessor, response, 404) + .await? + .expect("set-status-code"); + let status = responses.call_get_status_code(accessor, response).await?; + let invalid = responses + .call_set_status_code(accessor, response, 42) + .await?; + let headers = responses.call_get_headers(accessor, response).await?; + let headers = types.fields().call_copy_all(accessor, headers).await?; + let res = ready(accessor, Ok::<(), ErrorCode>(()))?; + let (_contents, _trailers) = + responses.call_consume_body(accessor, response, res).await?; + Ok((default_status, status, invalid, headers)) + }) + .await?; + let (default_status, status, invalid, headers) = results; + // the upstream values and errors are passed through + assert_eq!(default_status, 200); + assert_eq!(status, 404); + assert_eq!(invalid, Err(())); + assert_eq!(headers, vec![("x-a".to_string(), b"1".to_vec())]); + assert_eq!( + trace.recorder().masked_logs(), + vec![ + traced("OPERATION=wasi:http/types#fields.from-list ENTRIES-LEN=1"), + traced("OPERATION=wasi:http/types#response.new"), + traced("OPERATION=wasi:http/types#response.get-status-code SELF=&----"), + traced("OPERATION=wasi:http/types#response.set-status-code SELF=&---- STATUS-CODE=404"), + traced("OPERATION=wasi:http/types#response.get-status-code SELF=&----"), + traced("OPERATION=wasi:http/types#response.set-status-code SELF=&---- STATUS-CODE=42"), + traced("OPERATION=wasi:http/types#response.get-headers SELF=&----"), + traced("OPERATION=wasi:http/types#fields.copy-all SELF=&----"), + traced("OPERATION=wasi:http/types#response.consume-body THIS=&----"), + ] + ); + Ok(()) +} diff --git a/components/trace/Cargo.toml b/components/trace/Cargo.toml new file mode 100644 index 0000000..02e5734 --- /dev/null +++ b/components/trace/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "trace" +version = "0.1.0" +edition = "2024" +license = "Apache-2.0" + +[lib] +crate-type = ["cdylib"] +# shared with the other trace components +path = "../../crates/trace/src/lib.rs" + +[features] +default = ["client", "handler"] +client = [] +handler = [] + +[dependencies] +wit-bindgen = { workspace = true, features = ["async-spawn"] } + +[dev-dependencies] +test-harness = { workspace = true } +tokio = { workspace = true } +wasmtime = { workspace = true } diff --git a/components/trace/README.md b/components/trace/README.md new file mode 100644 index 0000000..849da90 --- /dev/null +++ b/components/trace/README.md @@ -0,0 +1,15 @@ +# `trace` + +Virtualizes the wasi:http/types, wasi:http/client and wasi:http/handler interfaces logging all method calls at the TRACE level and 'componentized-trace' component. + +Built from the sources shared with the other trace components in [`crates/trace`](../../crates/trace/src/lib.rs). + +## The `trace` World + +- exports `wasi:http/types` +- exports `wasi:http/client` +- exports `wasi:http/handler` +- imports `wasi:http/types` +- imports `wasi:http/client` +- imports `wasi:http/handler` +- imports `wasi:logging/logging` diff --git a/components/trace/tests/trace.rs b/components/trace/tests/trace.rs new file mode 100644 index 0000000..b417c90 --- /dev/null +++ b/components/trace/tests/trace.rs @@ -0,0 +1,94 @@ +//! Tests for `trace`, which traces `wasi:http/types`, `wasi:http/client` and `wasi:http/handler`. +//! Tests for the individual operations live with `trace-types`, `trace-client` and +//! `trace-handler`, which are built from the same sources. + +use test_harness::{Harness, LogEntry, Method, Scheme, SentRequest, UPSTREAM_STATUS}; + +fn traced(message: &str) -> LogEntry { + LogEntry::trace("componentized-trace", message) +} + +#[tokio::test(flavor = "multi_thread")] +async fn exports_types_client_and_handler() -> wasmtime::Result<()> { + let trace = Harness::new("trace").build().await?; + assert!(trace.exports().exports_types()); + assert!(trace.exports().exports_client()); + assert!(trace.exports().exports_handler()); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn send_and_handle_traced() -> wasmtime::Result<()> { + let mut trace = Harness::new("trace").build().await?; + let statuses = trace + .run(async |accessor, trace| { + let types = trace.wasi_http_types(); + + let request = trace + .new_request( + accessor, + Method::Get, + Scheme::Https, + "example.com", + "/client", + ) + .await?; + let response = trace + .wasi_http_client() + .call_send(accessor, request) + .await? + .expect("send"); + let sent = types + .response() + .call_get_status_code(accessor, response) + .await?; + + let request = trace + .new_request( + accessor, + Method::Put, + Scheme::Https, + "example.com", + "/handler", + ) + .await?; + let response = trace + .wasi_http_handler() + .call_handle(accessor, request) + .await? + .expect("handle"); + let handled = types + .response() + .call_get_status_code(accessor, response) + .await?; + + Ok((sent, handled)) + }) + .await?; + assert_eq!(statuses, (UPSTREAM_STATUS, UPSTREAM_STATUS)); + assert_eq!( + trace.recorder().requests(), + vec![ + SentRequest { + method: "GET".to_string(), + uri: "https://example.com/client".to_string(), + }, + SentRequest { + method: "PUT".to_string(), + uri: "https://example.com/handler".to_string(), + }, + ] + ); + let logs = trace.recorder().masked_logs(); + // types, client and handler are all traced + assert!(logs.contains(&traced("OPERATION=wasi:http/types#request.new"))); + assert!(logs.contains(&traced("OPERATION=wasi:http/client#send REQUEST=&----"))); + assert!(logs.contains(&traced("OPERATION=wasi:http/handler#handle REQUEST=&----"))); + assert_eq!( + logs.iter() + .filter(|log| log.message.contains("response.get-status-code")) + .count(), + 2 + ); + Ok(()) +} diff --git a/components/wit/deps/wasi-logging-0.1.0-draft/package.wit b/components/wit/deps/wasi-logging-0.1.0-draft/package.wit new file mode 100644 index 0000000..164cb5b --- /dev/null +++ b/components/wit/deps/wasi-logging-0.1.0-draft/package.wit @@ -0,0 +1,36 @@ +package wasi:logging@0.1.0-draft; + +/// WASI Logging is a logging API intended to let users emit log messages with +/// simple priority levels and context values. +interface logging { + /// A log level, describing a kind of message. + enum level { + /// Describes messages about the values of variables and the flow of + /// control within a program. + trace, + /// Describes messages likely to be of interest to someone debugging a + /// program. + debug, + /// Describes messages likely to be of interest to someone monitoring a + /// program. + info, + /// Describes messages indicating hazardous situations. + warn, + /// Describes messages indicating serious errors. + error, + /// Describes messages indicating fatal errors. + critical, + } + + /// Emit a log message. + /// + /// A log message has a `level` describing what kind of message is being + /// sent, a context, which is an uninterpreted string meant to help + /// consumers group similar messages, and a string containing the message + /// text. + log: func(level: level, context: string, message: string); +} + +world imports { + import logging; +} diff --git a/components/wit/worlds.wit b/components/wit/worlds.wit index 037bbc6..dc14a55 100644 --- a/components/wit/worlds.wit +++ b/components/wit/worlds.wit @@ -4,3 +4,32 @@ world client { import wasi:http/client@0.3.0; export componentized:http/client@0.1.0-dev; } + +world trace-componentized-client { + import componentized:http/client@0.1.0-dev; + import wasi:logging/logging@0.1.0-draft; + export componentized:http/client@0.1.0-dev; +} + +world trace-types { + import wasi:http/types@0.3.0; + import wasi:logging/logging@0.1.0-draft; + export wasi:http/types@0.3.0; +} + +world trace-handler { + include trace-types; + import wasi:http/handler@0.3.0; + export wasi:http/handler@0.3.0; +} + +world trace-client { + include trace-types; + import wasi:http/client@0.3.0; + export wasi:http/client@0.3.0; +} + +world trace { + include trace-client; + include trace-handler; +} diff --git a/components/wkg.lock b/components/wkg.lock index 3912411..dccfcbf 100644 --- a/components/wkg.lock +++ b/components/wkg.lock @@ -24,3 +24,12 @@ digest = "sha256:92cd8f3730c00226dc15626a2e7b21834dd187fc221f09818720d228585bbbf requirement = "=0.3.1" version = "0.3.1" digest = "sha256:fac6e40c9b8101cb0a32c76ae63271ffb7c9a66007214d57f3ae8048844d2b5f" + +[[packages]] +name = "wasi:logging" +registry = "wasi.dev" + +[[packages.versions]] +requirement = "=0.1.0-draft" +version = "0.1.0-draft" +digest = "sha256:09621a45b12b0a9cddc798517f778aac0e5ae4bd234077b3d70758d6cf625580" diff --git a/crates/test-harness/Cargo.toml b/crates/test-harness/Cargo.toml new file mode 100644 index 0000000..507f60c --- /dev/null +++ b/crates/test-harness/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "test-harness" +version = "0.1.0" +edition = "2024" +license = "Apache-2.0" +publish = false + +[dependencies] +bytes = { workspace = true } +http = { workspace = true } +http-body-util = { workspace = true } +tokio = { workspace = true } +wasmtime = { workspace = true } +wasmtime-wasi = { workspace = true } +wasmtime-wasi-http = { workspace = true } diff --git a/crates/test-harness/src/lib.rs b/crates/test-harness/src/lib.rs new file mode 100644 index 0000000..49c79d5 --- /dev/null +++ b/crates/test-harness/src/lib.rs @@ -0,0 +1,800 @@ +//! Test harness for the trace components. +//! +//! A [`Harness`] instantiates a component (`target/components/*/*.wasm`) with `wasi:http` from +//! wasmtime-wasi-http and captured `wasi:logging` output. Requests the component sends upstream, +//! with either `wasi:http/client` or `wasi:http/handler`, never reach the network, they are +//! recorded and answered with a canned response. + +use std::collections::HashSet; +use std::path::{Path, PathBuf}; +use std::pin::Pin; +use std::process::Command; +use std::sync::{Arc, Mutex}; +use std::task::Poll; + +use bytes::Bytes; +use http_body_util::{BodyExt, Empty}; +use tokio::sync::{mpsc, oneshot}; +use wasmtime::component::{ + Accessor, Component, FutureConsumer, FutureReader, HasData, HasSelf, Lift, Linker, Lower, + Resource, ResourceTable, Source, StreamConsumer, StreamReader, StreamResult, +}; +use wasmtime::error::Context as _; +use wasmtime::{Config, Engine, Result, Store, StoreContextMut, bail, format_err}; +use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView}; +use wasmtime_wasi_http::p3::bindings::http::client::HostWithStore as _; +use wasmtime_wasi_http::{ + RequestOptions, WasiBody, WasiHttp, WasiHttpCtx, WasiHttpCtxView, WasiHttpHooks, WasiHttpView, +}; + +use crate::bindings::componentized::http::client as upstream_client; +use crate::bindings::exports::componentized::http::client as http_client; +use crate::bindings::exports::wasi::http::{client, handler, types}; +use crate::bindings::wasi::logging::logging; + +pub mod bindings { + wasmtime::component::bindgen!({ + path: "../../components/wit", + inline: " + package componentized:test-harness; + + world harness { + import wasi:logging/logging@0.1.0-draft; + import componentized:http/client@0.1.0-dev; + export componentized:http/client@0.1.0-dev; + export wasi:http/types@0.3.0; + export wasi:http/client@0.3.0; + export wasi:http/handler@0.3.0; + } + ", + world: "componentized:test-harness/harness", + imports: { + "componentized:http/client": async | store, + }, + exports: { default: async | store }, + with: { + "wasi:clocks": wasmtime_wasi::p3::bindings::clocks, + }, + }); +} + +pub use bindings::exports::componentized::http::client::{ + ErrorCode as HttpClientErrorCode, Method as HttpClientMethod, +}; +pub use bindings::exports::wasi::http::types::{ErrorCode, Method, Scheme}; +pub use bindings::wasi::logging::logging::Level; +pub use wasmtime::component::ResourceAny; + +/// The status of the canned response to requests sent upstream. +pub const UPSTREAM_STATUS: u16 = 200; + +/// A header of the canned response to requests sent upstream, `(name, value)`. +pub const UPSTREAM_HEADER: (&str, &str) = ("x-upstream", "canned"); + +/// The number of trailing hex digits of a resource handle the trace components log. +const HANDLE_DIGITS: usize = 4; + +/// The prefix marking a logged value as a resource handle. +const HANDLE_PREFIX: char = '&'; + +/// The trailing hex digits of a logged resource handle, e.g. `00a3` for `&00a3`, `None` if the +/// value is not a handle. +fn handle_digits(value: &str) -> Option<&str> { + let digits = value.strip_prefix(HANDLE_PREFIX)?; + let is_hex = digits + .bytes() + .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b)); + (digits.len() == HANDLE_DIGITS && is_hex).then_some(digits) +} + +/// Replace the resource handles in a trace message, e.g. `SELF=&00a3` becomes `SELF=&----`. +/// +/// Handles are assigned by the runtime, masking them keeps assertions about what was logged +/// independent of the order resources were created and dropped. +pub fn mask_handles(message: &str) -> String { + message + .split(' ') + .map(|part| match part.split_once('=') { + Some((key, value)) if handle_digits(value).is_some() => { + format!("{key}={HANDLE_PREFIX}{}", "-".repeat(HANDLE_DIGITS)) + } + _ => part.to_string(), + }) + .collect::>() + .join(" ") +} + +/// A future for the guest that resolves to the value. +pub fn ready( + accessor: &Accessor, + value: T, +) -> Result> { + accessor.with(|store| FutureReader::new(store, async move { Ok::<_, wasmtime::Error>(value) })) +} + +/// Read every item written to a guest stream, until it closes. +pub async fn collect( + accessor: &Accessor, + stream: StreamReader, +) -> Result> { + let (tx, mut rx) = mpsc::unbounded_channel(); + accessor.with(|store| stream.pipe(store, ChannelConsumer(tx)))?; + let mut items = vec![]; + while let Some(item) = rx.recv().await { + items.push(item); + } + Ok(items) +} + +/// Wait for the value of a guest future. +/// +/// Only for futures the guest writes itself. A future the guest passes through from a +/// wasmtime-wasi-http host import is transferred host to host, which requires the same Rust type on +/// both ends, and the harness' exported types are generated separately from wasmtime-wasi-http's. +pub async fn resolve( + accessor: &Accessor, + future: FutureReader, +) -> Result { + let (tx, rx) = oneshot::channel(); + accessor.with(|store| future.pipe(store, OneshotConsumer(Some(tx))))?; + rx.await + .map_err(|_| format_err!("future closed without a value")) +} + +struct OneshotConsumer(Option>); + +impl FutureConsumer for OneshotConsumer { + type Item = T; + + fn poll_consume( + self: Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + store: StoreContextMut, + mut source: Source<'_, T>, + _finish: bool, + ) -> Poll> { + let mut item = None; + source.read(store, &mut item)?; + if let (Some(item), Some(tx)) = (item, self.get_mut().0.take()) { + // the receiver is only dropped when the test stopped waiting + let _ = tx.send(item); + } + Poll::Ready(Ok(())) + } +} + +struct ChannelConsumer(mpsc::UnboundedSender); + +impl StreamConsumer for ChannelConsumer { + type Item = T; + + fn poll_consume( + self: Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + store: StoreContextMut, + mut source: Source<'_, T>, + _finish: bool, + ) -> Poll> { + let mut item = None; + source.read(store, &mut item)?; + if let Some(item) = item { + if self.0.send(item).is_err() { + return Poll::Ready(Ok(StreamResult::Dropped)); + } + } + Poll::Ready(Ok(StreamResult::Completed)) + } +} + +/// Root of the workspace, where the Makefile lives. +fn workspace_dir() -> PathBuf { + let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); + dir.canonicalize().unwrap_or(dir) +} + +/// Path containing the built component. +pub fn component_path(name: &str) -> PathBuf { + workspace_dir().join(format!("target/components/{name}/{name}.wasm")) +} + +/// Rebuild the named component in `target/components/` with make, unless it was already built +/// by this process. +fn ensure_built(name: &str) -> Result<()> { + static BUILT: Mutex>> = Mutex::new(None); + + // hold the lock while building so concurrent tests don't run make over each other + let mut built = BUILT.lock().unwrap_or_else(|err| err.into_inner()); + let built = built.get_or_insert_with(HashSet::new); + if built.contains(name) { + return Ok(()); + } + + // the Makefile's targets are relative to the workspace, make matches them textually + let target = format!("target/components/{name}/{name}.wasm"); + let output = Command::new("make") + .arg("-C") + .arg(workspace_dir()) + .arg(&target) + .output() + .context("failed to run make")?; + if !output.status.success() { + bail!( + "failed to build {target}:\n{}{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + } + built.insert(name.to_string()); + Ok(()) +} + +/// A message logged by the test subject via `wasi:logging`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct LogEntry { + pub level: Level, + pub context: String, + pub message: String, +} + +impl LogEntry { + /// A trace message logged by the test subject. + pub fn trace(context: impl Into, message: impl Into) -> Self { + Self { + level: Level::Trace, + context: context.into(), + message: message.into(), + } + } + + /// The entry with the resource handles in its message masked, see [`mask_handles`]. + pub fn masked(self) -> Self { + Self { + message: mask_handles(&self.message), + ..self + } + } +} + +/// A resource handle in a trace message, e.g. `SELF=&00a3`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct LoggedHandle { + /// The traced operation, e.g. `wasi:http/types#request.get-method` + pub operation: String, + /// The name the handle is logged under, e.g. `SELF` + pub key: String, + /// The handle as logged, e.g. `&00a3` + pub value: String, +} + +/// The resource handles in a trace message. +pub fn logged_handles(message: &str) -> Vec { + let mut parts = message.split(' '); + let Some(operation) = parts + .next() + .and_then(|part| part.strip_prefix("OPERATION=")) + else { + return vec![]; + }; + parts + .filter_map(|part| part.split_once('=')) + .filter(|(_, value)| handle_digits(value).is_some()) + .map(|(key, value)| LoggedHandle { + operation: operation.to_string(), + key: key.to_string(), + value: value.to_string(), + }) + .collect() +} + +/// A request the test subject sent upstream. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SentRequest { + pub method: String, + pub uri: String, +} + +/// Observations recorded while the test subject runs. +#[derive(Clone, Default)] +pub struct Recorder { + logs: Arc>>, + requests: Arc>>, +} + +impl Recorder { + /// Messages logged by the test subject, in order. + pub fn logs(&self) -> Vec { + self.logs.lock().unwrap().clone() + } + + /// Messages logged by the test subject, in order, with resource handles masked. + pub fn masked_logs(&self) -> Vec { + self.logs().into_iter().map(LogEntry::masked).collect() + } + + /// Resource handles in the messages logged by the test subject, in order. + pub fn handles(&self) -> Vec { + self.logs() + .iter() + .flat_map(|log| logged_handles(&log.message)) + .collect() + } + + /// Values of the resource handles logged by operations starting with the prefix, in order, + /// e.g. `wasi:http/types#request.` for every request method. + pub fn handle_values(&self, operation_prefix: &str) -> Vec { + self.handles() + .into_iter() + .filter(|handle| handle.operation.starts_with(operation_prefix)) + .map(|handle| handle.value) + .collect() + } + + /// Requests the test subject sent upstream, in order. + pub fn requests(&self) -> Vec { + self.requests.lock().unwrap().clone() + } +} + +/// Answers requests sent upstream with a canned response, instead of the network. +struct Upstream { + recorder: Recorder, +} + +impl WasiHttpHooks for Upstream { + fn send_request( + &mut self, + request: http::Request, + _options: Option, + _fut: Box> + Send>, + ) -> Box< + dyn Future< + Output = Result< + ( + http::Response, + Box> + Send>, + ), + wasmtime_wasi_http::Error, + >, + > + Send, + > { + self.recorder.requests.lock().unwrap().push(SentRequest { + method: request.method().to_string(), + uri: request.uri().to_string(), + }); + Box::new(async move { + // the body is passed through the test subject to the harness host to host, which the + // harness can't read, see `resolve` + let body = Empty::::new() + .map_err(|never| match never {}) + .boxed_unsync(); + let response = http::Response::builder() + .status(UPSTREAM_STATUS) + .header(UPSTREAM_HEADER.0, UPSTREAM_HEADER.1) + .body(body) + .expect("canned response"); + let io = Box::new(async { Ok(()) }) + as Box> + Send>; + Ok((response, io)) + }) + } +} + +pub struct Ctx { + wasi: WasiCtx, + http: WasiHttpCtx, + upstream: Upstream, + table: ResourceTable, + recorder: Recorder, +} + +impl WasiView for Ctx { + fn ctx(&mut self) -> WasiCtxView<'_> { + WasiCtxView { + ctx: &mut self.wasi, + table: &mut self.table, + } + } +} + +impl WasiHttpView for Ctx { + fn http(&mut self) -> WasiHttpCtxView<'_> { + WasiHttpCtxView { + ctx: &mut self.http, + table: &mut self.table, + hooks: &mut self.upstream, + } + } +} + +impl logging::Host for Ctx { + fn log(&mut self, level: Level, context: String, message: String) { + self.recorder.logs.lock().unwrap().push(LogEntry { + level, + context, + message, + }); + } +} + +/// Upstream for `componentized:http/client`, requests are recorded and answered with a canned +/// response. +struct UpstreamClient; + +impl HasData for UpstreamClient { + type Data<'a> = &'a mut Ctx; +} + +impl upstream_client::Host for Ctx {} + +impl UpstreamClient { + fn respond( + accessor: &Accessor, + method: &str, + url: String, + body: Option>, + ) -> Result { + accessor.with(|mut store| { + store + .get() + .recorder + .requests + .lock() + .unwrap() + .push(SentRequest { + method: method.to_string(), + uri: url, + }); + if let Some(mut body) = body { + body.close(&mut store) + .map_err(|err| upstream_client::ErrorCode::Other(Some(err.to_string())))?; + } + let response_body = StreamReader::new(&mut store, Vec::::new()) + .map_err(|err| upstream_client::ErrorCode::Other(Some(err.to_string())))?; + let trailers = + FutureReader::new(&mut store, async { Ok::<_, wasmtime::Error>(Ok(vec![])) }) + .map_err(|err| upstream_client::ErrorCode::Other(Some(err.to_string())))?; + Ok(upstream_client::HttpResponse { + status: UPSTREAM_STATUS, + headers: vec![(UPSTREAM_HEADER.0.to_string(), UPSTREAM_HEADER.1.to_string())], + body: response_body, + trailers, + }) + }) + } +} + +impl upstream_client::HostWithStore for UpstreamClient { + async fn request( + accessor: &Accessor, + method: upstream_client::Method, + url: String, + _headers: Vec<(String, String)>, + body: Option>, + _options: Option, + ) -> Result { + use upstream_client::Method; + let method = match method { + Method::Get => "GET", + Method::Post => "POST", + Method::Put => "PUT", + Method::Delete => "DELETE", + Method::Patch => "PATCH", + Method::Head => "HEAD", + Method::Options => "OPTIONS", + Method::Trace => "TRACE", + Method::Query => "QUERY", + }; + Self::respond(accessor, method, url, body) + } + + async fn get( + accessor: &Accessor, + url: String, + _headers: Vec<(String, String)>, + _options: Option, + ) -> Result { + Self::respond(accessor, "GET", url, None) + } + + async fn post( + accessor: &Accessor, + url: String, + _headers: Vec<(String, String)>, + body: StreamReader, + _options: Option, + ) -> Result { + Self::respond(accessor, "POST", url, Some(body)) + } + + async fn put( + accessor: &Accessor, + url: String, + _headers: Vec<(String, String)>, + body: StreamReader, + _options: Option, + ) -> Result { + Self::respond(accessor, "PUT", url, Some(body)) + } + + async fn delete( + accessor: &Accessor, + url: String, + _headers: Vec<(String, String)>, + _options: Option, + ) -> Result { + Self::respond(accessor, "DELETE", url, None) + } + + async fn patch( + accessor: &Accessor, + url: String, + _headers: Vec<(String, String)>, + body: StreamReader, + _options: Option, + ) -> Result { + Self::respond(accessor, "PATCH", url, Some(body)) + } + + async fn head( + accessor: &Accessor, + url: String, + _headers: Vec<(String, String)>, + _options: Option, + ) -> Result { + Self::respond(accessor, "HEAD", url, None) + } + + async fn options( + accessor: &Accessor, + url: String, + _headers: Vec<(String, String)>, + _options: Option, + ) -> Result { + Self::respond(accessor, "OPTIONS", url, None) + } + + async fn trace( + accessor: &Accessor, + url: String, + _headers: Vec<(String, String)>, + _options: Option, + ) -> Result { + Self::respond(accessor, "TRACE", url, None) + } + + async fn query( + accessor: &Accessor, + url: String, + _headers: Vec<(String, String)>, + body: StreamReader, + _options: Option, + ) -> Result { + Self::respond(accessor, "QUERY", url, Some(body)) + } +} + +/// Define `wasi:http/handler` with the same implementation as `wasi:http/client`, the request is +/// sent upstream. +/// +/// wasmtime-wasi-http only defines `wasi:http/client`, the two have the same signature. +fn add_handler_to_linker(linker: &mut Linker) -> Result<()> { + type Request = wasmtime_wasi_http::p3::Request; + type Response = wasmtime_wasi_http::p3::Response; + type HttpErrorCode = wasmtime_wasi_http::p3::bindings::http::types::ErrorCode; + + linker + .instance("wasi:http/handler@0.3.0")? + .func_wrap_concurrent( + "handle", + |accessor: &Accessor, (request,): (Resource,)| { + Box::pin(async move { + let accessor = accessor.with_getter::(::http); + let result: Result, HttpErrorCode> = + match WasiHttp::send(&accessor, request).await { + Ok(response) => Ok(response), + Err(err) => Err(err.downcast()?), + }; + Ok((result,)) + }) + }, + ) +} + +/// Builds a subject instance for a test. +pub struct Harness { + subject: String, +} + +impl Harness { + /// Test the named component from `target/components/`, e.g. `trace-types`. + pub fn new(component_name: &str) -> Self { + Self { + subject: component_name.to_string(), + } + } + + /// Instantiate the test subject. + pub async fn build(self) -> Result { + let mut config = Config::new(); + config.wasm_component_model_async(true); + let engine = Engine::new(&config)?; + + ensure_built(&self.subject)?; + let path = component_path(&self.subject); + let component = Component::from_file(&engine, &path) + .with_context(|| format!("failed to load {}", path.display()))?; + + let mut linker = Linker::new(&engine); + wasmtime_wasi::p3::add_to_linker(&mut linker)?; + wasmtime_wasi_http::p3::add_to_linker(&mut linker)?; + add_handler_to_linker(&mut linker)?; + upstream_client::add_to_linker::<_, UpstreamClient>(&mut linker, |ctx| ctx)?; + logging::add_to_linker::<_, HasSelf>(&mut linker, |ctx| ctx)?; + + let recorder = Recorder::default(); + let mut store = Store::new( + &engine, + Ctx { + wasi: WasiCtxBuilder::new().inherit_stdio().build(), + http: WasiHttpCtx::new(), + upstream: Upstream { + recorder: recorder.clone(), + }, + table: ResourceTable::new(), + recorder: recorder.clone(), + }, + ); + let instance_pre = linker.instantiate_pre(&component)?; + let instance = instance_pre + .instantiate_async(&mut store) + .await + .with_context(|| format!("failed to instantiate {}", self.subject))?; + let exports = Exports { + name: self.subject, + types: match types::GuestIndices::new(&instance_pre) { + Ok(indices) => Some(indices.load(&mut store, &instance)?), + Err(_) => None, + }, + client: match client::GuestIndices::new(&instance_pre) { + Ok(indices) => Some(indices.load(&mut store, &instance)?), + Err(_) => None, + }, + handler: match handler::GuestIndices::new(&instance_pre) { + Ok(indices) => Some(indices.load(&mut store, &instance)?), + Err(_) => None, + }, + http_client: match http_client::GuestIndices::new(&instance_pre) { + Ok(indices) => Some(indices.load(&mut store, &instance)?), + Err(_) => None, + }, + }; + + Ok(TestSubject { + store, + exports, + recorder, + }) + } +} + +/// The test subject's exported interfaces. +/// +/// Accessing an interface the component does not export panics. +pub struct Exports { + name: String, + types: Option, + client: Option, + handler: Option, + http_client: Option, +} + +impl Exports { + /// The exported `wasi:http/types` interface. + pub fn wasi_http_types(&self) -> &types::Guest { + self.types + .as_ref() + .unwrap_or_else(|| panic!("{} does not export wasi:http/types", self.name)) + } + + /// The exported `wasi:http/client` interface. + pub fn wasi_http_client(&self) -> &client::Guest { + self.client + .as_ref() + .unwrap_or_else(|| panic!("{} does not export wasi:http/client", self.name)) + } + + /// The exported `wasi:http/handler` interface. + pub fn wasi_http_handler(&self) -> &handler::Guest { + self.handler + .as_ref() + .unwrap_or_else(|| panic!("{} does not export wasi:http/handler", self.name)) + } + + /// The exported `componentized:http/client` interface. + pub fn componentized_http_client(&self) -> &http_client::Guest { + self.http_client + .as_ref() + .unwrap_or_else(|| panic!("{} does not export componentized:http/client", self.name)) + } + + /// Whether the component exports `wasi:http/types`. + pub fn exports_types(&self) -> bool { + self.types.is_some() + } + + /// Whether the component exports `wasi:http/client`. + pub fn exports_client(&self) -> bool { + self.client.is_some() + } + + /// Whether the component exports `wasi:http/handler`. + pub fn exports_handler(&self) -> bool { + self.handler.is_some() + } + + /// Create a request with the exported `wasi:http/types`, without a body or trailers. + pub async fn new_request( + &self, + accessor: &Accessor, + method: Method, + scheme: Scheme, + authority: &str, + path_with_query: &str, + ) -> Result { + let types = self.wasi_http_types(); + let headers = types.fields().call_constructor(accessor).await?; + let trailers = ready(accessor, Ok(None))?; + let (request, _transmitted) = types + .request() + .call_new(accessor, headers, None, trailers, None) + .await?; + let request_methods = types.request(); + request_methods + .call_set_method(accessor, request, method) + .await? + .map_err(|()| format_err!("invalid method"))?; + request_methods + .call_set_scheme(accessor, request, Some(scheme)) + .await? + .map_err(|()| format_err!("invalid scheme"))?; + request_methods + .call_set_authority(accessor, request, Some(authority.to_string())) + .await? + .map_err(|()| format_err!("invalid authority"))?; + request_methods + .call_set_path_with_query(accessor, request, Some(path_with_query.to_string())) + .await? + .map_err(|()| format_err!("invalid path with query"))?; + Ok(request) + } +} + +/// An instantiated test subject. +pub struct TestSubject { + store: Store, + exports: Exports, + recorder: Recorder, +} + +impl TestSubject { + /// Logs and upstream requests recorded while the test subject runs. + pub fn recorder(&self) -> Recorder { + self.recorder.clone() + } + + /// The test subject's exported interfaces. + pub fn exports(&self) -> &Exports { + &self.exports + } + + /// Run a test body against the test subject's exports. + pub async fn run( + &mut self, + f: impl AsyncFnOnce(&Accessor, &Exports) -> Result + Send, + ) -> Result { + let exports = &self.exports; + self.store + .run_concurrent(async move |accessor| f(accessor, exports).await) + .await? + } +} diff --git a/crates/trace/src/client.rs b/crates/trace/src/client.rs new file mode 100644 index 0000000..d8038d3 --- /dev/null +++ b/crates/trace/src/client.rs @@ -0,0 +1,21 @@ +use crate::{ + Trace, + exports::wasi::http::{ + client::Guest, + types::{ErrorCode, Request, Response}, + }, + trace, + types::{TraceRequest, TraceResponse}, + wasi::http::client, +}; + +impl Guest for Trace { + #[doc = " This function may be used to either send an outgoing request over the"] + #[doc = " network or to forward it to another component."] + async fn send(request: Request) -> Result { + let request = request.into_inner::(); + trace!("OPERATION=wasi:http/client#send REQUEST={request}"); + let response = client::send(request.request).await?; + Ok(Response::new(TraceResponse::new(response))) + } +} diff --git a/crates/trace/src/handler.rs b/crates/trace/src/handler.rs new file mode 100644 index 0000000..5e85f30 --- /dev/null +++ b/crates/trace/src/handler.rs @@ -0,0 +1,21 @@ +use crate::{ + Trace, + exports::wasi::http::{ + handler::Guest, + types::{ErrorCode, Request, Response}, + }, + trace, + types::{TraceRequest, TraceResponse}, + wasi::http::handler, +}; + +impl Guest for Trace { + #[doc = " This function may be called with either an incoming request read from the"] + #[doc = " network or a request synthesized or forwarded by another component."] + async fn handle(request: Request) -> Result { + let request = request.into_inner::(); + trace!("OPERATION=wasi:http/handler#handle REQUEST={request}"); + let response = handler::handle(request.request).await?; + Ok(Response::new(TraceResponse::new(response))) + } +} diff --git a/crates/trace/src/lib.rs b/crates/trace/src/lib.rs new file mode 100644 index 0000000..db3b3b5 --- /dev/null +++ b/crates/trace/src/lib.rs @@ -0,0 +1,102 @@ +//! Virtualizes wasi:http interfaces, logging each call at the TRACE level. +//! +//! These sources are shared by the `trace`, `trace-client`, `trace-handler` and `trace-types` +//! components. Each component compiles them with the features for the interfaces it exports, +//! `wasi:http/types` is always exported: +//! +//! - `client`: exports `wasi:http/client` +//! - `handler`: exports `wasi:http/handler` + +use std::fmt::Display; + +mod types; + +#[cfg(feature = "client")] +mod client; + +#[cfg(feature = "handler")] +mod handler; + +#[macro_export] +macro_rules! trace { + ($dst:expr, $($arg:tt)*) => { + $crate::wasi::logging::logging::log( + $crate::wasi::logging::logging::Level::Trace, + "componentized-trace", + &format!($dst, $($arg)*), + ); + }; + ($dst:expr) => { + $crate::wasi::logging::logging::log( + $crate::wasi::logging::logging::Level::Trace, + "componentized-trace", + &format!($dst), + ); + }; +} + +/// The number of trailing hex digits of a resource handle to display +const HANDLE_DIGITS: u32 = 4; +const HANDLE_MODULUS: u32 = 16u32.pow(HANDLE_DIGITS); + +/// Write the trailing hex digits of a resource handle, padded with zeros and prefixed with `&`, +/// e.g. `&002a`. The prefix marks the value as a handle, a reference to a resource. +fn fmt_handle(f: &mut std::fmt::Formatter<'_>, handle: u32) -> std::fmt::Result { + write!( + f, + "&{:0width$x}", + handle % HANDLE_MODULUS, + width = HANDLE_DIGITS as usize + ) +} + +struct DisplayOption(Option); + +impl Display for DisplayOption { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self(Some(value)) => write!(f, "some<{value}>"), + Self(None) => f.write_str("none"), + } + } +} + +struct Trace; + +#[cfg(all(feature = "client", feature = "handler"))] +wit_bindgen::generate!({ + // `path` is relative to the manifest of the component compiling these sources, `components//` + path: "../wit", + world: "trace", + merge_structurally_equal_types: true, + generate_all, +}); + +#[cfg(all(feature = "client", not(feature = "handler")))] +wit_bindgen::generate!({ + // `path` is relative to the manifest of the component compiling these sources, `components//` + path: "../wit", + world: "trace-client", + merge_structurally_equal_types: true, + generate_all, +}); + +#[cfg(all(not(feature = "client"), feature = "handler"))] +wit_bindgen::generate!({ + // `path` is relative to the manifest of the component compiling these sources, `components//` + path: "../wit", + world: "trace-handler", + merge_structurally_equal_types: true, + generate_all, +}); + +#[cfg(all(not(feature = "client"), not(feature = "handler")))] +wit_bindgen::generate!({ + // `path` is relative to the manifest of the component compiling these sources, `components//` + path: "../wit", + world: "trace-types", + merge_structurally_equal_types: true, + generate_all, +}); + +export!(Trace); diff --git a/crates/trace/src/types.rs b/crates/trace/src/types.rs new file mode 100644 index 0000000..545d6af --- /dev/null +++ b/crates/trace/src/types.rs @@ -0,0 +1,620 @@ +use std::fmt::Display; + +use crate::{ + DisplayOption, Trace, + exports::wasi::http::types::{ + Duration, ErrorCode, FieldName, FieldValue, Fields, Guest, GuestFields, GuestRequest, + GuestRequestOptions, GuestResponse, HeaderError, Headers, Method, Request, RequestOptions, + RequestOptionsError, Response, Scheme, StatusCode, Trailers, + }, + fmt_handle, trace, + wasi::http::types, + wit_future, +}; + +impl Guest for Trace { + type Fields = TraceFields; + type Request = TraceRequest; + type RequestOptions = TraceRequestOptions; + type Response = TraceResponse; +} + +pub(crate) struct TraceFields { + fields: types::Fields, +} + +impl TraceFields { + fn new(fields: types::Fields) -> Self { + Self { fields } + } +} + +impl GuestFields for TraceFields { + #[doc = " Construct an empty HTTP Fields."] + #[doc = ""] + #[doc = " The resulting `fields` is mutable."] + #[allow(async_fn_in_trait)] + fn new() -> Self { + trace!("OPERATION=wasi:http/types#fields.new"); + Self::new(types::Fields::new()) + } + + #[doc = " Construct an HTTP Fields."] + #[doc = ""] + #[doc = " The resulting `fields` is mutable."] + #[doc = ""] + #[doc = " The list represents each name-value pair in the Fields. Names"] + #[doc = " which have multiple values are represented by multiple entries in this"] + #[doc = " list with the same name."] + #[doc = ""] + #[doc = " The tuple is a pair of the field name, represented as a string, and"] + #[doc = " Value, represented as a list of bytes. In a valid Fields, all names"] + #[doc = " and values are valid UTF-8 strings. However, values are not always"] + #[doc = " well-formed, so they are represented as a raw list of bytes."] + #[doc = ""] + #[doc = " An error result will be returned if any header or value was"] + #[doc = " syntactically invalid, if a header was forbidden, or if the"] + #[doc = " entries would exceed an implementation size limit."] + #[allow(async_fn_in_trait)] + fn from_list(entries: Vec<(FieldName, FieldValue)>) -> Result { + trace!( + "OPERATION=wasi:http/types#fields.from-list ENTRIES-LEN={}", + entries.len() + ); + match types::Fields::from_list(&entries) { + Ok(fields) => Ok(Fields::new(Self::new(fields))), + Err(err) => Err(err), + } + } + + #[doc = " Get all of the values corresponding to a name. If the name is not present"] + #[doc = " in this `fields`, an empty list is returned. However, if the name is"] + #[doc = " present but empty, this is represented by a list with one or more"] + #[doc = " empty field-values present."] + #[allow(async_fn_in_trait)] + fn get(&self, name: FieldName) -> Vec { + trace!("OPERATION=wasi:http/types#fields.get SELF={self} NAME={name}"); + self.fields.get(&name) + } + + #[doc = " Returns `true` when the name is present in this `fields`. If the name is"] + #[doc = " syntactically invalid, `false` is returned."] + #[allow(async_fn_in_trait)] + fn has(&self, name: FieldName) -> bool { + trace!("OPERATION=wasi:http/types#fields.has SELF={self} NAME={name}"); + self.fields.has(&name) + } + + #[doc = " Set all of the values for a name. Clears any existing values for that"] + #[doc = " name, if they have been set."] + #[doc = ""] + #[doc = " Fails with `header-error.immutable` if the `fields` are immutable."] + #[doc = ""] + #[doc = " Fails with `header-error.size-exceeded` if the name or values would"] + #[doc = " exceed an implementation-defined size limit."] + #[allow(async_fn_in_trait)] + fn set(&self, name: FieldName, value: Vec) -> Result<(), HeaderError> { + trace!( + "OPERATION=wasi:http/types#fields.set SELF={self} NAME={name} VALUE-LEN={}", + value.len() + ); + self.fields.set(&name, &value) + } + + #[doc = " Delete all values for a name. Does nothing if no values for the name"] + #[doc = " exist."] + #[doc = ""] + #[doc = " Fails with `header-error.immutable` if the `fields` are immutable."] + #[allow(async_fn_in_trait)] + fn delete(&self, name: FieldName) -> Result<(), HeaderError> { + trace!("OPERATION=wasi:http/types#fields.delete SELF={self} NAME={name}"); + self.fields.delete(&name) + } + + #[doc = " Delete all values for a name. Does nothing if no values for the name"] + #[doc = " exist."] + #[doc = ""] + #[doc = " Returns all values previously corresponding to the name, if any."] + #[doc = ""] + #[doc = " Fails with `header-error.immutable` if the `fields` are immutable."] + #[allow(async_fn_in_trait)] + fn get_and_delete(&self, name: FieldName) -> Result, HeaderError> { + trace!("OPERATION=wasi:http/types#fields.get-and-delete SELF={self} NAME={name}"); + self.fields.get_and_delete(&name) + } + + #[doc = " Append a value for a name. Does not change or delete any existing"] + #[doc = " values for that name."] + #[doc = ""] + #[doc = " Fails with `header-error.immutable` if the `fields` are immutable."] + #[doc = ""] + #[doc = " Fails with `header-error.size-exceeded` if the value would exceed"] + #[doc = " an implementation-defined size limit."] + #[allow(async_fn_in_trait)] + fn append(&self, name: FieldName, value: FieldValue) -> Result<(), HeaderError> { + trace!( + "OPERATION=wasi:http/types#fields.append SELF={self} NAME={name} VALUE={value}", + value = DisplayFieldValue(value.clone()) + ); + self.fields.append(&name, &value) + } + + #[doc = " Retrieve the full set of names and values in the Fields. Like the"] + #[doc = " constructor, the list represents each name-value pair."] + #[doc = ""] + #[doc = " The outer list represents each name-value pair in the Fields. Names"] + #[doc = " which have multiple values are represented by multiple entries in this"] + #[doc = " list with the same name."] + #[doc = ""] + #[doc = " The names and values are always returned in the original casing and in"] + #[doc = " the order in which they will be serialized for transport."] + #[allow(async_fn_in_trait)] + fn copy_all(&self) -> Vec<(FieldName, FieldValue)> { + trace!("OPERATION=wasi:http/types#fields.copy-all SELF={self}"); + self.fields.copy_all() + } + + #[doc = " Make a deep copy of the Fields. Equivalent in behavior to calling the"] + #[doc = " `fields` constructor on the return value of `copy-all`. The resulting"] + #[doc = " `fields` is mutable."] + #[allow(async_fn_in_trait)] + fn clone(&self) -> Fields { + trace!("OPERATION=wasi:http/types#fields.clone SELF={self}"); + Fields::new(Self::new(self.fields.clone())) + } +} + +impl Display for TraceFields { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt_handle(f, self.fields.handle()) + } +} + +struct DisplayFieldValue(FieldValue); + +impl Display for DisplayFieldValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self(value) = self; + f.write_str(&String::from_utf8_lossy(value)) + } +} + +pub(crate) struct TraceRequest { + pub(crate) request: types::Request, +} + +impl TraceRequest { + pub(crate) fn new(request: types::Request) -> Self { + Self { request } + } +} + +impl GuestRequest for TraceRequest { + #[doc = " Construct a new `request` with a default `method` of `GET`, and"] + #[doc = " `none` values for `path-with-query`, `scheme`, and `authority`."] + #[doc = ""] + #[doc = " `headers` is the HTTP Headers for the Request."] + #[doc = ""] + #[doc = " `contents` is the optional body content stream with `none`"] + #[doc = " representing a zero-length content stream."] + #[doc = " Once it is closed, `trailers` future must resolve to a result."] + #[doc = " If `trailers` resolves to an error, underlying connection"] + #[doc = " will be closed immediately."] + #[doc = ""] + #[doc = " `options` is optional `request-options` resource to be used"] + #[doc = " if the request is sent over a network connection."] + #[doc = ""] + #[doc = " It is possible to construct, or manipulate with the accessor functions"] + #[doc = " below, a `request` with an invalid combination of `scheme`"] + #[doc = " and `authority`, or `headers` which are not permitted to be sent."] + #[doc = " It is the obligation of the `handler.handle` implementation"] + #[doc = " to reject invalid constructions of `request`."] + #[doc = ""] + #[doc = " The returned future resolves to result of transmission of this request."] + #[allow(async_fn_in_trait)] + fn new( + headers: Headers, + contents: Option>, + trailers: wit_bindgen::FutureReader, ErrorCode>>, + options: Option, + ) -> (Request, wit_bindgen::FutureReader>) { + trace!("OPERATION=wasi:http/types#request.new"); + let headers = headers.into_inner::().fields; + let (trailers_tx, trailers_rx) = wit_future::new(|| Ok(None)); + wit_bindgen::spawn_local(async move { + let trailers = match trailers.await { + Ok(None) => Ok(None), + Ok(Some(trailers)) => Ok(Some(trailers.into_inner::().fields)), + Err(err) => Err(err), + }; + let _ = trailers_tx.write(trailers).await; + }); + let options = options.map(|options| options.into_inner::().options); + let (request, read) = types::Request::new(headers, contents, trailers_rx, options); + (Request::new(Self::new(request)), read) + } + + #[doc = " Get the Method for the Request."] + #[allow(async_fn_in_trait)] + fn get_method(&self) -> Method { + trace!("OPERATION=wasi:http/types#request.get-method SELF={self}"); + self.request.get_method() + } + + #[doc = " Set the Method for the Request. Fails if the string present in a"] + #[doc = " `method.other` argument is not a syntactically valid method."] + #[allow(async_fn_in_trait)] + fn set_method(&self, method: Method) -> Result<(), ()> { + trace!("OPERATION=wasi:http/types#request.set-method SELF={self} METHOD={method}"); + self.request.set_method(&method) + } + + #[doc = " Get the combination of the HTTP Path and Query for the Request. When"] + #[doc = " `none`, this represents an empty Path and empty Query."] + #[allow(async_fn_in_trait)] + fn get_path_with_query(&self) -> Option { + trace!("OPERATION=wasi:http/types#request.get-path-with-query SELF={self}"); + self.request.get_path_with_query() + } + + #[doc = " Set the combination of the HTTP Path and Query for the Request. When"] + #[doc = " `none`, this represents an empty Path and empty Query. Fails is the"] + #[doc = " string given is not a syntactically valid path and query uri component."] + #[allow(async_fn_in_trait)] + fn set_path_with_query(&self, path_with_query: Option) -> Result<(), ()> { + trace!( + "OPERATION=wasi:http/types#request.set-path-with-query SELF={self} PATH-WITH-QUERY={path_with_query}", + path_with_query = DisplayOption(path_with_query.clone()) + ); + self.request.set_path_with_query(path_with_query.as_deref()) + } + + #[doc = " Get the HTTP Related Scheme for the Request. When `none`, the"] + #[doc = " implementation may choose an appropriate default scheme."] + #[allow(async_fn_in_trait)] + fn get_scheme(&self) -> Option { + trace!("OPERATION=wasi:http/types#request.get-scheme SELF={self}"); + self.request.get_scheme() + } + + #[doc = " Set the HTTP Related Scheme for the Request. When `none`, the"] + #[doc = " implementation may choose an appropriate default scheme. Fails if the"] + #[doc = " string given is not a syntactically valid uri scheme."] + #[allow(async_fn_in_trait)] + fn set_scheme(&self, scheme: Option) -> Result<(), ()> { + trace!( + "OPERATION=wasi:http/types#request.set-scheme SELF={self} SCHEME={scheme}", + scheme = DisplayOption(scheme.clone()) + ); + self.request.set_scheme(scheme.as_ref()) + } + + #[doc = " Get the authority of the Request\'s target URI. A value of `none` may be used"] + #[doc = " with Related Schemes which do not require an authority. The HTTP and"] + #[doc = " HTTPS schemes always require an authority."] + #[allow(async_fn_in_trait)] + fn get_authority(&self) -> Option { + trace!("OPERATION=wasi:http/types#request.get-authority SELF={self}"); + self.request.get_authority() + } + + #[doc = " Set the authority of the Request\'s target URI. A value of `none` may be used"] + #[doc = " with Related Schemes which do not require an authority. The HTTP and"] + #[doc = " HTTPS schemes always require an authority. Fails if the string given is"] + #[doc = " not a syntactically valid URI authority."] + #[allow(async_fn_in_trait)] + fn set_authority(&self, authority: Option) -> Result<(), ()> { + trace!( + "OPERATION=wasi:http/types#request.set-authority SELF={self} AUTHORITY={authority}", + authority = DisplayOption(authority.clone()) + ); + self.request.set_authority(authority.as_deref()) + } + + #[doc = " Get the `request-options` to be associated with this request"] + #[doc = ""] + #[doc = " The returned `request-options` resource is immutable: `set-*` operations"] + #[doc = " will fail if invoked."] + #[doc = ""] + #[doc = " This `request-options` resource is a child: it must be dropped before"] + #[doc = " the parent `request` is dropped, or its ownership is transferred to"] + #[doc = " another component by e.g. `handler.handle`."] + #[allow(async_fn_in_trait)] + fn get_options(&self) -> Option { + trace!("OPERATION=wasi:http/types#request.get-options SELF={self}"); + self.request + .get_options() + .map(|options| RequestOptions::new(TraceRequestOptions::new(options))) + } + + #[doc = " Get the headers associated with the Request."] + #[doc = ""] + #[doc = " The returned `headers` resource is immutable: `set`, `append`, and"] + #[doc = " `delete` operations will fail with `header-error.immutable`."] + #[allow(async_fn_in_trait)] + fn get_headers(&self) -> Headers { + trace!("OPERATION=wasi:http/types#request.get-headers SELF={self}"); + Headers::new(TraceFields::new(self.request.get_headers())) + } + + #[doc = " Get body of the Request."] + #[doc = ""] + #[doc = " Stream returned by this method represents the contents of the body."] + #[doc = " Once the stream is reported as closed, callers should await the returned"] + #[doc = " future to determine whether the body was received successfully."] + #[doc = " The future will only resolve after the stream is reported as closed."] + #[doc = ""] + #[doc = " This function takes a `res` future as a parameter, which can be used to"] + #[doc = " communicate an error in handling of the request."] + #[doc = ""] + #[doc = " Note that function will move the `request`, but references to headers or"] + #[doc = " request options acquired from it previously will remain valid."] + #[allow(async_fn_in_trait)] + fn consume_body( + this: Request, + res: wit_bindgen::FutureReader>, + ) -> ( + wit_bindgen::StreamReader, + wit_bindgen::FutureReader, ErrorCode>>, + ) { + let this = this.into_inner::(); + trace!("OPERATION=wasi:http/types#request.consume-body THIS={this}"); + let request = this.request; + let (contents, trailers) = types::Request::consume_body(request, res); + let (trailers_tx, trailers_rx) = wit_future::new(|| Ok(None)); + wit_bindgen::spawn_local(async move { + let trailers = match trailers.await { + Ok(None) => Ok(None), + Ok(Some(trailers)) => Ok(Some(Trailers::new(TraceFields::new(trailers)))), + Err(err) => Err(err), + }; + let _ = trailers_tx.write(trailers).await; + }); + (contents, trailers_rx) + } +} + +impl Display for TraceRequest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt_handle(f, self.request.handle()) + } +} + +impl Display for Method { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + types::Method::Get => f.write_str("get"), + types::Method::Head => f.write_str("head"), + types::Method::Post => f.write_str("post"), + types::Method::Put => f.write_str("put"), + types::Method::Delete => f.write_str("delete"), + types::Method::Connect => f.write_str("connect"), + types::Method::Options => f.write_str("options"), + types::Method::Trace => f.write_str("trace"), + types::Method::Patch => f.write_str("patch"), + types::Method::Other(method) => f.write_str(&method.to_ascii_lowercase()), + } + } +} + +impl Display for Scheme { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + types::Scheme::Http => f.write_str("http"), + types::Scheme::Https => f.write_str("https"), + types::Scheme::Other(scheme) => write!(f, "other<{scheme}>"), + } + } +} + +pub(crate) struct TraceRequestOptions { + options: types::RequestOptions, +} + +impl TraceRequestOptions { + fn new(options: types::RequestOptions) -> Self { + Self { options } + } +} + +impl GuestRequestOptions for TraceRequestOptions { + #[doc = " Construct a default `request-options` value."] + #[allow(async_fn_in_trait)] + fn new() -> Self { + trace!("OPERATION=wasi:http/types#request-options.new"); + Self::new(types::RequestOptions::new()) + } + + #[doc = " The timeout for the initial connect to the HTTP Server."] + #[allow(async_fn_in_trait)] + fn get_connect_timeout(&self) -> Option { + trace!("OPERATION=wasi:http/types#request-options.get-connect-timeout SELF={self}"); + self.options.get_connect_timeout() + } + + #[doc = " Set the timeout for the initial connect to the HTTP Server. An error"] + #[doc = " return value indicates that this timeout is not supported or that this"] + #[doc = " handle is immutable."] + #[allow(async_fn_in_trait)] + fn set_connect_timeout(&self, duration: Option) -> Result<(), RequestOptionsError> { + trace!( + "OPERATION=wasi:http/types#request-options.set-connect-timeout SELF={self} DURATION={duration}", + duration = DisplayOption(duration) + ); + self.options.set_connect_timeout(duration) + } + + #[doc = " The timeout for receiving the first byte of the Response body."] + #[allow(async_fn_in_trait)] + fn get_first_byte_timeout(&self) -> Option { + trace!("OPERATION=wasi:http/types#request-options.get-first-byte-timeout SELF={self}"); + self.options.get_first_byte_timeout() + } + + #[doc = " Set the timeout for receiving the first byte of the Response body. An"] + #[doc = " error return value indicates that this timeout is not supported or that"] + #[doc = " this handle is immutable."] + #[allow(async_fn_in_trait)] + fn set_first_byte_timeout( + &self, + duration: Option, + ) -> Result<(), RequestOptionsError> { + trace!( + "OPERATION=wasi:http/types#request-options.set-first-byte-timeout SELF={self} DURATION={duration}", + duration = DisplayOption(duration) + ); + self.options.set_first_byte_timeout(duration) + } + + #[doc = " The timeout for receiving subsequent chunks of bytes in the Response"] + #[doc = " body stream."] + #[allow(async_fn_in_trait)] + fn get_between_bytes_timeout(&self) -> Option { + trace!("OPERATION=wasi:http/types#request-options.get-between-bytes-timeout SELF={self}"); + self.options.get_between_bytes_timeout() + } + + #[doc = " Set the timeout for receiving subsequent chunks of bytes in the Response"] + #[doc = " body stream. An error return value indicates that this timeout is not"] + #[doc = " supported or that this handle is immutable."] + #[allow(async_fn_in_trait)] + fn set_between_bytes_timeout( + &self, + duration: Option, + ) -> Result<(), RequestOptionsError> { + trace!( + "OPERATION=wasi:http/types#request-options.set-between-bytes-timeout SELF={self} DURATION={duration}", + duration = DisplayOption(duration) + ); + self.options.set_between_bytes_timeout(duration) + } + + #[doc = " Make a deep copy of the `request-options`."] + #[doc = " The resulting `request-options` is mutable."] + #[allow(async_fn_in_trait)] + fn clone(&self) -> RequestOptions { + trace!("OPERATION=wasi:http/types#request-options.clone SELF={self}"); + RequestOptions::new(Self::new(self.options.clone())) + } +} + +impl Display for TraceRequestOptions { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt_handle(f, self.options.handle()) + } +} + +pub(crate) struct TraceResponse { + response: types::Response, +} + +impl TraceResponse { + pub(crate) fn new(response: types::Response) -> Self { + Self { response } + } +} + +impl GuestResponse for TraceResponse { + #[doc = " Construct a new `response`, with a default `status-code` of `200`."] + #[doc = " If a different `status-code` is needed, it must be set via the"] + #[doc = " `set-status-code` method."] + #[doc = ""] + #[doc = " `headers` is the HTTP Headers for the Response."] + #[doc = ""] + #[doc = " `contents` is the optional body content stream with `none`"] + #[doc = " representing a zero-length content stream."] + #[doc = " Once it is closed, `trailers` future must resolve to a result."] + #[doc = " If `trailers` resolves to an error, underlying connection"] + #[doc = " will be closed immediately."] + #[doc = ""] + #[doc = " The returned future resolves to result of transmission of this response."] + #[allow(async_fn_in_trait)] + fn new( + headers: Headers, + contents: Option>, + trailers: wit_bindgen::FutureReader, ErrorCode>>, + ) -> (Response, wit_bindgen::FutureReader>) { + trace!("OPERATION=wasi:http/types#response.new"); + let headers = headers.into_inner::().fields; + let (trailers_tx, trailers_rx) = wit_future::new(|| Ok(None)); + wit_bindgen::spawn_local(async move { + let trailers = match trailers.await { + Ok(None) => Ok(None), + Ok(Some(trailers)) => Ok(Some(trailers.into_inner::().fields)), + Err(err) => Err(err), + }; + let _ = trailers_tx.write(trailers).await; + }); + let (response, read) = types::Response::new(headers, contents, trailers_rx); + (Response::new(Self::new(response)), read) + } + + #[doc = " Get the HTTP Status Code for the Response."] + #[allow(async_fn_in_trait)] + fn get_status_code(&self) -> StatusCode { + trace!("OPERATION=wasi:http/types#response.get-status-code SELF={self}"); + self.response.get_status_code() + } + + #[doc = " Set the HTTP Status Code for the Response. Fails if the status-code"] + #[doc = " given is not a valid http status code."] + #[allow(async_fn_in_trait)] + fn set_status_code(&self, status_code: StatusCode) -> Result<(), ()> { + trace!( + "OPERATION=wasi:http/types#response.set-status-code SELF={self} STATUS-CODE={status_code}" + ); + self.response.set_status_code(status_code) + } + + #[doc = " Get the headers associated with the Response."] + #[doc = ""] + #[doc = " The returned `headers` resource is immutable: `set`, `append`, and"] + #[doc = " `delete` operations will fail with `header-error.immutable`."] + #[allow(async_fn_in_trait)] + fn get_headers(&self) -> Headers { + trace!("OPERATION=wasi:http/types#response.get-headers SELF={self}"); + Headers::new(TraceFields::new(self.response.get_headers())) + } + + #[doc = " Get body of the Response."] + #[doc = ""] + #[doc = " Stream returned by this method represents the contents of the body."] + #[doc = " Once the stream is reported as closed, callers should await the returned"] + #[doc = " future to determine whether the body was received successfully."] + #[doc = " The future will only resolve after the stream is reported as closed."] + #[doc = ""] + #[doc = " This function takes a `res` future as a parameter, which can be used to"] + #[doc = " communicate an error in handling of the response."] + #[doc = ""] + #[doc = " Note that function will move the `response`, but references to headers"] + #[doc = " acquired from it previously will remain valid."] + #[allow(async_fn_in_trait)] + fn consume_body( + this: Response, + res: wit_bindgen::FutureReader>, + ) -> ( + wit_bindgen::StreamReader, + wit_bindgen::FutureReader, ErrorCode>>, + ) { + let this = this.into_inner::(); + trace!("OPERATION=wasi:http/types#response.consume-body THIS={this}"); + let response = this.response; + let (contents, trailers) = types::Response::consume_body(response, res); + let (trailers_tx, trailers_rx) = wit_future::new(|| Ok(None)); + wit_bindgen::spawn_local(async move { + let trailers = match trailers.await { + Ok(None) => Ok(None), + Ok(Some(trailers)) => Ok(Some(Trailers::new(TraceFields::new(trailers)))), + Err(err) => Err(err), + }; + let _ = trailers_tx.write(trailers).await; + }); + (contents, trailers_rx) + } +} + +impl Display for TraceResponse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt_handle(f, self.response.handle()) + } +}