diff --git a/examples/fetch-rs/Cargo.lock b/examples/fetch-rs/Cargo.lock index 6e87da3c..bc81eaf2 100644 --- a/examples/fetch-rs/Cargo.lock +++ b/examples/fetch-rs/Cargo.lock @@ -44,7 +44,7 @@ checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.110", ] [[package]] @@ -53,6 +53,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "2.8.0" @@ -132,7 +138,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.98", + "syn 2.0.110", ] [[package]] @@ -143,7 +149,18 @@ checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.110", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", ] [[package]] @@ -167,6 +184,15 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12a0bb14ac04a9fcf170d0bbbef949b44cc492f4452bd20c095636956f653642" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -177,11 +203,14 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" name = "fetch-rs" version = "0.1.0" dependencies = [ + "base64", + "encoding_rs", "scraper", "serde_json", "spin-executor", "spin-sdk", - "wit-bindgen-rt", + "url", + "wit-bindgen 0.47.0", ] [[package]] @@ -190,11 +219,17 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -265,7 +300,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.110", ] [[package]] @@ -332,6 +367,9 @@ name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", +] [[package]] name = "heck" @@ -342,6 +380,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "html5ever" version = "0.26.0" @@ -390,12 +434,114 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + [[package]] name = "id-arena" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "indexmap" version = "2.7.1" @@ -429,12 +575,24 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" +[[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.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + [[package]] name = "lock_api" version = "0.4.12" @@ -523,9 +681,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "phf" @@ -586,7 +744,7 @@ dependencies = [ "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.110", ] [[package]] @@ -619,6 +777,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -634,6 +801,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.110", +] + [[package]] name = "proc-macro2" version = "1.0.93" @@ -777,7 +954,7 @@ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.110", ] [[package]] @@ -871,7 +1048,7 @@ checksum = "7298013c6a0dc3361331cec17c744e45d76df7696d59b87801a9a3f5edbe0f54" dependencies = [ "futures", "once_cell", - "wit-bindgen", + "wit-bindgen 0.16.0", ] [[package]] @@ -907,7 +1084,7 @@ dependencies = [ "spin-executor", "spin-macro", "thiserror", - "wit-bindgen", + "wit-bindgen 0.16.0", ] [[package]] @@ -960,15 +1137,26 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.98" +version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" 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.110", +] + [[package]] name = "tendril" version = "0.4.3" @@ -997,7 +1185,17 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.110", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", ] [[package]] @@ -1024,12 +1222,30 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + [[package]] name = "utf-8" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "version_check" version = "0.9.5" @@ -1064,7 +1280,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.110", "wasm-bindgen-shared", ] @@ -1086,7 +1302,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.110", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1118,6 +1334,16 @@ dependencies = [ "leb128", ] +[[package]] +name = "wasm-encoder" +version = "0.240.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06d642d8c5ecc083aafe9ceb32809276a304547a3a6eeecceb5d8152598bc71f" +dependencies = [ + "leb128fmt", + "wasmparser 0.240.0", +] + [[package]] name = "wasm-metadata" version = "0.10.20" @@ -1134,6 +1360,18 @@ dependencies = [ "wasmparser 0.121.2", ] +[[package]] +name = "wasm-metadata" +version = "0.240.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee093e1e1ccffa005b9b778f7a10ccfd58e25a20eccad294a1a93168d076befb" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder 0.240.0", + "wasmparser 0.240.0", +] + [[package]] name = "wasmparser" version = "0.118.2" @@ -1155,6 +1393,18 @@ dependencies = [ "semver", ] +[[package]] +name = "wasmparser" +version = "0.240.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b722dcf61e0ea47440b53ff83ccb5df8efec57a69d150e4f24882e4eba7e24a4" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -1235,7 +1485,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b76f1d099678b4f69402a421e888bbe71bf20320c2f3f3565d0e7484dbe5bc20" dependencies = [ "bitflags", - "wit-bindgen-rust-macro", + "wit-bindgen-rust-macro 0.16.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a374235c3c0dff10537040b437073d09f1e38f13216b5f3cbc809c6226814e5c" +dependencies = [ + "bitflags", + "wit-bindgen-rust-macro 0.47.0", ] [[package]] @@ -1245,17 +1505,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75d55e1a488af2981fb0edac80d8d20a51ac36897a1bdef4abde33c29c1b6d0d" dependencies = [ "anyhow", - "wit-component", - "wit-parser", + "wit-component 0.18.2", + "wit-parser 0.13.2", ] [[package]] -name = "wit-bindgen-rt" -version = "0.26.0" +name = "wit-bindgen-core" +version = "0.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c7526379ace8709ee9ab9f2bb50f112d95581063a59ef3097d9c10153886c9" +checksum = "cdf62e62178415a705bda25dc01c54ed65c0f956e4efd00ca89447a9a84f4881" dependencies = [ - "bitflags", + "anyhow", + "heck 0.5.0", + "wit-parser 0.240.0", ] [[package]] @@ -1265,10 +1527,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a01ff9cae7bf5736750d94d91eb8a49f5e3a04aff1d1a3218287d9b2964510f8" dependencies = [ "anyhow", - "heck", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", + "heck 0.4.1", + "wasm-metadata 0.10.20", + "wit-bindgen-core 0.16.0", + "wit-component 0.18.2", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6d585319871ca18805056f69ddec7541770fc855820f9944029cb2b75ea108f" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap", + "prettyplease", + "syn 2.0.110", + "wasm-metadata 0.240.0", + "wit-bindgen-core 0.47.0", + "wit-component 0.240.0", ] [[package]] @@ -1280,10 +1558,25 @@ dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.98", - "wit-bindgen-core", - "wit-bindgen-rust", - "wit-component", + "syn 2.0.110", + "wit-bindgen-core 0.16.0", + "wit-bindgen-rust 0.16.0", + "wit-component 0.18.2", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde589435d322e88b8f708f70e313f60dfb7975ac4e7c623fef6f1e5685d90e8" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.110", + "wit-bindgen-core 0.47.0", + "wit-bindgen-rust 0.47.0", ] [[package]] @@ -1300,9 +1593,28 @@ dependencies = [ "serde_derive", "serde_json", "wasm-encoder 0.38.1", - "wasm-metadata", + "wasm-metadata 0.10.20", "wasmparser 0.118.2", - "wit-parser", + "wit-parser 0.13.2", +] + +[[package]] +name = "wit-component" +version = "0.240.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dc5474b078addc5fe8a72736de8da3acfb3ff324c2491133f8b59594afa1a20" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder 0.240.0", + "wasm-metadata 0.240.0", + "wasmparser 0.240.0", + "wit-parser 0.240.0", ] [[package]] @@ -1322,6 +1634,53 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "wit-parser" +version = "0.240.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9875ea3fa272f57cc1fc50f225a7b94021a7878c484b33792bccad0d93223439" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.240.0", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -1340,5 +1699,59 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.110", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", ] diff --git a/examples/fetch-rs/Cargo.toml b/examples/fetch-rs/Cargo.toml index b0644ab8..f7453a13 100644 --- a/examples/fetch-rs/Cargo.toml +++ b/examples/fetch-rs/Cargo.toml @@ -8,8 +8,11 @@ license = "MIT" serde_json = "1.0.137" spin-executor = "3.0.1" spin-sdk = "3.0.1" -wit-bindgen-rt = { version = "0.26.0", features = ["bitflags"] } +wit-bindgen = "0.47.0" scraper = "0.18.1" +encoding_rs = "0.8.34" +base64 = "0.22.1" +url = "2.5.4" [lib] crate-type = ["cdylib"] diff --git a/examples/fetch-rs/src/bindings.rs b/examples/fetch-rs/src/bindings.rs index 0dd2c8ae..4d639127 100644 --- a/examples/fetch-rs/src/bindings.rs +++ b/examples/fetch-rs/src/bindings.rs @@ -1,106 +1,633 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -// Generated by `wit-bindgen` 0.41.0. DO NOT EDIT! +// Generated by `wit-bindgen` 0.47.0. DO NOT EDIT! // Options used: -// * runtime_path: "wit_bindgen_rt" +pub type RequestOptions = component::fetch_rs::types::RequestOptions; +pub type HttpResponse = component::fetch_rs::types::HttpResponse; #[doc(hidden)] -#[allow(non_snake_case)] +#[allow(non_snake_case, unused_unsafe)] pub unsafe fn _export_fetch_cabi(arg0: *mut u8, arg1: usize) -> *mut u8 { - #[cfg(target_arch = "wasm32")] _rt::run_ctors_once(); - let len0 = arg1; - let bytes0 = _rt::Vec::from_raw_parts(arg0.cast(), len0, len0); - let result1 = T::fetch(_rt::string_lift(bytes0)); - let ptr2 = (&raw mut _RET_AREA.0).cast::(); - match result1 { - Ok(e) => { - *ptr2.add(0).cast::() = (0i32) as u8; - let vec3 = (e.into_bytes()).into_boxed_slice(); - let ptr3 = vec3.as_ptr().cast::(); - let len3 = vec3.len(); - ::core::mem::forget(vec3); - *ptr2.add(2 * ::core::mem::size_of::<*const u8>()).cast::() = len3; - *ptr2.add(::core::mem::size_of::<*const u8>()).cast::<*mut u8>() = ptr3 - .cast_mut(); - } - Err(e) => { - *ptr2.add(0).cast::() = (1i32) as u8; - let vec4 = (e.into_bytes()).into_boxed_slice(); - let ptr4 = vec4.as_ptr().cast::(); - let len4 = vec4.len(); - ::core::mem::forget(vec4); - *ptr2.add(2 * ::core::mem::size_of::<*const u8>()).cast::() = len4; - *ptr2.add(::core::mem::size_of::<*const u8>()).cast::<*mut u8>() = ptr4 - .cast_mut(); - } - }; - ptr2 + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result1 = { + let len0 = arg1; + let bytes0 = _rt::Vec::from_raw_parts(arg0.cast(), len0, len0); + T::fetch(_rt::string_lift(bytes0)) + }; + let ptr2 = (&raw mut _RET_AREA.0).cast::(); + match result1 { + Ok(e) => { + *ptr2.add(0).cast::() = (0i32) as u8; + let vec3 = (e.into_bytes()).into_boxed_slice(); + let ptr3 = vec3.as_ptr().cast::(); + let len3 = vec3.len(); + ::core::mem::forget(vec3); + *ptr2 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::() = len3; + *ptr2 + .add(::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>() = ptr3.cast_mut(); + } + Err(e) => { + *ptr2.add(0).cast::() = (1i32) as u8; + let vec4 = (e.into_bytes()).into_boxed_slice(); + let ptr4 = vec4.as_ptr().cast::(); + let len4 = vec4.len(); + ::core::mem::forget(vec4); + *ptr2 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::() = len4; + *ptr2 + .add(::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>() = ptr4.cast_mut(); + } + }; + ptr2 + } } #[doc(hidden)] #[allow(non_snake_case)] pub unsafe fn __post_return_fetch(arg0: *mut u8) { - let l0 = i32::from(*arg0.add(0).cast::()); - match l0 { - 0 => { - let l1 = *arg0.add(::core::mem::size_of::<*const u8>()).cast::<*mut u8>(); - let l2 = *arg0.add(2 * ::core::mem::size_of::<*const u8>()).cast::(); - _rt::cabi_dealloc(l1, l2, 1); + unsafe { + let l0 = i32::from(*arg0.add(0).cast::()); + match l0 { + 0 => { + let l1 = *arg0 + .add(::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l2 = *arg0 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::(); + _rt::cabi_dealloc(l1, l2, 1); + } + _ => { + let l3 = *arg0 + .add(::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l4 = *arg0 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::(); + _rt::cabi_dealloc(l3, l4, 1); + } } - _ => { - let l3 = *arg0.add(::core::mem::size_of::<*const u8>()).cast::<*mut u8>(); - let l4 = *arg0.add(2 * ::core::mem::size_of::<*const u8>()).cast::(); - _rt::cabi_dealloc(l3, l4, 1); + } +} +#[doc(hidden)] +#[allow(non_snake_case, unused_unsafe)] +pub unsafe fn _export_fetch_advanced_cabi( + arg0: *mut u8, + arg1: usize, + arg2: i32, + arg3: i32, + arg4: i32, + arg5: *mut u8, + arg6: usize, + arg7: i32, + arg8: *mut u8, + arg9: usize, + arg10: i32, + arg11: i32, + arg12: i32, + arg13: i32, + arg14: i32, + arg15: i32, +) -> *mut u8 { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result9 = { + let len0 = arg1; + let bytes0 = _rt::Vec::from_raw_parts(arg0.cast(), len0, len0); + T::fetch_advanced( + _rt::string_lift(bytes0), + component::fetch_rs::types::RequestOptions { + method: match arg2 { + 0 => None, + 1 => { + let e = component::fetch_rs::types::HttpMethod::_lift(arg3 as u8); + Some(e) + } + _ => _rt::invalid_enum_discriminant(), + }, + headers: match arg4 { + 0 => None, + 1 => { + let e = { + let base7 = arg5; + let len7 = arg6; + let mut result7 = _rt::Vec::with_capacity(len7); + for i in 0..len7 { + let base = + base7.add(i * (4 * ::core::mem::size_of::<*const u8>())); + let e7 = { + let l1 = *base.add(0).cast::<*mut u8>(); + let l2 = *base + .add(::core::mem::size_of::<*const u8>()) + .cast::(); + let len3 = l2; + let bytes3 = + _rt::Vec::from_raw_parts(l1.cast(), len3, len3); + let l4 = *base + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l5 = *base + .add(3 * ::core::mem::size_of::<*const u8>()) + .cast::(); + let len6 = l5; + let bytes6 = + _rt::Vec::from_raw_parts(l4.cast(), len6, len6); + + component::fetch_rs::types::HttpHeader { + name: _rt::string_lift(bytes3), + value: _rt::string_lift(bytes6), + } + }; + result7.push(e7); + } + _rt::cabi_dealloc( + base7, + len7 * (4 * ::core::mem::size_of::<*const u8>()), + ::core::mem::size_of::<*const u8>(), + ); + + result7 + }; + Some(e) + } + _ => _rt::invalid_enum_discriminant(), + }, + body: match arg7 { + 0 => None, + 1 => { + let e = { + let len8 = arg9; + let bytes8 = _rt::Vec::from_raw_parts(arg8.cast(), len8, len8); + + _rt::string_lift(bytes8) + }; + Some(e) + } + _ => _rt::invalid_enum_discriminant(), + }, + timeout_secs: match arg10 { + 0 => None, + 1 => { + let e = arg11 as u32; + Some(e) + } + _ => _rt::invalid_enum_discriminant(), + }, + follow_redirects: match arg12 { + 0 => None, + 1 => { + let e = _rt::bool_lift(arg13 as u8); + Some(e) + } + _ => _rt::invalid_enum_discriminant(), + }, + max_redirects: match arg14 { + 0 => None, + 1 => { + let e = arg15 as u32; + Some(e) + } + _ => _rt::invalid_enum_discriminant(), + }, + }, + ) + }; + let ptr10 = (&raw mut _RET_AREA.0).cast::(); + match result9 { + Ok(e) => { + *ptr10.add(0).cast::() = (0i32) as u8; + let component::fetch_rs::types::HttpResponse { + status: status11, + headers: headers11, + body: body11, + is_binary: is_binary11, + } = e; + *ptr10.add(::core::mem::size_of::<*const u8>()).cast::() = + (_rt::as_i32(status11)) as u16; + let vec15 = headers11; + let len15 = vec15.len(); + let layout15 = _rt::alloc::Layout::from_size_align( + vec15.len() * (4 * ::core::mem::size_of::<*const u8>()), + ::core::mem::size_of::<*const u8>(), + ) + .unwrap(); + let (result15, _cleanup15) = wit_bindgen::rt::Cleanup::new(layout15); + if let Some(cleanup) = _cleanup15 { + cleanup.forget(); + } + for (i, e) in vec15.into_iter().enumerate() { + let base = result15.add(i * (4 * ::core::mem::size_of::<*const u8>())); + { + let component::fetch_rs::types::HttpHeader { + name: name12, + value: value12, + } = e; + let vec13 = (name12.into_bytes()).into_boxed_slice(); + let ptr13 = vec13.as_ptr().cast::(); + let len13 = vec13.len(); + ::core::mem::forget(vec13); + *base + .add(::core::mem::size_of::<*const u8>()) + .cast::() = len13; + *base.add(0).cast::<*mut u8>() = ptr13.cast_mut(); + let vec14 = (value12.into_bytes()).into_boxed_slice(); + let ptr14 = vec14.as_ptr().cast::(); + let len14 = vec14.len(); + ::core::mem::forget(vec14); + *base + .add(3 * ::core::mem::size_of::<*const u8>()) + .cast::() = len14; + *base + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>() = ptr14.cast_mut(); + } + } + *ptr10 + .add(3 * ::core::mem::size_of::<*const u8>()) + .cast::() = len15; + *ptr10 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>() = result15; + let vec16 = (body11.into_bytes()).into_boxed_slice(); + let ptr16 = vec16.as_ptr().cast::(); + let len16 = vec16.len(); + ::core::mem::forget(vec16); + *ptr10 + .add(5 * ::core::mem::size_of::<*const u8>()) + .cast::() = len16; + *ptr10 + .add(4 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>() = ptr16.cast_mut(); + *ptr10 + .add(6 * ::core::mem::size_of::<*const u8>()) + .cast::() = (match is_binary11 { + true => 1, + false => 0, + }) as u8; + } + Err(e) => { + *ptr10.add(0).cast::() = (1i32) as u8; + let vec17 = (e.into_bytes()).into_boxed_slice(); + let ptr17 = vec17.as_ptr().cast::(); + let len17 = vec17.len(); + ::core::mem::forget(vec17); + *ptr10 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::() = len17; + *ptr10 + .add(::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>() = ptr17.cast_mut(); + } + }; + ptr10 + } +} +#[doc(hidden)] +#[allow(non_snake_case)] +pub unsafe fn __post_return_fetch_advanced(arg0: *mut u8) { + unsafe { + let l0 = i32::from(*arg0.add(0).cast::()); + match l0 { + 0 => { + let l1 = *arg0 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l2 = *arg0 + .add(3 * ::core::mem::size_of::<*const u8>()) + .cast::(); + let base7 = l1; + let len7 = l2; + for i in 0..len7 { + let base = base7.add(i * (4 * ::core::mem::size_of::<*const u8>())); + { + let l3 = *base.add(0).cast::<*mut u8>(); + let l4 = *base + .add(::core::mem::size_of::<*const u8>()) + .cast::(); + _rt::cabi_dealloc(l3, l4, 1); + let l5 = *base + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l6 = *base + .add(3 * ::core::mem::size_of::<*const u8>()) + .cast::(); + _rt::cabi_dealloc(l5, l6, 1); + } + } + _rt::cabi_dealloc( + base7, + len7 * (4 * ::core::mem::size_of::<*const u8>()), + ::core::mem::size_of::<*const u8>(), + ); + let l8 = *arg0 + .add(4 * ::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l9 = *arg0 + .add(5 * ::core::mem::size_of::<*const u8>()) + .cast::(); + _rt::cabi_dealloc(l8, l9, 1); + } + _ => { + let l10 = *arg0 + .add(::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + let l11 = *arg0 + .add(2 * ::core::mem::size_of::<*const u8>()) + .cast::(); + _rt::cabi_dealloc(l10, l11, 1); + } } } } pub trait Guest { + /// Fetch data from a URL and return the response body as a String + #[allow(async_fn_in_trait)] fn fetch(url: _rt::String) -> Result<_rt::String, _rt::String>; + /// Advanced fetch with full HTTP method support and options + #[allow(async_fn_in_trait)] + fn fetch_advanced( + url: _rt::String, + options: RequestOptions, + ) -> Result; } #[doc(hidden)] -macro_rules! __export_world_fetch_cabi { - ($ty:ident with_types_in $($path_to_types:tt)*) => { - const _ : () = { #[unsafe (export_name = "fetch")] unsafe extern "C" fn - export_fetch(arg0 : * mut u8, arg1 : usize,) -> * mut u8 { unsafe { - $($path_to_types)*:: _export_fetch_cabi::<$ty > (arg0, arg1) } } #[unsafe - (export_name = "cabi_post_fetch")] unsafe extern "C" fn _post_return_fetch(arg0 : - * mut u8,) { unsafe { $($path_to_types)*:: __post_return_fetch::<$ty > (arg0) } } - }; - }; + +macro_rules! __export_world_fetch_cabi{ + ($ty:ident with_types_in $($path_to_types:tt)*) => (const _: () = { + + #[unsafe(export_name = "fetch")] + unsafe extern "C" fn export_fetch(arg0: *mut u8,arg1: usize,) -> *mut u8 { + unsafe { $($path_to_types)*::_export_fetch_cabi::<$ty>(arg0, arg1) } + } + #[unsafe(export_name = "cabi_post_fetch")] + unsafe extern "C" fn _post_return_fetch(arg0: *mut u8,) { + unsafe { $($path_to_types)*::__post_return_fetch::<$ty>(arg0) } + } + #[unsafe(export_name = "fetch-advanced")] + unsafe extern "C" fn export_fetch_advanced(arg0: *mut u8,arg1: usize,arg2: i32,arg3: i32,arg4: i32,arg5: *mut u8,arg6: usize,arg7: i32,arg8: *mut u8,arg9: usize,arg10: i32,arg11: i32,arg12: i32,arg13: i32,arg14: i32,arg15: i32,) -> *mut u8 { + unsafe { $($path_to_types)*::_export_fetch_advanced_cabi::<$ty>(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15) } + } + #[unsafe(export_name = "cabi_post_fetch-advanced")] + unsafe extern "C" fn _post_return_fetch_advanced(arg0: *mut u8,) { + unsafe { $($path_to_types)*::__post_return_fetch_advanced::<$ty>(arg0) } + } + };); } #[doc(hidden)] pub(crate) use __export_world_fetch_cabi; + #[cfg_attr(target_pointer_width = "64", repr(align(8)))] #[cfg_attr(target_pointer_width = "32", repr(align(4)))] -struct _RetArea([::core::mem::MaybeUninit; 3 * ::core::mem::size_of::<*const u8>()]); -static mut _RET_AREA: _RetArea = _RetArea( - [::core::mem::MaybeUninit::uninit(); 3 * ::core::mem::size_of::<*const u8>()], -); -#[rustfmt::skip] +struct _RetArea([::core::mem::MaybeUninit; 7 * ::core::mem::size_of::<*const u8>()]); +static mut _RET_AREA: _RetArea = + _RetArea([::core::mem::MaybeUninit::uninit(); 7 * ::core::mem::size_of::<*const u8>()]); +#[allow(dead_code, clippy::all)] +pub mod component { + pub mod fetch_rs { + + #[allow(dead_code, async_fn_in_trait, unused_imports, clippy::all)] + pub mod types { + #[used] + #[doc(hidden)] + static __FORCE_SECTION_REF: fn() = + super::super::super::__link_custom_section_describing_imports; + + use super::super::super::_rt; + /// HTTP method for the request + #[repr(u8)] + #[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] + pub enum HttpMethod { + Get, + Head, + Post, + Put, + Patch, + Delete, + Options, + } + impl ::core::fmt::Debug for HttpMethod { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + HttpMethod::Get => f.debug_tuple("HttpMethod::Get").finish(), + HttpMethod::Head => f.debug_tuple("HttpMethod::Head").finish(), + HttpMethod::Post => f.debug_tuple("HttpMethod::Post").finish(), + HttpMethod::Put => f.debug_tuple("HttpMethod::Put").finish(), + HttpMethod::Patch => f.debug_tuple("HttpMethod::Patch").finish(), + HttpMethod::Delete => f.debug_tuple("HttpMethod::Delete").finish(), + HttpMethod::Options => f.debug_tuple("HttpMethod::Options").finish(), + } + } + } + + impl HttpMethod { + #[doc(hidden)] + pub unsafe fn _lift(val: u8) -> HttpMethod { + if !cfg!(debug_assertions) { + return unsafe { ::core::mem::transmute(val) }; + } + + match val { + 0 => HttpMethod::Get, + 1 => HttpMethod::Head, + 2 => HttpMethod::Post, + 3 => HttpMethod::Put, + 4 => HttpMethod::Patch, + 5 => HttpMethod::Delete, + 6 => HttpMethod::Options, + + _ => panic!("invalid enum discriminant"), + } + } + } + + /// HTTP header + #[derive(Clone)] + pub struct HttpHeader { + pub name: _rt::String, + pub value: _rt::String, + } + impl ::core::fmt::Debug for HttpHeader { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct("HttpHeader") + .field("name", &self.name) + .field("value", &self.value) + .finish() + } + } + /// Request options for advanced HTTP features + #[derive(Clone)] + pub struct RequestOptions { + /// HTTP method (default: GET) + pub method: Option, + /// Request headers + pub headers: Option<_rt::Vec>, + /// Request body (for POST, PUT, PATCH) + pub body: Option<_rt::String>, + /// Timeout in seconds (default: 30) + pub timeout_secs: Option, + /// Whether to follow redirects (default: true) + pub follow_redirects: Option, + /// Maximum number of redirects to follow (default: 10) + pub max_redirects: Option, + } + impl ::core::fmt::Debug for RequestOptions { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct("RequestOptions") + .field("method", &self.method) + .field("headers", &self.headers) + .field("body", &self.body) + .field("timeout-secs", &self.timeout_secs) + .field("follow-redirects", &self.follow_redirects) + .field("max-redirects", &self.max_redirects) + .finish() + } + } + /// HTTP response + #[derive(Clone)] + pub struct HttpResponse { + /// HTTP status code + pub status: u16, + /// Response headers + pub headers: _rt::Vec, + /// Response body + pub body: _rt::String, + /// Whether the body is binary (base64 encoded) or text + pub is_binary: bool, + } + impl ::core::fmt::Debug for HttpResponse { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_struct("HttpResponse") + .field("status", &self.status) + .field("headers", &self.headers) + .field("body", &self.body) + .field("is-binary", &self.is_binary) + .finish() + } + } + } + } +} mod _rt { #![allow(dead_code, clippy::all)] + pub use alloc_crate::string::String; + pub use alloc_crate::vec::Vec; + #[cfg(target_arch = "wasm32")] pub fn run_ctors_once() { - wit_bindgen_rt::run_ctors_once(); + wit_bindgen::rt::run_ctors_once(); } - pub use alloc_crate::vec::Vec; pub unsafe fn string_lift(bytes: Vec) -> String { if cfg!(debug_assertions) { String::from_utf8(bytes).unwrap() } else { - String::from_utf8_unchecked(bytes) + unsafe { String::from_utf8_unchecked(bytes) } } } pub unsafe fn cabi_dealloc(ptr: *mut u8, size: usize, align: usize) { if size == 0 { return; } - let layout = alloc::Layout::from_size_align_unchecked(size, align); - alloc::dealloc(ptr, layout); + unsafe { + let layout = alloc::Layout::from_size_align_unchecked(size, align); + alloc::dealloc(ptr, layout); + } + } + pub unsafe fn invalid_enum_discriminant() -> T { + if cfg!(debug_assertions) { + panic!("invalid enum discriminant") + } else { + unsafe { core::hint::unreachable_unchecked() } + } + } + pub unsafe fn bool_lift(val: u8) -> bool { + if cfg!(debug_assertions) { + match val { + 0 => false, + 1 => true, + _ => panic!("invalid bool discriminant"), + } + } else { + val != 0 + } + } + + pub fn as_i32(t: T) -> i32 { + t.as_i32() + } + + pub trait AsI32 { + fn as_i32(self) -> i32; + } + + impl<'a, T: Copy + AsI32> AsI32 for &'a T { + fn as_i32(self) -> i32 { + (*self).as_i32() + } + } + + impl AsI32 for i32 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + + impl AsI32 for u32 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + + impl AsI32 for i16 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + + impl AsI32 for u16 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + + impl AsI32 for i8 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + + impl AsI32 for u8 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + + impl AsI32 for char { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + + impl AsI32 for usize { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } } - pub use alloc_crate::string::String; - extern crate alloc as alloc_crate; pub use alloc_crate::alloc; + extern crate alloc as alloc_crate; } + /// Generates `#[unsafe(no_mangle)]` functions to export the specified type as /// the root implementation of all generated traits. /// @@ -119,30 +646,39 @@ mod _rt { /// ``` #[allow(unused_macros)] #[doc(hidden)] + macro_rules! __export_fetch_impl { - ($ty:ident) => { - self::export!($ty with_types_in self); - }; - ($ty:ident with_types_in $($path_to_types_root:tt)*) => { - $($path_to_types_root)*:: __export_world_fetch_cabi!($ty with_types_in - $($path_to_types_root)*); - }; + ($ty:ident) => (self::export!($ty with_types_in self);); + ($ty:ident with_types_in $($path_to_types_root:tt)*) => ( + $($path_to_types_root)*::__export_world_fetch_cabi!($ty with_types_in $($path_to_types_root)*); + ) } #[doc(inline)] pub(crate) use __export_fetch_impl as export; + #[cfg(target_arch = "wasm32")] -#[unsafe( - link_section = "component-type:wit-bindgen:0.41.0:component:fetch-rs:fetch:encoded world" -)] +#[unsafe(link_section = "component-type:wit-bindgen:0.47.0:component:fetch-rs:fetch:encoded world")] #[doc(hidden)] #[allow(clippy::octal_escapes)] -pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 184] = *b"\ -\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07=\x01A\x02\x01A\x03\x01\ -j\x01s\x01s\x01@\x01\x03urls\0\0\x04\0\x05fetch\x01\x01\x04\0\x18component:fetch\ --rs/fetch\x04\0\x0b\x0b\x01\0\x05fetch\x03\0\0\0G\x09producers\x01\x0cprocessed-\ -by\x02\x0dwit-component\x070.227.1\x10wit-bindgen-rust\x060.41.0"; +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 665] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\x9d\x04\x01A\x02\x01\ +A\x10\x01B\x0e\x01m\x07\x03get\x04head\x04post\x03put\x05patch\x06delete\x07opti\ +ons\x04\0\x0bhttp-method\x03\0\0\x01r\x02\x04names\x05values\x04\0\x0bhttp-heade\ +r\x03\0\x02\x01k\x01\x01p\x03\x01k\x05\x01ks\x01ky\x01k\x7f\x01r\x06\x06method\x04\ +\x07headers\x06\x04body\x07\x0ctimeout-secs\x08\x10follow-redirects\x09\x0dmax-r\ +edirects\x08\x04\0\x0frequest-options\x03\0\x0a\x01r\x04\x06status{\x07headers\x05\ +\x04bodys\x09is-binary\x7f\x04\0\x0dhttp-response\x03\0\x0c\x03\0\x18component:f\ +etch-rs/types\x05\0\x02\x03\0\0\x0bhttp-method\x03\0\x0bhttp-method\x03\0\x01\x02\ +\x03\0\0\x0bhttp-header\x03\0\x0bhttp-header\x03\0\x03\x02\x03\0\0\x0frequest-op\ +tions\x03\0\x0frequest-options\x03\0\x05\x02\x03\0\0\x0dhttp-response\x03\0\x0dh\ +ttp-response\x03\0\x07\x01j\x01s\x01s\x01@\x01\x03urls\0\x09\x04\0\x05fetch\x01\x0a\ +\x01j\x01\x08\x01s\x01@\x02\x03urls\x07options\x06\0\x0b\x04\0\x0efetch-advanced\ +\x01\x0c\x04\0\x18component:fetch-rs/fetch\x04\0\x0b\x0b\x01\0\x05fetch\x03\0\0\0\ +G\x09producers\x01\x0cprocessed-by\x02\x0dwit-component\x070.240.0\x10wit-bindge\ +n-rust\x060.47.0"; + #[inline(never)] #[doc(hidden)] pub fn __link_custom_section_describing_imports() { - wit_bindgen_rt::maybe_link_cabi_realloc(); + wit_bindgen::rt::maybe_link_cabi_realloc(); } diff --git a/examples/fetch-rs/src/lib.rs b/examples/fetch-rs/src/lib.rs index 4fddde91..4308e98a 100644 --- a/examples/fetch-rs/src/lib.rs +++ b/examples/fetch-rs/src/lib.rs @@ -1,12 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -use spin_sdk::http::{send, Request, Response}; +use spin_sdk::http::{send, Method, Request, Response}; +use url::Url; #[allow(warnings)] mod bindings; +use base64::Engine; +use bindings::component::fetch_rs::types::{HttpHeader, HttpMethod, HttpResponse, RequestOptions}; use bindings::Guest; +use encoding_rs::Encoding; use serde_json::Value; struct Component; @@ -34,6 +38,314 @@ impl Guest for Component { Ok(body.into_owned()) }) } + + fn fetch_advanced(url: String, options: RequestOptions) -> Result { + spin_executor::run(async move { + let result = fetch_with_retry(&url, &options, 3).await; + result.map_err(|e| e.to_string()) + }) + } +} + +/// Fetch with retry logic for transient errors +async fn fetch_with_retry( + url: &str, + options: &RequestOptions, + max_retries: u32, +) -> Result> { + let method = options.method.as_ref().unwrap_or(&HttpMethod::Get); + // Per RFC 7231, GET, HEAD, OPTIONS, PUT, and DELETE are considered idempotent. + // PUT is idempotent because it should replace the entire resource with the given representation. + let is_idempotent = matches!( + method, + HttpMethod::Get + | HttpMethod::Head + | HttpMethod::Options + | HttpMethod::Put + | HttpMethod::Delete + ); + + let mut attempt = 0; + let mut last_error = None; + + while attempt <= max_retries { + match fetch_once(url, options).await { + Ok(response) => { + // Check if we should retry based on status code + if is_transient_error(response.status) && attempt < max_retries { + // For transient errors, retry if idempotent + if !is_idempotent { + return Ok(response); + } + + last_error = Some(format!("Transient error: status {}", response.status)); + attempt += 1; + continue; + } + + return Ok(response); + } + Err(e) => { + // Check if it's a retryable network error + let error_str = e.to_string(); + if is_retryable_error(&error_str) && attempt < max_retries && is_idempotent { + last_error = Some(error_str); + attempt += 1; + continue; + } + + return Err(e); + } + } + } + + Err(last_error + .unwrap_or_else(|| "Max retries exceeded".to_string()) + .into()) +} + +/// Check if the error is retryable +fn is_retryable_error(error: &str) -> bool { + error.contains("timeout") + || error.contains("connection") + || error.contains("network") + || error.contains("dns") +} + +/// Check if status code represents a transient error +fn is_transient_error(status: u16) -> bool { + matches!(status, 429 | 502 | 503 | 504) +} + +/// Perform a single fetch request +async fn fetch_once( + url: &str, + options: &RequestOptions, +) -> Result> { + let mut current_url = url.to_string(); + let mut current_method = options.method; + let current_headers = options.headers.clone(); + let mut current_body = options.body.clone(); + + let max_redirects = if options.follow_redirects.unwrap_or(true) { + options.max_redirects.unwrap_or(10) + } else { + 0 + }; + let mut redirect_count = 0; + + loop { + // Build the request + let method = current_method.as_ref().unwrap_or(&HttpMethod::Get); + let spin_method = convert_method(method); + + let mut builder = Request::builder(); + builder.method(spin_method); + builder.uri(¤t_url); + + // Add custom headers + if let Some(headers) = ¤t_headers { + for header in headers { + builder.header(&header.name, &header.value); + } + } + + // Add request body for methods that support it + if let Some(body) = ¤t_body { + if matches!( + method, + HttpMethod::Post | HttpMethod::Put | HttpMethod::Patch + ) { + builder.body(body.as_str()); + } + } + + // Build the request + let request = builder.build(); + + // Send the request + let response: Response = send(request).await?; + + // Check for redirects + if is_redirect_status(response.status()) && redirect_count < max_redirects { + let location = response + .header("location") + .and_then(|v| v.as_str()) + .ok_or("Redirect response missing Location header")?; + + // Resolve relative URLs using proper URL parsing + current_url = resolve_relative_url(¤t_url, location)?; + + // Per HTTP spec: 301 and 302 with POST should convert to GET (historical behavior) + // 303 always converts to GET + let status = *response.status(); + if status == 303 + || ((status == 301 || status == 302) && matches!(method, HttpMethod::Post)) + { + current_method = Some(HttpMethod::Get); + current_body = None; // Clear body when converting to GET + } + + redirect_count += 1; + continue; + } + + // No more redirects, process the response + return process_response(response).await; + } +} + +/// Check if status code is a redirect +fn is_redirect_status(status: &u16) -> bool { + matches!(status, 301 | 302 | 303 | 307 | 308) +} + +/// Resolve relative URL against base URL using proper URL parsing +fn resolve_relative_url(base: &str, relative: &str) -> Result> { + let base_url = Url::parse(base)?; + let resolved = base_url.join(relative)?; + Ok(resolved.to_string()) +} + +/// Process the HTTP response +async fn process_response(response: Response) -> Result> { + let status = *response.status(); + + // Extract headers + let mut headers = Vec::new(); + for (name, value) in response.headers() { + if let Some(value_str) = value.as_str() { + headers.push(HttpHeader { + name: name.to_string(), + value: value_str.to_string(), + }); + } + } + + // Handle special status codes + if status == 204 || status == 304 { + // No Content or Not Modified - no body expected + return Ok(HttpResponse { + status, + headers, + body: String::new(), + is_binary: false, + }); + } + + // Handle 1xx informational responses + // These are interim responses and don't have a body. + // The client should continue waiting for the final response. + if (100..200).contains(&status) { + return Ok(HttpResponse { + status, + headers, + body: String::new(), + is_binary: false, + }); + } + + // Get the response body + let body_bytes = response.body(); + + // Detect charset from Content-Type header + let content_type = response + .header("content-type") + .and_then(|v| v.as_str()) + .unwrap_or(""); + + let (is_binary, body_string) = decode_body(body_bytes, content_type)?; + + Ok(HttpResponse { + status, + headers, + body: body_string, + is_binary, + }) +} + +/// Decode response body based on content type and charset +fn decode_body( + body_bytes: &[u8], + content_type: &str, +) -> Result<(bool, String), Box> { + // Check if content type indicates binary data + if is_binary_content_type(content_type) { + // Encode binary data as base64 + let encoded = base64::prelude::BASE64_STANDARD.encode(body_bytes); + return Ok((true, encoded)); + } + + // Try to extract charset from Content-Type header + let encoding = extract_charset(content_type); + + // Attempt to decode with the detected/specified encoding + let (decoded, _, had_errors) = encoding.decode(body_bytes); + + if had_errors { + // If decoding failed, try UTF-8 + match String::from_utf8(body_bytes.to_vec()) { + Ok(s) => Ok((false, s)), + Err(_) => { + // Fall back to lossy UTF-8 conversion + Ok((false, String::from_utf8_lossy(body_bytes).into_owned())) + } + } + } else { + Ok((false, decoded.into_owned())) + } +} + +/// Check if content type indicates binary data +fn is_binary_content_type(content_type: &str) -> bool { + let binary_types = [ + "image/", + "audio/", + "video/", + "application/octet-stream", + "application/pdf", + "application/zip", + "application/x-", + ]; + + binary_types + .iter() + .any(|&t| content_type.to_lowercase().contains(t)) +} + +/// Extract charset from Content-Type header +fn extract_charset(content_type: &str) -> &'static Encoding { + // Look for charset parameter in Content-Type + if let Some(charset_start) = content_type.find("charset=") { + let charset_value = &content_type[charset_start + 8..]; + let charset_name = charset_value + .split(';') + .next() + .unwrap_or("") + .trim() + .trim_matches('"') + .trim_matches('\''); + + if let Some(encoding) = Encoding::for_label(charset_name.as_bytes()) { + return encoding; + } + } + + // Default to UTF-8 + encoding_rs::UTF_8 +} + +/// Convert WIT HTTP method to spin-sdk Method +fn convert_method(method: &HttpMethod) -> Method { + match method { + HttpMethod::Get => Method::Get, + HttpMethod::Head => Method::Head, + HttpMethod::Post => Method::Post, + HttpMethod::Put => Method::Put, + HttpMethod::Patch => Method::Patch, + HttpMethod::Delete => Method::Delete, + HttpMethod::Options => Method::Options, + } } fn html_to_markdown(html: &str) -> String { @@ -43,8 +355,13 @@ fn html_to_markdown(html: &str) -> String { for element in fragment.select(&text_selector) { let tag_name = element.value().name(); - let text = element.text().collect::>().join(" ").trim().to_string(); - + let text = element + .text() + .collect::>() + .join(" ") + .trim() + .to_string(); + if text.is_empty() { continue; } @@ -63,7 +380,7 @@ fn html_to_markdown(html: &str) -> String { } else { markdown.push_str(&format!("{}\n\n", text)); } - }, + } _ => markdown.push_str(&format!("{}\n\n", text)), } } diff --git a/examples/fetch-rs/wit/world.wit b/examples/fetch-rs/wit/world.wit index effcfb09..915c2714 100644 --- a/examples/fetch-rs/wit/world.wit +++ b/examples/fetch-rs/wit/world.wit @@ -1,7 +1,59 @@ package component:fetch-rs; +interface types { + /// HTTP method for the request + enum http-method { + get, + head, + post, + put, + patch, + delete, + options, + } + + /// HTTP header + record http-header { + name: string, + value: string, + } + + /// Request options for advanced HTTP features + record request-options { + /// HTTP method (default: GET) + method: option, + /// Request headers + headers: option>, + /// Request body (for POST, PUT, PATCH) + body: option, + /// Timeout in seconds (default: 30) + timeout-secs: option, + /// Whether to follow redirects (default: true) + follow-redirects: option, + /// Maximum number of redirects to follow (default: 10) + max-redirects: option, + } + + /// HTTP response + record http-response { + /// HTTP status code + status: u16, + /// Response headers + headers: list, + /// Response body + body: string, + /// Whether the body is binary (base64 encoded) or text + is-binary: bool, + } +} + /// An example world for the component to target. world fetch { + use types.{http-method, http-header, request-options, http-response}; + /// Fetch data from a URL and return the response body as a String export fetch: func(url: string) -> result; + + /// Advanced fetch with full HTTP method support and options + export fetch-advanced: func(url: string, options: request-options) -> result; } diff --git a/tests/fetch_advanced_integration_test.rs b/tests/fetch_advanced_integration_test.rs new file mode 100644 index 00000000..d1caea23 --- /dev/null +++ b/tests/fetch_advanced_integration_test.rs @@ -0,0 +1,251 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +use anyhow::{Context, Result}; +use serde_json::json; +use tempfile::TempDir; +use wassette::LifecycleManager; + +mod common; +use common::build_fetch_component; + +async fn setup_lifecycle_manager() -> Result<(LifecycleManager, TempDir)> { + let tempdir = tempfile::tempdir().context("Failed to create temporary directory")?; + let manager = LifecycleManager::new(&tempdir).await?; + Ok((manager, tempdir)) +} + +/// Test that fetch-advanced properly enforces network permissions +#[tokio::test] +async fn test_fetch_advanced_permission_enforcement() -> Result<()> { + let (manager, _tempdir) = setup_lifecycle_manager().await?; + let component_path = build_fetch_component().await?; + + let component_id = manager + .load_component(&format!("file://{}", component_path.to_str().unwrap())) + .await? + .component_id; + + let target_url = "https://example.com/"; + + println!("Testing fetch-advanced without network permission..."); + + let options = json!({ + "method": "get", + "headers": null, + "body": null, + "timeout-secs": 30, + "follow-redirects": true, + "max-redirects": 10 + }); + + let result = manager + .execute_component_call( + &component_id, + "fetch-advanced", + &json!({"url": target_url, "options": options}).to_string(), + ) + .await; + + match result { + Ok(response) => { + println!("fetch-advanced response: {response}"); + + // Check if the response contains an error indicating the request was blocked + if response.contains("HttpRequestDenied") || response.contains("Err") { + println!("✅ Network request properly blocked by policy!"); + } else { + panic!( + "Expected network request to be blocked, but got successful response: {response}" + ); + } + } + Err(e) => { + let error_msg = e.to_string(); + println!("Error response: {error_msg}"); + + // Network should be denied + assert!( + error_msg.contains("HttpRequestDenied") || error_msg.contains("permission") || error_msg.contains("denied"), + "Expected permission-related error, got: {error_msg}" + ); + println!("✅ Network request properly blocked!"); + } + } + + Ok(()) +} + +/// Test that fetch-advanced handles POST method correctly +#[tokio::test] +async fn test_fetch_advanced_post_method() -> Result<()> { + let (manager, _tempdir) = setup_lifecycle_manager().await?; + let component_path = build_fetch_component().await?; + + let component_id = manager + .load_component(&format!("file://{}", component_path.to_str().unwrap())) + .await? + .component_id; + + let target_url = "https://example.com/post"; + + println!("Testing fetch-advanced POST method..."); + + let test_data = json!({"test": "data", "value": 123}); + let options = json!({ + "method": "post", + "headers": [ + {"name": "Content-Type", "value": "application/json"} + ], + "body": test_data.to_string(), + "timeout-secs": 30, + "follow-redirects": true, + "max-redirects": 10 + }); + + let result = manager + .execute_component_call( + &component_id, + "fetch-advanced", + &json!({"url": target_url, "options": options}).to_string(), + ) + .await; + + // Without network permission, this should fail + match result { + Ok(response) => { + println!("Response: {response}"); + assert!( + response.contains("HttpRequestDenied") || response.contains("Err"), + "Expected request to be denied without permission" + ); + println!("✅ POST method test passed - request properly blocked!"); + } + Err(e) => { + let error_msg = e.to_string(); + println!("Error: {error_msg}"); + assert!( + error_msg.contains("HttpRequestDenied") || error_msg.contains("permission") || error_msg.contains("denied"), + "Expected permission error" + ); + println!("✅ POST method test passed - request properly blocked!"); + } + } + + Ok(()) +} + +/// Test that fetch-advanced handles HEAD method correctly +#[tokio::test] +async fn test_fetch_advanced_head_method() -> Result<()> { + let (manager, _tempdir) = setup_lifecycle_manager().await?; + let component_path = build_fetch_component().await?; + + let component_id = manager + .load_component(&format!("file://{}", component_path.to_str().unwrap())) + .await? + .component_id; + + let target_url = "https://example.com/"; + + println!("Testing fetch-advanced HEAD method..."); + + let options = json!({ + "method": "head", + "headers": null, + "body": null, + "timeout-secs": 30, + "follow-redirects": true, + "max-redirects": 10 + }); + + let result = manager + .execute_component_call( + &component_id, + "fetch-advanced", + &json!({"url": target_url, "options": options}).to_string(), + ) + .await; + + // Without network permission, this should fail + match result { + Ok(response) => { + println!("Response: {response}"); + assert!( + response.contains("HttpRequestDenied") || response.contains("Err"), + "Expected request to be denied without permission" + ); + println!("✅ HEAD method test passed - request properly blocked!"); + } + Err(e) => { + let error_msg = e.to_string(); + println!("Error: {error_msg}"); + assert!( + error_msg.contains("HttpRequestDenied") || error_msg.contains("permission") || error_msg.contains("denied"), + "Expected permission error" + ); + println!("✅ HEAD method test passed - request properly blocked!"); + } + } + + Ok(()) +} + +/// Test that fetch-advanced handles custom headers correctly +#[tokio::test] +async fn test_fetch_advanced_with_headers() -> Result<()> { + let (manager, _tempdir) = setup_lifecycle_manager().await?; + let component_path = build_fetch_component().await?; + + let component_id = manager + .load_component(&format!("file://{}", component_path.to_str().unwrap())) + .await? + .component_id; + + let target_url = "https://example.com/headers"; + + println!("Testing fetch-advanced with custom headers..."); + + let options = json!({ + "method": "get", + "headers": [ + {"name": "X-Custom-Header", "value": "test-value"}, + {"name": "User-Agent", "value": "wassette-test"} + ], + "body": null, + "timeout-secs": 30, + "follow-redirects": true, + "max-redirects": 10 + }); + + let result = manager + .execute_component_call( + &component_id, + "fetch-advanced", + &json!({"url": target_url, "options": options}).to_string(), + ) + .await; + + // Without network permission, this should fail + match result { + Ok(response) => { + println!("Response: {response}"); + assert!( + response.contains("HttpRequestDenied") || response.contains("Err"), + "Expected request to be denied without permission" + ); + println!("✅ Custom headers test passed - request properly blocked!"); + } + Err(e) => { + let error_msg = e.to_string(); + println!("Error: {error_msg}"); + assert!( + error_msg.contains("HttpRequestDenied") || error_msg.contains("permission") || error_msg.contains("denied"), + "Expected permission error" + ); + println!("✅ Custom headers test passed - request properly blocked!"); + } + } + + Ok(()) +}