diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 41823bc..6260a74 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -16,6 +16,7 @@ updates: wasm-tools: patterns: - wasmparser + - wasmprinter - wat - wit-component - wit-parser diff --git a/Cargo.lock b/Cargo.lock index 9297fbf..341433c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,18 +2,50 @@ # 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 = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[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.6", +] + [[package]] name = "auditable-serde" version = "0.9.0" @@ -26,6 +58,12 @@ dependencies = [ "topological-sort", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "beef" version = "0.5.2" @@ -39,754 +77,2062 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" [[package]] -name = "cfg-if" -version = "1.0.4" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] [[package]] -name = "crc32fast" -version = "1.5.2" +name = "bumpalo" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" dependencies = [ - "cfg-if", + "allocator-api2", ] [[package]] -name = "displaydoc" -version = "0.2.7" +name = "cc" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +checksum = "f74872d07caf508b30a21f6836e7d7016a2eaf7d9ff4f48deaa58cd8a0407630" dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", + "find-msvc-tools", + "jobserver", + "libc", + "shlex", ] [[package]] -name = "equivalent" -version = "1.0.2" +name = "cfg-if" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" [[package]] -name = "extract-wit" -version = "0.1.0" +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" dependencies = [ - "anyhow", - "semver", - "wasmparser 0.259.0", - "wit-bindgen", - "wit-component", - "wit-parser", + "thiserror 2.0.21", ] [[package]] -name = "filesystem-loader" -version = "0.1.0" -dependencies = [ - "wit-bindgen", -] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" [[package]] -name = "fixedbitset" -version = "0.4.2" +name = "cpp_demangle" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +checksum = "0667304c32ea56cb4cd6d2d7c0cfe9a2f8041229db8c033af7f8d69492429def" +dependencies = [ + "cfg-if", +] [[package]] -name = "flate2" -version = "1.1.10" +name = "cpufeatures" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ - "crc32fast", - "miniz_oxide", - "zlib-rs", + "libc", ] [[package]] -name = "fnv" -version = "1.0.7" +name = "cranelift-assembler-x64" +version = "0.136.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "658e8623bea025d142fb11c299b8c6572ffe9fd663b52887c4dc124735cc130b" +dependencies = [ + "cranelift-assembler-x64-meta", +] [[package]] -name = "foldhash" -version = "0.2.0" +name = "cranelift-assembler-x64-meta" +version = "0.136.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +checksum = "a4b12b8c55c31c3d8598c2de9a657c3b978f905f6886c43e02216e0b15fe45a3" +dependencies = [ + "cranelift-srcgen", +] [[package]] -name = "form_urlencoded" -version = "1.2.2" +name = "cranelift-bforest" +version = "0.136.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +checksum = "894b984f82c5a36f05fdd4973fbf4d37d69cd5cdf8c16e8ffe3af4a9cdfe193c" dependencies = [ - "percent-encoding", + "cranelift-entity", + "wasmtime-internal-core", ] [[package]] -name = "hashbrown" -version = "0.17.1" +name = "cranelift-bitset" +version = "0.136.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +checksum = "3a1e4c34abe94193ada09f821f474be87ac8a1fbbacd87058571ef04c1bf4009" dependencies = [ - "foldhash", "serde", - "serde_core", + "serde_derive", + "wasmtime-internal-core", ] [[package]] -name = "heck" -version = "0.5.0" +name = "cranelift-codegen" +version = "0.136.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "http-loader" -version = "0.1.0" +checksum = "63826cef0bf70e61d56cc930aa4ee423638f66dc3cfc10d683e73138a6d177b8" dependencies = [ - "wit-bindgen", + "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 = "icu_collections" -version = "2.3.0" +name = "cranelift-codegen-meta" +version = "0.136.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +checksum = "26de7849112293681a19f7525c689e1d73dc6eb2687355170fb7458c4da8d353" dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", + "cranelift-assembler-x64-meta", + "cranelift-codegen-shared", + "cranelift-srcgen", + "heck", + "pulley-interpreter", ] [[package]] -name = "icu_locale_core" -version = "2.3.0" +name = "cranelift-codegen-shared" +version = "0.136.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] +checksum = "df315a8b1cfd6e620971e3d4249bd2c4528167717e8f48a6317a87c55891bcb8" [[package]] -name = "icu_normalizer" -version = "2.3.0" +name = "cranelift-control" +version = "0.136.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +checksum = "2c3fff7610fb17f228f16598e1a044db79a12806ece7496ff2c69cf1a27a79a5" dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", + "arbitrary", ] [[package]] -name = "icu_normalizer_data" -version = "2.3.0" +name = "cranelift-entity" +version = "0.136.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" +checksum = "6158b1ac381792ad11e670abbcd15db8b1737e84bfa8a7f2bd7eb41388a92530" +dependencies = [ + "cranelift-bitset", + "serde", + "serde_derive", + "wasmtime-internal-core", +] [[package]] -name = "icu_properties" -version = "2.3.0" +name = "cranelift-frontend" +version = "0.136.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +checksum = "099361a0919067086dcde624a281ce719834cebf8544d6c24646f5e924107234" dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", + "cranelift-codegen", + "hashbrown 0.17.1", + "log", + "smallvec", + "target-lexicon", ] [[package]] -name = "icu_properties_data" -version = "2.3.0" +name = "cranelift-isle" +version = "0.136.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" +checksum = "2a2f30ea3fd5a11f7d3473976d3b62b5b3f2b62fa943c85e0331d88bf505082c" [[package]] -name = "icu_provider" -version = "2.3.1" +name = "cranelift-native" +version = "0.136.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +checksum = "397508a8d817dc74019603ac5a4c4e40b4df627e5aed4f2f8088345d4c01d401" dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", + "cranelift-codegen", + "libc", + "target-lexicon", ] [[package]] -name = "id-arena" -version = "2.3.0" +name = "cranelift-srcgen" +version = "0.136.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +checksum = "b4944ef05513e1c1a85a945cf8f29d95ddea8bcbb19e0bfd4e71063450967420" [[package]] -name = "idna" -version = "1.1.0" +name = "crc32fast" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", + "cfg-if", ] [[package]] -name = "idna_adapter" -version = "1.2.2" +name = "crossbeam-deque" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" dependencies = [ - "icu_normalizer", - "icu_properties", + "crossbeam-epoch", + "crossbeam-utils", ] [[package]] -name = "indexmap" -version = "2.14.2" +name = "crossbeam-epoch" +version = "0.9.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" dependencies = [ - "equivalent", - "hashbrown", - "serde", - "serde_core", + "crossbeam-utils", ] [[package]] -name = "itoa" -version = "1.0.18" +name = "crossbeam-utils" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" [[package]] -name = "lazy_static" -version = "1.5.0" +name = "crypto-common" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] [[package]] -name = "leb128fmt" -version = "0.1.0" +name = "debugid" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "uuid", +] [[package]] -name = "litemap" -version = "0.8.3" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] [[package]] -name = "log" -version = "0.4.34" +name = "directories-next" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] [[package]] -name = "logos" -version = "0.14.4" +name = "dirs-sys-next" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7251356ef8cb7aec833ddf598c6cb24d17b689d20b993f9d11a3d764e34e6458" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ - "logos-derive", + "libc", + "redox_users", + "winapi", ] [[package]] -name = "logos-codegen" -version = "0.14.4" +name = "displaydoc" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59f80069600c0d66734f5ff52cc42f2dabd6b29d205f333d61fd7832e9e9963f" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ - "beef", - "fnv", - "lazy_static", "proc-macro2", "quote", - "regex-syntax", - "syn 2.0.119", + "syn 3.0.6", ] [[package]] -name = "logos-derive" -version = "0.14.4" +name = "either" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24fb722b06a9dc12adb0963ed585f19fc61dc5413e6a9be9422ef92c091e731d" -dependencies = [ - "logos-codegen", -] +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" [[package]] -name = "macro-string" -version = "0.3.0" +name = "embedded-io" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e7b3a5bbb2f63aaa223a671ea1bd0e7bc16bd30ce387324cf63995deaddbbd2" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" [[package]] -name = "memchr" -version = "2.8.3" +name = "embedded-io" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" [[package]] -name = "miette" -version = "7.6.0" +name = "encoding_rs" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" dependencies = [ "cfg-if", - "miette-derive", - "serde", - "unicode-width", + "core_detect", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", ] [[package]] -name = "miette-derive" -version = "7.6.0" +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "miniz_oxide" -version = "0.9.1" +name = "errno" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ - "adler2", - "simd-adler32", + "libc", + "windows-sys", ] [[package]] -name = "oci-loader" +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "filesystem-loader" version = "0.1.0" dependencies = [ "wit-bindgen", ] [[package]] -name = "path-loader-router" -version = "0.1.0" +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 = "flate2" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" dependencies = [ - "wit-bindgen", + "crc32fast", + "miniz_oxide", + "zlib-rs", ] [[package]] -name = "percent-encoding" -version = "2.3.2" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "petgraph" -version = "0.6.5" +name = "foldhash" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ - "fixedbitset", - "indexmap", + "percent-encoding", ] [[package]] -name = "potential_utf" -version = "0.1.6" +name = "futures" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ - "zerovec", + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", ] [[package]] -name = "prettyplease" -version = "0.3.0" +name = "futures-channel" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bfe0f4c752e450fc2faf62654f1c134747922825d5b04ca717b8874f41a40c0" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ - "proc-macro2", - "syn 3.0.5", + "futures-core", + "futures-sink", ] [[package]] -name = "proc-macro2" -version = "1.0.107" +name = "futures-core" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ - "unicode-ident", + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", ] [[package]] -name = "quote" -version = "1.0.47" +name = "fxprof-processed-profile" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +checksum = "25234f20a3ec0a962a61770cfe39ecf03cb529a6e474ad8cff025ed497eda557" dependencies = [ - "proc-macro2", + "bitflags", + "debugid", + "rustc-hash", + "serde", + "serde_derive", + "serde_json", ] [[package]] -name = "regex-syntax" -version = "0.8.11" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] [[package]] -name = "semver" -version = "1.0.28" +name = "getrandom" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "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", +] + +[[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 = "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 = "serde" -version = "1.0.229" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http-loader" +version = "0.1.0" dependencies = [ - "serde_core", - "serde_derive", + "wit-bindgen", ] [[package]] -name = "serde_core" -version = "1.0.229" +name = "icu_collections" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" dependencies = [ - "serde_derive", + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "serde_derive" -version = "1.0.229" +name = "icu_locale_core" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", ] [[package]] -name = "serde_json" -version = "1.0.151" +name = "icu_normalizer" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", ] [[package]] -name = "simd-adler32" -version = "0.3.10" +name = "icu_normalizer_data" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" [[package]] -name = "smallvec" -version = "1.16.1" +name = "icu_properties" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] [[package]] -name = "spdx" -version = "0.13.5" +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +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 = "081670c233dfbed55690cc0cd38424e0e24ac1b2673d0b408b3f7b684738dfa9" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ + "idna_adapter", "smallvec", + "utf8_iter", ] [[package]] -name = "stable_deref_trait" -version = "1.2.1" +name = "idna_adapter" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] [[package]] -name = "syn" -version = "2.0.119" +name = "indexmap" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[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 = "lazy_static" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "logos" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7251356ef8cb7aec833ddf598c6cb24d17b689d20b993f9d11a3d764e34e6458" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-codegen" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59f80069600c0d66734f5ff52cc42f2dabd6b29d205f333d61fd7832e9e9963f" +dependencies = [ + "beef", + "fnv", + "lazy_static", + "proc-macro2", + "quote", + "regex-syntax", + "syn 2.0.119", +] + +[[package]] +name = "logos-derive" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24fb722b06a9dc12adb0963ed585f19fc61dc5413e6a9be9422ef92c091e731d" +dependencies = [ + "logos-codegen", +] + +[[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.6", +] + +[[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 = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "cfg-if", + "miette-derive", + "serde", + "unicode-width 0.1.14", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[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 = "oci-loader" +version = "0.1.0" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "path-loader-router" +version = "0.1.0" +dependencies = [ + "wit-bindgen", +] + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +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.6", +] + +[[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 = "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 = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[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", +] + +[[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.6", +] + +[[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", + "digest", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[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.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" +dependencies = [ + "serde", +] + +[[package]] +name = "spdx" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "646c5a367da914e3babcb9cd8e91c37ccaac42325b8f86f036513ad103f2a716" +dependencies = [ + "smallvec", +] + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[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", +] + +[[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-loader-harness" +version = "0.1.0" +dependencies = [ + "wit-bindgen", +] + +[[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.6", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[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 = "topological-sort" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea68304e134ecd095ac6c3574494fc62b909f416c4fca77e440530221e549d3d" + +[[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.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[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 = "wac-graph" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0418130ea1c8f995ac51fd495080942604bca6455e6fb11f110aa1cefd3f549" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "petgraph", + "semver", + "thiserror 1.0.69", + "wac-types", + "wasm-encoder 0.258.3", + "wasm-metadata 0.258.3", + "wasmparser 0.258.3", +] + +[[package]] +name = "wac-loader" +version = "0.1.0" +dependencies = [ + "anyhow", + "indexmap", + "wac-graph", + "wac-parser", + "wasmparser 0.260.0", + "wat", + "wit-bindgen", +] + +[[package]] +name = "wac-parser" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d411b5b87c7d6eeeff5df6f29c743186f1b8ddb5d0d818e1b481dc9e68318517" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "logos", + "miette", + "semver", + "serde", + "thiserror 1.0.69", + "wac-graph", + "wasm-encoder 0.258.3", + "wasm-metadata 0.258.3", + "wasmparser 0.258.3", +] + +[[package]] +name = "wac-types" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5580726576266490359b1457abbc0393e1780295e48a6892500f2b7e68edc74d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "semver", + "wasm-encoder 0.258.3", + "wasmparser 0.258.3", +] + +[[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.6", + "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.260.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e9692da9ef627c5ee12b45378f68ac2689ce306f88078f5cf849b7c3444ed8" +dependencies = [ + "leb128fmt", + "wasmparser 0.260.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", + "auditable-serde", + "flate2", + "indexmap", + "serde", + "serde_derive", + "serde_json", + "spdx", + "url", + "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 = "wasm-metadata" +version = "0.260.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b777ac62b820a7e0fd6b1d6beb2712bfa15a89f611e6cd792ec60468f66d6fa" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder 0.260.0", + "wasmparser 0.260.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.260.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c94519d78304dc4a4bb5925eda6ff153bf06167467cff9233dc9593bae28fdd" +dependencies = [ + "bitflags", + "hashbrown 0.17.1", + "indexmap", + "semver", + "serde", ] [[package]] -name = "syn" -version = "3.0.5" +name = "wasmparser" +version = "0.261.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +checksum = "4f20f20e44f7e8aeb6744823ea9d869ede51e51be4fdaedede2852282e54d2d8" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "bitflags", + "indexmap", + "semver", ] [[package]] -name = "synstructure" -version = "0.13.2" +name = "wasmprinter" +version = "0.258.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +checksum = "14a457ba8670a3db3d357c1aeac3ed4fe378a76054a221789f0df0ea507e292b" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "anyhow", + "termcolor", + "wasmparser 0.258.3", ] [[package]] -name = "test-loader-harness" -version = "0.1.0" +name = "wasmtime" +version = "49.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1801b9c9f91e16e352ed77656824e7c4556a7538dbfc1a50b37243f0e12ea045" dependencies = [ - "wit-bindgen", + "addr2line", + "async-trait", + "bitflags", + "bumpalo", + "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", + "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 = "thiserror" -version = "1.0.69" +name = "wasmtime-internal-cache" +version = "49.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "fdf564a08f96acec55a335e3185d1df98ba3feee47e178f5bc2429e723d30d0f" dependencies = [ - "thiserror-impl", + "base64", + "directories-next", + "log", + "postcard", + "rustix", + "serde", + "serde_derive", + "sha2", + "toml", + "wasmtime-environ", + "windows-sys", + "zstd", ] [[package]] -name = "thiserror-impl" -version = "1.0.69" +name = "wasmtime-internal-component-macro" +version = "49.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +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 = "tinystr" -version = "0.8.4" +name = "wasmtime-internal-component-util" +version = "49.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" -dependencies = [ - "displaydoc", - "zerovec", -] +checksum = "97fe14bd5ea6444a09f6baa3865559c93c91158930e7fe6d127429a9372f6165" [[package]] -name = "topological-sort" -version = "0.2.2" +name = "wasmtime-internal-core" +version = "49.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea68304e134ecd095ac6c3574494fc62b909f416c4fca77e440530221e549d3d" +checksum = "2053fd1d90080e502ca4ea39d86c3528587a40220b9572a874c8b742c219a00c" +dependencies = [ + "anyhow", + "hashbrown 0.17.1", + "libm", + "serde", +] [[package]] -name = "unicode-ident" -version = "1.0.24" +name = "wasmtime-internal-cranelift" +version = "49.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +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 = "unicode-width" -version = "0.1.14" +name = "wasmtime-internal-fiber" +version = "49.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +checksum = "1b913875bab1b78fada489e55d5e0a10b53286fb4bb09f5657212f549c9af36c" +dependencies = [ + "cc", + "libc", + "rustix", + "wasmtime-environ", + "wasmtime-internal-versioned-export-macros", + "windows-sys", +] [[package]] -name = "url" -version = "2.5.8" +name = "wasmtime-internal-jit-debug" +version = "49.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +checksum = "0356a1a09dee1239904e09e962456d9f6edfa5da61c5e3e1b0e4669710f48fba" dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", + "cc", + "object", + "rustix", + "wasmtime-internal-versioned-export-macros", ] [[package]] -name = "utf8_iter" -version = "1.0.4" +name = "wasmtime-internal-jit-icache-coherence" +version = "49.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +checksum = "5dead9a2d7b5d835698a2e1a32006e7d0b5711f4697b24bab1564bb2488e3d7a" +dependencies = [ + "libc", + "wasmtime-internal-core", + "windows-sys", +] [[package]] -name = "wac-graph" -version = "0.12.0" +name = "wasmtime-internal-unwinder" +version = "49.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0418130ea1c8f995ac51fd495080942604bca6455e6fb11f110aa1cefd3f549" +checksum = "69ffac8fc893de2958b263d9d7274cf4c531ba9ff582558348d4e28d97b8a0f2" dependencies = [ - "anyhow", - "id-arena", - "indexmap", + "cranelift-codegen", "log", - "petgraph", - "semver", - "thiserror", - "wac-types", - "wasm-encoder 0.258.0", - "wasm-metadata 0.258.0", - "wasmparser 0.258.0", + "object", + "wasmtime-environ", ] [[package]] -name = "wac-loader" -version = "0.1.0" +name = "wasmtime-internal-versioned-export-macros" +version = "49.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a20f0dcb6c6a006363d3f85b5c633675730487b0dc23726b2d40495872407fb" dependencies = [ - "anyhow", - "indexmap", - "wac-graph", - "wac-parser", - "wit-bindgen", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "wac-parser" -version = "0.12.0" +name = "wasmtime-internal-wit-bindgen" +version = "49.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d411b5b87c7d6eeeff5df6f29c743186f1b8ddb5d0d818e1b481dc9e68318517" +checksum = "80758f73f5f5daacd8a6d1a8513336a4f9a58d26c4f1c73eb80502cb143e2626" dependencies = [ "anyhow", - "id-arena", + "bitflags", + "heck", "indexmap", - "log", - "logos", - "miette", - "semver", - "serde", - "thiserror", - "wac-graph", - "wasm-encoder 0.258.0", - "wasm-metadata 0.258.0", - "wasmparser 0.258.0", + "wit-component 0.258.3", + "wit-parser 0.258.3", ] [[package]] -name = "wac-types" -version = "0.12.0" +name = "wast" +version = "261.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5580726576266490359b1457abbc0393e1780295e48a6892500f2b7e68edc74d" +checksum = "776443145731a4062e5b0d392892a2005909b6ab72d9fdc3cad53dd1a714e44a" dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "semver", - "wasm-encoder 0.258.0", - "wasmparser 0.258.0", + "bumpalo", + "leb128fmt", + "memchr", + "unicode-width 0.2.2", + "wasm-encoder 0.261.0", ] [[package]] -name = "wasm-encoder" -version = "0.258.0" +name = "wat" +version = "1.261.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e974fe6821a8cf64575d51ea2194e2c8f77e7b66e9afe7419ce8a97f9ee0d251" +checksum = "a7b4d1a49ea73a8f3326e74e3a05db667001b16bd1035ed3356fc1a0ed05ca7f" dependencies = [ - "leb128fmt", - "wasmparser 0.258.0", + "wast", ] [[package]] -name = "wasm-encoder" -version = "0.259.0" +name = "winapi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d0246511d901aacf25d2dc9111f0054947de5e093fe662099e709ff7530dc3" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ - "leb128fmt", - "wasmparser 0.259.0", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] [[package]] -name = "wasm-metadata" -version = "0.258.0" +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a11585adb92fe9b55ad1d760e8d8fb5d87e0d2e303cb8eed57f078d54293a2" -dependencies = [ - "anyhow", - "auditable-serde", - "flate2", - "indexmap", - "serde", - "serde_derive", - "serde_json", - "spdx", - "url", - "wasm-encoder 0.258.0", - "wasmparser 0.258.0", -] +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] -name = "wasm-metadata" -version = "0.259.0" +name = "winapi-util" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7bf119eb01f4a246864c10bd87c8f26c566350abb25aa4d5273c415bbab338" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder 0.259.0", - "wasmparser 0.259.0", + "windows-sys", ] [[package]] -name = "wasmparser" -version = "0.258.0" +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9a61719f93a87b16d325921e251800c4833f8fab50fa21c7de73aed50086313" -dependencies = [ - "bitflags", - "hashbrown", - "indexmap", - "semver", - "serde", -] +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "wasmparser" -version = "0.259.0" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f7c12eac7bb587801590f6a67ff0bc84d0748513864b71108e4b311cc3df694" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "bitflags", - "hashbrown", - "indexmap", - "semver", - "serde", + "windows-link", ] +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" + [[package]] name = "wit-bindgen" version = "0.62.0" @@ -805,7 +2151,7 @@ checksum = "72ad22a37ecbc0e1fdca34d2b670ba41368cb0920735643e00fdf39a16ee5431" dependencies = [ "anyhow", "heck", - "wit-parser", + "wit-parser 0.259.0", ] [[package]] @@ -818,10 +2164,10 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 3.0.5", + "syn 3.0.6", "wasm-metadata 0.259.0", "wit-bindgen-core", - "wit-component", + "wit-component 0.259.0", ] [[package]] @@ -835,11 +2181,30 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.6", "wit-bindgen-core", "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" @@ -856,7 +2221,46 @@ dependencies = [ "wasm-encoder 0.259.0", "wasm-metadata 0.259.0", "wasmparser 0.259.0", - "wit-parser", + "wit-parser 0.259.0", +] + +[[package]] +name = "wit-component" +version = "0.260.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389106e4fbac7c5bbece1496c442d2aceb1926c18884a3ad76f7559c160b8883" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder 0.260.0", + "wasm-metadata 0.260.0", + "wasmparser 0.260.0", + "wat", + "wit-parser 0.260.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]] @@ -866,7 +2270,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6390f4f02493ce678d509c859cf1698e38929fb75c8012a6ee2f7d6b6cb66cd7" dependencies = [ "anyhow", - "hashbrown", + "hashbrown 0.17.1", "id-arena", "indexmap", "log", @@ -878,6 +2282,40 @@ dependencies = [ "wasmparser 0.259.0", ] +[[package]] +name = "wit-parser" +version = "0.260.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3152a979bbe19638bb0655cc919d5a68ba1b7b2258f8f43f7c74430004ae7f5e" +dependencies = [ + "anyhow", + "hashbrown 0.17.1", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-ident", + "wasmparser 0.260.0", +] + +[[package]] +name = "wit-tools" +version = "0.1.0" +dependencies = [ + "anyhow", + "id-arena", + "semver", + "wasmparser 0.260.0", + "wasmtime", + "wat", + "wit-bindgen", + "wit-component 0.260.0", + "wit-parser 0.260.0", +] + [[package]] name = "writeable" version = "0.6.4" @@ -897,13 +2335,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", "synstructure", ] @@ -918,13 +2356,13 @@ dependencies = [ [[package]] name = "zerofrom-derive" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.6", "synstructure", ] @@ -958,17 +2396,45 @@ checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 3.0.5", + "syn 3.0.6", ] [[package]] name = "zlib-rs" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" [[package]] 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 66d3303..cd033f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,13 @@ [workspace] resolver = "2" members = [ - "components/extract-wit", "components/filesystem-loader", "components/http-loader", "components/oci-loader", "components/path-loader-router", "components/test-loader-harness", "components/wac-loader", + "components/wit-tools", ] [workspace.dependencies] @@ -15,7 +15,28 @@ anyhow = "1" indexmap = "2.14.0" wac-graph = "0.12.0" wac-parser = "0.12.0" -wasmparser = "0.259.0" +wasmparser = "0.260.0" +wasmtime = "49.0.2" +wat = "1.260.0" wit-bindgen = "0.62.0" -wit-component = "0.259.0" -wit-parser = "0.259.0" +wit-component = "0.260.0" +wit-parser = "0.260.0" + +# wasmtime compiles components in the native tests, an unoptimized cranelift is very slow. These +# crates are only built for the host, the components are unaffected. +[profile.dev.package.cranelift-codegen] +opt-level = 3 +[profile.dev.package.cranelift-frontend] +opt-level = 3 +[profile.dev.package.cranelift-entity] +opt-level = 3 +[profile.dev.package.cranelift-bforest] +opt-level = 3 +[profile.dev.package.regalloc2] +opt-level = 3 +[profile.dev.package.wasmtime-internal-cranelift] +opt-level = 3 +[profile.dev.package.wasmtime-environ] +opt-level = 3 +[profile.dev.package.wasmtime] +opt-level = 3 diff --git a/Makefile b/Makefile index 8e35ec1..e3e6890 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ relpath = $(if $(filter $(CURDIR),$(abspath $(1))),.,$(patsubst $(CURDIR)/%,%,$( .PHONY: all -all: components +all: tools wit components test .PHONY: clean clean: clean-wit diff --git a/README.md b/README.md index 172f296..c2eb736 100644 --- a/README.md +++ b/README.md @@ -30,13 +30,13 @@ The cli tools the build uses, [`wasm-tools`](https://github.com/bytecodealliance ### Components -- [`extract-wit`](./components/extract-wit/) - [`filesystem-loader`](./components/filesystem-loader/) - [`http-loader`](./components/http-loader/) - [`oci-loader`](./components/oci-loader/) - [`path-loader`](./components/path-loader/) - [`path-loader-router`](./components/path-loader-router/) - [`wac-loader`](./components/wac-loader/) +- [`wit-tools`](./components/wit-tools/) ## Community diff --git a/components/extract-wit/README.md b/components/extract-wit/README.md deleted file mode 100644 index 283c8ca..0000000 --- a/components/extract-wit/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# `extract-wit` - -Extracts the WIT (Wasm Interface Type) from a component. diff --git a/components/extract-wit/src/lib.rs b/components/extract-wit/src/lib.rs deleted file mode 100644 index 3b2dcd0..0000000 --- a/components/extract-wit/src/lib.rs +++ /dev/null @@ -1,553 +0,0 @@ -use std::{collections::BTreeMap, fmt::Display}; - -use crate::{ - componentized::component::types::{Component, Error}, - exports::componentized::component::wit::{ - Docs, Enum, EnumCase, Flag, Flags, Function, FunctionKind, Guest, Handle, IncludeName, - Interface, InterfaceId, List, Map, Package, PackageId, PackageName, Param, Record, - RecordField, Result as Result_, Stability, Stable, Tuple, Type, TypeDef, TypeDefKind, - TypeId, TypeOwner, Unstable, Variant, VariantCase, Version, VersionIdentifier, Wit, World, - WorldId, WorldInclude, WorldItem, WorldItemInterface, WorldKey, WorldSummary, - }, -}; - -pub(crate) struct ExtractWit; - -impl Guest for ExtractWit { - #[allow(async_fn_in_trait)] - async fn extract(component: Component) -> Result { - let decoded = wit_component::decode(&component)?; - - let component_world_id = match &decoded { - wit_component::DecodedWasm::Component(_, world_id) => Some(*world_id), - wit_component::DecodedWasm::WitPackage(..) => None, - }; - - Wit::new(decoded.resolve(), decoded.package(), component_world_id) - } - - #[allow(async_fn_in_trait)] - async fn summarize_world(component: Component) -> Result { - let wit = Self::extract(component).await?; - let world = wit - .worlds - .get(&wit.component_world.expect("must be a component")) - .expect("component world must exist"); - - let extract_name = |key: &WorldKey, item: &WorldItem| match key { - WorldKey::Name(name) => match item { - WorldItem::Interface(iface) => { - iface.external_id.clone().unwrap_or(name.to_string()) - } - WorldItem::Function(..) => "--unknown--".to_owned(), - WorldItem::Type { .. } => "--unknown--".to_owned(), - }, - WorldKey::Interface(interface_id) => { - let iface = wit.interfaces.get(interface_id).unwrap(); - match (iface.name.clone(), iface.package.clone()) { - (Some(name), Some(package_id)) => { - let package = wit.packages.get(&package_id).unwrap(); - let namespace = package.name.namespace.clone(); - let package_name = package.name.name.clone(); - let version = package - .name - .version - .clone() - .map(|v| format!("@{v}")) - .unwrap_or("".to_owned()); - format!("{namespace}:{package_name}/{name}{version}") - } - _ => "--unknown--".to_owned(), - } - } - }; - - Ok(WorldSummary { - imports: world - .imports - .iter() - .map(|(key, item)| extract_name(key, item)) - .collect(), - exports: world - .exports - .iter() - .map(|(key, item)| extract_name(key, item)) - .collect(), - }) - } -} - -impl Wit { - fn new( - resolve: &wit_parser::Resolve, - package_id: wit_parser::PackageId, - component_world_id: Option, - ) -> Result { - let wit = Self { - worlds: resolve.worlds.clone().into_iter().fold( - BTreeMap::new(), - |mut worlds, (id, world)| { - worlds.insert(Self::world_id(id), Self::world(world)); - worlds - }, - ), - interfaces: resolve.interfaces.clone().into_iter().fold( - BTreeMap::new(), - |mut interfaces, (id, interface)| { - interfaces.insert(Self::interface_id(id), Self::interface(interface)); - interfaces - }, - ), - types: resolve.types.clone().into_iter().fold( - BTreeMap::new(), - |mut types, (id, type_def)| { - types.insert(Self::type_id(id), Self::type_def(type_def)); - types - }, - ), - packages: resolve.packages.clone().into_iter().fold( - BTreeMap::new(), - |mut packages, (id, package)| { - packages.insert(Self::package_id(id), Self::package(package)); - packages - }, - ), - - default_package: Self::package_id(package_id), - component_world: component_world_id.map(Self::world_id), - }; - - if wit.packages.get(&wit.default_package.clone()).is_none() { - Err(Error::Other(Some("decoded package must exist".to_string())))?; - } - if let Some(world_id) = wit.component_world.clone() { - if wit.worlds.get(&world_id).is_none() { - Err(Error::Other(Some( - "component world must exist if set".to_string(), - )))?; - } - } - - Ok(wit) - } - - fn world_id(id: wit_parser::WorldId) -> WorldId { - WorldId::from(format!("world:{}", id.index())) - } - - fn world(world: wit_parser::World) -> World { - World { - name: world.name, - imports: world - .imports - .into_iter() - .map(|import| Self::world_entry(import)) - .collect(), - exports: world - .exports - .into_iter() - .map(|export| Self::world_entry(export)) - .collect(), - package: world.package.map(Self::package_id), - docs: Self::docs(world.docs), - stability: Self::stability(world.stability), - includes: world - .includes - .into_iter() - .map(|include| WorldInclude { - stability: Self::stability(include.stability), - id: Self::world_id(include.id), - names: include - .names - .into_iter() - .map(|name| IncludeName { - name: name.name, - as_: name.as_, - }) - .collect(), - }) - .collect(), - } - } - - fn world_entry( - (key, item): (wit_parser::WorldKey, wit_parser::WorldItem), - ) -> (WorldKey, WorldItem) { - ( - match key { - wit_parser::WorldKey::Name(name) => WorldKey::Name(name), - wit_parser::WorldKey::Interface(id) => WorldKey::Interface(Self::interface_id(id)), - }, - match item { - wit_parser::WorldItem::Interface { - id, - stability, - external_id, - docs, - .. - } => WorldItem::Interface(WorldItemInterface { - id: Self::interface_id(id), - stability: Self::stability(stability), - external_id: external_id, - docs: Self::docs(docs), - }), - wit_parser::WorldItem::Function(function) => { - WorldItem::Function(Self::function(function)) - } - wit_parser::WorldItem::Type { id, .. } => WorldItem::Type(Self::type_id(id)), - }, - ) - } - - fn interface_id(id: wit_parser::InterfaceId) -> InterfaceId { - InterfaceId::from(format!("interface:{}", id.index())) - } - - fn interface(interface: wit_parser::Interface) -> Interface { - Interface { - name: interface.name, - types: interface - .types - .into_iter() - .map(|(name, id)| (name, Self::type_id(id))) - .collect(), - functions: interface - .functions - .into_iter() - .map(|(name, function)| (name, Self::function(function))) - .collect(), - docs: Self::docs(interface.docs), - stability: Self::stability(interface.stability), - package: interface.package.map(Self::package_id), - } - } - - fn function(function: wit_parser::Function) -> Function { - Function { - name: function.name, - kind: match function.kind { - wit_parser::FunctionKind::Freestanding => FunctionKind::Freestanding, - wit_parser::FunctionKind::AsyncFreestanding => FunctionKind::AsyncFreestanding, - wit_parser::FunctionKind::Method(id) => FunctionKind::Method(Self::type_id(id)), - wit_parser::FunctionKind::AsyncMethod(id) => { - FunctionKind::AsyncMethod(Self::type_id(id)) - } - wit_parser::FunctionKind::Static(id) => FunctionKind::Static(Self::type_id(id)), - wit_parser::FunctionKind::AsyncStatic(id) => { - FunctionKind::AsyncStatic(Self::type_id(id)) - } - wit_parser::FunctionKind::Constructor(id) => { - FunctionKind::Constructor(Self::type_id(id)) - } - }, - params: function - .params - .into_iter() - .map(|param| Param { - name: param.name, - type_: Self::type_(param.ty), - }) - .collect(), - result: function.result.map(Self::type_), - docs: Self::docs(function.docs), - stability: Self::stability(function.stability), - external_id: function.external_id, - } - } - - fn type_id(id: wit_parser::TypeId) -> TypeId { - TypeId::from(format!("type:{}", id.index())) - } - - fn type_(type_: wit_parser::Type) -> Type { - match type_ { - wit_parser::Type::Bool => Type::Bool, - wit_parser::Type::U8 => Type::U8, - wit_parser::Type::U16 => Type::U16, - wit_parser::Type::U32 => Type::U32, - wit_parser::Type::U64 => Type::U64, - wit_parser::Type::S8 => Type::S8, - wit_parser::Type::S16 => Type::S16, - wit_parser::Type::S32 => Type::S32, - wit_parser::Type::S64 => Type::S64, - wit_parser::Type::F32 => Type::F32, - wit_parser::Type::F64 => Type::F64, - wit_parser::Type::Char => Type::Char, - wit_parser::Type::String => Type::String, - wit_parser::Type::ErrorContext => Type::ErrorContext, - wit_parser::Type::Id(id) => Type::Id(Self::type_id(id)), - } - } - - fn type_def(type_def: wit_parser::TypeDef) -> TypeDef { - TypeDef { - name: type_def.name, - kind: Self::type_def_kind(type_def.kind), - owner: match type_def.owner { - wit_parser::TypeOwner::World(id) => TypeOwner::World(Self::world_id(id)), - wit_parser::TypeOwner::Interface(id) => { - TypeOwner::Interface(Self::interface_id(id)) - } - wit_parser::TypeOwner::None => TypeOwner::None, - }, - docs: Self::docs(type_def.docs), - stability: Self::stability(type_def.stability), - external_id: type_def.external_id, - } - } - - fn type_def_kind(kind: wit_parser::TypeDefKind) -> TypeDefKind { - match kind { - wit_parser::TypeDefKind::Record(record) => TypeDefKind::Record(Self::record(record)), - wit_parser::TypeDefKind::Resource => TypeDefKind::Resource, - wit_parser::TypeDefKind::Handle(handle) => TypeDefKind::Handle(Self::handle(handle)), - wit_parser::TypeDefKind::Flags(flags) => TypeDefKind::Flags(Self::flags(flags)), - wit_parser::TypeDefKind::Tuple(tuple) => TypeDefKind::Tuple(Self::tuple(tuple)), - wit_parser::TypeDefKind::Variant(variant) => { - TypeDefKind::Variant(Self::variant(variant)) - } - wit_parser::TypeDefKind::Enum(enum_) => TypeDefKind::Enum(Self::enum_(enum_)), - wit_parser::TypeDefKind::Option(option) => TypeDefKind::Option(Self::type_(option)), - wit_parser::TypeDefKind::Result(result) => TypeDefKind::Result(Self::result(result)), - wit_parser::TypeDefKind::List(list) => TypeDefKind::List(Self::list(list)), - wit_parser::TypeDefKind::FixedLengthList(type_, length) => { - TypeDefKind::List(Self::list_fixed_length(type_, length)) - } - wit_parser::TypeDefKind::Map(key, value) => TypeDefKind::Map(Self::map(key, value)), - - wit_parser::TypeDefKind::Future(future) => TypeDefKind::Future(future.map(Self::type_)), - wit_parser::TypeDefKind::Stream(stream) => TypeDefKind::Stream(stream.map(Self::type_)), - wit_parser::TypeDefKind::Type(type_) => TypeDefKind::Type(Self::type_(type_)), - wit_parser::TypeDefKind::Unknown => TypeDefKind::Unknown, - } - } - - fn record(record: wit_parser::Record) -> Record { - Record { - fields: record - .fields - .into_iter() - .map(|field| RecordField { - name: field.name, - type_: Self::type_(field.ty), - docs: Self::docs(field.docs), - }) - .collect(), - } - } - - fn handle(handle: wit_parser::Handle) -> Handle { - match handle { - wit_parser::Handle::Own(id) => Handle::Own(Self::type_id(id)), - wit_parser::Handle::Borrow(id) => Handle::Borrow(Self::type_id(id)), - } - } - - fn flags(flags: wit_parser::Flags) -> Flags { - Flags { - flags: flags - .flags - .into_iter() - .map(|flag| Flag { - name: flag.name, - docs: Self::docs(flag.docs), - }) - .collect(), - } - } - - fn tuple(tuple: wit_parser::Tuple) -> Tuple { - Tuple { - types: tuple.types.into_iter().map(Self::type_).collect(), - } - } - - fn variant(variant: wit_parser::Variant) -> Variant { - Variant { - cases: variant - .cases - .into_iter() - .map(|case| VariantCase { - name: case.name, - type_: case.ty.map(Self::type_), - docs: Self::docs(case.docs), - }) - .collect(), - } - } - - fn enum_(enum_: wit_parser::Enum) -> Enum { - Enum { - cases: enum_ - .cases - .into_iter() - .map(|case| EnumCase { - name: case.name, - docs: Self::docs(case.docs), - }) - .collect(), - } - } - - fn result(result: wit_parser::Result_) -> Result_ { - Result_ { - ok: result.ok.map(Self::type_), - err: result.err.map(Self::type_), - } - } - - fn list(type_: wit_parser::Type) -> List { - List { - type_: Self::type_(type_), - fixed_length: None, - } - } - - fn list_fixed_length(type_: wit_parser::Type, fixed_length: u32) -> List { - List { - type_: Self::type_(type_), - fixed_length: Some(fixed_length), - } - } - - fn map(key: wit_parser::Type, value: wit_parser::Type) -> Map { - Map { - key: Self::type_(key), - value: Self::type_(value), - } - } - - fn package_id(id: wit_parser::PackageId) -> PackageId { - PackageId::from(format!("package:{}", id.index())) - } - - fn package(package: wit_parser::Package) -> Package { - Package { - name: PackageName { - namespace: package.name.namespace, - name: package.name.name, - version: package.name.version.map(Self::version), - }, - docs: Self::docs(package.docs), - interfaces: package - .interfaces - .into_iter() - .map(|(name, id)| (name, Self::interface_id(id))) - .collect(), - worlds: package - .worlds - .into_iter() - .map(|(name, id)| (name, Self::world_id(id))) - .collect(), - } - } - - fn docs(docs: wit_parser::Docs) -> Docs { - Docs { - contents: docs.contents, - } - } - - fn stability(stability: wit_parser::Stability) -> Stability { - match stability { - wit_parser::Stability::Unknown => Stability::Unknown, - wit_parser::Stability::Unstable { - feature, - deprecated, - } => Stability::Unstable(Unstable { - feature, - deprecated: deprecated.map(Self::version), - }), - wit_parser::Stability::Stable { since, deprecated } => Stability::Stable(Stable { - deprecated: deprecated.map(Self::version), - since: Self::version(since), - }), - } - } - - fn version(version: semver::Version) -> Version { - Version { - major: version.major, - minor: version.minor, - patch: version.patch, - prerelease: Self::version_identifiers(version.pre.as_str()), - build_metadata: Self::version_identifiers(version.build.as_str()), - } - } - - fn version_identifiers(identifiers: &str) -> Option> { - match identifiers.is_empty() { - true => None, - false => Some( - identifiers - .split('.') - .map(|identifier| match identifier.parse::() { - Ok(val) => VersionIdentifier::Numeric(val), - Err(_) => VersionIdentifier::String(identifier.into()), - }) - .collect(), - ), - } - } -} - -impl From for Error { - fn from(value: anyhow::Error) -> Self { - Self::Other(Some(value.to_string())) - } -} - -impl Display for Version { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let Version { - major, - minor, - patch, - prerelease, - build_metadata, - } = self; - let prerelease = prerelease - .clone() - .map(|chunks| { - chunks - .into_iter() - .map(|chunk| chunk.to_string()) - .collect::>() - .join(".") - }) - .map(|s| format!("-{s}")) - .unwrap_or("".to_string()); - let build_metadata = build_metadata - .clone() - .map(|chunks| { - chunks - .into_iter() - .map(|chunk| chunk.to_string()) - .collect::>() - .join(".") - }) - .map(|s| format!("+{s}")) - .unwrap_or("".to_string()); - f.write_fmt(format_args!( - "{major}.{minor}.{patch}{prerelease}{build_metadata}" - )) - } -} - -impl Display for VersionIdentifier { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - VersionIdentifier::String(vi) => f.write_fmt(format_args!("{vi}")), - VersionIdentifier::Numeric(vi) => f.write_fmt(format_args!("{vi}")), - } - } -} - -wit_bindgen::generate!({ - path: "../wit", - world: "extract-wit", - merge_structurally_equal_types: true, - generate_all -}); - -export!(ExtractWit); diff --git a/components/filesystem-loader/src/lib.rs b/components/filesystem-loader/src/lib.rs index a1f3470..397d77e 100644 --- a/components/filesystem-loader/src/lib.rs +++ b/components/filesystem-loader/src/lib.rs @@ -1,10 +1,8 @@ use std::path::Path; use crate::{ - exports::componentized::component::{ - path_loader::Guest, - types::{Component, Error}, - }, + componentized::component::types::{ErrorCode, Wasm}, + exports::componentized::component::path_loader::Guest, wasi::filesystem::{preopens, types as filesystem}, }; @@ -12,26 +10,40 @@ pub(crate) struct FilesystemLoader; impl Guest for FilesystemLoader { #[allow(async_fn_in_trait)] - async fn load(path: String) -> Result { - let (dir, path) = resolve(&path)?; - let file = dir - .open_at( - filesystem::PathFlags::SYMLINK_FOLLOW, - path, - filesystem::OpenFlags::empty(), - filesystem::DescriptorFlags::READ, - ) - .await?; - let (data, result) = file.read_via_stream(0); - let component = data.collect().await; - result.await?; - Ok(component) + async fn load(path: String) -> Result { + load_path(&path) + .await + .map_err(|error| name_not_found(error, &path)) + } +} + +async fn load_path(path: &str) -> Result { + let (dir, path) = resolve(path)?; + let file = dir + .open_at( + filesystem::PathFlags::SYMLINK_FOLLOW, + path, + filesystem::OpenFlags::empty(), + filesystem::DescriptorFlags::READ, + ) + .await?; + let (data, result) = file.read_via_stream(0); + let component = data.collect().await; + result.await?; + Ok(component) +} + +/// Names the loaded path in a `not-found` error that does not name what was not found. +fn name_not_found(error: ErrorCode, path: &str) -> ErrorCode { + match error { + ErrorCode::NotFound(None) => ErrorCode::NotFound(Some(path.to_string())), + error => error, } } /// Resolve a path to the preopened directory that contains it and the path relative to that /// directory. When multiple preopens match, the most specific one wins. -fn resolve(path: &str) -> Result<(filesystem::Descriptor, String), Error> { +fn resolve(path: &str) -> Result<(filesystem::Descriptor, String), ErrorCode> { let path = Path::new(path); let mut resolved: Option<(usize, filesystem::Descriptor, String)> = None; for (dir, name) in preopens::get_directories() { @@ -50,14 +62,14 @@ fn resolve(path: &str) -> Result<(filesystem::Descriptor, String), Error> { } } resolved.map(|(_, dir, path)| (dir, path)).ok_or_else(|| { - Error::Other(Some(format!( + ErrorCode::Other(Some(format!( "filesystem: no preopen for path {}", path.display() ))) }) } -impl From for Error { +impl From for ErrorCode { fn from(value: filesystem::ErrorCode) -> Self { match value { filesystem::ErrorCode::Access => Self::Other(Some("filesystem: access".to_string())), @@ -101,7 +113,7 @@ impl From for Error { filesystem::ErrorCode::NoDevice => { Self::Other(Some("filesystem: no-device".to_string())) } - filesystem::ErrorCode::NoEntry => Self::Other(Some("filesystem: no-entry".to_string())), + filesystem::ErrorCode::NoEntry => Self::NotFound(None), filesystem::ErrorCode::NoLock => Self::Other(Some("filesystem: no-lock".to_string())), filesystem::ErrorCode::InsufficientMemory => { Self::Other(Some("filesystem: insufficient-memory".to_string())) diff --git a/components/http-loader/src/lib.rs b/components/http-loader/src/lib.rs index 5d4602a..5f37e5d 100644 --- a/components/http-loader/src/lib.rs +++ b/components/http-loader/src/lib.rs @@ -1,21 +1,30 @@ use crate::{ - componentized::http::client::{self as http}, + componentized::{ + component::types::{ErrorCode, Wasm}, + http::client::{self as http}, + }, exports::componentized::component::path_loader::Guest, - exports::componentized::component::types::{Component, Error}, }; pub(crate) struct HttpLoader; impl Guest for HttpLoader { #[allow(async_fn_in_trait)] - async fn load(path: String) -> Result { - let response = http::get(path, vec![], None).await?; + async fn load(path: String) -> Result { + let response = http::get(path.clone(), vec![], None).await?; + match response.status { + 200..=299 => {} + 404 | 410 => Err(ErrorCode::NotFound(Some(path)))?, + status => Err(ErrorCode::Other(Some(format!( + "unexpected HTTP status {status} for {path}" + ))))?, + } let component = response.body.collect().await; Ok(component) } } -impl From for Error { +impl From for ErrorCode { fn from(value: http::ErrorCode) -> Self { match value { http::ErrorCode::RedirectLimitExceeded((_, count)) => { diff --git a/components/oci-loader/src/lib.rs b/components/oci-loader/src/lib.rs index 3b9c07b..1d92e90 100644 --- a/components/oci-loader/src/lib.rs +++ b/components/oci-loader/src/lib.rs @@ -1,62 +1,79 @@ use crate::{ - componentized::oci::client::{self as oci, Digest, Reference}, - exports::componentized::component::{ - path_loader::Guest, - types::{Component, Error}, + componentized::{ + component::types::{ErrorCode, Malformed, Wasm}, + oci::client::{self as oci, Digest, Reference}, }, + exports::componentized::component::path_loader::Guest, }; pub(crate) struct OCILoader; impl Guest for OCILoader { #[allow(async_fn_in_trait)] - async fn load(path: String) -> Result { - let manifest_reference = oci::parse_reference(&path)?; - let manifest = match oci::get_manifest(manifest_reference.clone()).await? { - oci::Manifest::OciImageV1(oci_image_manifest_v1) => oci_image_manifest_v1, - _ => Err(Error::Other(Some("unexpected manifest".to_string())))?, - }; - match manifest.config.media_type { - oci::MediaType::ApplicationVndWasmConfigV0(oci::MediaTypeSuffix::Json) => {} - _ => Err(Error::Other(Some( - "unexpected config media type".to_string(), - )))?, - }; - if manifest.layers.len() != 1 { - Err(Error::Other(Some("unknown artifact layout".to_string())))? - } - let component_descriptor = manifest.layers.first().unwrap(); - match component_descriptor.media_type { - oci::MediaType::ApplicationWasm => {} - _ => Err(Error::Other(Some( - "unknown artifact media type".to_string(), - )))?, - } + async fn load(path: String) -> Result { + load_reference(&path) + .await + .map_err(|error| name_not_found(error, &path)) + } +} - let config_reference = manifest_reference.with_digest(&manifest.config.digest); - let config = - match oci::get_config(config_reference, Some(manifest.config.media_type.clone())) - .await? - { - oci::Config::WasmV0(wasm_config_v0) => wasm_config_v0, - _ => Err(Error::Other(Some("unexpected config".to_string())))?, - }; - if config.component.is_none() { - Err(Error::Other(Some("not a component".to_string())))? - } +async fn load_reference(path: &str) -> Result { + let manifest_reference = oci::parse_reference(path)?; + let manifest = match oci::get_manifest(manifest_reference.clone()).await? { + oci::Manifest::OciImageV1(oci_image_manifest_v1) => oci_image_manifest_v1, + _ => Err(ErrorCode::Other(Some("unexpected manifest".to_string())))?, + }; + match manifest.config.media_type { + oci::MediaType::ApplicationVndWasmConfigV0(oci::MediaTypeSuffix::Json) => {} + _ => Err(ErrorCode::Other(Some( + "unexpected config media type".to_string(), + )))?, + }; + if manifest.layers.len() != 1 { + Err(ErrorCode::Other(Some( + "unknown artifact layout".to_string(), + )))? + } + let component_descriptor = manifest.layers.first().unwrap(); + match component_descriptor.media_type { + oci::MediaType::ApplicationWasm => {} + _ => Err(ErrorCode::Other(Some( + "unknown artifact media type".to_string(), + )))?, + } + + let config_reference = manifest_reference.with_digest(&manifest.config.digest); + let config = + match oci::get_config(config_reference, Some(manifest.config.media_type.clone())).await? { + oci::Config::WasmV0(wasm_config_v0) => wasm_config_v0, + _ => Err(ErrorCode::Other(Some("unexpected config".to_string())))?, + }; + if config.component.is_none() { + Err(ErrorCode::NotComponent(None))? + } - let component_reference = - manifest_reference.with_digest(&manifest.layers.first().unwrap().digest); - let component = oci::get_blob(component_reference).await?; - if component_descriptor.size != component.len() as u64 { - Err(Error::Other(Some(format!( + let component_reference = + manifest_reference.with_digest(&manifest.layers.first().unwrap().digest); + let component = oci::get_blob(component_reference).await?; + if component_descriptor.size != component.len() as u64 { + Err(ErrorCode::Malformed(Malformed { + name: None, + message: format!( "component size mismatch: expected {expected}, actual {actual}", expected = component_descriptor.size, actual = component.len() - ))))? - } + ), + }))? + } + + Ok(component) +} - Ok(component) +/// Names the loaded path in a `not-found` error that does not name what was not found. +fn name_not_found(error: ErrorCode, path: &str) -> ErrorCode { + match error { + ErrorCode::NotFound(None) => ErrorCode::NotFound(Some(path.to_string())), + error => error, } } @@ -71,12 +88,10 @@ impl Reference { } } -impl From for Error { +impl From for ErrorCode { fn from(value: oci::ErrorCode) -> Self { match value { - oci::ErrorCode::BlobUnknown(message) => { - Self::Other(Some(format!("OCI error blob-unknown: {message}"))) - } + oci::ErrorCode::BlobUnknown(_) => Self::NotFound(None), oci::ErrorCode::BlobUploadInvalid(message) => { Self::Other(Some(format!("OCI error blob-upload-invalid: {message}"))) } @@ -86,21 +101,15 @@ impl From for Error { oci::ErrorCode::DigestInvalid(message) => { Self::Other(Some(format!("OCI error digest-invalid: {message}"))) } - oci::ErrorCode::ManifestBlobUnknown(message) => { - Self::Other(Some(format!("OCI error manifest-blob-unknown: {message}"))) - } + oci::ErrorCode::ManifestBlobUnknown(_) => Self::NotFound(None), oci::ErrorCode::ManifestInvalid(message) => { Self::Other(Some(format!("OCI error manifest-invalid: {message}"))) } - oci::ErrorCode::ManifestUnknown(message) => { - Self::Other(Some(format!("OCI error manifest-unknown: {message}"))) - } + oci::ErrorCode::ManifestUnknown(_) => Self::NotFound(None), oci::ErrorCode::NameInvalid(message) => { Self::Other(Some(format!("OCI error name-invalid: {message}"))) } - oci::ErrorCode::NameUnknown(message) => { - Self::Other(Some(format!("OCI error name-unknown: {message}"))) - } + oci::ErrorCode::NameUnknown(_) => Self::NotFound(None), oci::ErrorCode::SizeInvalid(message) => { Self::Other(Some(format!("OCI error size-invalid: {message}"))) } diff --git a/components/path-loader-router/src/lib.rs b/components/path-loader-router/src/lib.rs index 0b2d13c..2e59fb5 100644 --- a/components/path-loader-router/src/lib.rs +++ b/components/path-loader-router/src/lib.rs @@ -1,13 +1,13 @@ -use crate::exports::componentized::component::{ - path_loader::Guest, - types::{Component, Error}, +use crate::{ + componentized::component::types::{ErrorCode, Malformed, Wasm}, + exports::componentized::component::path_loader::Guest, }; pub(crate) struct PathLoader; impl Guest for PathLoader { #[allow(async_fn_in_trait)] - async fn load(path: String) -> Result { + async fn load(path: String) -> Result { if path.starts_with("http:") { http_loader::load(path).await } else if path.starts_with("https:") { @@ -15,15 +15,19 @@ impl Guest for PathLoader { } else if path.starts_with("file:///") { filesystem_loader::load(path[7..].to_string()).await } else if path.starts_with("file://") { - Err(Error::Other(Some(format!( - "invalid filesystem-path loader: {path}" - )))) + Err(ErrorCode::Malformed(Malformed { + name: None, + message: format!("invalid filesystem-path loader: {path}"), + })) } else if path.starts_with("file:") { filesystem_loader::load(path[5..].to_string()).await } else if path.starts_with("oci://") { oci_loader::load(path[6..].to_string()).await } else { - Err(Error::Other(Some(format!("unknown path loader: {path}")))) + Err(ErrorCode::Malformed(Malformed { + name: None, + message: format!("unknown path loader: {path}"), + })) } } } diff --git a/components/test-loader-harness/src/lib.rs b/components/test-loader-harness/src/lib.rs index 217b9ef..f80a4d5 100644 --- a/components/test-loader-harness/src/lib.rs +++ b/components/test-loader-harness/src/lib.rs @@ -1,5 +1,8 @@ use crate::{ - componentized::component::{path_loader, types::Error}, + componentized::component::{ + path_loader, + types::{ErrorCode, Malformed}, + }, exports::wasi::cli::run::Guest, wasi::cli::{environment, stderr, stdout}, }; @@ -41,8 +44,20 @@ impl Guest for LoaderHarness { } Err(e) => { let message = match e { - Error::Other(Some(message)) => message, - Error::Other(None) => "--unknown error--".to_string(), + ErrorCode::Malformed(Malformed { + name: Some(name), + message, + }) => format!("{name} is malformed: {message}"), + ErrorCode::Malformed(Malformed { + name: None, + message, + }) => format!("malformed: {message}"), + ErrorCode::NotComponent(Some(name)) => format!("{name} is not a component"), + ErrorCode::NotComponent(None) => "not a component".to_string(), + ErrorCode::NotFound(Some(name)) => format!("{name} not found"), + ErrorCode::NotFound(None) => "not found".to_string(), + ErrorCode::Other(Some(message)) => message, + ErrorCode::Other(None) => "--unknown error--".to_string(), }; let _ = write_to!(stderr, format!("{}\n", message).into_bytes()); Err(()) diff --git a/components/test-loader/README.md b/components/test-loader/README.md index 35f19b2..d3b2521 100644 --- a/components/test-loader/README.md +++ b/components/test-loader/README.md @@ -6,9 +6,10 @@ Test harness for a path loader. make components/test-loader && \ wasmtime run \ -Wcomponent-model-map \ + -Wcomponent-model-implements \ -Shttp \ --dir=. \ - ./lib/test-loader.wasm \ + ./target/components/test-loader/test-loader.wasm \ oci://ghcr.io/componentized/config/empty:0.2.1 \ | wasm-tools component wit ``` diff --git a/components/wac-loader/Cargo.toml b/components/wac-loader/Cargo.toml index a5fb403..6e18402 100644 --- a/components/wac-loader/Cargo.toml +++ b/components/wac-loader/Cargo.toml @@ -12,4 +12,8 @@ anyhow = { workspace = true } indexmap = { workspace = true } wac-graph = { workspace = true } wac-parser = { workspace = true } +wasmparser = { workspace = true } wit-bindgen = { workspace = true } + +[dev-dependencies] +wat = { workspace = true } diff --git a/components/wac-loader/src/lib.rs b/components/wac-loader/src/lib.rs index ef5d866..b1e3735 100644 --- a/components/wac-loader/src/lib.rs +++ b/components/wac-loader/src/lib.rs @@ -5,24 +5,31 @@ use wac_graph::{ }; use wac_parser::Document; -use crate::exports::componentized::component::{ - types::{Component, Error}, - wac_loader::{Dependency, Guest, Plan}, +use crate::{ + componentized::component::types::{ErrorCode, Malformed, Wasm}, + exports::componentized::component::wac_loader::{Dependency, Guest, Plan}, }; pub(crate) struct WacLoader; impl Guest for WacLoader { #[allow(async_fn_in_trait)] - async fn plug(socket: Component, plugs: Vec) -> Result { + async fn plug(socket: Wasm, plugs: Vec) -> Result { let mut graph = CompositionGraph::new(); - let socket = Package::from_bytes("socket", None, socket, graph.types_mut())?; + let socket = Package::from_bytes( + "socket", + None, + component("socket", socket)?, + graph.types_mut(), + )?; let socket = graph.register_package(socket)?; let mut graph_plugs = Vec::new(); for (i, plug) in plugs.into_iter().enumerate() { - let plug = Package::from_bytes(&format!("plug:{i}"), None, plug, graph.types_mut())?; + let name = format!("plug:{i}"); + let plug = component(&name, plug)?; + let plug = Package::from_bytes(&name, None, plug, graph.types_mut())?; let plug = graph.register_package(plug)?; graph_plugs.push(plug); } @@ -34,16 +41,16 @@ impl Guest for WacLoader { } #[allow(async_fn_in_trait)] - async fn compose(plan: Plan, deps: Vec) -> Result { + async fn compose(plan: Plan, deps: Vec) -> Result { match plan { Plan::Wac(script) => { let document = Document::parse(&script)?; - let (names, components): (Vec, Vec) = deps.into_iter().unzip(); + let (names, components): (Vec, Vec) = deps.into_iter().unzip(); let mut dependencies = IndexMap::new(); - for (pkg, component) in names.iter().zip(components) { + for (pkg, wasm) in names.iter().zip(components) { let key = BorrowedPackageKey::from_name_and_version(pkg, None); - dependencies.insert(key, component); + dependencies.insert(key, component(pkg, wasm)?); } let resolution = document.resolve(dependencies)?; let component = resolution.encode(EncodeOptions::default())?; @@ -54,39 +61,64 @@ impl Guest for WacLoader { } } -impl From for Error { - fn from(value: anyhow::Error) -> Self { - Self::Other(Some(value.to_string())) +/// The wasm when it is a valid component, otherwise a `not-component` or `malformed` error naming +/// it. +fn component(name: &str, wasm: Wasm) -> Result { + if !wasmparser::Parser::is_component(&wasm) { + return Err(ErrorCode::NotComponent(Some(name.to_string()))); } + wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all()) + .validate_all(&wasm) + .map_err(|error| { + ErrorCode::Malformed(Malformed { + name: Some(name.to_string()), + message: error.to_string(), + }) + })?; + Ok(wasm) } -impl From for Error { - fn from(value: wac_graph::EncodeError) -> Self { - Self::Other(Some(value.to_string())) +impl From for ErrorCode { + fn from(err: anyhow::Error) -> Self { + Self::Other(Some(err.to_string())) } } -impl From for Error { - fn from(value: wac_graph::PlugError) -> Self { - Self::Other(Some(value.to_string())) +impl From for ErrorCode { + fn from(err: wac_graph::EncodeError) -> Self { + Self::Other(Some(err.to_string())) } } -impl From for Error { - fn from(value: wac_graph::RegisterPackageError) -> Self { - Self::Other(Some(value.to_string())) +impl From for ErrorCode { + fn from(err: wac_graph::PlugError) -> Self { + Self::Other(Some(err.to_string())) } } -impl From for Error { - fn from(value: wac_parser::Error) -> Self { - Self::Other(Some(value.to_string())) +impl From for ErrorCode { + fn from(err: wac_graph::RegisterPackageError) -> Self { + Self::Other(Some(err.to_string())) } } -impl From for Error { - fn from(value: wac_parser::resolution::Error) -> Self { - Self::Other(Some(value.to_string())) +impl From for ErrorCode { + fn from(err: wac_parser::Error) -> Self { + Self::Malformed(Malformed { + name: None, + message: format!("malformed wac script: {err}"), + }) + } +} + +impl From for ErrorCode { + fn from(err: wac_parser::resolution::Error) -> Self { + match err { + wac_parser::resolution::Error::UnknownPackage { name, .. } => { + Self::NotFound(Some(name.to_string())) + } + err => Self::Other(Some(err.to_string())), + } } } @@ -98,3 +130,6 @@ wit_bindgen::generate!({ }); export!(WacLoader); + +#[cfg(test)] +mod tests; diff --git a/components/wac-loader/src/tests.rs b/components/wac-loader/src/tests.rs new file mode 100644 index 0000000..f04311d --- /dev/null +++ b/components/wac-loader/src/tests.rs @@ -0,0 +1,124 @@ +use std::{ + pin::pin, + task::{Context, Poll, Waker}, +}; + +use crate::{ + WacLoader, + componentized::component::types::{ErrorCode, Malformed}, + exports::componentized::component::wac_loader::{Guest, Plan}, +}; + +/// Polls a future that completes without waiting, the loader does not await I/O. +fn ready(future: impl Future) -> T { + match pin!(future).poll(&mut Context::from_waker(Waker::noop())) { + Poll::Ready(value) => value, + Poll::Pending => panic!("future is not ready"), + } +} + +fn empty_component() -> Vec { + wat::parse_str("(component)").expect("valid wat") +} + +fn core_module() -> Vec { + wat::parse_str("(module)").expect("valid wat") +} + +#[test] +fn plug_socket_not_component() { + let error = + ready(WacLoader::plug(core_module(), vec![empty_component()])).expect_err("fails to plug"); + + assert!(matches!(error, ErrorCode::NotComponent(Some(name)) if name == "socket")); +} + +#[test] +fn plug_names_plug_not_component() { + let error = ready(WacLoader::plug( + empty_component(), + vec![empty_component(), core_module()], + )) + .expect_err("fails to plug"); + + assert!(matches!(error, ErrorCode::NotComponent(Some(name)) if name == "plug:1")); +} + +#[test] +fn plug_bytes_not_component() { + let error = ready(WacLoader::plug(b"not wasm".to_vec(), vec![])).expect_err("fails to plug"); + + assert!(matches!(error, ErrorCode::NotComponent(Some(name)) if name == "socket")); +} + +#[test] +fn compose_names_dependency_not_component() { + let error = ready(WacLoader::compose( + Plan::Wac("package test:composition;".to_string()), + vec![ + ("test:ok".to_string(), empty_component()), + ("test:core".to_string(), core_module()), + ], + )) + .expect_err("fails to compose"); + + assert!(matches!(error, ErrorCode::NotComponent(Some(name)) if name == "test:core")); +} + +#[test] +fn compose_components() { + let composed = ready(WacLoader::compose( + Plan::Wac("package test:composition;".to_string()), + vec![("test:ok".to_string(), empty_component())], + )) + .expect("composes"); + + assert!(wasmparser::Parser::is_component(&composed)); +} + +/// A component header followed by an invalid section. +fn malformed_component() -> Vec { + let mut bytes = empty_component()[..8].to_vec(); + bytes.extend([0xff, 0xff]); + bytes +} + +#[test] +fn plug_names_malformed_plug() { + let error = ready(WacLoader::plug( + empty_component(), + vec![malformed_component()], + )) + .expect_err("fails to plug"); + + assert!(matches!( + error, + ErrorCode::Malformed(Malformed { name: Some(name), message }) + if name == "plug:0" && !message.is_empty() + )); +} + +#[test] +fn compose_names_malformed_dependency() { + let error = ready(WacLoader::compose( + Plan::Wac("package test:composition;".to_string()), + vec![("test:broken".to_string(), malformed_component())], + )) + .expect_err("fails to compose"); + + assert!(matches!( + error, + ErrorCode::Malformed(Malformed { name: Some(name), .. }) if name == "test:broken" + )); +} + +#[test] +fn compose_unknown_package() { + let error = ready(WacLoader::compose( + Plan::Wac("package test:composition;\n\nlet missing = new test:missing {};".to_string()), + vec![("test:ok".to_string(), empty_component())], + )) + .expect_err("fails to compose"); + + assert!(matches!(error, ErrorCode::NotFound(Some(name)) if name == "test:missing")); +} diff --git a/components/extract-wit/Cargo.toml b/components/wit-tools/Cargo.toml similarity index 62% rename from components/extract-wit/Cargo.toml rename to components/wit-tools/Cargo.toml index e1797bb..9a0f1d8 100644 --- a/components/extract-wit/Cargo.toml +++ b/components/wit-tools/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "extract-wit" +name = "wit-tools" version = "0.1.0" edition = "2024" license = "Apache-2.0" @@ -9,8 +9,14 @@ crate-type = ["cdylib"] [dependencies] anyhow = { workspace = true } +id-arena = "2" semver = "1" wasmparser = { workspace = true } wit-bindgen = { workspace = true } wit-component = { workspace = true } wit-parser = { workspace = true } + +[dev-dependencies] +wasmtime = { workspace = true } +wat = { workspace = true } +wit-component = { workspace = true, features = ["dummy-module"] } diff --git a/components/wit-tools/README.md b/components/wit-tools/README.md new file mode 100644 index 0000000..c9c7099 --- /dev/null +++ b/components/wit-tools/README.md @@ -0,0 +1,5 @@ +# `wit-tools` + +Extracts the WIT (Wasm Interface Type) from a component. + +Tracks support in [`bytecodealliance/wasm-tools`](https://github.com/bytecodealliance/wasm-tools). diff --git a/components/wit-tools/src/lib.rs b/components/wit-tools/src/lib.rs new file mode 100644 index 0000000..d767330 --- /dev/null +++ b/components/wit-tools/src/lib.rs @@ -0,0 +1,1147 @@ +use std::{ + collections::{BTreeMap, HashMap}, + fmt::Display, +}; + +use id_arena::{Arena, ArenaBehavior, DefaultArenaBehavior, Id}; + +use crate::{ + componentized::component::types::{ErrorCode, Malformed}, + exports::componentized::component::wit::{ + Docs, Enum, EnumCase, Flag, Flags, Function, FunctionKind, Guest, Handle, IncludeName, + Interface, InterfaceId, List, Map, Package, PackageId, PackageName, Param, PrintOptions, + PrintedPackages, Record, RecordField, Result as Result_, Stability, Stable, Summary, Tuple, + Type, TypeDef, TypeDefKind, TypeId, TypeOwner, Unstable, Variant, VariantCase, Version, + VersionIdentifier, Wit, WitSource, World, WorldId, WorldInclude, WorldItem, + WorldItemInterface, WorldKey, + }, +}; + +pub(crate) struct WitTools; + +impl Guest for WitTools { + fn parse(source: WitSource) -> Result { + match source { + WitSource::Parsed(wit) => Ok(wit), + source => { + let (resolve, package_id, component_world_id) = Self::resolve(source)?; + Wit::new(&resolve, package_id, component_world_id) + } + } + } + + fn print(source: WitSource, options: Option) -> Result { + let additional_packages = options + .and_then(|options| options.packages) + .unwrap_or(PrintedPackages::All); + let (resolve, package_id, _) = Self::resolve(source)?; + let output = wit_component::OutputToString::default(); + let mut printer = wit_component::WitPrinter::new(output); + let mut nested_package_ids: Vec> = match additional_packages { + PrintedPackages::Default => vec![], + PrintedPackages::All => resolve.packages.iter().map(|(id, _pkg)| id).collect(), + PrintedPackages::Selected(names) => names + .iter() + .map(|name| Self::select_packages(&resolve, name)) + .collect::, _>>()? + .into_iter() + .flatten() + .collect(), + }; + nested_package_ids.retain(|id| id != &package_id); + nested_package_ids.sort_by(|a, b| { + let a = &resolve.packages.get(*a).unwrap().name; + let b = &resolve.packages.get(*b).unwrap().name; + a.cmp(b) + }); + nested_package_ids.dedup(); + printer.print(&resolve, package_id, &nested_package_ids)?; + Ok(printer.output.to_string()) + } + + fn summarize(source: WitSource) -> Result { + let wit = Self::parse(source)?; + let world_id = wit + .component_world + .as_ref() + .ok_or_else(|| ErrorCode::NotComponent(None))?; + let world = wit.worlds.get(world_id).ok_or_else(|| { + ErrorCode::Malformed(Malformed { + name: None, + message: format!("unknown world id: {world_id}"), + }) + })?; + + let extract_name = |key: &WorldKey, item: &WorldItem| match key { + WorldKey::Name(name) => match item { + WorldItem::Interface(iface) => { + iface.external_id.clone().unwrap_or(name.to_string()) + } + WorldItem::Function(..) => "--unknown--".to_owned(), + WorldItem::Type { .. } => "--unknown--".to_owned(), + }, + WorldKey::Interface(interface_id) => { + let iface = wit.interfaces.get(interface_id).unwrap(); + match (iface.name.clone(), iface.package.clone()) { + (Some(name), Some(package_id)) => { + let package = wit.packages.get(&package_id).unwrap(); + let namespace = package.name.namespace.clone(); + let package_name = package.name.name.clone(); + let version = package + .name + .version + .clone() + .map(|v| format!("@{v}")) + .unwrap_or("".to_owned()); + format!("{namespace}:{package_name}/{name}{version}") + } + _ => "--unknown--".to_owned(), + } + } + }; + + Ok(Summary { + imports: world + .imports + .iter() + .map(|(key, item)| extract_name(key, item)) + .collect(), + exports: world + .exports + .iter() + .map(|(key, item)| extract_name(key, item)) + .collect(), + }) + } +} + +impl Wit { + fn new( + resolve: &wit_parser::Resolve, + package_id: wit_parser::PackageId, + component_world_id: Option, + ) -> Result { + let wit = Self { + worlds: resolve.worlds.clone().into_iter().fold( + BTreeMap::new(), + |mut worlds, (id, world)| { + worlds.insert(Self::world_id(id), Self::world(world)); + worlds + }, + ), + interfaces: resolve.interfaces.clone().into_iter().fold( + BTreeMap::new(), + |mut interfaces, (id, interface)| { + interfaces.insert(Self::interface_id(id), Self::interface(interface)); + interfaces + }, + ), + types: resolve.types.clone().into_iter().fold( + BTreeMap::new(), + |mut types, (id, type_def)| { + types.insert(Self::type_id(id), Self::type_def(type_def)); + types + }, + ), + packages: resolve.packages.clone().into_iter().fold( + BTreeMap::new(), + |mut packages, (id, package)| { + packages.insert(Self::package_id(id), Self::package(package)); + packages + }, + ), + + default_package: Self::package_id(package_id), + component_world: component_world_id.map(Self::world_id), + }; + + if wit.packages.get(&wit.default_package.clone()).is_none() { + Err(ErrorCode::Malformed(Malformed { + name: None, + message: "decoded package must exist".to_string(), + }))?; + } + if let Some(world_id) = wit.component_world.clone() { + if wit.worlds.get(&world_id).is_none() { + Err(ErrorCode::Malformed(Malformed { + name: None, + message: "component world must exist if set".to_string(), + }))?; + } + } + + Ok(wit) + } + + fn world_id(id: wit_parser::WorldId) -> WorldId { + WorldId::from(format!("world:{}", id.index())) + } + + fn world(world: wit_parser::World) -> World { + World { + name: world.name, + imports: world + .imports + .into_iter() + .map(|import| Self::world_entry(import)) + .collect(), + exports: world + .exports + .into_iter() + .map(|export| Self::world_entry(export)) + .collect(), + package: world.package.map(Self::package_id), + docs: Self::docs(world.docs), + stability: Self::stability(world.stability), + includes: world + .includes + .into_iter() + .map(|include| WorldInclude { + stability: Self::stability(include.stability), + id: Self::world_id(include.id), + names: include + .names + .into_iter() + .map(|name| IncludeName { + name: name.name, + as_: name.as_, + }) + .collect(), + }) + .collect(), + } + } + + fn world_entry( + (key, item): (wit_parser::WorldKey, wit_parser::WorldItem), + ) -> (WorldKey, WorldItem) { + ( + match key { + wit_parser::WorldKey::Name(name) => WorldKey::Name(name), + wit_parser::WorldKey::Interface(id) => WorldKey::Interface(Self::interface_id(id)), + }, + match item { + wit_parser::WorldItem::Interface { + id, + stability, + external_id, + docs, + .. + } => WorldItem::Interface(WorldItemInterface { + id: Self::interface_id(id), + stability: Self::stability(stability), + external_id: external_id, + docs: Self::docs(docs), + }), + wit_parser::WorldItem::Function(function) => { + WorldItem::Function(Self::function(function)) + } + wit_parser::WorldItem::Type { id, .. } => WorldItem::Type(Self::type_id(id)), + }, + ) + } + + fn interface_id(id: wit_parser::InterfaceId) -> InterfaceId { + InterfaceId::from(format!("interface:{}", id.index())) + } + + fn interface(interface: wit_parser::Interface) -> Interface { + Interface { + name: interface.name, + types: interface + .types + .into_iter() + .map(|(name, id)| (name, Self::type_id(id))) + .collect(), + functions: interface + .functions + .into_iter() + .map(|(name, function)| (name, Self::function(function))) + .collect(), + docs: Self::docs(interface.docs), + stability: Self::stability(interface.stability), + package: interface.package.map(Self::package_id), + } + } + + fn function(function: wit_parser::Function) -> Function { + Function { + name: function.name, + kind: match function.kind { + wit_parser::FunctionKind::Freestanding => FunctionKind::Freestanding, + wit_parser::FunctionKind::AsyncFreestanding => FunctionKind::AsyncFreestanding, + wit_parser::FunctionKind::Method(id) => FunctionKind::Method(Self::type_id(id)), + wit_parser::FunctionKind::AsyncMethod(id) => { + FunctionKind::AsyncMethod(Self::type_id(id)) + } + wit_parser::FunctionKind::Static(id) => FunctionKind::Static(Self::type_id(id)), + wit_parser::FunctionKind::AsyncStatic(id) => { + FunctionKind::AsyncStatic(Self::type_id(id)) + } + wit_parser::FunctionKind::Constructor(id) => { + FunctionKind::Constructor(Self::type_id(id)) + } + wit_parser::FunctionKind::Getter => FunctionKind::Getter, + wit_parser::FunctionKind::Setter => FunctionKind::Setter, + wit_parser::FunctionKind::MethodGetter(id) => { + FunctionKind::MethodGetter(Self::type_id(id)) + } + wit_parser::FunctionKind::MethodSetter(id) => { + FunctionKind::MethodSetter(Self::type_id(id)) + } + wit_parser::FunctionKind::StaticGetter(id) => { + FunctionKind::StaticGetter(Self::type_id(id)) + } + wit_parser::FunctionKind::StaticSetter(id) => { + FunctionKind::StaticSetter(Self::type_id(id)) + } + }, + params: function + .params + .into_iter() + .map(|param| Param { + name: param.name, + type_: Self::type_(param.ty), + }) + .collect(), + result: function.result.map(Self::type_), + docs: Self::docs(function.docs), + stability: Self::stability(function.stability), + external_id: function.external_id, + } + } + + fn type_id(id: wit_parser::TypeId) -> TypeId { + TypeId::from(format!("type:{}", id.index())) + } + + fn type_(type_: wit_parser::Type) -> Type { + match type_ { + wit_parser::Type::Bool => Type::Bool, + wit_parser::Type::U8 => Type::U8, + wit_parser::Type::U16 => Type::U16, + wit_parser::Type::U32 => Type::U32, + wit_parser::Type::U64 => Type::U64, + wit_parser::Type::S8 => Type::S8, + wit_parser::Type::S16 => Type::S16, + wit_parser::Type::S32 => Type::S32, + wit_parser::Type::S64 => Type::S64, + wit_parser::Type::F32 => Type::F32, + wit_parser::Type::F64 => Type::F64, + wit_parser::Type::Char => Type::Char, + wit_parser::Type::String => Type::String, + wit_parser::Type::ErrorContext => Type::ErrorContext, + wit_parser::Type::Id(id) => Type::Id(Self::type_id(id)), + } + } + + fn type_def(type_def: wit_parser::TypeDef) -> TypeDef { + TypeDef { + name: type_def.name, + kind: Self::type_def_kind(type_def.kind), + owner: match type_def.owner { + wit_parser::TypeOwner::World(id) => TypeOwner::World(Self::world_id(id)), + wit_parser::TypeOwner::Interface(id) => { + TypeOwner::Interface(Self::interface_id(id)) + } + wit_parser::TypeOwner::None => TypeOwner::None, + }, + docs: Self::docs(type_def.docs), + stability: Self::stability(type_def.stability), + external_id: type_def.external_id, + } + } + + fn type_def_kind(kind: wit_parser::TypeDefKind) -> TypeDefKind { + match kind { + wit_parser::TypeDefKind::Record(record) => TypeDefKind::Record(Self::record(record)), + wit_parser::TypeDefKind::Resource => TypeDefKind::Resource, + wit_parser::TypeDefKind::Handle(handle) => TypeDefKind::Handle(Self::handle(handle)), + wit_parser::TypeDefKind::Flags(flags) => TypeDefKind::Flags(Self::flags(flags)), + wit_parser::TypeDefKind::Tuple(tuple) => TypeDefKind::Tuple(Self::tuple(tuple)), + wit_parser::TypeDefKind::Variant(variant) => { + TypeDefKind::Variant(Self::variant(variant)) + } + wit_parser::TypeDefKind::Enum(enum_) => TypeDefKind::Enum(Self::enum_(enum_)), + wit_parser::TypeDefKind::Option(option) => TypeDefKind::Option(Self::type_(option)), + wit_parser::TypeDefKind::Result(result) => TypeDefKind::Result(Self::result(result)), + wit_parser::TypeDefKind::List(list) => TypeDefKind::List(Self::list(list)), + wit_parser::TypeDefKind::FixedLengthList(type_, length) => { + TypeDefKind::List(Self::list_fixed_length(type_, length)) + } + wit_parser::TypeDefKind::Map(key, value) => TypeDefKind::Map(Self::map(key, value)), + + wit_parser::TypeDefKind::Future(future) => TypeDefKind::Future(future.map(Self::type_)), + wit_parser::TypeDefKind::Stream(stream) => TypeDefKind::Stream(stream.map(Self::type_)), + wit_parser::TypeDefKind::Type(type_) => TypeDefKind::Type(Self::type_(type_)), + wit_parser::TypeDefKind::Unknown => TypeDefKind::Unknown, + } + } + + fn record(record: wit_parser::Record) -> Record { + Record { + fields: record + .fields + .into_iter() + .map(|field| RecordField { + name: field.name, + type_: Self::type_(field.ty), + docs: Self::docs(field.docs), + }) + .collect(), + } + } + + fn handle(handle: wit_parser::Handle) -> Handle { + match handle { + wit_parser::Handle::Own(id) => Handle::Own(Self::type_id(id)), + wit_parser::Handle::Borrow(id) => Handle::Borrow(Self::type_id(id)), + } + } + + fn flags(flags: wit_parser::Flags) -> Flags { + Flags { + flags: flags + .flags + .into_iter() + .map(|flag| Flag { + name: flag.name, + docs: Self::docs(flag.docs), + }) + .collect(), + } + } + + fn tuple(tuple: wit_parser::Tuple) -> Tuple { + Tuple { + types: tuple.types.into_iter().map(Self::type_).collect(), + } + } + + fn variant(variant: wit_parser::Variant) -> Variant { + Variant { + cases: variant + .cases + .into_iter() + .map(|case| VariantCase { + name: case.name, + type_: case.ty.map(Self::type_), + docs: Self::docs(case.docs), + }) + .collect(), + } + } + + fn enum_(enum_: wit_parser::Enum) -> Enum { + Enum { + cases: enum_ + .cases + .into_iter() + .map(|case| EnumCase { + name: case.name, + docs: Self::docs(case.docs), + }) + .collect(), + } + } + + fn result(result: wit_parser::Result_) -> Result_ { + Result_ { + ok: result.ok.map(Self::type_), + err: result.err.map(Self::type_), + } + } + + fn list(type_: wit_parser::Type) -> List { + List { + type_: Self::type_(type_), + fixed_length: None, + } + } + + fn list_fixed_length(type_: wit_parser::Type, fixed_length: u32) -> List { + List { + type_: Self::type_(type_), + fixed_length: Some(fixed_length), + } + } + + fn map(key: wit_parser::Type, value: wit_parser::Type) -> Map { + Map { + key: Self::type_(key), + value: Self::type_(value), + } + } + + fn package_id(id: wit_parser::PackageId) -> PackageId { + PackageId::from(format!("package:{}", id.index())) + } + + fn package(package: wit_parser::Package) -> Package { + Package { + name: PackageName { + namespace: package.name.namespace, + name: package.name.name, + version: package.name.version.map(Self::version), + }, + docs: Self::docs(package.docs), + interfaces: package + .interfaces + .into_iter() + .map(|(name, id)| (name, Self::interface_id(id))) + .collect(), + worlds: package + .worlds + .into_iter() + .map(|(name, id)| (name, Self::world_id(id))) + .collect(), + } + } + + fn docs(docs: wit_parser::Docs) -> Docs { + Docs { + contents: docs.contents, + } + } + + fn stability(stability: wit_parser::Stability) -> Stability { + match stability { + wit_parser::Stability::Unknown => Stability::Unknown, + wit_parser::Stability::Unstable { + feature, + deprecated, + } => Stability::Unstable(Unstable { + feature, + deprecated: deprecated.map(Self::version), + }), + wit_parser::Stability::Stable { since, deprecated } => Stability::Stable(Stable { + deprecated: deprecated.map(Self::version), + since: Self::version(since), + }), + } + } + + fn version(version: semver::Version) -> Version { + Version { + major: version.major, + minor: version.minor, + patch: version.patch, + prerelease: Self::version_identifiers(version.pre.as_str()), + build_metadata: Self::version_identifiers(version.build.as_str()), + } + } + + fn version_identifiers(identifiers: &str) -> Option> { + match identifiers.is_empty() { + true => None, + false => Some( + identifiers + .split('.') + .map(|identifier| match identifier.parse::() { + Ok(val) => VersionIdentifier::Numeric(val), + Err(_) => VersionIdentifier::String(identifier.into()), + }) + .collect(), + ), + } + } +} + +impl WitTools { + /// Resolves a source into its packages, the default package, and the world of a component. + fn resolve( + source: WitSource, + ) -> Result< + ( + wit_parser::Resolve, + wit_parser::PackageId, + Option, + ), + ErrorCode, + > { + match source { + WitSource::Wasm(component) => Self::decode(&component), + WitSource::Wit(text) => { + let mut resolve = wit_parser::Resolve::default(); + // keep `@unstable` items, rather than dropping those without an enabled feature + resolve.all_features = true; + let package_id = resolve + .push_str("source.wit", &text) + .map_err(Self::malformed)?; + Ok((resolve, package_id, None)) + } + WitSource::Parsed(wit) => { + let component_world = wit.component_world.clone(); + let (resolve, package_id, ids) = wit.into_resolve()?; + let component_world_id = component_world + .map(|world_id| ids.world_id(&world_id)) + .transpose()?; + Ok((resolve, package_id, component_world_id)) + } + } + } + + /// Decodes a component, or a WIT package encoded as a component. Anything else, e.g. a core + /// module, is not a component. + fn decode( + component: &[u8], + ) -> Result< + ( + wit_parser::Resolve, + wit_parser::PackageId, + Option, + ), + ErrorCode, + > { + if !wasmparser::Parser::is_component(component) { + Err(ErrorCode::NotComponent(None))?; + } + let decoded = wit_component::decode(component).map_err(Self::malformed)?; + let package_id = decoded.package(); + Ok(match decoded { + wit_component::DecodedWasm::Component(resolve, world_id) => { + (resolve, package_id, Some(world_id)) + } + wit_component::DecodedWasm::WitPackage(resolve, _) => (resolve, package_id, None), + }) + } + + /// A `malformed` error for the single source of a call. + fn malformed(error: impl Display) -> ErrorCode { + ErrorCode::Malformed(Malformed { + name: None, + message: error.to_string(), + }) + } + + /// The packages with the name, every version of the package when the name has no version. + fn select_packages( + resolve: &wit_parser::Resolve, + name: &PackageName, + ) -> Result, ErrorCode> { + let version = name.version.clone().map(ResolveIds::version).transpose()?; + let package_ids: Vec<_> = resolve + .packages + .iter() + .filter(|(_, package)| { + package.name.namespace == name.namespace + && package.name.name == name.name + && (version.is_none() || package.name.version == version) + }) + .map(|(id, _)| id) + .collect(); + if package_ids.is_empty() { + Err(ErrorCode::NotFound(Some(name.to_string())))?; + } + Ok(package_ids) + } +} + +impl Wit { + fn into_resolve( + self, + ) -> Result<(wit_parser::Resolve, wit_parser::PackageId, ResolveIds), ErrorCode> { + let mut resolve = wit_parser::Resolve::default(); + let ids = ResolveIds { + worlds: ResolveIds::assign(self.worlds.keys(), &resolve.worlds), + interfaces: ResolveIds::assign(self.interfaces.keys(), &resolve.interfaces), + types: ResolveIds::assign(self.types.keys(), &resolve.types), + packages: ResolveIds::assign(self.packages.keys(), &resolve.packages), + }; + + for (id, package) in ResolveIds::ordered(self.packages, &ids.packages) { + let package = ids.package(package)?; + resolve.package_names.insert(package.name.clone(), id); + ResolveIds::alloc(&mut resolve.packages, id, package); + } + for (id, interface) in ResolveIds::ordered(self.interfaces, &ids.interfaces) { + ResolveIds::alloc(&mut resolve.interfaces, id, ids.interface(interface)?); + } + for (id, type_def) in ResolveIds::ordered(self.types, &ids.types) { + ResolveIds::alloc(&mut resolve.types, id, ids.type_def(type_def)?); + } + for (id, world) in ResolveIds::ordered(self.worlds, &ids.worlds) { + ResolveIds::alloc(&mut resolve.worlds, id, ids.world(world)?); + } + + let package_id = ids.package_id(&self.default_package)?; + Ok((resolve, package_id, ids)) + } +} + +/// Maps the string ids used by `Wit` to the arena ids of a `wit_parser::Resolve`. +struct ResolveIds { + worlds: HashMap, + interfaces: HashMap, + types: HashMap, + packages: HashMap, +} + +impl ResolveIds { + /// Assigns sequential arena ids, ordered by the numeric suffix of each id + /// (`type:2` before `type:10`) so the original arena order is preserved. + fn assign<'a, T>( + keys: impl Iterator, + arena: &Arena, + ) -> HashMap> { + let arena_id = DefaultArenaBehavior::::arena_id(arena.next_id()); + let mut keys: Vec<&String> = keys.collect(); + keys.sort_by_key(|key| { + let index = key + .rsplit_once(':') + .and_then(|(_, index)| index.parse::().ok()); + (index, key.to_string()) + }); + keys.into_iter() + .enumerate() + .map(|(index, key)| { + ( + key.clone(), + DefaultArenaBehavior::::new_id(arena_id, arena.len() + index), + ) + }) + .collect() + } + + fn ordered(items: BTreeMap, ids: &HashMap>) -> Vec<(Id, V)> { + let mut items: Vec<(Id, V)> = items + .into_iter() + .map(|(key, value)| (ids[&key], value)) + .collect(); + items.sort_by_key(|(id, _)| id.index()); + items + } + + fn alloc(arena: &mut Arena, id: Id, item: T) { + let allocated = arena.alloc(item); + debug_assert_eq!(allocated, id); + } + + fn lookup(ids: &HashMap>, kind: &str, id: &str) -> Result, ErrorCode> { + ids.get(id).copied().ok_or_else(|| { + ErrorCode::Malformed(Malformed { + name: None, + message: format!("unknown {kind} id: {id}"), + }) + }) + } + + fn world_id(&self, id: &WorldId) -> Result { + Self::lookup(&self.worlds, "world", id) + } + + fn interface_id(&self, id: &InterfaceId) -> Result { + Self::lookup(&self.interfaces, "interface", id) + } + + fn type_id(&self, id: &TypeId) -> Result { + Self::lookup(&self.types, "type", id) + } + + fn package_id(&self, id: &PackageId) -> Result { + Self::lookup(&self.packages, "package", id) + } + + fn world(&self, world: World) -> Result { + Ok(wit_parser::World { + name: world.name, + imports: world + .imports + .into_iter() + .map(|entry| self.world_entry(entry)) + .collect::>()?, + exports: world + .exports + .into_iter() + .map(|entry| self.world_entry(entry)) + .collect::>()?, + package: world.package.map(|id| self.package_id(&id)).transpose()?, + docs: Self::docs(world.docs), + stability: Self::stability(world.stability)?, + includes: world + .includes + .into_iter() + .map(|include| { + Ok(wit_parser::WorldInclude { + stability: Self::stability(include.stability)?, + id: self.world_id(&include.id)?, + names: include + .names + .into_iter() + .map(|name| wit_parser::IncludeName { + name: name.name, + as_: name.as_, + }) + .collect(), + span: Default::default(), + }) + }) + .collect::>()?, + span: Default::default(), + }) + } + + fn world_entry( + &self, + (key, item): (WorldKey, WorldItem), + ) -> Result<(wit_parser::WorldKey, wit_parser::WorldItem), ErrorCode> { + Ok(( + match key { + WorldKey::Name(name) => wit_parser::WorldKey::Name(name), + WorldKey::Interface(id) => wit_parser::WorldKey::Interface(self.interface_id(&id)?), + }, + match item { + WorldItem::Interface(iface) => wit_parser::WorldItem::Interface { + id: self.interface_id(&iface.id)?, + stability: Self::stability(iface.stability)?, + external_id: iface.external_id, + docs: Self::docs(iface.docs), + span: Default::default(), + }, + WorldItem::Function(function) => { + wit_parser::WorldItem::Function(self.function(function)?) + } + WorldItem::Type(id) => wit_parser::WorldItem::Type { + id: self.type_id(&id)?, + span: Default::default(), + }, + }, + )) + } + + fn interface(&self, interface: Interface) -> Result { + Ok(wit_parser::Interface { + name: interface.name, + types: interface + .types + .into_iter() + .map(|(name, id)| Ok((name, self.type_id(&id)?))) + .collect::>()?, + functions: interface + .functions + .into_iter() + .map(|(name, function)| Ok((name, self.function(function)?))) + .collect::>()?, + docs: Self::docs(interface.docs), + stability: Self::stability(interface.stability)?, + package: interface + .package + .map(|id| self.package_id(&id)) + .transpose()?, + span: Default::default(), + clone_of: None, + }) + } + + fn function(&self, function: Function) -> Result { + Ok(wit_parser::Function { + name: function.name, + kind: match function.kind { + FunctionKind::Freestanding => wit_parser::FunctionKind::Freestanding, + FunctionKind::AsyncFreestanding => wit_parser::FunctionKind::AsyncFreestanding, + FunctionKind::Method(id) => wit_parser::FunctionKind::Method(self.type_id(&id)?), + FunctionKind::AsyncMethod(id) => { + wit_parser::FunctionKind::AsyncMethod(self.type_id(&id)?) + } + FunctionKind::Static(id) => wit_parser::FunctionKind::Static(self.type_id(&id)?), + FunctionKind::AsyncStatic(id) => { + wit_parser::FunctionKind::AsyncStatic(self.type_id(&id)?) + } + FunctionKind::Constructor(id) => { + wit_parser::FunctionKind::Constructor(self.type_id(&id)?) + } + FunctionKind::Getter => wit_parser::FunctionKind::Getter, + FunctionKind::Setter => wit_parser::FunctionKind::Setter, + FunctionKind::MethodGetter(id) => { + wit_parser::FunctionKind::MethodGetter(self.type_id(&id)?) + } + FunctionKind::MethodSetter(id) => { + wit_parser::FunctionKind::MethodSetter(self.type_id(&id)?) + } + FunctionKind::StaticGetter(id) => { + wit_parser::FunctionKind::StaticGetter(self.type_id(&id)?) + } + FunctionKind::StaticSetter(id) => { + wit_parser::FunctionKind::StaticSetter(self.type_id(&id)?) + } + }, + params: function + .params + .into_iter() + .map(|param| { + Ok(wit_parser::Param { + name: param.name, + ty: self.type_(param.type_)?, + span: Default::default(), + }) + }) + .collect::>()?, + result: function.result.map(|ty| self.type_(ty)).transpose()?, + docs: Self::docs(function.docs), + stability: Self::stability(function.stability)?, + span: Default::default(), + external_id: function.external_id, + }) + } + + fn type_(&self, type_: Type) -> Result { + Ok(match type_ { + Type::Bool => wit_parser::Type::Bool, + Type::U8 => wit_parser::Type::U8, + Type::U16 => wit_parser::Type::U16, + Type::U32 => wit_parser::Type::U32, + Type::U64 => wit_parser::Type::U64, + Type::S8 => wit_parser::Type::S8, + Type::S16 => wit_parser::Type::S16, + Type::S32 => wit_parser::Type::S32, + Type::S64 => wit_parser::Type::S64, + Type::F32 => wit_parser::Type::F32, + Type::F64 => wit_parser::Type::F64, + Type::Char => wit_parser::Type::Char, + Type::String => wit_parser::Type::String, + Type::ErrorContext => wit_parser::Type::ErrorContext, + Type::Id(id) => wit_parser::Type::Id(self.type_id(&id)?), + }) + } + + fn type_def(&self, type_def: TypeDef) -> Result { + Ok(wit_parser::TypeDef { + name: type_def.name, + kind: self.type_def_kind(type_def.kind)?, + owner: match type_def.owner { + TypeOwner::World(id) => wit_parser::TypeOwner::World(self.world_id(&id)?), + TypeOwner::Interface(id) => { + wit_parser::TypeOwner::Interface(self.interface_id(&id)?) + } + TypeOwner::None => wit_parser::TypeOwner::None, + }, + docs: Self::docs(type_def.docs), + stability: Self::stability(type_def.stability)?, + span: Default::default(), + external_id: type_def.external_id, + }) + } + + fn type_def_kind(&self, kind: TypeDefKind) -> Result { + Ok(match kind { + TypeDefKind::Record(record) => wit_parser::TypeDefKind::Record(wit_parser::Record { + fields: record + .fields + .into_iter() + .map(|field| { + Ok(wit_parser::Field { + name: field.name, + ty: self.type_(field.type_)?, + docs: Self::docs(field.docs), + span: Default::default(), + }) + }) + .collect::>()?, + }), + TypeDefKind::Resource => wit_parser::TypeDefKind::Resource, + TypeDefKind::Handle(handle) => wit_parser::TypeDefKind::Handle(match handle { + Handle::Own(id) => wit_parser::Handle::Own(self.type_id(&id)?), + Handle::Borrow(id) => wit_parser::Handle::Borrow(self.type_id(&id)?), + }), + TypeDefKind::Flags(flags) => wit_parser::TypeDefKind::Flags(wit_parser::Flags { + flags: flags + .flags + .into_iter() + .map(|flag| wit_parser::Flag { + name: flag.name, + docs: Self::docs(flag.docs), + span: Default::default(), + }) + .collect(), + }), + TypeDefKind::Tuple(tuple) => wit_parser::TypeDefKind::Tuple(wit_parser::Tuple { + types: tuple + .types + .into_iter() + .map(|ty| self.type_(ty)) + .collect::>()?, + }), + TypeDefKind::Variant(variant) => { + wit_parser::TypeDefKind::Variant(wit_parser::Variant { + cases: variant + .cases + .into_iter() + .map(|case| { + Ok(wit_parser::Case { + name: case.name, + ty: case.type_.map(|ty| self.type_(ty)).transpose()?, + docs: Self::docs(case.docs), + span: Default::default(), + }) + }) + .collect::>()?, + }) + } + TypeDefKind::Enum(enum_) => wit_parser::TypeDefKind::Enum(wit_parser::Enum { + cases: enum_ + .cases + .into_iter() + .map(|case| wit_parser::EnumCase { + name: case.name, + docs: Self::docs(case.docs), + span: Default::default(), + }) + .collect(), + }), + TypeDefKind::Option(ty) => wit_parser::TypeDefKind::Option(self.type_(ty)?), + TypeDefKind::Result(result) => wit_parser::TypeDefKind::Result(wit_parser::Result_ { + ok: result.ok.map(|ty| self.type_(ty)).transpose()?, + err: result.err.map(|ty| self.type_(ty)).transpose()?, + }), + TypeDefKind::List(list) => match list.fixed_length { + Some(length) => { + wit_parser::TypeDefKind::FixedLengthList(self.type_(list.type_)?, length) + } + None => wit_parser::TypeDefKind::List(self.type_(list.type_)?), + }, + TypeDefKind::Map(map) => { + wit_parser::TypeDefKind::Map(self.type_(map.key)?, self.type_(map.value)?) + } + TypeDefKind::Future(ty) => { + wit_parser::TypeDefKind::Future(ty.map(|ty| self.type_(ty)).transpose()?) + } + TypeDefKind::Stream(ty) => { + wit_parser::TypeDefKind::Stream(ty.map(|ty| self.type_(ty)).transpose()?) + } + TypeDefKind::Type(ty) => wit_parser::TypeDefKind::Type(self.type_(ty)?), + TypeDefKind::Unknown => wit_parser::TypeDefKind::Unknown, + }) + } + + fn package(&self, package: Package) -> Result { + Ok(wit_parser::Package { + name: wit_parser::PackageName { + namespace: package.name.namespace, + name: package.name.name, + version: package.name.version.map(Self::version).transpose()?, + }, + docs: Self::docs(package.docs), + interfaces: package + .interfaces + .into_iter() + .map(|(name, id)| Ok((name, self.interface_id(&id)?))) + .collect::>()?, + worlds: package + .worlds + .into_iter() + .map(|(name, id)| Ok((name, self.world_id(&id)?))) + .collect::>()?, + }) + } + + fn docs(docs: Docs) -> wit_parser::Docs { + wit_parser::Docs { + contents: docs.contents, + } + } + + fn stability(stability: Stability) -> Result { + Ok(match stability { + Stability::Unknown => wit_parser::Stability::Unknown, + Stability::Unstable(Unstable { + feature, + deprecated, + }) => wit_parser::Stability::Unstable { + feature, + deprecated: deprecated.map(Self::version).transpose()?, + }, + Stability::Stable(Stable { since, deprecated }) => wit_parser::Stability::Stable { + since: Self::version(since)?, + deprecated: deprecated.map(Self::version).transpose()?, + }, + }) + } + + fn version(version: Version) -> Result { + semver::Version::parse(&version.to_string()).map_err(|err| { + ErrorCode::Malformed(Malformed { + name: None, + message: format!("invalid version {version}: {err}"), + }) + }) + } +} + +impl From for ErrorCode { + fn from(value: anyhow::Error) -> Self { + Self::Other(Some(value.to_string())) + } +} + +impl Display for PackageName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let PackageName { + namespace, + name, + version, + } = self; + f.write_fmt(format_args!("{namespace}:{name}"))?; + if let Some(version) = version { + f.write_fmt(format_args!("@{version}"))?; + } + Ok(()) + } +} + +impl Display for Version { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Version { + major, + minor, + patch, + prerelease, + build_metadata, + } = self; + let prerelease = prerelease + .clone() + .map(|chunks| { + chunks + .into_iter() + .map(|chunk| chunk.to_string()) + .collect::>() + .join(".") + }) + .map(|s| format!("-{s}")) + .unwrap_or("".to_string()); + let build_metadata = build_metadata + .clone() + .map(|chunks| { + chunks + .into_iter() + .map(|chunk| chunk.to_string()) + .collect::>() + .join(".") + }) + .map(|s| format!("+{s}")) + .unwrap_or("".to_string()); + f.write_fmt(format_args!( + "{major}.{minor}.{patch}{prerelease}{build_metadata}" + )) + } +} + +impl Display for VersionIdentifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + VersionIdentifier::String(vi) => f.write_fmt(format_args!("{vi}")), + VersionIdentifier::Numeric(vi) => f.write_fmt(format_args!("{vi}")), + } + } +} + +wit_bindgen::generate!({ + path: "../wit", + world: "wit-tools", + merge_structurally_equal_types: true, + generate_all +}); + +export!(WitTools); + +#[cfg(test)] +mod tests; diff --git a/components/wit-tools/src/tests.rs b/components/wit-tools/src/tests.rs new file mode 100644 index 0000000..f234bcf --- /dev/null +++ b/components/wit-tools/src/tests.rs @@ -0,0 +1,911 @@ +use std::collections::BTreeMap; + +use crate::{ + WitTools, + componentized::component::types::{ErrorCode, Malformed}, + exports::componentized::component::wit::{ + Guest, PackageName, PrintOptions, PrintedPackages, Stability, Type, TypeDefKind, Version, + VersionIdentifier, WitSource, WorldItem, WorldKey, + }, +}; + +/// Exercises each kind of type, function, docs and stability a `Wit` models. +const KITCHEN_SINK: &str = r#" +/// A package with one of everything. +package test:kitchen-sink@1.2.3-alpha.1+build.5; + +/// An interface with types. +@since(version = 1.0.0) +interface types { + /// A record. + record point { + /// The x coordinate. + x: s32, + y: s32, + } + + flags permissions { + /// Read access. + read, + write, + execute, + } + + enum color { + /// The color red. + red, + green, + blue, + } + + variant shape { + /// A circle with a radius. + circle(f64), + square(f64), + empty, + } + + type alias = point; + type pair = tuple; + type maybe = option; + type fallible = result; + type ok-only = result; + type err-only = result<_, char>; + type empty-result = result; + type points = list; + type quad = list; + type lookup = map; + type values = stream; + type unit-stream = stream; + type pending = future; + type unit-future = future; + + /// A resource. + resource counter { + /// Creates a counter. + constructor(start: u32); + increment: func(); + get: func() -> u32; + merge: static func(a: borrow, b: own) -> counter; + wait: async func(); + } + + @unstable(feature = experimental) + unstable-func: func(); + + @since(version = 1.0.0) + @deprecated(version = 1.1.0) + deprecated-func: func(); + + /// A free function. + distance: func(a: point, b: point) -> f64; + sleep: async func(ms: u64); +} + +interface consumer { + use types.{point, counter}; + + centroid: func(points: list) -> point; + total: func(c: borrow) -> u32; +} + +/// A world. +world app { + import types; + import log: func(message: string); + import host: interface { + now: func() -> u64; + } + + use types.{point}; + type local-point = point; + + export consumer; + export run: func(p: local-point); +} + +world extended { + include app with { log as logger } + export extra: func(); +} +"#; + +const SIMPLE: &str = r#" +package test:simple@0.1.0; + +interface greeter { + greet: func(name: string) -> string; +} + +world hello { + import greeter; + export greeter; + export run: func(); +} +"#; + +fn resolve(wit: &str) -> (wit_parser::Resolve, wit_parser::PackageId) { + resolve_with_deps(&[], wit) +} + +/// Resolves `wit` after each of the `deps` packages it uses. +fn resolve_with_deps(deps: &[&str], wit: &str) -> (wit_parser::Resolve, wit_parser::PackageId) { + let mut resolve = wit_parser::Resolve::default(); + resolve.all_features = true; + for (i, dep) in deps.iter().enumerate() { + resolve + .push_str(format!("dep{i}.wit"), dep) + .expect("valid dependency wit"); + } + let package_id = resolve.push_str("test.wit", wit).expect("valid wit"); + (resolve, package_id) +} + +/// Encodes a WIT package as a binary package. +fn wit_package(wit: &str) -> Vec { + let (resolve, package_id) = resolve(wit); + wit_component::encode(&resolve, package_id, false).expect("encodes package") +} + +/// Encodes a component targeting the named world, with dummy implementations of its exports. +fn component(wit: &str, world: &str) -> Vec { + component_with_deps(&[], wit, world) +} + +/// Encodes a component targeting the named world of `wit`, which uses the `deps` packages. +fn component_with_deps(deps: &[&str], wit: &str, world: &str) -> Vec { + let (resolve, package_id) = resolve_with_deps(deps, wit); + let world = resolve + .select_world(&[package_id], Some(world)) + .expect("world exists"); + let mut module = + wit_component::dummy_module(&resolve, world, wit_parser::ManglingAndAbi::Standard32); + wit_component::embed_component_metadata( + &mut module, + &resolve, + world, + wit_component::StringEncoding::UTF8, + false, + ) + .expect("embeds metadata"); + wit_component::ComponentEncoder::default() + .module(&module) + .expect("valid module") + .validate(true) + .encode() + .expect("encodes component") +} + +/// Prints the WIT of encoded bytes directly with `wit-component`, the output `print` should match: +/// the decoded package, followed by every other package nested, sorted by name. +fn expected_print(bytes: &[u8]) -> String { + let decoded = wit_component::decode(bytes).expect("decodes"); + let mut nested: Vec<_> = decoded + .resolve() + .packages + .iter() + .map(|(id, _)| id) + .filter(|id| *id != decoded.package()) + .collect(); + nested.sort_by(|a, b| { + decoded.resolve().packages[*a] + .name + .cmp(&decoded.resolve().packages[*b].name) + }); + let mut printer = wit_component::WitPrinter::default(); + printer + .print(decoded.resolve(), decoded.package(), &nested) + .expect("prints"); + printer.output.to_string() +} + +/// The message of a `malformed` error for the single source of a call. +fn malformed_message(error: ErrorCode) -> String { + match error { + ErrorCode::Malformed(Malformed { + name: None, + message, + }) => message, + error => panic!("expected a malformed error without a name, got {error:?}"), + } +} + +#[test] +fn parse_wit_package() { + let wit = WitTools::parse(WitSource::Wasm(wit_package(SIMPLE))).expect("parses"); + + assert_eq!(wit.component_world, None); + + let package = &wit.packages[&wit.default_package]; + assert_eq!(package.name.namespace, "test"); + assert_eq!(package.name.name, "simple"); + assert_eq!( + package.name.version.as_ref().map(ToString::to_string), + Some("0.1.0".to_string()) + ); + assert_eq!( + package + .interfaces + .iter() + .map(|(name, _)| name.as_str()) + .collect::>(), + vec!["greeter"] + ); + assert_eq!( + package + .worlds + .iter() + .map(|(name, _)| name.as_str()) + .collect::>(), + vec!["hello"] + ); + + let (_, greeter_id) = &package.interfaces[0]; + let greeter = &wit.interfaces[greeter_id]; + assert_eq!(greeter.name.as_deref(), Some("greeter")); + let (name, greet) = &greeter.functions[0]; + assert_eq!(name, "greet"); + assert_eq!(greet.params.len(), 1); + assert_eq!(greet.params[0].name, "name"); + assert!(matches!(greet.params[0].type_, Type::String)); + assert!(matches!(greet.result, Some(Type::String))); +} + +#[test] +fn parse_component() { + let wit = WitTools::parse(WitSource::Wasm(component(SIMPLE, "hello"))).expect("parses"); + + let world_id = wit.component_world.clone().expect("has a component world"); + let world = &wit.worlds[&world_id]; + assert_eq!(world.imports.len(), 1); + assert!(matches!( + &world.imports[0], + (WorldKey::Interface(_), WorldItem::Interface(_)) + )); + assert_eq!(world.exports.len(), 2); + assert!(world.exports.iter().any( + |(key, item)| matches!(key, WorldKey::Name(name) if name == "run") + && matches!(item, WorldItem::Function(_)) + )); +} + +#[test] +fn parse_ids_are_prefixed_by_kind() { + let wit = WitTools::parse(WitSource::Wasm(wit_package(KITCHEN_SINK))).expect("parses"); + + assert!(wit.packages.keys().all(|id| id.starts_with("package:"))); + assert!(wit.interfaces.keys().all(|id| id.starts_with("interface:"))); + assert!(wit.types.keys().all(|id| id.starts_with("type:"))); + assert!(wit.worlds.keys().all(|id| id.starts_with("world:"))); +} + +#[test] +fn parse_type_def_kinds() { + let wit = WitTools::parse(WitSource::Wasm(wit_package(KITCHEN_SINK))).expect("parses"); + + let kind = |name: &str| { + wit.types + .values() + .find(|type_def| type_def.name.as_deref() == Some(name)) + .map(|type_def| &type_def.kind) + .unwrap_or_else(|| panic!("type {name} exists")) + }; + + assert!(matches!(kind("point"), TypeDefKind::Record(r) if r.fields.len() == 2)); + assert!(matches!(kind("permissions"), TypeDefKind::Flags(f) if f.flags.len() == 3)); + assert!(matches!(kind("color"), TypeDefKind::Enum(e) if e.cases.len() == 3)); + assert!(matches!(kind("shape"), TypeDefKind::Variant(v) if v.cases.len() == 3)); + assert!(matches!(kind("alias"), TypeDefKind::Type(Type::Id(_)))); + assert!(matches!(kind("pair"), TypeDefKind::Tuple(t) if t.types.len() == 4)); + assert!(matches!(kind("maybe"), TypeDefKind::Option(Type::S8))); + assert!(matches!( + kind("fallible"), + TypeDefKind::Result(r) if matches!(r.ok, Some(Type::S16)) && matches!(r.err, Some(Type::String)) + )); + assert!(matches!( + kind("empty-result"), + TypeDefKind::Result(r) if r.ok.is_none() && r.err.is_none() + )); + assert!(matches!(kind("points"), TypeDefKind::List(l) if l.fixed_length.is_none())); + assert!(matches!( + kind("quad"), + TypeDefKind::List(l) if l.fixed_length == Some(4) && matches!(l.type_, Type::F32) + )); + assert!(matches!( + kind("lookup"), + TypeDefKind::Map(m) if matches!(m.key, Type::String) && matches!(m.value, Type::Bool) + )); + assert!(matches!( + kind("values"), + TypeDefKind::Stream(Some(Type::U8)) + )); + assert!(matches!(kind("unit-stream"), TypeDefKind::Stream(None))); + assert!(matches!( + kind("pending"), + TypeDefKind::Future(Some(Type::String)) + )); + assert!(matches!(kind("unit-future"), TypeDefKind::Future(None))); + assert!(matches!(kind("counter"), TypeDefKind::Resource)); +} + +#[test] +fn parse_docs_and_stability() { + let wit = WitTools::parse(WitSource::Wasm(wit_package(KITCHEN_SINK))).expect("parses"); + + let package = &wit.packages[&wit.default_package]; + assert_eq!( + package.docs.contents.as_deref(), + Some("A package with one of everything.") + ); + + let types = wit + .interfaces + .values() + .find(|iface| iface.name.as_deref() == Some("types")) + .expect("types interface"); + assert_eq!( + types.docs.contents.as_deref(), + Some("An interface with types.") + ); + assert!(matches!( + &types.stability, + Stability::Stable(stable) if stable.since.to_string() == "1.0.0" && stable.deprecated.is_none() + )); + + let function = |name: &str| { + &types + .functions + .iter() + .find(|(n, _)| n == name) + .unwrap_or_else(|| panic!("function {name} exists")) + .1 + }; + assert!(matches!( + &function("unstable-func").stability, + Stability::Unstable(unstable) if unstable.feature == "experimental" + )); + assert!(matches!( + &function("deprecated-func").stability, + Stability::Stable(stable) + if stable.deprecated.as_ref().map(ToString::to_string).as_deref() == Some("1.1.0") + )); + assert_eq!( + function("distance").docs.contents.as_deref(), + Some("A free function.") + ); +} + +#[test] +fn parse_invalid_bytes() { + let error = + WitTools::parse(WitSource::Wasm(b"not a component".to_vec())).expect_err("fails to parse"); + assert!(matches!(error, ErrorCode::NotComponent(None))); +} + +#[test] +fn parse_invalid_component() { + // a component header followed by an invalid section + let mut bytes = wit_package(SIMPLE)[..8].to_vec(); + bytes.extend([0xff, 0xff]); + + let error = WitTools::parse(WitSource::Wasm(bytes)).expect_err("fails to parse"); + assert!(!malformed_message(error).is_empty()); +} + +#[test] +fn print_wit_package() { + let bytes = wit_package(SIMPLE); + let wit = WitTools::parse(WitSource::Wasm(bytes.clone())).expect("parses"); + assert_eq!( + WitTools::print(WitSource::Parsed(wit), None).expect("prints"), + expected_print(&bytes) + ); +} + +#[test] +fn print_kitchen_sink() { + let bytes = wit_package(KITCHEN_SINK); + let wit = WitTools::parse(WitSource::Wasm(bytes.clone())).expect("parses"); + let printed = WitTools::print(WitSource::Parsed(wit), None).expect("prints"); + + assert_eq!(printed, expected_print(&bytes)); + assert!(printed.starts_with( + "/// A package with one of everything.\npackage test:kitchen-sink@1.2.3-alpha.1+build.5;" + )); +} + +#[test] +fn print_component() { + let bytes = component(KITCHEN_SINK, "app"); + let wit = WitTools::parse(WitSource::Wasm(bytes.clone())).expect("parses"); + assert_eq!( + WitTools::print(WitSource::Parsed(wit), None).expect("prints"), + expected_print(&bytes) + ); +} + +const ZEBRA: &str = r#" +package test:zebra@2.0.0; + +interface stripes { + count: func() -> u32; +} +"#; + +const APPLE: &str = r#" +package test:apple@1.0.0; + +interface seeds { + count: func() -> u32; +} +"#; + +const ORCHARD: &str = r#" +package test:orchard; + +world orchard { + import test:zebra/stripes@2.0.0; + import test:apple/seeds@1.0.0; + export run: func(); +} +"#; + +#[test] +fn print_nests_other_packages_sorted_by_name() { + let bytes = component_with_deps(&[ZEBRA, APPLE], ORCHARD, "orchard"); + let wit = WitTools::parse(WitSource::Wasm(bytes.clone())).expect("parses"); + let printed = WitTools::print(WitSource::Parsed(wit), None).expect("prints"); + + assert_eq!(printed, expected_print(&bytes)); + + // the default package is printed first and only once + assert!(printed.starts_with("package root:component;\n")); + assert_eq!(printed.matches("package root:component").count(), 1); + + // every other package is nested, sorted by name regardless of the order they were imported + let apple = printed + .find("package test:apple@1.0.0 {") + .expect("nests test:apple"); + let zebra = printed + .find("package test:zebra@2.0.0 {") + .expect("nests test:zebra"); + assert!(apple < zebra, "test:apple is nested before test:zebra"); +} + +#[test] +fn print_wit_package_has_no_nested_packages() { + let printed = WitTools::print( + WitSource::Parsed(WitTools::parse(WitSource::Wasm(wit_package(SIMPLE))).expect("parses")), + None, + ) + .expect("prints"); + + assert_eq!(printed.matches("package ").count(), 1); +} + +#[test] +fn print_component_is_reparseable() { + let bytes = component_with_deps(&[ZEBRA, APPLE], ORCHARD, "orchard"); + let printed = WitTools::print( + WitSource::Parsed(WitTools::parse(WitSource::Wasm(bytes)).expect("parses")), + None, + ) + .expect("prints"); + + let reprinted = WitTools::print( + WitSource::Parsed(WitTools::parse(WitSource::Wasm(wit_package(&printed))).expect("parses")), + None, + ) + .expect("prints"); + assert_eq!(printed, reprinted); +} + +#[test] +fn print_is_reparseable() { + let wit = WitTools::parse(WitSource::Wasm(wit_package(KITCHEN_SINK))).expect("parses"); + let printed = WitTools::print(WitSource::Parsed(wit), None).expect("prints"); + + let reprinted = WitTools::print( + WitSource::Parsed(WitTools::parse(WitSource::Wasm(wit_package(&printed))).expect("parses")), + None, + ) + .expect("prints"); + assert_eq!(printed, reprinted); +} + +#[test] +fn print_unknown_type_id() { + let mut wit = WitTools::parse(WitSource::Wasm(wit_package(SIMPLE))).expect("parses"); + let (_, iface_id) = wit.packages[&wit.default_package].interfaces[0].clone(); + let iface = wit.interfaces.get_mut(&iface_id).expect("interface exists"); + iface + .types + .push(("missing".to_string(), "type:9999".to_string())); + + let error = WitTools::print(WitSource::Parsed(wit), None).expect_err("fails to print"); + assert_eq!(malformed_message(error), "unknown type id: type:9999"); +} + +#[test] +fn print_unknown_default_package() { + let mut wit = WitTools::parse(WitSource::Wasm(wit_package(SIMPLE))).expect("parses"); + wit.default_package = "package:9999".to_string(); + + let error = WitTools::print(WitSource::Parsed(wit), None).expect_err("fails to print"); + assert_eq!(malformed_message(error), "unknown package id: package:9999"); +} + +#[test] +fn print_invalid_version() { + let mut wit = WitTools::parse(WitSource::Wasm(wit_package(SIMPLE))).expect("parses"); + let package = wit + .packages + .get_mut(&wit.default_package.clone()) + .expect("package exists"); + package.name.version = Some(Version { + major: 1, + minor: 0, + patch: 0, + prerelease: Some(vec![VersionIdentifier::String("not valid".to_string())]), + build_metadata: None, + }); + + let error = WitTools::print(WitSource::Parsed(wit), None).expect_err("fails to print"); + assert!( + malformed_message(error).starts_with("invalid version 1.0.0-not valid:"), + "unexpected error" + ); +} + +#[test] +fn print_preserves_order_of_ids_beyond_nine() { + // ids sort numerically, not lexically, `type:10` follows `type:9` + let wit = WitTools::parse(WitSource::Wasm(wit_package(KITCHEN_SINK))).expect("parses"); + assert!(wit.types.len() > 10, "needs more than ten types"); + + let bytes = wit_package(KITCHEN_SINK); + assert_eq!( + WitTools::print(WitSource::Parsed(wit), None).expect("prints"), + expected_print(&bytes) + ); +} + +#[test] +fn resolve_ids_assign_orders_numerically() { + let arena = id_arena::Arena::<()>::new(); + let keys = ["type:10", "type:2", "type:0", "type:1"].map(String::from); + + let ids = crate::ResolveIds::assign(keys.iter(), &arena); + + let indexes: BTreeMap<&str, usize> = ids + .iter() + .map(|(key, id)| (key.as_str(), id.index())) + .collect(); + assert_eq!( + indexes, + BTreeMap::from([("type:0", 0), ("type:1", 1), ("type:2", 2), ("type:10", 3)]) + ); +} + +#[test] +fn summarize() { + let summary = + WitTools::summarize(WitSource::Wasm(component(KITCHEN_SINK, "app"))).expect("summarizes"); + + assert_eq!( + summary.imports, + vec![ + "test:kitchen-sink/types@1.2.3-alpha.1+build.5", + "host", + // `use types.{point}`, `type local-point` and `log` are not interfaces + "--unknown--", + "--unknown--", + "--unknown--", + ] + ); + assert_eq!( + summary.exports, + vec![ + // `run` is a function, not an interface + "--unknown--", + "test:kitchen-sink/consumer@1.2.3-alpha.1+build.5", + ] + ); +} + +#[test] +fn version_display() { + let version = Version { + major: 1, + minor: 2, + patch: 3, + prerelease: Some(vec![ + VersionIdentifier::String("alpha".to_string()), + VersionIdentifier::Numeric(1), + ]), + build_metadata: Some(vec![ + VersionIdentifier::String("build".to_string()), + VersionIdentifier::Numeric(5), + ]), + }; + assert_eq!(version.to_string(), "1.2.3-alpha.1+build.5"); + + let version = Version { + major: 0, + minor: 1, + patch: 0, + prerelease: None, + build_metadata: None, + }; + assert_eq!(version.to_string(), "0.1.0"); +} + +fn print_orchard(packages: Option) -> Result { + let bytes = component_with_deps(&[ZEBRA, APPLE], ORCHARD, "orchard"); + let wit = WitTools::parse(WitSource::Wasm(bytes)).expect("parses"); + WitTools::print(WitSource::Parsed(wit), Some(PrintOptions { packages })) +} + +/// A package name from `namespace:name` or `namespace:name@version`. +fn package_name(name: &str) -> PackageName { + let (name, version) = match name.split_once('@') { + Some((name, version)) => (name, Some(version)), + None => (name, None), + }; + let (namespace, name) = name.split_once(':').expect("namespace:name"); + PackageName { + namespace: namespace.to_string(), + name: name.to_string(), + version: version + .map(|version| crate::Wit::version(semver::Version::parse(version).expect("semver"))), + } +} + +fn nested_packages(printed: &str) -> Vec<&str> { + printed + .lines() + .filter_map(|line| line.strip_prefix("package ")?.strip_suffix(" {")) + .collect() +} + +#[test] +fn print_options_default_to_all_packages() { + let without_options = { + let bytes = component_with_deps(&[ZEBRA, APPLE], ORCHARD, "orchard"); + WitTools::print( + WitSource::Parsed(WitTools::parse(WitSource::Wasm(bytes)).expect("parses")), + None, + ) + .expect("prints") + }; + let all = print_orchard(Some(PrintedPackages::All)).expect("prints"); + let unset = print_orchard(None).expect("prints"); + + assert_eq!( + nested_packages(&all), + vec!["test:apple@1.0.0", "test:zebra@2.0.0"] + ); + assert_eq!(without_options, all); + assert_eq!(unset, all); +} + +#[test] +fn print_no_additional_packages() { + let printed = print_orchard(Some(PrintedPackages::Default)).expect("prints"); + + assert!(printed.starts_with("package root:component;\n")); + assert_eq!(nested_packages(&printed), Vec::<&str>::new()); +} + +#[test] +fn print_selected_packages() { + let printed = print_orchard(Some(PrintedPackages::Selected(vec![package_name( + "test:zebra@2.0.0", + )]))) + .expect("prints"); + + assert_eq!(nested_packages(&printed), vec!["test:zebra@2.0.0"]); +} + +#[test] +fn print_selected_packages_sorted_by_name() { + let printed = print_orchard(Some(PrintedPackages::Selected(vec![ + package_name("test:zebra@2.0.0"), + package_name("test:apple@1.0.0"), + ]))) + .expect("prints"); + + assert_eq!( + nested_packages(&printed), + vec!["test:apple@1.0.0", "test:zebra@2.0.0"] + ); +} + +#[test] +fn print_selected_package_without_version() { + let printed = print_orchard(Some(PrintedPackages::Selected(vec![package_name( + "test:apple", + )]))) + .expect("prints"); + + assert_eq!(nested_packages(&printed), vec!["test:apple@1.0.0"]); +} + +#[test] +fn print_selected_package_once() { + let printed = print_orchard(Some(PrintedPackages::Selected(vec![ + package_name("test:apple"), + package_name("test:apple@1.0.0"), + // the default package is never nested + package_name("root:component"), + ]))) + .expect("prints"); + + assert_eq!(nested_packages(&printed), vec!["test:apple@1.0.0"]); + assert_eq!(printed.matches("package root:component").count(), 1); +} + +#[test] +fn print_selected_unknown_package() { + let error = print_orchard(Some(PrintedPackages::Selected(vec![package_name( + "test:apple@9.9.9", + )]))) + .expect_err("fails to print"); + + assert!(matches!(error, ErrorCode::NotFound(Some(name)) if name == "test:apple@9.9.9")); +} + +#[test] +fn parse_text() { + let wit = WitTools::parse(WitSource::Wit(SIMPLE.to_string())).expect("parses"); + + assert_eq!(wit.component_world, None); + let package = &wit.packages[&wit.default_package]; + assert_eq!(package.name.to_string(), "test:simple@0.1.0"); +} + +#[test] +fn parse_invalid_text() { + let error = WitTools::parse(WitSource::Wit( + "package test:broken;\n\ninterface {".to_string(), + )) + .expect_err("fails to parse"); + + assert!(!malformed_message(error).is_empty()); +} + +#[test] +fn parse_text_with_unknown_package() { + // packages the text uses must be nested within it + let error = WitTools::parse(WitSource::Wit(ORCHARD.to_string())).expect_err("fails to parse"); + + assert!(malformed_message(error).contains("test:zebra")); +} + +#[test] +fn print_text_matches_encoded_package() { + let from_text = + WitTools::print(WitSource::Wit(KITCHEN_SINK.to_string()), None).expect("prints"); + let from_component = + WitTools::print(WitSource::Wasm(wit_package(KITCHEN_SINK)), None).expect("prints"); + + assert_eq!(from_text, from_component); + // `@unstable` items are kept + assert!(from_text.contains("unstable-func: func();")); +} + +#[test] +fn print_text_with_nested_packages() { + let text = format!( + "{ORCHARD}\n{}\n{}", + nest(ZEBRA, "test:zebra@2.0.0"), + nest(APPLE, "test:apple@1.0.0") + ); + + let printed = WitTools::print(WitSource::Wit(text), None).expect("prints"); + + assert!(printed.starts_with("package test:orchard;\n")); + assert_eq!( + nested_packages(&printed), + vec!["test:apple@1.0.0", "test:zebra@2.0.0"] + ); +} + +/// Rewrites a package as nested package syntax, `package name { ... }`. +fn nest(wit: &str, name: &str) -> String { + let body = wit.replace(&format!("package {name};"), ""); + format!("package {name} {{\n{body}\n}}") +} + +#[test] +fn print_component_matches_parsed() { + let bytes = component(KITCHEN_SINK, "app"); + + let from_component = WitTools::print(WitSource::Wasm(bytes.clone()), None).expect("prints"); + let from_parsed = WitTools::print( + WitSource::Parsed(WitTools::parse(WitSource::Wasm(bytes.clone())).expect("parses")), + None, + ) + .expect("prints"); + + assert_eq!(from_component, expected_print(&bytes)); + assert_eq!(from_component, from_parsed); +} + +#[test] +fn parse_parsed_is_unchanged() { + let wit = WitTools::parse(WitSource::Wasm(wit_package(KITCHEN_SINK))).expect("parses"); + let printed = WitTools::print(WitSource::Parsed(wit.clone()), None).expect("prints"); + + let reparsed = WitTools::parse(WitSource::Parsed(wit)).expect("parses"); + + assert_eq!( + WitTools::print(WitSource::Parsed(reparsed), None).expect("prints"), + printed + ); +} + +#[test] +fn summarize_parsed() { + let bytes = component(KITCHEN_SINK, "app"); + let wit = WitTools::parse(WitSource::Wasm(bytes.clone())).expect("parses"); + + let from_parsed = WitTools::summarize(WitSource::Parsed(wit)).expect("summarizes"); + let from_component = WitTools::summarize(WitSource::Wasm(bytes)).expect("summarizes"); + + assert_eq!(from_parsed.imports, from_component.imports); + assert_eq!(from_parsed.exports, from_component.exports); +} + +#[test] +fn summarize_wit_package() { + for source in [ + WitSource::Wasm(wit_package(SIMPLE)), + WitSource::Wit(SIMPLE.to_string()), + ] { + let error = WitTools::summarize(source).expect_err("fails to summarize"); + + assert!(matches!(error, ErrorCode::NotComponent(None))); + } +} + +#[test] +fn summarize_unknown_component_world() { + let mut wit = WitTools::parse(WitSource::Wasm(component(SIMPLE, "hello"))).expect("parses"); + wit.component_world = Some("world:9999".to_string()); + + let error = WitTools::summarize(WitSource::Parsed(wit)).expect_err("fails to summarize"); + + assert_eq!(malformed_message(error), "unknown world id: world:9999"); +} + +#[test] +fn parse_core_module() { + let source = WitSource::Wasm(wat::parse_str("(module)").expect("valid wat")); + let error = WitTools::parse(source).expect_err("fails to parse"); + + assert!(matches!(error, ErrorCode::NotComponent(None))); +} + +#[test] +fn core_module_with_component_metadata() { + // a module embedding component metadata, as built by `wit-bindgen`, before `wasm-tools + // component new` + let (resolve, package_id) = resolve(SIMPLE); + let world = resolve + .select_world(&[package_id], Some("hello")) + .expect("world exists"); + let mut module = + wit_component::dummy_module(&resolve, world, wit_parser::ManglingAndAbi::Standard32); + wit_component::embed_component_metadata( + &mut module, + &resolve, + world, + wit_component::StringEncoding::UTF8, + false, + ) + .expect("embeds metadata"); + + let error = WitTools::print(WitSource::Wasm(module), None).expect_err("fails to print"); + assert!(matches!(error, ErrorCode::NotComponent(None))); +} diff --git a/components/wit-tools/tests/component.rs b/components/wit-tools/tests/component.rs new file mode 100644 index 0000000..245b63e --- /dev/null +++ b/components/wit-tools/tests/component.rs @@ -0,0 +1,590 @@ +//! Runs the built `wit-tools` component in wasmtime. The component is rebuilt with +//! `make components/wit-tools` before the first test reads it, so it is never stale. +//! +//! The component is called with dynamic `Val`s rather than `bindgen!`, the `%result` record in the +//! `wit` interface generates a `Result` struct that shadows `std::result::Result` in the bindings. +//! TODO switch to `bindgen!` once https://github.com/bytecodealliance/wasmtime/pull/14543 is released. + +use std::{ + path::Path, + process::Command, + sync::{Once, OnceLock}, +}; + +use wasmtime::{ + Config, Engine, Store, + component::{Component, Instance, Linker, Val}, +}; + +const WIT_INTERFACE: &str = "componentized:component/wit@0.0.0-dev"; + +/// The root of the repository, where the Makefile is. +fn repo_root() -> &'static Path { + Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../..")) +} + +/// Rebuilds the component with make, once for every test. make skips the build when the component +/// is up to date with its sources. +fn build() { + static BUILD: Once = Once::new(); + BUILD.call_once(|| { + let output = Command::new("make") + .arg("components/wit-tools") + .current_dir(repo_root()) + .output() + .expect("runs make"); + assert!( + output.status.success(), + "`make components/wit-tools` failed\n{}{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + ); + }); +} + +/// A file built by `make components/wit-tools`, relative to the `target` directory. +fn built_file(path: &str) -> Vec { + build(); + let path = repo_root().join("target").join(path); + std::fs::read(&path).unwrap_or_else(|err| panic!("reading {}: {err}", path.display())) +} + +fn component_bytes(file: &str) -> Vec { + built_file(&format!("components/wit-tools/{file}")) +} + +struct Harness { + store: Store<()>, + instance: Instance, +} + +/// Compiles a component once, shared by every test, compiling is far slower than instantiating. +fn compile( + compiled: &'static OnceLock<(Engine, Component)>, + file: &str, +) -> &'static (Engine, Component) { + compiled.get_or_init(|| { + let mut config = Config::new(); + config.wasm_component_model_map(true); + let engine = Engine::new(&config).expect("engine"); + let component = Component::new(&engine, component_bytes(file)).expect("valid component"); + (engine, component) + }) +} + +impl Harness { + /// A fresh instance of a compiled component, each test has its own store. + fn new((engine, component): &(Engine, Component)) -> Self { + let linker = Linker::new(engine); + let mut store = Store::new(engine, ()); + let instance = linker + .instantiate(&mut store, component) + .expect("instantiates"); + + Self { store, instance } + } + + fn release() -> Self { + static COMPILED: OnceLock<(Engine, Component)> = OnceLock::new(); + Self::new(compile(&COMPILED, "wit-tools.wasm")) + } + + fn debug() -> Self { + static COMPILED: OnceLock<(Engine, Component)> = OnceLock::new(); + Self::new(compile(&COMPILED, "wit-tools.debug.wasm")) + } + + /// Calls a function of the `wit` interface, returning its `result<_, error-code>`. + fn call(&mut self, name: &str, params: &[Val]) -> wasmtime::Result> { + let iface = self + .instance + .get_export_index(&mut self.store, None, WIT_INTERFACE) + .expect("exports the wit interface"); + let func = self + .instance + .get_export_index(&mut self.store, Some(&iface), name) + .and_then(|index| self.instance.get_func(&mut self.store, index)) + .unwrap_or_else(|| panic!("exports {name}")); + + let mut results = [Val::Bool(false)]; + func.call(&mut self.store, params, &mut results)?; + + let [Val::Result(result)] = results else { + panic!("{name} returns a result"); + }; + Ok(match result { + Ok(value) => Ok(*value.expect("ok has a value")), + Err(error) => Err(error_message(*error.expect("err has a value"))), + }) + } + + fn parse(&mut self, component: &[u8]) -> Result { + self.parse_source(wasm_source(component)) + } + + fn parse_source(&mut self, source: Val) -> Result { + self.call("parse", &[source]).expect("parse does not trap") + } + + fn print(&mut self, wit: Val) -> Result { + self.print_with_options(wit, Val::Option(None)) + } + + fn print_with_options(&mut self, wit: Val, options: Val) -> Result { + self.print_source(parsed_source(wit), options) + } + + fn print_source(&mut self, source: Val, options: Val) -> Result { + self.call("print", &[source, options]) + .expect("print does not trap") + .map(|printed| string(&printed).to_string()) + } + + fn summarize(&mut self, source: Val) -> Result { + self.call("summarize", &[source]) + .expect("summarize does not trap") + } +} + +fn bytes(bytes: &[u8]) -> Val { + Val::List(bytes.iter().copied().map(Val::U8).collect()) +} + +/// A `wit-source` of a component, or a WIT package encoded as a component. +fn wasm_source(component: &[u8]) -> Val { + Val::Variant("wasm".to_string(), Some(Box::new(bytes(component)))) +} + +/// A `wit-source` of WIT text. +fn wit_source(text: &str) -> Val { + Val::Variant( + "wit".to_string(), + Some(Box::new(Val::String(text.to_string()))), + ) +} + +/// A `wit-source` of a `wit` from `parse`. +fn parsed_source(wit: Val) -> Val { + Val::Variant("parsed".to_string(), Some(Box::new(wit))) +} + +fn error_message(error: Val) -> String { + match error { + Val::Variant(case, Some(message)) if case == "other" => match *message { + Val::Option(Some(message)) => string(&message).to_string(), + Val::Option(None) => String::new(), + other => panic!("unexpected error message {other:?}"), + }, + Val::Variant(case, Some(malformed)) if case == "malformed" => { + let message = string(field(&malformed, "message")); + match option(field(&malformed, "name")) { + Some(name) => format!("malformed {}: {message}", string(name)), + None => format!("malformed: {message}"), + } + } + Val::Variant(case, Some(name)) if case == "not-found" => match *name { + Val::Option(Some(name)) => format!("not-found: {}", string(&name)), + Val::Option(None) => "not-found".to_string(), + other => panic!("unexpected not-found name {other:?}"), + }, + Val::Variant(case, Some(name)) if case == "not-component" => match *name { + Val::Option(Some(name)) => format!("not-component: {}", string(&name)), + Val::Option(None) => "not-component".to_string(), + other => panic!("unexpected not-component name {other:?}"), + }, + other => panic!("unexpected error {other:?}"), + } +} + +fn field<'a>(record: &'a Val, name: &str) -> &'a Val { + let Val::Record(fields) = record else { + panic!("expected a record, got {record:?}"); + }; + fields + .iter() + .find_map(|(key, value)| (key == name).then_some(value)) + .unwrap_or_else(|| panic!("record has field {name}")) +} + +fn field_mut<'a>(record: &'a mut Val, name: &str) -> &'a mut Val { + let Val::Record(fields) = record else { + panic!("expected a record"); + }; + fields + .iter_mut() + .find_map(|(key, value)| (key == name).then_some(value)) + .unwrap_or_else(|| panic!("record has field {name}")) +} + +fn entry<'a>(map: &'a Val, key: &str) -> &'a Val { + let Val::Map(entries) = map else { + panic!("expected a map, got {map:?}"); + }; + entries + .iter() + .find_map(|(k, v)| (string(k) == key).then_some(v)) + .unwrap_or_else(|| panic!("map has key {key}")) +} + +fn string(val: &Val) -> &str { + match val { + Val::String(s) => s, + other => panic!("expected a string, got {other:?}"), + } +} + +fn strings(val: &Val) -> Vec<&str> { + match val { + Val::List(items) => items.iter().map(string).collect(), + other => panic!("expected a list, got {other:?}"), + } +} + +fn option(val: &Val) -> Option<&Val> { + match val { + Val::Option(value) => value.as_deref(), + other => panic!("expected an option, got {other:?}"), + } +} + +/// Prints the WIT of encoded bytes directly with `wit-component`: the decoded package, followed by +/// every other package nested, sorted by name. +fn expected_print(bytes: &[u8]) -> String { + let decoded = wit_component::decode(bytes).expect("decodes"); + let mut nested: Vec<_> = decoded + .resolve() + .packages + .iter() + .map(|(id, _)| id) + .filter(|id| *id != decoded.package()) + .collect(); + nested.sort_by(|a, b| { + decoded.resolve().packages[*a] + .name + .cmp(&decoded.resolve().packages[*b].name) + }); + let mut printer = wit_component::WitPrinter::default(); + printer + .print(decoded.resolve(), decoded.package(), &nested) + .expect("prints"); + printer.output.to_string() +} + +const PACKAGE: &str = r#" +package test:integration@1.0.0; + +interface shapes { + record point { x: f64, y: f64 } + variant shape { circle(f64), polygon(list) } + resource canvas { + constructor(); + draw: func(shape: shape) -> result<_, string>; + } +} + +world painter { + import shapes; + export render: func() -> list; +} +"#; + +fn wit_package(wit: &str) -> Vec { + let mut resolve = wit_parser::Resolve::default(); + let package_id = resolve.push_str("test.wit", wit).expect("valid wit"); + wit_component::encode(&resolve, package_id, false).expect("encodes package") +} + +#[test] +fn summarize_itself() { + let mut harness = Harness::release(); + + let summary = harness + .summarize(wasm_source(&component_bytes("wit-tools.wasm"))) + .expect("summarizes"); + + assert_eq!( + strings(field(&summary, "imports")), + vec!["componentized:component/types@0.0.0-dev"] + ); + assert_eq!( + strings(field(&summary, "exports")), + vec!["componentized:component/wit@0.0.0-dev"] + ); +} + +#[test] +fn summarize_wit_package() { + let mut harness = Harness::release(); + + for source in [wasm_source(&wit_package(PACKAGE)), wit_source(PACKAGE)] { + let error = harness.summarize(source).expect_err("fails to summarize"); + + assert_eq!(error, "not-component"); + } +} + +#[test] +fn summarize_parsed() { + let mut harness = Harness::release(); + let own_bytes = component_bytes("wit-tools.wasm"); + let wit = harness.parse(&own_bytes).expect("parses"); + + let summary = harness.summarize(parsed_source(wit)).expect("summarizes"); + + assert_eq!( + strings(field(&summary, "exports")), + vec!["componentized:component/wit@0.0.0-dev"] + ); +} + +#[test] +fn parse_itself() { + let mut harness = Harness::release(); + + let wit = harness + .parse(&component_bytes("wit-tools.wasm")) + .expect("parses"); + + let world_id = string(option(field(&wit, "component-world")).expect("is a component")); + let world = entry(field(&wit, "worlds"), world_id); + assert_eq!(string(field(world, "name")), "root"); + + let package = entry( + field(&wit, "packages"), + string(field(&wit, "default-package")), + ); + let name = field(package, "name"); + assert_eq!(string(field(name, "namespace")), "root"); + assert_eq!(string(field(name, "name")), "component"); +} + +#[test] +fn parse_wit_package() { + let mut harness = Harness::release(); + + let wit = harness.parse(&wit_package(PACKAGE)).expect("parses"); + + assert!(option(field(&wit, "component-world")).is_none()); + let package = entry( + field(&wit, "packages"), + string(field(&wit, "default-package")), + ); + let name = field(package, "name"); + assert_eq!(string(field(name, "namespace")), "test"); + assert_eq!(string(field(name, "name")), "integration"); +} + +#[test] +fn parse_invalid_bytes() { + let mut harness = Harness::release(); + + let error = harness + .parse(b"not a component") + .expect_err("fails to parse"); + + assert_eq!(error, "not-component"); +} + +#[test] +fn print_itself() { + let mut harness = Harness::release(); + let own_bytes = component_bytes("wit-tools.wasm"); + + let wit = harness.parse(&own_bytes).expect("parses"); + let printed = harness.print(wit).expect("prints"); + + assert_eq!(printed, expected_print(&own_bytes)); + assert!(printed.starts_with("package root:component;\n")); + assert_eq!(printed.matches("package root:component").count(), 1); + assert!(printed.contains("package componentized:component@0.0.0-dev {")); +} + +#[test] +fn print_wit_package() { + let mut harness = Harness::release(); + let bytes = wit_package(PACKAGE); + + let wit = harness.parse(&bytes).expect("parses"); + let printed = harness.print(wit).expect("prints"); + + assert_eq!(printed, expected_print(&bytes)); +} + +#[test] +fn print_unknown_type_id() { + let mut harness = Harness::release(); + let mut wit = harness.parse(&wit_package(PACKAGE)).expect("parses"); + + let Val::Map(interfaces) = field_mut(&mut wit, "interfaces") else { + panic!("interfaces is a map"); + }; + let (_, shapes) = interfaces.first_mut().expect("has an interface"); + let Val::List(types) = field_mut(shapes, "types") else { + panic!("types is a list"); + }; + types.push(Val::Tuple(vec![ + Val::String("missing".to_string()), + Val::String("type:9999".to_string()), + ])); + + let error = harness.print(wit).expect_err("fails to print"); + + assert_eq!(error, "malformed: unknown type id: type:9999"); +} + +#[test] +fn debug_build_matches_release() { + let own_bytes = component_bytes("wit-tools.wasm"); + let mut release = Harness::release(); + let mut debug = Harness::debug(); + + let release_wit = release.parse(&own_bytes).expect("parses"); + let debug_wit = debug.parse(&own_bytes).expect("parses"); + + assert_eq!( + release.print(release_wit).expect("prints"), + debug.print(debug_wit).expect("prints") + ); +} + +/// A `package-name` without a version, selecting every version of the package. +fn package_name(namespace: &str, name: &str) -> Val { + Val::Record(vec![ + ("namespace".to_string(), Val::String(namespace.to_string())), + ("name".to_string(), Val::String(name.to_string())), + ("version".to_string(), Val::Option(None)), + ]) +} + +/// `option` with `packages` set to the `printed-packages` case. +fn print_options(packages: Val) -> Val { + Val::Option(Some(Box::new(Val::Record(vec![( + "packages".to_string(), + Val::Option(Some(Box::new(packages))), + )])))) +} + +#[test] +fn print_itself_without_additional_packages() { + let mut harness = Harness::release(); + let wit = harness + .parse(&component_bytes("wit-tools.wasm")) + .expect("parses"); + + let printed = harness + .print_with_options( + wit, + print_options(Val::Variant("default".to_string(), None)), + ) + .expect("prints"); + + assert_eq!( + printed, + "package root:component;\n\nworld root {\n import componentized:component/types@0.0.0-dev;\n\n export componentized:component/wit@0.0.0-dev;\n}\n" + ); +} + +#[test] +fn print_itself_selected_packages() { + let mut harness = Harness::release(); + let wit = harness + .parse(&component_bytes("wit-tools.wasm")) + .expect("parses"); + + let selected = Val::Variant( + "selected".to_string(), + Some(Box::new(Val::List(vec![package_name( + "componentized", + "component", + )]))), + ); + let printed = harness + .print_with_options(wit.clone(), print_options(selected)) + .expect("prints"); + + let all = harness + .print_with_options(wit, print_options(Val::Variant("all".to_string(), None))) + .expect("prints"); + // componentized:component is the only other package + assert_eq!(printed, all); + assert!(printed.contains("package componentized:component@0.0.0-dev {")); +} + +#[test] +fn print_unknown_selected_package() { + let mut harness = Harness::release(); + let wit = harness + .parse(&component_bytes("wit-tools.wasm")) + .expect("parses"); + + let selected = Val::Variant( + "selected".to_string(), + Some(Box::new(Val::List(vec![package_name("wasi", "cli")]))), + ); + let error = harness + .print_with_options(wit, print_options(selected)) + .expect_err("fails to print"); + + assert_eq!(error, "not-found: wasi:cli"); +} + +#[test] +fn parse_text() { + let mut harness = Harness::release(); + + let wit = harness.parse_source(wit_source(PACKAGE)).expect("parses"); + + assert!(option(field(&wit, "component-world")).is_none()); + let package = entry( + field(&wit, "packages"), + string(field(&wit, "default-package")), + ); + assert_eq!(string(field(field(package, "name"), "name")), "integration"); +} + +#[test] +fn parse_invalid_text() { + let mut harness = Harness::release(); + + let error = harness + .parse_source(wit_source("package test:broken;\n\ninterface {")) + .expect_err("fails to parse"); + + assert!(error.starts_with("malformed: "), "{error}"); +} + +#[test] +fn print_text() { + let mut harness = Harness::release(); + + let printed = harness + .print_source(wit_source(PACKAGE), Val::Option(None)) + .expect("prints"); + + assert_eq!(printed, expected_print(&wit_package(PACKAGE))); +} + +#[test] +fn print_component() { + let mut harness = Harness::release(); + let own_bytes = component_bytes("wit-tools.wasm"); + + let printed = harness + .print_source(wasm_source(&own_bytes), Val::Option(None)) + .expect("prints"); + + assert_eq!(printed, expected_print(&own_bytes)); +} + +#[test] +fn parse_core_module() { + let mut harness = Harness::release(); + // the core module the component was made from, before `wasm-tools component new` + let core_module = built_file("wasm32-unknown-unknown/release/wit_tools.wasm"); + + for source in [wasm_source(&core_module)] { + let error = harness.parse_source(source).expect_err("fails to parse"); + + assert_eq!(error, "not-component"); + } +} diff --git a/components/wit/worlds.wit b/components/wit/worlds.wit index 81abea2..dc62d4e 100644 --- a/components/wit/worlds.wit +++ b/components/wit/worlds.wit @@ -1,48 +1,39 @@ package componentized:components; -world extract-wit { +world wit-tools { export componentized:component/wit@0.0.0-dev; } world filesystem-loader { import wasi:filesystem/types@0.3.0; import wasi:filesystem/preopens@0.3.0; - export componentized:component/types@0.0.0-dev; + import componentized:component/types@0.0.0-dev; export componentized:component/path-loader@0.0.0-dev; } world http-loader { import componentized:http/client@0.1.0-dev; - export componentized:component/types@0.0.0-dev; + import componentized:component/types@0.0.0-dev; export componentized:component/path-loader@0.0.0-dev; } world oci-loader { import componentized:oci/client@0.0.0-dev; - export componentized:component/types@0.0.0-dev; + import componentized:component/types@0.0.0-dev; export componentized:component/path-loader@0.0.0-dev; } world path-loader { - import filesystem-loader: interface { - use componentized:component/types@0.0.0-dev.{component, error}; - load: async func(path: string) -> result; - } - import http-loader: interface { - use componentized:component/types@0.0.0-dev.{component, error}; - load: async func(path: string) -> result; - } - import oci-loader: interface { - use componentized:component/types@0.0.0-dev.{component, error}; - load: async func(path: string) -> result; - } - - export componentized:component/types@0.0.0-dev; + import filesystem-loader: componentized:component/path-loader@0.0.0-dev; + import http-loader: componentized:component/path-loader@0.0.0-dev; + import oci-loader: componentized:component/path-loader@0.0.0-dev; + + import componentized:component/types@0.0.0-dev; export componentized:component/path-loader@0.0.0-dev; } world wac-loader { - export componentized:component/types@0.0.0-dev; + import componentized:component/types@0.0.0-dev; export componentized:component/wac-loader@0.0.0-dev; } diff --git a/components/wkg.lock b/components/wkg.lock index 2f1fc11..3a281e5 100644 --- a/components/wkg.lock +++ b/components/wkg.lock @@ -9,7 +9,7 @@ registry = "componentized.dev" [[packages.versions]] requirement = "=0.1.0-dev" version = "0.1.0-dev" -digest = "sha256:f74b53af4b5ecb4c77e8bd47887920e0626efb2e45e8a9386f2519910faf5e5c" +digest = "sha256:2a9e83508ed025c55436c118a1e06ff68b6d00bc61085ba875daeec6452435f5" [[packages]] name = "componentized:oci" diff --git a/wit/loader.wit b/wit/loader.wit deleted file mode 100644 index d57cd4a..0000000 --- a/wit/loader.wit +++ /dev/null @@ -1,20 +0,0 @@ -interface path-loader { - use types.{component, error}; - - load: async func(path: string) -> result; -} - -interface wac-loader { - use types.{component, error}; - - type %package = string; - type dependency = tuple<%package, component>; - - variant plan { - wac(string), - // TODO add graph model - } - - plug: async func(socket: component, plugs: list) -> result; - compose: async func(plan: plan, dependencies: list) -> result; -} \ No newline at end of file diff --git a/wit/loaders.wit b/wit/loaders.wit new file mode 100644 index 0000000..283f5ce --- /dev/null +++ b/wit/loaders.wit @@ -0,0 +1,38 @@ +/// Loads a component from a path. +@since(version = 0.0.0-dev) +interface path-loader { + use types.{wasm, error-code}; + + /// Loads the component at `path`. The forms of path supported depend on the + /// implementation, e.g. a filesystem path, an HTTP URL or an OCI reference. + @since(version = 0.0.0-dev) + load: async func(path: string) -> result; +} + +/// Composes components with WAC (WebAssembly Compositions). +@since(version = 0.0.0-dev) +interface wac-loader { + use types.{wasm, error-code}; + + type package-name = string; + + /// A component, named by the package a WAC composition refers to it as. + @since(version = 0.0.0-dev) + type dependency = tuple; + + /// How to compose components. + @since(version = 0.0.0-dev) + variant plan { + /// A WAC script. + wac(string), + // TODO add graph model + } + + /// Plugs the exports of each of `plugs` into the matching imports of `socket`, returning the + /// composed component. + @since(version = 0.0.0-dev) + plug: async func(socket: wasm, plugs: list) -> result; + /// Composes the `dependencies` as described by `plan`, returning the composed component. + @since(version = 0.0.0-dev) + compose: async func(plan: plan, dependencies: list) -> result; +} \ No newline at end of file diff --git a/wit/types.wit b/wit/types.wit index fc9140d..608533c 100644 --- a/wit/types.wit +++ b/wit/types.wit @@ -1,9 +1,32 @@ +/// Types shared by the interfaces in this package. +@since(version = 0.0.0-dev) interface types { - variant error { + /// An error from a call. + @since(version = 0.0.0-dev) + variant error-code { + /// A value cannot be parsed to its expected type. + malformed(malformed), + /// A wasm that must be a component is not, e.g. a core module. Calls accepting multiple + /// wasms name the one that is not a component. + not-component(option), + /// The item does not exist, optionally naming it, e.g. a path, URL, reference or package. + not-found(option), + /// Any other error, with an optional message. other(option), } - type component = list; + /// A `malformed` input. + @since(version = 0.0.0-dev) + record malformed { + /// Calls accepting multiple inputs name the one that is malformed. + name: option, + /// Why the input is invalid. + message: string, + } + + /// The bytes of a WebAssembly binary, a component unless stated otherwise. + @since(version = 0.0.0-dev) + type wasm = list; } diff --git a/wit/wit.wit b/wit/wit.wit index 73de25f..d9aa466 100644 --- a/wit/wit.wit +++ b/wit/wit.wit @@ -1,9 +1,20 @@ +/// Tools for working with the WIT (Wasm Interface Type) of a component. +/// +/// A component, or a WIT package encoded as a component, is parsed into a `wit` record, a model of +/// the types, interfaces, worlds and packages it references. The model mirrors the `Resolve` from +/// the `wit-parser` crate. Items reference each other by id, an opaque string unique within a +/// `wit`, e.g. `type:4`. +@since(version = 0.0.0-dev) interface wit { - use types.{component, error}; + use types.{wasm, error-code}; + /// The id of a `type-def` in `wit.types`. + @since(version = 0.0.0-dev) type type-id = string; + /// A wit primitive, or a reference to a `type-def`. + @since(version = 0.0.0-dev) variant %type { %bool, %s8, @@ -22,6 +33,8 @@ interface wit { id(type-id), } + /// The wit type kind of a `type-def`, and its definition. + @since(version = 0.0.0-dev) variant type-def-kind { %record(%record), %resource, @@ -37,226 +50,435 @@ interface wit { %future(option<%type>), %stream(option<%type>), %type(%type), + /// A type whose definition is unknown. unknown, } + /// A wit `record` type. + @since(version = 0.0.0-dev) record %record { fields: list, } + /// A field of a wit `record`. + @since(version = 0.0.0-dev) record record-field { name: string, %type: %type, docs: docs, } + /// A handle to a wit resource. + @since(version = 0.0.0-dev) variant handle { + /// An owned handle, `own`. %own(type-id), + /// A borrowed handle, `borrow`. %borrow(type-id), } + /// A wit `flags` type. + @since(version = 0.0.0-dev) record %flags { %flags: list, } + /// A flag of a wit `flags` type. + @since(version = 0.0.0-dev) record flag { name: string, docs: docs, } + /// A wit `tuple` type. + @since(version = 0.0.0-dev) record %tuple { types: list<%type>, } + /// A wit `variant` type. + @since(version = 0.0.0-dev) record %variant { cases: list, } + /// A case of a wit `variant`. + @since(version = 0.0.0-dev) record variant-case { name: string, + /// The type of the cases value. %type: option<%type>, docs: docs, } + /// A wit `enum` type. + @since(version = 0.0.0-dev) record %enum { cases: list, } + /// A case of a wit `enum`. + @since(version = 0.0.0-dev) record enum-case { name: string, docs: docs, } + /// A wit `result` type. + @since(version = 0.0.0-dev) record %result { + /// The type of an ok result, absent for `result<_, e>` and `result`. ok: option<%type>, + /// The type of an error result, absent for `result` and `result`. err: option<%type>, } + /// A wit `list` type. + @since(version = 0.0.0-dev) record %list { %type: %type, + /// The length of a fixed length list, e.g. `list`. fixed-length: option, } + /// A wit `map` type. + @since(version = 0.0.0-dev) record %map { key: %type, value: %type, } + /// A function, freestanding or part of a resource. + @since(version = 0.0.0-dev) record function { + /// The name of the function. For a resource function, e.g. `[method]r.f`. name: string, + /// The kind of the function. kind: function-kind, + /// The parameters of the function, in order. params: list, + /// The type the function returns, absent when it returns nothing. %result: option<%type>, docs: docs, stability: stability, + /// The `@external-id` annotation of the item, if any. external-id: option, } + /// The kind of a wit `function`. + @since(version = 0.0.0-dev) variant function-kind { + /// A function, e.g. `f: func();`. freestanding, + /// An async function, e.g. `f: async func();`. async-freestanding, + /// A method of the resource, the first parameter is `borrow`. method(type-id), + /// An async method of the resource. async-method(type-id), + /// A static function of the resource. %static(type-id), + /// An async static function of the resource. async-static(type-id), + /// A constructor of the resource. %constructor(type-id), - } - + /// A getter, which takes no parameters and returns a value. + getter, + /// A setter, which takes one parameter. + setter, + /// A getter of the resource, the first parameter is `borrow`. + method-getter(type-id), + /// A setter of the resource, the first parameter is `borrow`. + method-setter(type-id), + /// A static getter of the resource. + static-getter(type-id), + /// A static setter of the resource. + static-setter(type-id), + } + + /// A parameter of a wit `function`. + @since(version = 0.0.0-dev) record param { name: string, %type: %type, } + /// Where a `type-def` is defined. + @since(version = 0.0.0-dev) variant type-owner { + /// Defined in a world. %world(world-id), + /// Defined in an interface. %interface(interface-id), + /// Not defined in a world or interface, e.g. an anonymous `list`. none, } + /// Documentation comments. + @since(version = 0.0.0-dev) record docs { + /// The text of the comments, without the `///`, absent when there are none. contents: option, } + /// The stability of an item, from its `@since` or `@unstable` annotation. + @since(version = 0.0.0-dev) variant stability { + /// Neither `@since` nor `@unstable`. unknown, + /// Annotated `@unstable`. unstable(unstable), + /// Annotated `@since`. stable(stable), } + /// An item annotated `@unstable(feature = ...)`. + @since(version = 0.0.0-dev) record unstable { + /// The feature that enables the item. feature: string, + /// The version deprecating the item, from `@deprecated`. deprecated: option, } + /// An item annotated `@since(version = ...)`. + @since(version = 0.0.0-dev) record stable { + /// The version the item is stable since. since: version, + /// The version deprecating the item, from `@deprecated`. deprecated: option, } + /// A type definition. + @since(version = 0.0.0-dev) record type-def { + /// The name of the type, absent for an anonymous type, e.g. `list`. name: option, + /// The kind of the type, and its definition. kind: type-def-kind, + /// Where the type is defined. owner: type-owner, + /// Documentation comments on the item. docs: docs, + /// The stability of the item. stability: stability, + /// The `@external-id` annotation of the item, if any. external-id: option, } + /// A semantic version. + @since(version = 0.0.0-dev) record version { major: u64, minor: u64, patch: u64, + /// The prerelease identifiers, e.g. `alpha.1` in `1.0.0-alpha.1`. prerelease: option>, + /// The build metadata identifiers, e.g. `build.5` in `1.0.0+build.5`. build-metadata: option>, } + /// A dot separated identifier in the prerelease or build metadata of a `version`. + @since(version = 0.0.0-dev) variant version-identifier { + /// An alphanumeric identifier, e.g. `alpha`. %string(string), + /// A numeric identifier, e.g. `1`. numeric(u64), } + /// The id of an `interface` in `wit.interfaces`. + @since(version = 0.0.0-dev) type interface-id = string; + /// A wit `interface`. + @since(version = 0.0.0-dev) record %interface { + /// The name of the interface, absent for an interface defined inline in a world. name: option, + /// The types of the interface, by name. types: list>, + /// The functions of the interface, by name. functions: list>, docs: docs, stability: stability, + /// The package of the interface. %package: option, } + /// The id of a `world` in `wit.worlds`. + @since(version = 0.0.0-dev) type world-id = string; + /// A wit `world`. + @since(version = 0.0.0-dev) record %world { name: string, + /// The items (interfaces, functions and types) the world imports. imports: list>, + /// The items (interfaces, functions and types) the world exports. exports: list>, + /// The package of the world. %package: option, docs: docs, stability: stability, + /// The worlds the world includes, empty once the includes are resolved. includes: list, } + /// An item imported or exported by a world. + @since(version = 0.0.0-dev) variant world-item { + /// An interface. %interface(world-item-interface), + /// A function. function(function), + /// A type. %type(type-id) } + /// An interface imported or exported by a world. + @since(version = 0.0.0-dev) record world-item-interface { + /// The interface. id: interface-id, stability: stability, + /// The `@external-id` annotation of the import or export, if any. external-id: option, docs: docs, } + /// An `include` of another world. + @since(version = 0.0.0-dev) record world-include { stability: stability, + /// The world included. id: world-id, + /// The items renamed by the include. names: list, } + /// An item renamed by an `include ... with { name as as }`. + @since(version = 0.0.0-dev) record include-name { + /// The name of the item in the included world. name: string, + /// The name of the item in the including world. %as: string, } + /// The name of an item imported or exported by a world. + @since(version = 0.0.0-dev) variant world-key { + /// A named item, e.g. `import foo: func();` or an inline interface. name(string), + /// An interface imported or exported by its own name, e.g. `import wasi:cli/stdout;`. %interface(interface-id), } + /// The id of a `package` in `wit.packages`. + @since(version = 0.0.0-dev) type package-id = string; + /// A wit package of interfaces and worlds. + @since(version = 0.0.0-dev) record %package { name: package-name, docs: docs, + /// The interfaces of the package, by name. interfaces: list>, + /// The worlds of the package, by name. worlds: list>, } + /// The name of a package, e.g. `wasi:cli@0.3.0`. + @since(version = 0.0.0-dev) record package-name { + /// The namespace, e.g. `wasi` in `wasi:cli`. namespace: string, + /// The name, e.g. `cli` in `wasi:cli`. name: string, + /// The version, absent for an unversioned package. version: option, } + /// The WIT of a component or WIT package. + @since(version = 0.0.0-dev) record wit { + /// Every interface, keyed by id. interfaces: map, + /// Every package, keyed by id. packages: map, + /// Every type, keyed by id. types: map, + /// Every world, keyed by id. worlds: map, + /// The package that was parsed. For a component, the synthesized `root:component` package + /// that contains the component's world. default-package: package-id, + /// The world of the component, absent when a WIT package was parsed. component-world: option, } - record world-summary { + /// The WIT to work with. + @since(version = 0.0.0-dev) + variant wit-source { + /// A component, or a WIT package encoded as a WebAssembly binary module. Anything else, + /// e.g. a core module, is a `not-component` error. + wasm(wasm), + /// WIT text, a package that may contain nested packages. Packages it uses must be nested. + wit(string), + /// A `wit` from `parse`, possibly modified. + parsed(wit), + } + + /// Options for `print`. + @since(version = 0.0.0-dev) + record print-options { + /// The packages to print. The default package is always printed first. Defaults to `all`. + packages: option, + } + + /// Which packages, other than the default package, `print` includes. + @since(version = 0.0.0-dev) + variant printed-packages { + /// Every package. + all, + /// Only the default package. + default, + /// The packages with these names, in addition to the default package. A name without a + /// version selects every version of the package. Naming a package that is not in the `wit` + /// is a `not-found` error. + selected(list), + } + + /// The names of the imports and exports of a component's world. + @since(version = 0.0.0-dev) + record summary { + /// Each import, e.g. `wasi:cli/stdout@0.3.0` for an interface, otherwise its name. imports: list, + /// Each export, e.g. `wasi:cli/run@0.3.0` for an interface, otherwise its name. exports: list, } - extract: async func(component: component) -> result; - summarize-world: async func(component: component) -> result; + /// Parses a source into a `wit`, a `parsed` source is returned as is. + @since(version = 0.0.0-dev) + parse: func(source: wit-source) -> result; + + /// Prints a source as WIT text. + /// + /// The default package is printed first, followed by the additional packages in nested + /// `package namespace:name { ... }` blocks, sorted by name. Without options every package is + /// printed. A `parsed` source may be modified before printing, a reference to an id that + /// does not exist is an error. + @since(version = 0.0.0-dev) + print: func(source: wit-source, options: option) -> result; + + /// Summarizes the imports and exports of a component's world. + /// + /// The source must have a component world, a WIT package (encoded or text) is an error. + @since(version = 0.0.0-dev) + summarize: func(source: wit-source) -> result; } diff --git a/wit/worlds.wit b/wit/worlds.wit index d5e75ce..442d755 100644 --- a/wit/worlds.wit +++ b/wit/worlds.wit @@ -1,5 +1,7 @@ package componentized:component@0.0.0-dev; +/// Imports every interface in the package, for consumers of the package. +@since(version = 0.0.0-dev) world imports { import types; import path-loader;