diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..6c157867 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "github-actions" + directory: "." + schedule: + interval: "weekly" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..cd1647c7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: Continuous Integration + +on: + push: + branches: [ master ] + pull_request: + +env: + CARGO_TERM_COLOR: always + +jobs: + check: + name: Check + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Check Code Formatting + run: cargo fmt --all --check + + - name: Check Code With Clippy + run: cargo clippy --workspace --all-targets -- -D warnings + + - name: Run Tests + run: cargo test --workspace --all-targets --no-fail-fast diff --git a/.gitignore b/.gitignore index acd38ca8..6427c3da 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,7 @@ node_modules/ .DS_Store .vscode -*.log +target/ +.binpath +#.vscode +#.log diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..15ebd88b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1831 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +dependencies = [ + "nodrop", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "async-trait" +version = "0.1.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "basic-toml" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bfc506e7a2370ec239e1d072507b2a80c833083699d3c6fa176fbb4de8448c6" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "blake2-rfc" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +dependencies = [ + "arrayvec 0.4.12", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12024c4645c97566567129c204f65d5815a8c9aecf30fcbe682b2fe034996d36" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a1ec454bc3eead8719cb56e15dbbfecdbc14e4b3a3ae4936cc6e31f5fc0d07" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "colored" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +dependencies = [ + "is-terminal", + "lazy_static", + "windows-sys", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "cpufeatures" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dlmalloc" +version = "0.1.4" +source = "git+https://github.com/gear-tech/dlmalloc-rust.git#15352f969112faa463302f2490bbb7f6e1cb904d" +dependencies = [ + "libc", + "libc-print", + "page_size", + "static_assertions", + "str-buf", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "enum-iterator" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7add3873b5dd076766ee79c8e406ad1a472c385476b9e38849f8eec24f1be689" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "futures" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-io" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" + +[[package]] +name = "futures-sink" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "galloc" +version = "1.0.1" +source = "git+https://github.com/gear-tech/gear.git?tag=v1.0.1#a3fe307bd8e0bdef14b323c4e2361d7df824a5f2" +dependencies = [ + "dlmalloc", +] + +[[package]] +name = "gcore" +version = "1.0.1" +source = "git+https://github.com/gear-tech/gear.git?tag=v1.0.1#a3fe307bd8e0bdef14b323c4e2361d7df824a5f2" +dependencies = [ + "gear-core-errors", + "gear-stack-buffer", + "gsys", + "parity-scale-codec", + "static_assertions", +] + +[[package]] +name = "gear-core" +version = "1.0.1" +source = "git+https://github.com/gear-tech/gear.git?tag=v1.0.1#a3fe307bd8e0bdef14b323c4e2361d7df824a5f2" +dependencies = [ + "blake2-rfc", + "byteorder", + "derive_more", + "enum-iterator", + "gear-core-errors", + "gear-wasm-instrument", + "hashbrown", + "hex", + "log", + "num-traits", + "parity-scale-codec", + "paste", + "scale-info", + "static_assertions", + "wasmparser-nostd", +] + +[[package]] +name = "gear-core-errors" +version = "1.0.1" +source = "git+https://github.com/gear-tech/gear.git?tag=v1.0.1#a3fe307bd8e0bdef14b323c4e2361d7df824a5f2" +dependencies = [ + "derive_more", + "enum-iterator", + "scale-info", +] + +[[package]] +name = "gear-stack-buffer" +version = "1.0.1" +source = "git+https://github.com/gear-tech/gear.git?tag=v1.0.1#a3fe307bd8e0bdef14b323c4e2361d7df824a5f2" + +[[package]] +name = "gear-wasm" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfbfa701dc65e683fcd2fb24f046bcef22634acbdf47ad14724637dc39ad05b" + +[[package]] +name = "gear-wasm-builder" +version = "0.1.2" +source = "git+https://github.com/gear-tech/gear.git?tag=v1.0.1#a3fe307bd8e0bdef14b323c4e2361d7df824a5f2" +dependencies = [ + "anyhow", + "cargo_metadata", + "chrono", + "colored", + "dirs", + "gear-core", + "gear-wasm-instrument", + "gmeta", + "log", + "once_cell", + "pathdiff", + "pwasm-utils", + "regex", + "thiserror", + "toml", + "wasmparser-nostd", + "which", +] + +[[package]] +name = "gear-wasm-instrument" +version = "1.0.1" +source = "git+https://github.com/gear-tech/gear.git?tag=v1.0.1#a3fe307bd8e0bdef14b323c4e2361d7df824a5f2" +dependencies = [ + "enum-iterator", + "gwasm-instrument", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "gmeta" +version = "1.0.1" +source = "git+https://github.com/gear-tech/gear.git?tag=v1.0.1#a3fe307bd8e0bdef14b323c4e2361d7df824a5f2" +dependencies = [ + "blake2-rfc", + "derive_more", + "hex", + "scale-info", +] + +[[package]] +name = "gstd" +version = "1.0.1" +source = "git+https://github.com/gear-tech/gear.git?tag=v1.0.1#a3fe307bd8e0bdef14b323c4e2361d7df824a5f2" +dependencies = [ + "bs58", + "futures", + "galloc", + "gcore", + "gear-core-errors", + "gstd-codegen", + "hashbrown", + "hex", + "parity-scale-codec", + "primitive-types", + "scale-info", + "static_assertions", +] + +[[package]] +name = "gstd-codegen" +version = "0.1.0" +source = "git+https://github.com/gear-tech/gear.git?tag=v1.0.1#a3fe307bd8e0bdef14b323c4e2361d7df824a5f2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "gsys" +version = "1.0.1" +source = "git+https://github.com/gear-tech/gear.git?tag=v1.0.1#a3fe307bd8e0bdef14b323c4e2361d7df824a5f2" + +[[package]] +name = "gwasm-instrument" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcb127cb43d375de7cdacffd0e4e1c746e52381d11a0465909ae6fbecb99c6c3" +dependencies = [ + "gear-wasm", +] + +[[package]] +name = "handlebars" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faa67bab9ff362228eb3d00bd024a4965d8231bbb7921167f0cfa66c6626b225" +dependencies = [ + "log", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "hashbrown" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "indexmap" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "indexmap-nostd" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "libc-print" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17f111e2175c779daaf5e89fe3a3b0776b0adec218bc1159c56e4d3f58032f5" +dependencies = [ + "libc", +] + +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.1", + "libc", + "redox_syscall", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "parity-scale-codec" +version = "3.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881331e34fa842a2fb61cc2db9643a8fedc615e47cfcc52597d1af0db9a7e8fe" +dependencies = [ + "arrayvec 0.7.4", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" +dependencies = [ + "proc-macro-crate 2.0.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "parity-wasm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "pest" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "pest_meta" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "scale-info", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97dc5fea232fc28d2f597b37c4876b348a40e33f3b02cc975c8d006d78d94b1a" +dependencies = [ + "toml_datetime", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pwasm-utils" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ecdabd73c8beaf98c66e45aff3032b56260ee49eb5d0d1222ecce269bfafda7" +dependencies = [ + "byteorder", + "log", + "parity-wasm", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "sails-idlgen" +version = "0.0.1" +dependencies = [ + "convert_case 0.6.0", + "handlebars", + "parity-scale-codec", + "sails-service", + "scale-info", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "sails-macros" +version = "0.0.1" +dependencies = [ + "parity-scale-codec", + "proc-macro-error", + "sails-macros-core", + "scale-info", + "tokio", + "trybuild", +] + +[[package]] +name = "sails-macros-core" +version = "0.0.1" +dependencies = [ + "convert_case 0.6.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "sails-service" +version = "0.0.1" +dependencies = [ + "async-trait", + "hashbrown", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "scale-info" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0a159d0c45c12b20c5a844feb1fe4bea86e28f17b92a5f0c42193634d3782" +dependencies = [ + "cfg-if", + "derive_more", + "parity-scale-codec", + "scale-info-derive", + "serde", +] + +[[package]] +name = "scale-info-derive" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "912e55f6d20e0e80d63733872b40e1227c0bce1e1ab81ba67d696339bfd7fd29" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[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.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "serde_json" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" +dependencies = [ + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "str-buf" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e75b72ee54e2f93c3ea1354066162be893ee5e25773ab743de3e088cecbb4f31" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "this-that" +version = "0.1.0" +dependencies = [ + "gear-wasm-builder", + "gstd", + "gsys", + "parity-scale-codec", + "sails-idlgen", + "sails-service", + "this-that-app", +] + +[[package]] +name = "this-that-app" +version = "0.1.0" +dependencies = [ + "gmeta", + "gstd", + "parity-scale-codec", + "sails-macros", + "sails-service", + "scale-info", +] + +[[package]] +name = "this-that-svc" +version = "0.1.0" +dependencies = [ + "gear-wasm-builder", + "gstd", + "gsys", + "parity-scale-codec", + "sails-idlgen", + "sails-service", + "this-that-svc-app", +] + +[[package]] +name = "this-that-svc-app" +version = "0.1.0" +dependencies = [ + "gmeta", + "gstd", + "parity-scale-codec", + "sails-macros", + "sails-service", + "scale-info", +] + +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "tokio" +version = "1.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.19.15", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "trybuild" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196a58260a906cedb9bf6d8034b6379d0c11f552416960452f267402ceeddff1" +dependencies = [ + "basic-toml", + "glob", + "once_cell", + "serde", + "serde_derive", + "serde_json", + "termcolor", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.37", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" + +[[package]] +name = "wasmparser-nostd" +version = "0.100.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9157cab83003221bfd385833ab587a039f5d6fa7304854042ba358a3b09e0724" +dependencies = [ + "indexmap-nostd", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +dependencies = [ + "memchr", +] + +[[package]] +name = "zerocopy" +version = "0.7.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd369a67c0edfef15010f980c3cbe45d7f651deac2cd67ce097cd801de16557" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..da74a9fb --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,42 @@ +[workspace.package] +version = "0.0.1" +authors = ["Gear Technologies"] +edition = "2021" +license = "GPL-3.0" + +[workspace] +resolver = "2" +members = [ + "examples/this-that/app", + "examples/this-that/wasm", + "examples/this-that-svc/app", + "examples/this-that-svc/wasm", + "idlgen", + "macros", + "macros/core", + "service", +] + +[workspace.dependencies] +async-trait = "0.1" +convert-case = { package = "convert_case", version = "0.6" } +gmeta = { git = "https://github.com/gear-tech/gear.git", tag = "v1.0.1" } +gstd = { git = "https://github.com/gear-tech/gear.git", tag = "v1.0.1" } +gsys = { git = "https://github.com/gear-tech/gear.git", tag = "v1.0.1" } +gwasm-builder = { package = "gear-wasm-builder", git = "https://github.com/gear-tech/gear.git", tag = "v1.0.1" } +handlebars = "4.4" +hashbrown = "0.14" +parity-scale-codec = { version = "3.6.9", default-features = false } +proc-macro-error = "1.0.4" +proc-macro2 = { version = "1", default-features = false } +quote = "1.0.33" +sails-idlgen = { path = "idlgen" } +sails-macros = { path = "macros" } +sails-service = { path = "service" } +scale-info = { version = "2.5.0", default-features = false } +serde = "1.0" +serde-json = { package = "serde_json", version = "1.0" } +syn = "2.0.32" +thiserror = "1.0" +tokio = "1.32" +trybuild = "1" diff --git a/LICENSE b/LICENSE index f288702d..f17b32f5 100644 --- a/LICENSE +++ b/LICENSE @@ -672,3 +672,29 @@ may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . + + "CLASSPATH" EXCEPTION TO THE GPL + + Linking this library statically or dynamically with other modules is making +a combined work based on this library. Thus, the terms and conditions of the +GNU General Public License cover the whole combination. + + As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent modules, +and to copy and distribute the resulting executable under terms of your +choice, provided that you also meet, for each linked independent module, +the terms and conditions of the license of that module. An independent +module is a module which is not derived from or based on this library. +If you modify this library, you may extend this exception to your version +of the library, but you are not obligated to do so. If you do not wish to +do so, delete this exception statement from your version. + + NOTE + + Individual files contain the following tag instead of the full license text. + + SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + + This enables machine processing of license information based on the SPDX +License Identifiers that are here available: http://spdx.org/licenses/ diff --git a/examples/this-that-svc/app/Cargo.toml b/examples/this-that-svc/app/Cargo.toml new file mode 100644 index 00000000..5666d2a4 --- /dev/null +++ b/examples/this-that-svc/app/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "this-that-svc-app" +version = "0.1.0" +edition = "2021" + +[dependencies] +gmeta.workspace = true +gstd = { workspace = true, features = ["debug"] } +sails-macros.workspace = true +sails-service.workspace = true +parity-scale-codec = { workspace = true, features = ["derive"] } +scale-info = { workspace = true, features = ["derive"] } diff --git a/examples/this-that-svc/app/src/lib.rs b/examples/this-that-svc/app/src/lib.rs new file mode 100644 index 00000000..9c14328d --- /dev/null +++ b/examples/this-that-svc/app/src/lib.rs @@ -0,0 +1,60 @@ +#![no_std] + +use gstd::{debug, prelude::*}; +use sails_macros::gservice; + +pub struct MyService; + +#[gservice] +impl MyService { + pub const fn new() -> Self { + Self + } + + pub async fn do_this( + &mut self, + p1: u32, + p2: String, + p3: (Option, u8), + p4: TupleStruct, + ) -> (String, u32) { + debug!("Handling 'do_this': {}, {}, {:?}, {:?}", p1, p2, p3, p4); + (p2, p1) + } + + pub fn do_that(&mut self, param: DoThatParam) -> Result<(String, u32), (String,)> { + debug!("Handling 'do_that': {:?}", param); + Ok((param.p2, param.p1)) + } + + pub fn this(&self) -> u32 { + debug!("Handling 'this'"); + 42 + } + + // That + pub fn that(&self) -> Result { + debug!("Handling 'that'"); + Ok("Forty two".into()) + } +} + +#[derive(Debug, Decode, TypeInfo)] +pub struct TupleStruct(bool); + +#[derive(Debug, Decode, TypeInfo)] +pub struct DoThatParam { + pub p1: u32, + pub p2: String, + pub p3: ManyVariants, +} + +#[derive(Debug, Decode, TypeInfo)] +pub enum ManyVariants { + One, + Two(u32), + Three(Option), + Four { a: u32, b: Option }, + Five(String, u32), + Six((u32,)), +} diff --git a/examples/this-that-svc/wasm/Cargo.toml b/examples/this-that-svc/wasm/Cargo.toml new file mode 100644 index 00000000..34c700da --- /dev/null +++ b/examples/this-that-svc/wasm/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "this-that-svc" +version = "0.1.0" +edition = "2021" + +[dependencies] +gstd.workspace = true +gsys.workspace = true +parity-scale-codec.workspace = true +sails-service.workspace = true +this-that-svc-app = { path = "../app" } + +[build-dependencies] +gwasm-builder.workspace = true +sails-idlgen.workspace = true +this-that-svc-app = { path = "../app" } diff --git a/examples/this-that-svc/wasm/build.rs b/examples/this-that-svc/wasm/build.rs new file mode 100644 index 00000000..b0e3a117 --- /dev/null +++ b/examples/this-that-svc/wasm/build.rs @@ -0,0 +1,35 @@ +fn main() { + gwasm_builder::build(); + let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()); + let wasm_target_dir = wasm_target_dir(&out_dir); + // TODO: This path could be generated based on the path to the generated wasm file, but it + // needs to be returned from the `gwasm_builder::build...` methods. + let idl_path = wasm_target_dir.join("this_that_svc.sails.idl"); + let idl_file = std::fs::File::create(idl_path).expect("failed to create IDL file"); + sails_idlgen::generate_serivce_idl_ex::(idl_file) + .expect("failed to write IDL file"); +} + +// TODO: This code is copy-pasted from the wasm-build. It would be nice if `wasm_builder::build...` +// methods returned a path to generated wasm file so it could be used for generating a path +// to the IDL file for the cases when IDL generator is used as a part of build script. +fn wasm_target_dir(out_dir: &std::path::Path) -> std::path::PathBuf { + let profile: String = out_dir + .components() + .rev() + .take_while(|c| c.as_os_str() != "target") + .collect::>() + .into_iter() + .rev() + .take_while(|c| c.as_os_str() != "build") + .last() + .expect("Path should have subdirs in the `target` dir") + .as_os_str() + .to_string_lossy() + .into(); + + let mut target_dir = out_dir.to_path_buf(); + while !target_dir.ends_with("target") && target_dir.pop() {} + + target_dir.join("wasm32-unknown-unknown").join(profile) +} diff --git a/examples/this-that-svc/wasm/src/lib.rs b/examples/this-that-svc/wasm/src/lib.rs new file mode 100644 index 00000000..4afd4156 --- /dev/null +++ b/examples/this-that-svc/wasm/src/lib.rs @@ -0,0 +1,17 @@ +#![no_std] + +use gstd::msg; +use this_that_svc_app::{handlers as service_handlers, MyService}; + +static mut MY_SERVICE: MyService = MyService::new(); + +fn my_service() -> &'static mut MyService { + unsafe { &mut MY_SERVICE } +} + +#[gstd::async_main] +async fn main() { + let input_bytes = msg::load_bytes().expect("Failed to read input"); + let output_bytes = service_handlers::process_request(my_service(), &input_bytes).await; + msg::reply_bytes(output_bytes, 0).expect("Failed to send output"); +} diff --git a/examples/this-that/app/Cargo.toml b/examples/this-that/app/Cargo.toml new file mode 100644 index 00000000..804e5722 --- /dev/null +++ b/examples/this-that/app/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "this-that-app" +version = "0.1.0" +edition = "2021" + +[dependencies] +gmeta.workspace = true +gstd = { workspace = true, features = ["debug"] } +sails-macros.workspace = true +sails-service.workspace = true +parity-scale-codec = { workspace = true, features = ["derive"] } +scale-info = { workspace = true, features = ["derive"] } + +[features] +"handlers" = [] diff --git a/examples/this-that/app/src/lib.rs b/examples/this-that/app/src/lib.rs new file mode 100644 index 00000000..4340670c --- /dev/null +++ b/examples/this-that/app/src/lib.rs @@ -0,0 +1,113 @@ +#![no_std] + +use gmeta::{InOut, Metadata}; +#[allow(unused_imports)] +use gstd::debug; +use gstd::prelude::*; +use sails_macros::{command_handlers, query_handlers}; +use sails_service::{BoxedFuture, SimpleService}; + +pub struct ProgramMetadata; + +// TODO: This might be generated by some macro like `gprogram` +impl Metadata for ProgramMetadata { + type Init = (); + type Handle = InOut; // Uses generated types + type Others = (); + type Reply = (); + type Signal = (); + type State = InOut; // Uses generated types +} + +// TODO: The 2 below structs and their impls could be generated by the `commmand_handlers!`/`query_handlers!` macros +pub struct CommandProcessorMeta; + +impl sails_service::CommandProcessorMeta for CommandProcessorMeta { + type Request = commands::Commands; + type Response = commands::CommandResponses; + type ProcessFn = fn(Self::Request) -> BoxedFuture<(Self::Response, bool)>; +} + +pub struct QueryProcessorMeta; + +impl sails_service::QueryProcessorMeta for QueryProcessorMeta { + type Request = queries::Queries; + type Response = queries::QueryResponses; + type ProcessFn = fn(Self::Request) -> (Self::Response, bool); +} + +pub type Service = SimpleService; + +#[derive(Debug, Encode, Decode, TypeInfo)] +pub struct DoThatParam { + pub p1: u32, + pub p2: String, + pub p3: ManyVariants, +} + +#[derive(Debug, Encode, Decode, TypeInfo)] +pub struct TupleStruct(bool); + +#[derive(Debug, Encode, Decode, TypeInfo)] +pub enum ManyVariants { + One, + Two(u32), + Three(Option), + Four { a: u32, b: Option }, + Five(String, u32), + Six((u32,)), // IDEA can't handle meta with single element tuple +} + +#[command_handlers] +pub mod commands { + use super::*; + + // This + async fn do_this( + p1: u32, + p2: String, + p3: (Option, u8), + p4: TupleStruct, + ) -> Result<(String, u32), String> { + debug!("Handling 'do_this': {}, {}, {:?}, {:?}", p1, p2, p3, p4); + Ok((p2, p1)) + } + + // That + fn do_that( + param: DoThatParam, + ) -> Result<(String, u32), (String,) /* IDEA can't handle meta with single element tuple */> + { + debug!("Handling 'do_that': {:?}", param); + Ok((param.p2, param.p1)) + } + + // Fail + fn fail(message: String) -> Result<(), String> { + debug!("Handling 'fail': {}", message); + Err(message) + } +} + +#[query_handlers] +pub mod queries { + use super::*; + + // This + fn this() -> Result { + debug!("Handling 'this'"); + Ok(42) + } + + // That + fn that() -> Result { + debug!("Handling 'that'"); + Ok("Forty two".into()) + } + + // Fail + fn fail() -> Result<(), String> { + debug!("Handling 'fail'"); + Err("Failed".into()) + } +} diff --git a/examples/this-that/wasm/Cargo.toml b/examples/this-that/wasm/Cargo.toml new file mode 100644 index 00000000..3fbd1a8b --- /dev/null +++ b/examples/this-that/wasm/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "this-that" +version = "0.1.0" +edition = "2021" + +[dependencies] +gstd.workspace = true +gsys.workspace = true +parity-scale-codec.workspace = true +sails-service.workspace = true +this-that-app = { path = "../app", features = ["handlers"] } + +[build-dependencies] +gwasm-builder.workspace = true +sails-idlgen.workspace = true +this-that-app = { path = "../app" } diff --git a/examples/this-that/wasm/build.rs b/examples/this-that/wasm/build.rs new file mode 100644 index 00000000..f25e3ca6 --- /dev/null +++ b/examples/this-that/wasm/build.rs @@ -0,0 +1,38 @@ +fn main() { + gwasm_builder::build_with_metadata::(); + let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()); + let wasm_target_dir = wasm_target_dir(&out_dir); + // TODO: This path could be generated based on the path to the generated wasm file, but it + // needs to be returned from the `gwasm_builder::build...` methods. + let idl_path = wasm_target_dir.join("this_that.sails.idl"); + let idl_file = std::fs::File::create(idl_path).expect("failed to create IDL file"); + sails_idlgen::generate_serivce_idl::< + this_that_app::CommandProcessorMeta, + this_that_app::QueryProcessorMeta, + >(None, idl_file) + .expect("failed to write IDL file"); +} + +// TODO: This code is copy-pasted from the wasm-build. It would be nice if `wasm_builder::build...` +// methods returned a path to generated wasm file so it could be used for generating a path +// to the IDL file for the cases when IDL generator is used as a part of build script. +fn wasm_target_dir(out_dir: &std::path::Path) -> std::path::PathBuf { + let profile: String = out_dir + .components() + .rev() + .take_while(|c| c.as_os_str() != "target") + .collect::>() + .into_iter() + .rev() + .take_while(|c| c.as_os_str() != "build") + .last() + .expect("Path should have subdirs in the `target` dir") + .as_os_str() + .to_string_lossy() + .into(); + + let mut target_dir = out_dir.to_path_buf(); + while !target_dir.ends_with("target") && target_dir.pop() {} + + target_dir.join("wasm32-unknown-unknown").join(profile) +} diff --git a/examples/this-that/wasm/src/lib.rs b/examples/this-that/wasm/src/lib.rs new file mode 100644 index 00000000..dacfe9aa --- /dev/null +++ b/examples/this-that/wasm/src/lib.rs @@ -0,0 +1,47 @@ +#![no_std] + +use gstd::{msg, vec, Box}; +use sails_service::{CompositeService, Service as ServiceTrait}; +use this_that_app::{commands::handlers as c_handlers, queries::handlers as q_handlers, Service}; + +static SERVICE: Service = Service::new( + |command| Box::pin(c_handlers::process_commands(command)), + q_handlers::process_queries, +); + +fn _composite_service() -> &'static CompositeService { + static mut COMPOSITE_SERVICE: Option = None; + unsafe { + COMPOSITE_SERVICE.get_or_insert_with(|| { + CompositeService::new(vec![( + "this-that", + Box::new(Service::new( + |command| Box::pin(c_handlers::process_commands(command)), + q_handlers::process_queries, + )) as Box, + )]) + }) + } +} + +// TODO: This function might be generated by some macro like `gprogram` +#[gstd::async_main] +async fn main() { + let input = msg::load_bytes().expect("This needs to be handled in some way: read error"); + let (output, is_error) = SERVICE.process_command(&input).await; + if is_error { + unsafe { + gsys::gr_panic(output.as_ptr(), output.len() as u32); + } + } + msg::reply(output, 0).expect("This needs to be handled in a consistent way: reply error"); +} + +// TODO: This function might be generated by some macro like `gprogram` +#[no_mangle] +extern "C" fn state() { + let output = msg::with_read_on_stack(|input| { + SERVICE.process_query(input.expect("This needs to be handled in some way: read error")) + }); + msg::reply(output, 0).expect("This needs to be handled in a consistent way: reply error"); +} diff --git a/idlgen/Cargo.toml b/idlgen/Cargo.toml new file mode 100644 index 00000000..fbaeeec4 --- /dev/null +++ b/idlgen/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "sails-idlgen" +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +convert-case.workspace = true +handlebars.workspace = true +sails-service.workspace = true +scale-info = { workspace = true, features = ["serde"] } +serde.workspace = true +serde-json.workspace = true +thiserror.workspace = true + +[dev-dependencies] +parity-scale-codec = { workspace = true, features = ["derive"] } +scale-info = { workspace = true, features = ["derive"] } diff --git a/idlgen/hbs/composite.hbs b/idlgen/hbs/composite.hbs new file mode 100644 index 00000000..04795547 --- /dev/null +++ b/idlgen/hbs/composite.hbs @@ -0,0 +1,5 @@ +type {{{lookup @root/type_names id}}} = record { +{{#each fields}} + {{#if name}}{{name}}: {{/if}}{{{lookup @root/type_names type}}}; +{{/each}} +}; diff --git a/idlgen/hbs/idl.hbs b/idlgen/hbs/idl.hbs new file mode 100644 index 00000000..451e0eab --- /dev/null +++ b/idlgen/hbs/idl.hbs @@ -0,0 +1,26 @@ +{{#each complex_types as |type|}} + {{#each type.type.def}} + {{~> (deref @key) id=type.id}} + {{/each}} + +{{/each}} +service { +{{#each commands.type.def.variant.variants}} + async {{name}} : ( + {{~#each fields~}} + {{#if @index}}, {{/if}}{{{lookup @root/type_names type}}} + {{~/each~}} + ) -> {{#with (lookup @root/commandResponses.type.def.variant.variants @index)}} + {{{~lookup @root/type_names fields.[0].type}}}; + {{/with}} +{{/each}} +{{#each queries.type.def.variant.variants}} + {{name}} : ( + {{~#each fields~}} + {{#if @index}}, {{/if}}{{{lookup @root/type_names type}}} + {{~/each~}} + ) -> {{#with (lookup @root/queryResponses.type.def.variant.variants @index)}} + {{{~lookup @root/type_names fields.[0].type}}} query; + {{/with}} +{{/each}} +} diff --git a/idlgen/hbs/idl_ex.hbs b/idlgen/hbs/idl_ex.hbs new file mode 100644 index 00000000..f286d920 --- /dev/null +++ b/idlgen/hbs/idl_ex.hbs @@ -0,0 +1,14 @@ +{{#each complex_types as |type|}} + {{#each type.type.def}} + {{~> (deref @key) id=type.id}} + {{/each}} + +{{/each}} +service { +{{#each commands.type.def.variant.variants}} + {{name}} : ({{#with (lookup @root/all_types fields.[0].type)}}{{#each type.def.composite.fields}}{{#if @index}}, {{/if}}{{name}}: {{{lookup @root/type_names type}}}{{/each}}{{/with}}) -> {{{lookup @root/type_names fields.[1].type}}} +{{/each}} +{{#each queries.type.def.variant.variants}} + query {{name}} : ({{#with (lookup @root/all_types fields.[0].type)}}{{#each type.def.composite.fields}}{{#if @index}}, {{/if}}{{name}}: {{{lookup @root/type_names type}}}{{/each}}{{/with}}) -> {{{lookup @root/type_names fields.[1].type}}} +{{/each}} +} diff --git a/idlgen/hbs/variant.hbs b/idlgen/hbs/variant.hbs new file mode 100644 index 00000000..a49ac715 --- /dev/null +++ b/idlgen/hbs/variant.hbs @@ -0,0 +1,11 @@ +type {{{lookup @root/type_names id}}} = variant { +{{#each variants}} + {{name}} + {{~#if fields.[1]}}: record { {{#each fields~}} {{#if @index}}; {{/if}}{{#if name}}{{name}}: {{/if}}{{{lookup @root/type_names type}}}{{/each}} } + {{~else}} + {{~#if fields.[0]}}: {{{lookup @root/type_names fields.[0].type}}} + {{~/if}} + {{/if~}} + ; +{{/each}} +}; diff --git a/idlgen/src/errors.rs b/idlgen/src/errors.rs new file mode 100644 index 00000000..08d404c5 --- /dev/null +++ b/idlgen/src/errors.rs @@ -0,0 +1,33 @@ +// This file is part of Gear. + +// Copyright (C) 2021-2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Errors returned by IDL generator. + +pub type Result = std::result::Result; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("type with id {0} is not found in the type registry")] + UnknownType(u32), + #[error("type `{0}` is not supported")] + UnsupprotedType(String), + #[error(transparent)] + BadTemplate(#[from] Box), + #[error(transparent)] + FailedRendering(#[from] Box), +} diff --git a/idlgen/src/lib.rs b/idlgen/src/lib.rs new file mode 100644 index 00000000..ce4dd4cd --- /dev/null +++ b/idlgen/src/lib.rs @@ -0,0 +1,395 @@ +// This file is part of Gear. + +// Copyright (C) 2021-2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Functionality for generating IDL files describing some service based on its Rust code. + +use handlebars::{handlebars_helper, Handlebars}; +use sails_service::{CommandProcessorMeta, QueryProcessorMeta, ServiceMeta}; +use scale_info::PortableType; +use serde::Serialize; +use service_types::{ServiceTypes, ServiceTypesEx}; +use std::io; + +mod errors; +mod service_types; +mod type_names; + +const IDL_EX_TEMPLATE: &str = include_str!("../hbs/idl_ex.hbs"); +const IDL_TEMPLATE: &str = include_str!("../hbs/idl.hbs"); +const COMPOSITE_TEMPLATE: &str = include_str!("../hbs/composite.hbs"); +const VARIANT_TEMPLATE: &str = include_str!("../hbs/variant.hbs"); + +pub fn generate_serivce_idl( + _service_name: Option<&str>, + idl_writer: impl io::Write, +) -> errors::Result<()> +where + C: CommandProcessorMeta, + Q: QueryProcessorMeta, +{ + let service_info = ServiceTypes::::new(); + + let service_all_type_names = type_names::resolve_type_names(service_info.all_types_registry())?; + + let service_idl_data = ServiceIdlData { + complex_types: service_info.complex_types().collect(), + commands: service_info.command_types().0, + command_responses: service_info.command_types().1, + queries: service_info.query_types().0, + query_responses: service_info.query_types().1, + type_names: service_all_type_names.values().collect(), + }; + + let mut handlebars = Handlebars::new(); + handlebars + .register_template_string("idl", IDL_TEMPLATE) + .map_err(Box::new)?; + handlebars + .register_template_string("composite", COMPOSITE_TEMPLATE) + .map_err(Box::new)?; + handlebars + .register_template_string("variant", VARIANT_TEMPLATE) + .map_err(Box::new)?; + handlebars.register_helper("deref", Box::new(deref)); + + handlebars + .render_to_write("idl", &service_idl_data, idl_writer) + .map_err(Box::new)?; + + Ok(()) +} + +pub fn generate_serivce_idl_ex(idl_writer: impl io::Write) -> errors::Result<()> { + let service_info = ServiceTypesEx::::new(); + + let service_all_type_names = type_names::resolve_type_names(service_info.all_types_registry())?; + + let service_idl_data = ServiceIdlDataEx { + type_names: service_all_type_names.values().collect(), + all_types: service_info.all_types_registry().types.iter().collect(), + complex_types: service_info.complex_types().collect(), + commands: service_info.commands_type(), + queries: service_info.queries_type(), + }; + + let mut handlebars = Handlebars::new(); + handlebars + .register_template_string("idl", IDL_EX_TEMPLATE) + .map_err(Box::new)?; + handlebars + .register_template_string("composite", COMPOSITE_TEMPLATE) + .map_err(Box::new)?; + handlebars + .register_template_string("variant", VARIANT_TEMPLATE) + .map_err(Box::new)?; + handlebars.register_helper("deref", Box::new(deref)); + + handlebars + .render_to_write("idl", &service_idl_data, idl_writer) + .map_err(Box::new)?; + + Ok(()) +} + +#[derive(serde::Serialize)] +struct ServiceIdlData<'a> { + complex_types: Vec<&'a PortableType>, + commands: &'a PortableType, + #[serde(rename = "commandResponses")] + command_responses: &'a PortableType, + queries: &'a PortableType, + #[serde(rename = "queryResponses")] + query_responses: &'a PortableType, + type_names: Vec<&'a String>, +} + +#[derive(Serialize)] +struct ServiceIdlDataEx<'a> { + type_names: Vec<&'a String>, + all_types: Vec<&'a PortableType>, + complex_types: Vec<&'a PortableType>, + commands: &'a PortableType, + queries: &'a PortableType, +} + +handlebars_helper!(deref: |v: String| { v }); + +#[cfg(test)] +mod tests { + use super::*; + use parity_scale_codec::{Decode, Encode}; + use sails_service::BoxedFuture; + use scale_info::TypeInfo; + use std::result::Result as StdResult; + + #[allow(dead_code)] + #[derive(TypeInfo, Decode)] + pub struct GenericStruct { + pub p1: T, + } + + #[allow(dead_code)] + #[derive(TypeInfo, Decode)] + pub enum GenericEnum { + Variant1(T1), + Variant2(T2), + } + + #[allow(dead_code)] + #[derive(TypeInfo, Decode)] + pub struct DoThatParam { + pub p1: u32, + pub p2: String, + pub p3: ManyVariants, + } + + #[allow(dead_code)] + #[derive(TypeInfo, Decode)] + pub struct ThatParam { + pub p1: ManyVariants, + } + + #[allow(dead_code)] + #[derive(TypeInfo, Decode)] + pub struct TupleStruct(bool); + + #[allow(dead_code)] + #[derive(TypeInfo, Decode)] + pub enum ManyVariants { + One, + Two(u32), + Three(Option>), + Four { a: u32, b: Option }, + Five(String, Vec), + Six((u32,)), + Seven(GenericEnum), + } + + #[allow(dead_code)] + #[derive(TypeInfo, Decode)] + enum Commands { + DoThis( + u32, + String, + (Option, u8), + TupleStruct, + GenericStruct, + GenericStruct, + ), + DoThat(DoThatParam), + Fail(String), + } + + #[allow(dead_code)] + #[derive(TypeInfo, Encode)] + enum CommandResponses { + DoThis(StdResult<(String, u32), String>), + DoThat(StdResult<(String, u32), (String,)>), + Fail(StdResult<(), String>), + } + + struct TestCommandProcessorMeta; + + impl CommandProcessorMeta for TestCommandProcessorMeta { + type Request = Commands; + type Response = CommandResponses; + type ProcessFn = fn(Self::Request) -> BoxedFuture<(Self::Response, bool)>; + } + + #[allow(dead_code)] + #[derive(TypeInfo, Decode)] + enum Queries { + This( + u32, + String, + (Option, u8), + TupleStruct, + GenericEnum, + ), + That(ThatParam), + Fail(String), + } + + #[allow(dead_code)] + #[derive(TypeInfo, Encode)] + enum QueryResponses { + This(StdResult<(String, u32), String>), + That(StdResult<(String, u32), (String,)>), + Fail(StdResult<(), String>), + } + + struct TestQueryProcessorMeta; + + impl QueryProcessorMeta for TestQueryProcessorMeta { + type Request = Queries; + type Response = QueryResponses; + type ProcessFn = fn(Self::Request) -> (Self::Response, bool); + } + + #[test] + fn idl_generation_works_for_commands() { + let mut idl = Vec::new(); + generate_serivce_idl::(None, &mut idl).unwrap(); + let generated_idl = String::from_utf8(idl).unwrap(); + + const EXPECTED_IDL: &str = r"type SailsIdlgenTestsTupleStruct = record { + bool; +}; + +type SailsIdlgenTestsGenericStruct = record { + p1: u32; +}; + +type SailsIdlgenTestsGenericStruct = record { + p1: str; +}; + +type SailsIdlgenTestsDoThatParam = record { + p1: u32; + p2: str; + p3: SailsIdlgenTestsManyVariants; +}; + +type SailsIdlgenTestsManyVariants = variant { + One; + Two: u32; + Three: opt vec u32; + Four: record { a: u32; b: opt u16 }; + Five: record { str; vec u8 }; + Six: record { u32 }; + Seven: SailsIdlgenTestsGenericEnum; +}; + +type SailsIdlgenTestsGenericEnum = variant { + Variant1: u32; + Variant2: str; +}; + +service { + async DoThis : (u32, str, record { opt str; u8 }, SailsIdlgenTestsTupleStruct, SailsIdlgenTestsGenericStruct, SailsIdlgenTestsGenericStruct) -> result (record { str; u32 }, str); + async DoThat : (SailsIdlgenTestsDoThatParam) -> result (record { str; u32 }, record { str }); + async Fail : (str) -> result (null, str); +} +"; + assert_eq!(generated_idl, EXPECTED_IDL); + } + + #[test] + fn idl_generation_works_for_queries() { + let mut idl = Vec::new(); + generate_serivce_idl::<(), TestQueryProcessorMeta>(None, &mut idl).unwrap(); + let generated_idl = String::from_utf8(idl).unwrap(); + + const EXPECTED_IDL: &str = r"type SailsIdlgenTestsTupleStruct = record { + bool; +}; + +type SailsIdlgenTestsGenericEnum = variant { + Variant1: bool; + Variant2: u32; +}; + +type SailsIdlgenTestsThatParam = record { + p1: SailsIdlgenTestsManyVariants; +}; + +type SailsIdlgenTestsManyVariants = variant { + One; + Two: u32; + Three: opt vec u32; + Four: record { a: u32; b: opt u16 }; + Five: record { str; vec u8 }; + Six: record { u32 }; + Seven: SailsIdlgenTestsGenericEnum; +}; + +type SailsIdlgenTestsGenericEnum = variant { + Variant1: u32; + Variant2: str; +}; + +service { + This : (u32, str, record { opt str; u8 }, SailsIdlgenTestsTupleStruct, SailsIdlgenTestsGenericEnum) -> result (record { str; u32 }, str) query; + That : (SailsIdlgenTestsThatParam) -> result (record { str; u32 }, record { str }) query; + Fail : (str) -> result (null, str) query; +} +"; + assert_eq!(generated_idl, EXPECTED_IDL); + } + + #[test] + fn idl_generation_works_for_commands_and_queries() { + let mut idl = Vec::new(); + generate_serivce_idl::(None, &mut idl) + .unwrap(); + let generated_idl = String::from_utf8(idl).unwrap(); + + const EXPECTED_IDL: &str = r"type SailsIdlgenTestsTupleStruct = record { + bool; +}; + +type SailsIdlgenTestsGenericStruct = record { + p1: u32; +}; + +type SailsIdlgenTestsGenericStruct = record { + p1: str; +}; + +type SailsIdlgenTestsDoThatParam = record { + p1: u32; + p2: str; + p3: SailsIdlgenTestsManyVariants; +}; + +type SailsIdlgenTestsManyVariants = variant { + One; + Two: u32; + Three: opt vec u32; + Four: record { a: u32; b: opt u16 }; + Five: record { str; vec u8 }; + Six: record { u32 }; + Seven: SailsIdlgenTestsGenericEnum; +}; + +type SailsIdlgenTestsGenericEnum = variant { + Variant1: u32; + Variant2: str; +}; + +type SailsIdlgenTestsGenericEnum = variant { + Variant1: bool; + Variant2: u32; +}; + +type SailsIdlgenTestsThatParam = record { + p1: SailsIdlgenTestsManyVariants; +}; + +service { + async DoThis : (u32, str, record { opt str; u8 }, SailsIdlgenTestsTupleStruct, SailsIdlgenTestsGenericStruct, SailsIdlgenTestsGenericStruct) -> result (record { str; u32 }, str); + async DoThat : (SailsIdlgenTestsDoThatParam) -> result (record { str; u32 }, record { str }); + async Fail : (str) -> result (null, str); + This : (u32, str, record { opt str; u8 }, SailsIdlgenTestsTupleStruct, SailsIdlgenTestsGenericEnum) -> result (record { str; u32 }, str) query; + That : (SailsIdlgenTestsThatParam) -> result (record { str; u32 }, record { str }) query; + Fail : (str) -> result (null, str) query; +} +"; + assert_eq!(generated_idl, EXPECTED_IDL); + } +} diff --git a/idlgen/src/service_types.rs b/idlgen/src/service_types.rs new file mode 100644 index 00000000..9230cb22 --- /dev/null +++ b/idlgen/src/service_types.rs @@ -0,0 +1,190 @@ +// This file is part of Gear. + +// Copyright (C) 2021-2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Struct describing the types of a service comprised of command and query handlers. + +use sails_service::{CommandProcessorMeta, QueryProcessorMeta, ServiceMeta}; +use scale_info::{MetaType, PortableRegistry, PortableType, Registry}; +use std::marker::PhantomData; + +pub(crate) struct ServiceTypes { + type_registry: PortableRegistry, + commands_type_id: u32, + command_responses_type_id: u32, + queries_type_id: u32, + query_responses_type_id: u32, + _commands: PhantomData, + _queries: PhantomData, +} + +impl ServiceTypes +where + C: CommandProcessorMeta, + Q: QueryProcessorMeta, +{ + pub fn new() -> Self { + // TODO: Validate HandlerTypes - both C and Q must be enums with variants having the same names in the same order + let mut type_registry = Registry::new(); + let commands_type_id = type_registry + .register_type(&MetaType::new::()) + .id; + let command_responses_type_id = type_registry + .register_type(&MetaType::new::()) + .id; + let queries_type_id = type_registry + .register_type(&MetaType::new::()) + .id; + let query_responses_type_id = type_registry + .register_type(&MetaType::new::()) + .id; + let type_registry = PortableRegistry::from(type_registry); + Self { + type_registry, + commands_type_id, + command_responses_type_id, + queries_type_id, + query_responses_type_id, + _commands: PhantomData, + _queries: PhantomData, + } + } + + pub fn complex_types(&self) -> impl Iterator { + self.type_registry.types.iter().filter(|ty| { + !ty.ty.path.namespace().is_empty() + && ty.id != self.commands_type_id + && ty.id != self.command_responses_type_id + && ty.id != self.queries_type_id + && ty.id != self.query_responses_type_id + }) + } + + pub fn command_types(&self) -> (&PortableType, &PortableType) { + let commands_type = self.resolve_handler_type(self.commands_type_id); + let command_responses_type = self.resolve_handler_type(self.command_responses_type_id); + (commands_type, command_responses_type) + } + + pub fn query_types(&self) -> (&PortableType, &PortableType) { + let queries_type = self.resolve_handler_type(self.queries_type_id); + let query_responses_type = self.resolve_handler_type(self.query_responses_type_id); + (queries_type, query_responses_type) + } + + pub fn all_types_registry(&self) -> &PortableRegistry { + &self.type_registry + } + + fn resolve_handler_type(&self, handler_type_id: u32) -> &PortableType { + self.type_registry + .types + .iter() + .find(|ty| ty.id == handler_type_id) + .unwrap_or_else(|| { + panic!( + "type with id {} not found while it was registered previously", + handler_type_id + ) + }) + } +} + +pub(crate) struct ServiceTypesEx { + type_registry: PortableRegistry, + commands_type_id: u32, + queries_type_id: u32, + _service: PhantomData, +} + +impl ServiceTypesEx { + pub fn new() -> Self { + // TODO: Validate HandlerTypes - both C and Q must be enums with variants having the same names in the same order + let mut type_registry = Registry::new(); + let commands_type_id = type_registry + .register_type(&MetaType::new::()) + .id; + let queries_type_id = type_registry + .register_type(&MetaType::new::()) + .id; + let type_registry = PortableRegistry::from(type_registry); + Self { + type_registry, + commands_type_id, + queries_type_id, + _service: PhantomData, + } + } + + pub fn complex_types(&self) -> impl Iterator { + self.type_registry.types.iter().filter(|ty| { + !ty.ty.path.namespace().is_empty() + && ty.id != self.commands_type_id + && ty.id != self.queries_type_id + && !self.command_params_type_ids().any(|id| id == ty.id) + && !self.query_params_type_ids().any(|id| id == ty.id) + }) + } + + pub fn commands_type(&self) -> &PortableType { + self.type_registry + .types + .iter() + .find(|ty| ty.id == self.commands_type_id) + .unwrap_or_else(|| { + panic!( + "type with id {} not found while it was registered previously", + self.commands_type_id + ) + }) + } + + pub fn queries_type(&self) -> &PortableType { + self.type_registry + .types + .iter() + .find(|ty| ty.id == self.queries_type_id) + .unwrap_or_else(|| { + panic!( + "type with id {} not found while it was registered previously", + self.queries_type_id + ) + }) + } + + pub fn all_types_registry(&self) -> &PortableRegistry { + &self.type_registry + } + + fn command_params_type_ids(&self) -> impl Iterator + '_ { + match &self.commands_type().ty.type_def { + scale_info::TypeDef::Variant(variant) => { + variant.variants.iter().map(|v| v.fields[0].ty.id) + } + _ => panic!("Commands type is not a variant"), + } + } + + fn query_params_type_ids(&self) -> impl Iterator + '_ { + match &self.queries_type().ty.type_def { + scale_info::TypeDef::Variant(variant) => { + variant.variants.iter().map(|v| v.fields[0].ty.id) + } + _ => panic!("Queries type is not a variant"), + } + } +} diff --git a/idlgen/src/type_names.rs b/idlgen/src/type_names.rs new file mode 100644 index 00000000..d1c9f3e8 --- /dev/null +++ b/idlgen/src/type_names.rs @@ -0,0 +1,276 @@ +// This file is part of Gear. + +// Copyright (C) 2021-2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Type names resolution. + +use crate::errors::{Error, Result}; +use convert_case::{Case, Casing}; +use scale_info::{ + form::PortableForm, PortableRegistry, Type, TypeDef, TypeDefPrimitive, TypeDefTuple, TypeInfo, +}; +use std::collections::BTreeMap; + +pub(super) fn resolve_type_names( + type_registry: &PortableRegistry, +) -> Result> { + type_registry.types.iter().try_fold( + BTreeMap::::new(), + |mut resolved_type_names, ty| { + resolve_type_name(type_registry, ty.id, &mut resolved_type_names) + .map(|_| resolved_type_names) + }, + ) +} + +fn resolve_type_name( + type_registry: &PortableRegistry, + type_id: u32, + resolved_type_names: &mut BTreeMap, +) -> Result { + if let Some(type_name) = resolved_type_names.get(&type_id) { + return Ok(type_name.clone()); + } + + let type_info = type_registry + .resolve(type_id) + .ok_or_else(|| Error::UnknownType(type_id))?; + + let type_name = match &type_info.type_def { + TypeDef::Primitive(primitive_def) => primitive_type_name(primitive_def)?, + TypeDef::Tuple(tuple_def) => { + tuple_type_name(type_registry, tuple_def, resolved_type_names)? + } + TypeDef::Sequence(sequence_def) => array_type_name( + type_registry, + sequence_def.type_param.id, + resolved_type_names, + )?, + TypeDef::Composite(_) => type_name_by_path(type_registry, type_info, resolved_type_names)?, + TypeDef::Variant(_) => { + let result_type_info = std::result::Result::<(), ()>::type_info(); + let option_type_info = std::option::Option::<()>::type_info(); + if result_type_info.path.segments == type_info.path.segments { + result_type_name(type_registry, type_info, resolved_type_names)? + } else if option_type_info.path.segments == type_info.path.segments { + option_type_name(type_registry, type_info, resolved_type_names)? + } else { + type_name_by_path(type_registry, type_info, resolved_type_names)? + } + } + _ => { + return Err(Error::UnsupprotedType(format!("{type_info:?}"))); + } + }; + + resolved_type_names.insert(type_id, type_name.clone()); + Ok(type_name) +} + +fn result_type_name( + type_registry: &PortableRegistry, + type_info: &Type, + resolved_type_names: &mut BTreeMap, +) -> Result { + let ok_type_id = type_info + .type_params + .iter() + .find(|param| param.name == "T") + .ok_or_else(|| Error::UnsupprotedType(format!("{type_info:?}")))? + .ty + .ok_or_else(|| Error::UnsupprotedType(format!("{type_info:?}")))?; + let err_type_id = type_info + .type_params + .iter() + .find(|param| param.name == "E") + .ok_or_else(|| Error::UnsupprotedType(format!("{type_info:?}")))? + .ty + .ok_or_else(|| Error::UnsupprotedType(format!("{type_info:?}")))?; + let ok_type_name = resolve_type_name(type_registry, ok_type_id.id, resolved_type_names)?; + let err_type_name = resolve_type_name(type_registry, err_type_id.id, resolved_type_names)?; + Ok(format!("result ({ok_type_name}, {err_type_name})")) +} + +fn option_type_name( + type_registry: &PortableRegistry, + type_info: &Type, + resolved_type_names: &mut BTreeMap, +) -> Result { + let some_type_id = type_info + .type_params + .iter() + .find(|param| param.name == "T") + .ok_or_else(|| Error::UnsupprotedType(format!("{type_info:?}")))? + .ty + .ok_or_else(|| Error::UnsupprotedType(format!("{type_info:?}")))?; + let some_type_name = resolve_type_name(type_registry, some_type_id.id, resolved_type_names)?; + Ok(format!("opt {}", some_type_name)) +} + +fn type_name_by_path( + type_registry: &PortableRegistry, + type_info: &Type, + resolved_type_names: &mut BTreeMap, +) -> Result { + let type_name = type_info + .path + .segments + .iter() + .map(|segment| segment.to_case(Case::Pascal)) + .collect::>() + .join(""); + let type_param_names = type_info + .type_params + .iter() + .map(|type_param| { + let type_param_id = type_param + .ty + .ok_or_else(|| Error::UnsupprotedType(format!("{type_info:?}")))? + .id; + resolve_type_name(type_registry, type_param_id, resolved_type_names) + }) + .collect::>>()? + .join(", "); + let type_name = if type_param_names.is_empty() { + type_name + } else { + format!("{}<{}>", type_name, type_param_names) + }; + if type_name.is_empty() { + Err(Error::UnsupprotedType(format!("{type_info:?}"))) + } else { + Ok(type_name) + } +} + +fn tuple_type_name( + type_registry: &PortableRegistry, + tuple_def: &TypeDefTuple, + resolved_type_names: &mut BTreeMap, +) -> Result { + let fields = tuple_def + .fields + .iter() + .map(|field| resolve_type_name(type_registry, field.id, resolved_type_names)) + .collect::>>()? + .join("; "); + if fields.is_empty() { + Ok("null".into()) // For the () type + } else { + Ok(format!("record {{ {} }}", fields)) + } +} + +fn array_type_name( + type_registry: &PortableRegistry, + item_type_id: u32, + resolved_type_names: &mut BTreeMap, +) -> Result { + let item_type_name = resolve_type_name(type_registry, item_type_id, resolved_type_names)?; + Ok(format!("vec {}", item_type_name)) +} + +fn primitive_type_name(type_def: &TypeDefPrimitive) -> Result { + match type_def { + TypeDefPrimitive::Bool => Ok("bool".into()), + TypeDefPrimitive::Char => Ok("char".into()), + TypeDefPrimitive::Str => Ok("str".into()), + TypeDefPrimitive::U8 => Ok("u8".into()), + TypeDefPrimitive::U16 => Ok("u16".into()), + TypeDefPrimitive::U32 => Ok("u32".into()), + TypeDefPrimitive::U64 => Ok("u64".into()), + TypeDefPrimitive::U128 => Ok("u128".into()), + TypeDefPrimitive::U256 => Err(Error::UnsupprotedType("u256".into())), // Rust doesn't have it + TypeDefPrimitive::I8 => Ok("i8".into()), + TypeDefPrimitive::I16 => Ok("i16".into()), + TypeDefPrimitive::I32 => Ok("i32".into()), + TypeDefPrimitive::I64 => Ok("i64".into()), + TypeDefPrimitive::I128 => Ok("i128".into()), + TypeDefPrimitive::I256 => Err(Error::UnsupprotedType("i256".into())), // Rust doesn't have it + } +} + +#[cfg(test)] +mod tests { + use super::*; + use scale_info::{MetaType, Registry}; + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct GenericStruct { + field: T, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + enum GenericEnum { + Variant1(T1), + Variant2(T2), + } + + #[test] + fn generic_struct_type_name_resolution_works() { + let mut registry = Registry::new(); + let u32_struct_id = registry + .register_type(&MetaType::new::>()) + .id; + let string_struct_id = registry + .register_type(&MetaType::new::>()) + .id; + let portable_registry = PortableRegistry::from(registry); + + let type_names = resolve_type_names(&portable_registry).unwrap(); + + let u32_struct_name = type_names.get(&u32_struct_id).unwrap(); + assert_eq!( + u32_struct_name, + "SailsIdlgenTypeNamesTestsGenericStruct" + ); + + let string_struct_name = type_names.get(&string_struct_id).unwrap(); + assert_eq!( + string_struct_name, + "SailsIdlgenTypeNamesTestsGenericStruct" + ); + } + + #[test] + fn generic_variant_type_name_resolution_works() { + let mut registry = Registry::new(); + let u32_string_enum_id = registry + .register_type(&MetaType::new::>()) + .id; + let bool_u32_enum_id = registry + .register_type(&MetaType::new::>()) + .id; + let portable_registry = PortableRegistry::from(registry); + + let type_names = resolve_type_names(&portable_registry).unwrap(); + + let u32_string_enum_name = type_names.get(&u32_string_enum_id).unwrap(); + assert_eq!( + u32_string_enum_name, + "SailsIdlgenTypeNamesTestsGenericEnum" + ); + + let bool_u32_enum_name = type_names.get(&bool_u32_enum_id).unwrap(); + assert_eq!( + bool_u32_enum_name, + "SailsIdlgenTypeNamesTestsGenericEnum" + ); + } +} diff --git a/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 00000000..fd46462d --- /dev/null +++ b/macros/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "sails-macros" +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true + +[lib] +proc-macro = true + +[dependencies] +sails-macros-core = { path = "./core" } +proc-macro-error.workspace = true + +[dev-dependencies] +parity-scale-codec = { workspace = true, features = ["derive"] } +scale-info = { workspace = true, features = ["derive"] } +tokio = { workspace = true, features = ["full"] } +trybuild.workspace = true + +[features] +default = [ + "handlers", +] # Just for the sake of testing. I could not find any better solution https://github.com/rust-lang/cargo/issues/2911 +handlers = [] diff --git a/macros/core/Cargo.toml b/macros/core/Cargo.toml new file mode 100644 index 00000000..6e67ef0a --- /dev/null +++ b/macros/core/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "sails-macros-core" +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +convert-case.workspace = true +proc-macro-error.workspace = true +proc-macro2.workspace = true +quote.workspace = true +syn = { workspace = true, features = ["full"] } diff --git a/macros/core/src/lib.rs b/macros/core/src/lib.rs new file mode 100644 index 00000000..c932b208 --- /dev/null +++ b/macros/core/src/lib.rs @@ -0,0 +1,225 @@ +// This file is part of Gear. + +// Copyright (C) 2021-2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Implemntation of the procedural macros exposed via the `gprogram-framework-macros` crate. + +use proc_macro2::TokenStream as TokenStream2; + +mod processors; + +const COMMAND_ENUM_NAME: &str = "Commands"; +const COMMAND_RESPONSES_ENUM_NAME: &str = "CommandResponses"; +const QUERY_ENUM_NAME: &str = "Queries"; +const QUERY_RESPONSES_ENUM_NAME: &str = "QueryResponses"; + +pub fn command_handlers_core(mod_tokens: TokenStream2) -> TokenStream2 { + processors::generate(mod_tokens, COMMAND_ENUM_NAME, COMMAND_RESPONSES_ENUM_NAME) +} + +pub fn query_handlers_core(mod_tokens: TokenStream2) -> TokenStream2 { + processors::generate(mod_tokens, QUERY_ENUM_NAME, QUERY_RESPONSES_ENUM_NAME) +} + +pub fn gservice_core(impl_tokens: TokenStream2) -> TokenStream2 { + processors::gservice(impl_tokens) +} + +#[cfg(test)] +mod tests { + use super::*; + use quote::quote; + + #[test] + fn gservice_core_works() { + let input = quote! { + impl SomeService { + pub async fn do_this(&mut self, p1: u32, p2: String) -> u32 { + p1 + } + + pub fn this(&self, p1: bool) -> bool { + p1 + } + } + }; + let expected = quote!( + impl SomeService { + pub async fn do_this(&mut self, p1: u32, p2: String) -> u32 { + p1 + } + pub fn this(&self, p1: bool) -> bool { + p1 + } + } + + #[derive(Decode, TypeInfo)] + pub struct DoThisParams { + p1: u32, + p2: String + } + + #[derive(Decode, TypeInfo)] + pub struct ThisParams { + p1: bool + } + + pub mod meta { + use super::*; + + #[derive(TypeInfo)] + pub enum CommandsMeta { + DoThis(DoThisParams, u32), + } + + #[derive(TypeInfo)] + pub enum QueriesMeta { + This(ThisParams, bool), + } + + pub struct ServiceMeta; + + impl sails_service::ServiceMeta for ServiceMeta { + type Commands = CommandsMeta; + type Queries = QueriesMeta; + } + } + + pub mod handlers { + use super::*; + pub async fn process_request(service: &mut SomeService, mut input: &[u8]) -> Vec { + if input.starts_with("DoThis/".as_bytes()) { + return do_this(service, &input["DoThis/".as_bytes().len()..]).await; + } + if input.starts_with("This/".as_bytes()) { + return this(service, &input["This/".as_bytes().len()..]).await; + } + panic!("Unknown request"); + } + async fn do_this(service: &mut SomeService, mut input: &[u8]) -> Vec { + let request = DoThisParams::decode(&mut input).expect("Failed to decode request"); + let result = service.do_this(request.p1, request.p2).await; + return result.encode(); + } + async fn this(service: &SomeService, mut input: &[u8]) -> Vec { + let request = ThisParams::decode(&mut input).expect("Failed to decode request"); + let result = service.this(request.p1); + return result.encode(); + } + } + + ); + assert_eq!(expected.to_string(), gservice_core(input).to_string()); + } + + #[test] + fn command_handlers_core_works() { + let input = quote! { + mod commands { + use super::*; + + struct SomeStruct {} + + fn do_this(p: SomeStruct) {} + } + }; + let expected = quote! { + mod commands { + extern crate parity_scale_codec as commands_scale_codec; + extern crate scale_info as commands_scale_info; + + #[derive(commands_scale_codec::Encode, commands_scale_codec::Decode, commands_scale_info::TypeInfo)] + pub enum Commands { + DoThis(SomeStruct,), + } + + #[derive(commands_scale_codec::Encode, commands_scale_codec::Decode, commands_scale_info::TypeInfo)] + pub enum CommandResponses { + DoThis(()), + } + + use super::*; + + struct SomeStruct {} + + #[cfg(feature = "handlers")] + pub mod handlers { + use super::*; + + pub fn process_commands(request: Commands) -> (CommandResponses, bool) { + match request { + Commands::DoThis(v0) => { + let result: Result<_, _> = do_this(v0); + let is_error = result.is_err(); + (CommandResponses::DoThis(result), is_error) + } + } + } + + fn do_this(p: SomeStruct) {} + } + } + }; + assert_eq!( + expected.to_string(), + command_handlers_core(input).to_string() + ); + } + + #[test] + fn query_handlers_core_works() { + let input = quote! { + pub(crate) mod queries { + fn this() {} + } + }; + let expected = quote! { + pub(crate) mod queries { + extern crate parity_scale_codec as queries_scale_codec; + extern crate scale_info as queries_scale_info; + + #[derive(queries_scale_codec::Encode, queries_scale_codec::Decode, queries_scale_info::TypeInfo)] + pub enum Queries { + This(), + } + + #[derive(queries_scale_codec::Encode, queries_scale_codec::Decode, queries_scale_info::TypeInfo)] + pub enum QueryResponses { + This(()), + } + + #[cfg(feature = "handlers")] + pub mod handlers { + use super::*; + + pub fn process_queries(request: Queries) -> (QueryResponses, bool) { + match request { + Queries::This() => { + let result: Result<_, _> = this(); + let is_error = result.is_err(); + (QueryResponses::This(result), is_error) + } + } + } + + fn this() {} + } + } + }; + assert_eq!(expected.to_string(), query_handlers_core(input).to_string()); + } +} diff --git a/macros/core/src/processors.rs b/macros/core/src/processors.rs new file mode 100644 index 00000000..5544e832 --- /dev/null +++ b/macros/core/src/processors.rs @@ -0,0 +1,645 @@ +// This file is part of Gear. + +// Copyright (C) 2021-2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Supporting functions and structures for the `command_handlers` and `query_handlers` macros. + +use convert_case::{Case, Casing}; +use proc_macro2::TokenStream as TokenStream2; +use proc_macro_error::abort; +use quote::{quote, ToTokens}; +use syn::{self, spanned::Spanned, Ident, Receiver, Signature, Type, TypePath}; + +pub(super) fn gservice(impl_tokens: TokenStream2) -> TokenStream2 { + let handlers_impl = syn::parse2::(impl_tokens.clone()) + .unwrap_or_else(|err| abort!(err.span(), "Failed to parse handlers impl: {}", err)); + + let handler_funcs = handler_funcs(&handlers_impl).collect::>(); + + if handler_funcs.is_empty() { + abort!( + handlers_impl, + "No handlers found. Try either defining one or removing the macro usage" + ); + } + + let service_type = service_type(&handlers_impl.self_ty); + + let mut params_structs = vec![]; + let mut invocation_funcs = vec![]; + let mut invocations = vec![]; + let mut commands_meta_variants = vec![]; + let mut queries_meta_variants = vec![]; + + for handler_func in &handler_funcs { + let handler = Handler::from(handler_func); + let handler_generator = HandlerGenerator::from(service_type, handler); + let invocation_func_ident = handler_generator.invocation_func_ident(); + let invocation_path = invocation_func_ident.to_string().to_case(Case::Pascal); + let invocation_route = format!("{}/", invocation_path); + + params_structs.push(handler_generator.params_struct()); + invocation_funcs.push(handler_generator.invocation_func()); + invocations.push(quote!( + if input.starts_with(#invocation_route.as_bytes()) { + return #invocation_func_ident(service, &input[#invocation_route.as_bytes().len()..]).await; + } + )); + + let params_struct_ident = handler_generator.params_struct_ident(); + let result_type = handler_generator.result_type(); + let invocation_path = Ident::new(&invocation_path, proc_macro2::Span::call_site()); + let handler_meta_variant = quote!( + #invocation_path(#params_struct_ident, #result_type), + ); + if handler_generator.is_query() { + queries_meta_variants.push(handler_meta_variant); + } else { + commands_meta_variants.push(handler_meta_variant); + } + } + + quote!( + #impl_tokens + + #(#[derive(Decode, TypeInfo)] #params_structs)* + + pub mod meta { + use super::*; + + #[derive(TypeInfo)] + pub enum CommandsMeta { + #(#commands_meta_variants)* + } + + #[derive(TypeInfo)] + pub enum QueriesMeta { + #(#queries_meta_variants)* + } + + pub struct ServiceMeta; + + impl sails_service::ServiceMeta for ServiceMeta { + type Commands = CommandsMeta; + type Queries = QueriesMeta; + } + } + + pub mod handlers { + use super::*; + + pub async fn process_request(service: &mut #service_type, mut input: &[u8]) -> Vec { + #(#invocations)* + panic!("Unknown request"); + } + + #(#invocation_funcs)* + } + ) +} + +fn service_type(service_type: &Type) -> &TypePath { + if let syn::Type::Path(type_path) = service_type { + type_path + } else { + abort!( + service_type.span(), + "Failed to parse service type: {}", + service_type.to_token_stream() + ) + } +} + +fn handler_funcs(handlers_impl: &syn::ItemImpl) -> impl Iterator { + handlers_impl.items.iter().filter_map(|item| { + if let syn::ImplItem::Fn(fn_item) = item { + if matches!(fn_item.vis, syn::Visibility::Public(_)) && fn_item.sig.receiver().is_some() + { + return Some(&fn_item.sig); + } + } + None + }) +} + +struct Handler<'a> { + func: &'a Ident, + receiver: &'a Receiver, + params: Vec<(&'a Ident, &'a Type)>, + result: &'a Type, + is_async: bool, + is_query: bool, +} + +impl<'a> Handler<'a> { + fn from(handler_signature: &'a Signature) -> Self { + let func = &handler_signature.ident; + let receiver = handler_signature.receiver().unwrap_or_else(|| { + abort!( + handler_signature.span(), + "Handler must be a public method of service" + ) + }); + let params = params(handler_signature).collect(); + let result = result(handler_signature); + Self { + func, + receiver, + params, + result, + is_async: handler_signature.asyncness.is_some(), + is_query: receiver.mutability.is_none(), + } + } +} + +fn params(handler_signature: &syn::Signature) -> impl Iterator { + handler_signature.inputs.iter().skip(1).map(|arg| { + if let syn::FnArg::Typed(arg) = arg { + let arg_ident = if let syn::Pat::Ident(arg_ident) = arg.pat.as_ref() { + &arg_ident.ident + } else { + abort!(arg.span(), "Unnamed arguments are not supported"); + }; + (arg_ident, arg.ty.as_ref()) + } else { + abort!(arg.span(), "Arguments of the Self type are not supported"); + } + }) +} + +fn result(handler_signature: &Signature) -> &Type { + if let syn::ReturnType::Type(_, ty) = &handler_signature.output { + ty.as_ref() + } else { + abort!( + handler_signature.output.span(), + "Failed to parse return type" + ); + } +} + +struct HandlerGenerator<'a> { + service_type: &'a TypePath, + handler: Handler<'a>, +} + +impl<'a> HandlerGenerator<'a> { + fn from(service_type: &'a TypePath, handler: Handler<'a>) -> Self { + Self { + service_type, + handler, + } + } + + fn params_struct_ident(&self) -> Ident { + syn::Ident::new( + &format!( + "{}Params", + self.handler.func.to_string().to_case(Case::Pascal) + ), + proc_macro2::Span::call_site(), + ) + } + + fn result_type(&self) -> Type { + self.handler.result.clone() + } + + fn handler_func_ident(&self) -> Ident { + self.handler.func.clone() + } + + fn invocation_func_ident(&self) -> Ident { + self.handler_func_ident() + } + + fn is_query(&self) -> bool { + self.handler.is_query + } + + fn params_struct(&self) -> TokenStream2 { + let params_struct_ident = self.params_struct_ident(); + let params_struct_members = self.handler.params.iter().map(|item| { + let arg_ident = item.0; + let arg_type = item.1; + quote!(#arg_ident: #arg_type) + }); + + quote!( + pub struct #params_struct_ident { + #(#params_struct_members),* + } + ) + } + + fn invocation_func(&self) -> TokenStream2 { + let invocation_func_ident = self.invocation_func_ident(); + let service_ref = self.handler.receiver.reference.as_ref().map(|r| r.0); + let service_lifetime = self.handler.receiver.reference.as_ref().map(|r| &r.1); + let service_mut = self.handler.receiver.mutability; + let service_type = self.service_type; + let params_struct_ident = self.params_struct_ident(); + let handler_func_ident = self.handler_func_ident(); + let handler_func_params = self.handler.params.iter().map(|item| { + let param_ident = item.0; + quote!(request.#param_ident) + }); + + let await_token = if self.handler.is_async { + quote!(.await) + } else { + quote!() + }; + + quote!( + async fn #invocation_func_ident(service: #service_ref #service_lifetime #service_mut #service_type, mut input: &[u8]) -> Vec { + let request = #params_struct_ident::decode(&mut input).expect("Failed to decode request"); + let result = service.#handler_func_ident(#(#handler_func_params),*)#await_token; + return result.encode(); + } + ) + } +} + +/// Generates a processor function with requests it can process and responses it can return. +/// The processor function essentially acts as a router for the requests on their way to the +/// handlers. +pub(super) fn generate( + mod_tokens: TokenStream2, + request_enum_name: &str, + response_enum_name: &str, +) -> TokenStream2 { + let handlers_mod = syn::parse2::(mod_tokens) + .unwrap_or_else(|err| abort!(err.span(), "Failed to parse handlers module: {}", err)); + let handlers_mod_ident = &handlers_mod.ident; + let handlers_mod_visibility = &handlers_mod.vis; + let (handlers_mod_funcs, handlers_mod_non_funcs) = split_handlers_mod(&handlers_mod); + + let request_enum_ident = syn::Ident::new(request_enum_name, proc_macro2::Span::call_site()); + let response_enum_ident = syn::Ident::new(response_enum_name, proc_macro2::Span::call_site()); + let function_ident = get_processor_function_ident(request_enum_name); + + if handlers_mod_funcs.is_empty() { + abort!( + handlers_mod, + "No handlers found. Please either define one or remove the macro usage" + ); + } + + let processor_tokens = ProcessorTokens::from( + &handlers_mod_funcs, + &request_enum_ident, + &response_enum_ident, + &function_ident, + ); + + let request_enum = processor_tokens.request_enum; + let response_enum = processor_tokens.response_enum; + let function = processor_tokens.function; + + let scale_codec_crate_ident = get_scale_codec_crate_ident(request_enum_name); + let scale_info_crate_ident = get_scale_info_crate_ident(request_enum_name); + + quote!( + #handlers_mod_visibility mod #handlers_mod_ident { + extern crate parity_scale_codec as #scale_codec_crate_ident; + extern crate scale_info as #scale_info_crate_ident; + + #[derive(#scale_codec_crate_ident::Encode, #scale_codec_crate_ident::Decode, #scale_info_crate_ident::TypeInfo)] + #request_enum + + #[derive(#scale_codec_crate_ident::Encode, #scale_codec_crate_ident::Decode, #scale_info_crate_ident::TypeInfo)] + #response_enum + + #(#handlers_mod_non_funcs)* + + #[cfg(feature = "handlers")] // TODO: Make this configurable? + pub mod handlers { + use super::*; + + #function + + #(#handlers_mod_funcs)* + } + } + ) +} + +struct ProcessorTokens { + request_enum: TokenStream2, + response_enum: TokenStream2, + function: TokenStream2, +} + +impl ProcessorTokens { + fn from( + handlers_mod_funcs: &[&syn::ItemFn], + request_enum_ident: &syn::Ident, + response_enum_ident: &syn::Ident, + function_ident: &syn::Ident, + ) -> ProcessorTokens { + let handlers_signatures = handlers_mod_funcs.iter().map(|item_fn| &item_fn.sig); + + let handlers_tokens = handlers_signatures + .map(|handler_signature| { + HandlerTokens::from(request_enum_ident, response_enum_ident, handler_signature) + }) + .collect::>(); + + let request_enum_variants = handlers_tokens + .iter() + .map(|handler_tokens| &handler_tokens.request_enum_variant); + + let response_enum_variants = handlers_tokens + .iter() + .map(|handler_tokens| &handler_tokens.response_enum_variant); + + let call_match_arms = handlers_tokens + .iter() + .map(|handler_tokens| &handler_tokens.call_match_arm); + + let has_async_handler = handlers_tokens + .iter() + .any(|handler_tokens| handler_tokens.is_async); + + let fn_signature = if has_async_handler { + quote!(async fn #function_ident(request: #request_enum_ident) -> (#response_enum_ident, bool)) + } else { + quote!(fn #function_ident(request: #request_enum_ident) -> (#response_enum_ident, bool)) + }; + + ProcessorTokens { + request_enum: quote!( + pub enum #request_enum_ident { + #(#request_enum_variants)* + } + ), + response_enum: quote!( + pub enum #response_enum_ident { + #(#response_enum_variants)* + } + ), + function: quote!( + pub #fn_signature { + match request { + #(#call_match_arms)* + } + } + ), + } + } +} + +struct HandlerTokens { + request_enum_variant: TokenStream2, + response_enum_variant: TokenStream2, + call_match_arm: TokenStream2, + is_async: bool, +} + +impl HandlerTokens { + fn from( + request_enum_ident: &syn::Ident, + response_enum_ident: &syn::Ident, + handler_signature: &syn::Signature, + ) -> Self { + let enum_variant_name = syn::Ident::new( + &handler_signature.ident.to_string().to_case(Case::Pascal), + proc_macro2::Span::call_site(), + ); + + let response_enum_variant = { + let response_type = Self::response_type(handler_signature); + quote!( + #enum_variant_name(#response_type), + ) + }; + + let (arg_types, arg_types_count) = Self::arg_types(handler_signature); + + let request_enum_variant = quote!( + #enum_variant_name(#(#arg_types,)*), + ); + + let call_match_arm = { + let call_param_idents = (0..arg_types_count) + .map(|idx| syn::Ident::new(&format!("v{}", idx), proc_macro2::Span::call_site())) + .collect::>(); + let call_ident = &handler_signature.ident; + let call = if handler_signature.asyncness.is_some() { + quote!(#call_ident(#(#call_param_idents),*).await) + } else { + quote!(#call_ident(#(#call_param_idents),*)) + }; + quote!( + #request_enum_ident::#enum_variant_name(#(#call_param_idents),*) => { + let result: Result<_, _> = #call; + let is_error = result.is_err(); + (#response_enum_ident::#enum_variant_name(result), is_error) + } + ) + }; + + Self { + request_enum_variant, + response_enum_variant, + call_match_arm, + is_async: handler_signature.asyncness.is_some(), + } + } + + fn arg_types( + handler_signature: &syn::Signature, + ) -> (impl Iterator + '_, usize) { + ( + handler_signature.inputs.iter().map(Self::arg_type), + handler_signature.inputs.len(), + ) + } + + fn response_type(handler_signature: &syn::Signature) -> syn::Type { + Self::return_type(&handler_signature.output) + } + + fn arg_type(arg: &syn::FnArg) -> &syn::Type { + if let syn::FnArg::Typed(arg) = arg { + arg.ty.as_ref() + } else { + abort!(arg.span(), "Arguments of the Self type are not supported"); + } + } + + fn return_type(output: &syn::ReturnType) -> syn::Type { + if let syn::ReturnType::Type(_, ty) = output { + ty.as_ref().clone() + } else { + syn::parse2::(quote!(())) + .unwrap_or_else(|err| abort!(err.span(), "Failed to parse return type: {}", err)) + } + } +} + +fn split_handlers_mod(handlers_mod: &syn::ItemMod) -> (Vec<&syn::ItemFn>, Vec<&syn::Item>) { + let (handlers_mod_funcs, handlers_mod_non_funcs): (Vec<&syn::Item>, Vec<&syn::Item>) = + handlers_mod + .content + .as_ref() + .unwrap_or_else(|| abort!(handlers_mod, "Handlers module must be inline")) + .1 + .iter() + .partition(|item| matches!(item, syn::Item::Fn(_))); + let handlers_mod_funcs = handlers_mod_funcs + .iter() + .filter_map(|item_fn| match item_fn { + syn::Item::Fn(item_fn) => Some(item_fn), + _ => None, + }) + .collect(); + (handlers_mod_funcs, handlers_mod_non_funcs) +} + +fn get_scale_codec_crate_ident(prefix: &str) -> syn::Ident { + syn::Ident::new( + format!("{}_scale_codec", prefix.to_case(Case::Snake)).as_str(), + proc_macro2::Span::call_site(), + ) +} + +fn get_scale_info_crate_ident(prefix: &str) -> syn::Ident { + syn::Ident::new( + format!("{}_scale_info", prefix.to_case(Case::Snake)).as_str(), + proc_macro2::Span::call_site(), + ) +} + +fn get_processor_function_ident(suffix: &str) -> syn::Ident { + syn::Ident::new( + format!("process_{}", suffix.to_case(Case::Snake)).as_str(), + proc_macro2::Span::call_site(), + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn handler_tokens_work_for_func_with_default_return_type() { + let signature = syn::parse2::(quote! { + fn do_this(p1: u32, p2: String) + }) + .unwrap(); + + let handler_tokens = HandlerTokens::from( + &syn::Ident::new("Commands", proc_macro2::Span::call_site()), + &syn::Ident::new("CommandResponses", proc_macro2::Span::call_site()), + &signature, + ); + + assert_eq!( + quote!(DoThis(u32, String,),).to_string(), + handler_tokens.request_enum_variant.to_string() + ); + assert_eq!( + quote!(DoThis(()),).to_string(), + handler_tokens.response_enum_variant.to_string() + ); + assert_eq!( + quote!( + Commands::DoThis(v0, v1) => { + let result: Result<_, _> = do_this(v0, v1); + let is_error = result.is_err(); + (CommandResponses::DoThis(result), is_error) + } + ) + .to_string(), + handler_tokens.call_match_arm.to_string() + ); + assert!(!handler_tokens.is_async); + } + + #[test] + fn handler_tokens_work_for_func_without_args() { + let signature = syn::parse2::(quote! { + fn do_this() + }) + .unwrap(); + + let handler_tokens = HandlerTokens::from( + &syn::Ident::new("Commands", proc_macro2::Span::call_site()), + &syn::Ident::new("CommandResponses", proc_macro2::Span::call_site()), + &signature, + ); + + assert_eq!( + quote!(DoThis(),).to_string(), + handler_tokens.request_enum_variant.to_string() + ); + assert_eq!( + quote!(DoThis(()),).to_string(), + handler_tokens.response_enum_variant.to_string() + ); + assert_eq!( + quote!( + Commands::DoThis() => { + let result: Result<_, _> = do_this(); + let is_error = result.is_err(); + (CommandResponses::DoThis(result), is_error) + } + ) + .to_string(), + handler_tokens.call_match_arm.to_string() + ); + assert!(!handler_tokens.is_async); + } + + #[test] + fn handler_tokens_work_for_async_func() { + let signature = syn::parse2::(quote! { + async fn do_this(p1: (u32, u8)) + }) + .unwrap(); + + let handler_tokens = HandlerTokens::from( + &syn::Ident::new("Commands", proc_macro2::Span::call_site()), + &syn::Ident::new("CommandResponses", proc_macro2::Span::call_site()), + &signature, + ); + + assert_eq!( + quote!(DoThis((u32, u8),),).to_string(), + handler_tokens.request_enum_variant.to_string() + ); + assert_eq!( + quote!(DoThis(()),).to_string(), + handler_tokens.response_enum_variant.to_string() + ); + assert_eq!( + quote!( + Commands::DoThis(v0) => { + let result: Result<_, _> = do_this(v0).await; + let is_error = result.is_err(); + (CommandResponses::DoThis(result), is_error) + } + ) + .to_string(), + handler_tokens.call_match_arm.to_string() + ); + assert!(handler_tokens.is_async); + } +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs new file mode 100644 index 00000000..f7821da2 --- /dev/null +++ b/macros/src/lib.rs @@ -0,0 +1,41 @@ +// This file is part of Gear. + +// Copyright (C) 2021-2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Procedural macros for the `gprogram-framework`. + +use proc_macro::TokenStream; +use proc_macro_error::proc_macro_error; +use sails_macros_core::{command_handlers_core, gservice_core, query_handlers_core}; + +#[proc_macro_error] +#[proc_macro_attribute] +pub fn command_handlers(_attrs: TokenStream, mod_tokens: TokenStream) -> TokenStream { + command_handlers_core(mod_tokens.into()).into() +} + +#[proc_macro_error] +#[proc_macro_attribute] +pub fn query_handlers(_attrs: TokenStream, mod_tokens: TokenStream) -> TokenStream { + query_handlers_core(mod_tokens.into()).into() +} + +#[proc_macro_error] +#[proc_macro_attribute] +pub fn gservice(_attrs: TokenStream, impl_tokens: TokenStream) -> TokenStream { + gservice_core(impl_tokens.into()).into() +} diff --git a/macros/tests/integration_tests.rs b/macros/tests/integration_tests.rs new file mode 100644 index 00000000..1cdaac42 --- /dev/null +++ b/macros/tests/integration_tests.rs @@ -0,0 +1,55 @@ +// This file is part of Gear. + +// Copyright (C) 2021-2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Integration tests for functionality provided by the `gprogram-framework-macros` crate. + +#[test] +fn command_handlers_work() { + let t = trybuild::TestCases::new(); + t.pass("tests/ui/command_handlers_work.rs"); +} + +#[test] +fn query_handlers_work() { + let t = trybuild::TestCases::new(); + t.pass("tests/ui/query_handlers_work.rs"); +} + +#[test] +fn no_command_handlers() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/no_command_handlers.rs"); +} + +#[test] +fn no_query_handlers() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/no_query_handlers.rs"); +} + +#[test] +fn command_handler_returns_result() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/command_handler_returns_result.rs"); +} + +#[test] +fn query_handler_returns_result() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/query_handler_returns_result.rs"); +} diff --git a/macros/tests/ui/command_handler_returns_result.rs b/macros/tests/ui/command_handler_returns_result.rs new file mode 100644 index 00000000..64a41d42 --- /dev/null +++ b/macros/tests/ui/command_handler_returns_result.rs @@ -0,0 +1,10 @@ +use sails_macros::command_handlers; + +#[command_handlers] +mod commands { + fn do_this(value: u32) -> u32 { + value + } +} + +fn main() {} diff --git a/macros/tests/ui/command_handler_returns_result.stderr b/macros/tests/ui/command_handler_returns_result.stderr new file mode 100644 index 00000000..eb4f7ffa --- /dev/null +++ b/macros/tests/ui/command_handler_returns_result.stderr @@ -0,0 +1,30 @@ +error[E0308]: mismatched types + --> tests/ui/command_handler_returns_result.rs:3:1 + | +3 | #[command_handlers] + | ^^^^^^^^^^^^^^^^^^^ + | | + | expected `Result<_, _>`, found `u32` + | expected due to this + | + = note: expected enum `Result<_, _>` + found type `u32` + = note: this error originates in the attribute macro `command_handlers` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0308]: mismatched types + --> tests/ui/command_handler_returns_result.rs:3:1 + | +3 | #[command_handlers] + | ^^^^^^^^^^^^^^^^^^^ + | | + | expected `u32`, found `Result<_, _>` + | arguments to this enum variant are incorrect + | + = note: expected type `u32` + found enum `Result<_, _>` +note: tuple variant defined here + --> tests/ui/command_handler_returns_result.rs:3:1 + | +3 | #[command_handlers] + | ^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the attribute macro `command_handlers` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/macros/tests/ui/command_handlers_work.rs b/macros/tests/ui/command_handlers_work.rs new file mode 100644 index 00000000..809ef815 --- /dev/null +++ b/macros/tests/ui/command_handlers_work.rs @@ -0,0 +1,33 @@ +use parity_scale_codec::{Decode, Encode}; +use sails_macros::command_handlers; +use scale_info::TypeInfo; + +#[derive(Debug, Clone, Encode, Decode, TypeInfo)] +pub struct DoThatParam { + p1: u32, + p2: String, +} + +#[command_handlers] +mod commands { + use super::*; + + fn do_this(p1: u32, p2: String) -> Result<(String, u32), u32> { + Ok((p2, p1)) + } + + async fn do_that(p1: DoThatParam) -> Result<(u32, String), String> { + Ok((p1.p1, p1.p2)) + } +} + +#[tokio::main] +async fn main() { + let do_this_cmd = commands::Commands::DoThis(1, "2".into()); + let _do_that_cmd = commands::Commands::DoThat(DoThatParam { + p1: 1, + p2: "2".into(), + }); + let (_response, _is_error): (commands::CommandResponses, bool) = + commands::handlers::process_commands(do_this_cmd).await; +} diff --git a/macros/tests/ui/no_command_handlers.rs b/macros/tests/ui/no_command_handlers.rs new file mode 100644 index 00000000..b7ac30a2 --- /dev/null +++ b/macros/tests/ui/no_command_handlers.rs @@ -0,0 +1,6 @@ +use sails_macros::command_handlers; + +#[command_handlers] +mod commands {} + +fn main() {} diff --git a/macros/tests/ui/no_command_handlers.stderr b/macros/tests/ui/no_command_handlers.stderr new file mode 100644 index 00000000..4b0fa0a4 --- /dev/null +++ b/macros/tests/ui/no_command_handlers.stderr @@ -0,0 +1,5 @@ +error: No handlers found. Please either define one or remove the macro usage + --> tests/ui/no_command_handlers.rs:4:1 + | +4 | mod commands {} + | ^^^^^^^^^^^^^^^ diff --git a/macros/tests/ui/no_query_handlers.rs b/macros/tests/ui/no_query_handlers.rs new file mode 100644 index 00000000..68099fd9 --- /dev/null +++ b/macros/tests/ui/no_query_handlers.rs @@ -0,0 +1,6 @@ +use sails_macros::query_handlers; + +#[query_handlers] +mod queries {} + +fn main() {} diff --git a/macros/tests/ui/no_query_handlers.stderr b/macros/tests/ui/no_query_handlers.stderr new file mode 100644 index 00000000..11261e86 --- /dev/null +++ b/macros/tests/ui/no_query_handlers.stderr @@ -0,0 +1,5 @@ +error: No handlers found. Please either define one or remove the macro usage + --> tests/ui/no_query_handlers.rs:4:1 + | +4 | mod queries {} + | ^^^^^^^^^^^^^^ diff --git a/macros/tests/ui/query_handler_returns_result.rs b/macros/tests/ui/query_handler_returns_result.rs new file mode 100644 index 00000000..22351cd8 --- /dev/null +++ b/macros/tests/ui/query_handler_returns_result.rs @@ -0,0 +1,10 @@ +use sails_macros::query_handlers; + +#[query_handlers] +mod queries { + fn this(value: u32) -> u32 { + value + } +} + +fn main() {} diff --git a/macros/tests/ui/query_handler_returns_result.stderr b/macros/tests/ui/query_handler_returns_result.stderr new file mode 100644 index 00000000..4bb6dde3 --- /dev/null +++ b/macros/tests/ui/query_handler_returns_result.stderr @@ -0,0 +1,30 @@ +error[E0308]: mismatched types + --> tests/ui/query_handler_returns_result.rs:3:1 + | +3 | #[query_handlers] + | ^^^^^^^^^^^^^^^^^ + | | + | expected `Result<_, _>`, found `u32` + | expected due to this + | + = note: expected enum `Result<_, _>` + found type `u32` + = note: this error originates in the attribute macro `query_handlers` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0308]: mismatched types + --> tests/ui/query_handler_returns_result.rs:3:1 + | +3 | #[query_handlers] + | ^^^^^^^^^^^^^^^^^ + | | + | expected `u32`, found `Result<_, _>` + | arguments to this enum variant are incorrect + | + = note: expected type `u32` + found enum `Result<_, _>` +note: tuple variant defined here + --> tests/ui/query_handler_returns_result.rs:3:1 + | +3 | #[query_handlers] + | ^^^^^^^^^^^^^^^^^ + = note: this error originates in the attribute macro `query_handlers` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/macros/tests/ui/query_handlers_work.rs b/macros/tests/ui/query_handlers_work.rs new file mode 100644 index 00000000..268088da --- /dev/null +++ b/macros/tests/ui/query_handlers_work.rs @@ -0,0 +1,32 @@ +use parity_scale_codec::{Decode, Encode}; +use sails_macros::query_handlers; +use scale_info::TypeInfo; + +#[derive(Debug, Clone, Encode, Decode, TypeInfo)] +pub struct DoThatParam { + p1: u32, + p2: String, +} + +#[query_handlers] +mod queries { + use super::*; + + fn this(p1: u32, p2: String) -> Result<(String, u32), u32> { + Ok((p2, p1)) + } + + fn that(p1: DoThatParam) -> Result<(u32, String), String> { + Ok((p1.p1, p1.p2)) + } +} + +fn main() { + let this_query = queries::Queries::This(1, "2".into()); + let _that_query = queries::Queries::That(DoThatParam { + p1: 1, + p2: "2".into(), + }); + let (_response, _is_error): (queries::QueryResponses, bool) = + queries::handlers::process_queries(this_query); +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..c390ac4a --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "nightly-2023-10-14" +targets = ["wasm32-unknown-unknown"] +profile = "minimal" +components = ["rustfmt", "clippy"] diff --git a/service/Cargo.toml b/service/Cargo.toml new file mode 100644 index 00000000..ad08e9be --- /dev/null +++ b/service/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "sails-service" +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +async-trait.workspace = true +hashbrown.workspace = true +parity-scale-codec.workspace = true +scale-info.workspace = true diff --git a/service/src/lib.rs b/service/src/lib.rs new file mode 100644 index 00000000..1b368e86 --- /dev/null +++ b/service/src/lib.rs @@ -0,0 +1,129 @@ +// This file is part of Gear. + +// Copyright (C) 2021-2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Traits and structs for a simple service comprised of command and query dispatchers. + +#![no_std] + +extern crate alloc; + +use alloc::{borrow::ToOwned, boxed::Box, format, string::String, vec::Vec}; +use async_trait::async_trait; +use hashbrown::HashMap; +pub use meta::{BoxedFuture, CommandProcessorMeta, QueryProcessorMeta, ServiceMeta}; +use parity_scale_codec::{Decode, Encode}; + +mod meta; + +#[async_trait] +pub trait Service { + // TODO: Make something up for error handling (some sort of Result) + async fn process_command(&self, input: &[u8]) -> (Vec, bool); + + // TODO: Make something up for error handling (some sort of Result) + fn process_query(&self, input: &[u8]) -> Vec; +} + +pub struct SimpleService { + process_command: C::ProcessFn, + process_query: Q::ProcessFn, +} + +impl SimpleService { + pub const fn new(process_command: C::ProcessFn, process_query: Q::ProcessFn) -> Self { + Self { + process_command, + process_query, + } + } +} + +#[async_trait] +impl Service for SimpleService { + async fn process_command(&self, mut input: &[u8]) -> (Vec, bool) { + let request = C::Request::decode(&mut input).expect("Failed to decode request"); + let (response, is_error) = (self.process_command)(request).await; + (response.encode(), is_error) + } + + fn process_query(&self, mut input: &[u8]) -> Vec { + let request = Q::Request::decode(&mut input).expect("Failed to decode request"); + let (response, _) = (self.process_query)(request); + response.encode() + } +} + +pub struct CompositeService { + // TODO: It might be cheaper and more progmatic to use a simple Vec<(String, Box)> + // as there is no expectation of a large number of services + services: HashMap>, +} + +impl CompositeService { + pub fn new<'a>(services: impl IntoIterator)>) -> Self { + let services = services + .into_iter() + .try_fold(HashMap::new(), |mut services, service| { + let service_route = Self::to_service_route(service.0); + services + .try_insert(service_route, service.1) + .map_err(|e| format!("Service with name {} already exists", e.entry.key()))?; + Ok::<_, String>(services) + }) + .expect("Duplicate service name"); + Self { services } + } + + fn to_service_route(name: &str) -> String { + if name.is_empty() { + panic!("Service name cannot be empty"); + } + if name.contains('/') { + panic!("Service name cannot contain '/'"); + } + let mut service_route = name.to_owned(); + service_route.push('/'); + service_route + } + + fn select_service_by_route<'a>(&'a self, input: &'a [u8]) -> (&(dyn Service + Sync), &[u8]) { + self.services + .iter() + .find_map(|(service_route, service)| { + if input.starts_with(service_route.as_bytes()) { + Some((service.as_ref(), &input[service_route.len()..])) + } else { + None + } + }) + .expect("Service not found by route") + } +} + +#[async_trait] +impl Service for CompositeService { + async fn process_command(&self, input: &[u8]) -> (Vec, bool) { + let (selected_service, input) = self.select_service_by_route(input); + selected_service.process_command(input).await + } + + fn process_query(&self, input: &[u8]) -> Vec { + let (selected_service, input) = self.select_service_by_route(input); + selected_service.process_query(input) + } +} diff --git a/service/src/meta.rs b/service/src/meta.rs new file mode 100644 index 00000000..e948f37f --- /dev/null +++ b/service/src/meta.rs @@ -0,0 +1,59 @@ +// This file is part of Gear. + +// Copyright (C) 2021-2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Traits for describing request processors. + +extern crate alloc; + +use alloc::boxed::Box; +use core::{future::Future, pin::Pin}; +use parity_scale_codec::{Decode, Encode}; +use scale_info::StaticTypeInfo; + +pub type BoxedFuture = Pin + Send>>; + +pub trait CommandProcessorMeta { + type Request: StaticTypeInfo + Decode; + type Response: StaticTypeInfo + Encode; + // TODO: Make something up for error handling (some sort of Result) + type ProcessFn: Fn(Self::Request) -> BoxedFuture<(Self::Response, bool)> + Sync; +} + +impl CommandProcessorMeta for () { + type Request = (); + type Response = (); + type ProcessFn = fn(Self::Request) -> BoxedFuture<(Self::Response, bool)>; +} + +pub trait QueryProcessorMeta { + type Request: StaticTypeInfo + Decode; + type Response: StaticTypeInfo + Encode; + // TODO: Make something up for error handling (some sort of Result) + type ProcessFn: Fn(Self::Request) -> (Self::Response, bool) + Sync; +} + +impl QueryProcessorMeta for () { + type Request = (); + type Response = (); + type ProcessFn = fn(Self::Request) -> (Self::Response, bool); +} + +pub trait ServiceMeta { + type Commands: StaticTypeInfo; + type Queries: StaticTypeInfo; +}