From ea3c3e9f51feb78617bec401e4fbeb28284628c2 Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Fri, 3 May 2024 10:41:06 -0700 Subject: [PATCH] Candid type selector (#544) * replace dhall with toml * checkpoint * checkpoint * checkpoint * checkpoint * checkpoint * checkpoint * checkpoint * random works * test * fix * fix * Refactor Rust bindgen with the new config (#546) * checkpoint * checkpoint * connect with didc and test * checkpoint * vis * vis * checkpoint * fix merging semantics * checkpoint * fix * fix * fix root config fallback * checkpoint * add unmatched config * fix attr * handlebar * update test, diff is only space and newline * forget to commit test * move pp_actor inside impl * fix * refactor * init args * add test * add_config * clippy * changelog and bump version --- Cargo.lock | 778 +++++--------- Changelog.md | 20 +- rust/candid/Cargo.toml | 2 +- rust/candid/src/ser.rs | 8 +- rust/candid/src/types/mod.rs | 8 +- rust/candid_parser/Cargo.toml | 11 +- rust/candid_parser/src/bindings/rust.rs | 970 +++++++++++------- .../candid_parser/src/bindings/rust_agent.hbs | 22 + rust/candid_parser/src/bindings/rust_call.hbs | 21 + rust/candid_parser/src/configs.rs | 439 +++++++- rust/candid_parser/src/error.rs | 8 +- rust/candid_parser/src/lib.rs | 2 - rust/candid_parser/src/random.rs | 195 ++-- rust/candid_parser/tests/assets/example.toml | 11 + rust/candid_parser/tests/assets/ok/actor.rs | 1 + rust/candid_parser/tests/assets/ok/class.rs | 1 + rust/candid_parser/tests/assets/ok/comment.rs | 1 + rust/candid_parser/tests/assets/ok/cyclic.rs | 13 +- rust/candid_parser/tests/assets/ok/empty.rs | 5 +- rust/candid_parser/tests/assets/ok/escape.rs | 1 + rust/candid_parser/tests/assets/ok/example.rs | 164 ++- .../candid_parser/tests/assets/ok/fieldnat.rs | 15 +- rust/candid_parser/tests/assets/ok/keyword.rs | 21 +- .../tests/assets/ok/management.rs | 87 +- .../tests/assets/ok/recursion.rs | 8 +- .../tests/assets/ok/recursive_class.rs | 2 + rust/candid_parser/tests/assets/ok/service.rs | 1 + rust/candid_parser/tests/assets/ok/unicode.rs | 8 +- rust/candid_parser/tests/parse_type.rs | 26 +- tools/didc/Cargo.toml | 2 +- tools/didc/random.dhall | 30 - tools/didc/random.toml | 24 + tools/didc/src/main.rs | 68 +- 33 files changed, 1597 insertions(+), 1376 deletions(-) create mode 100644 rust/candid_parser/src/bindings/rust_agent.hbs create mode 100644 rust/candid_parser/src/bindings/rust_call.hbs create mode 100644 rust/candid_parser/tests/assets/example.toml delete mode 100644 tools/didc/random.dhall create mode 100644 tools/didc/random.toml diff --git a/Cargo.lock b/Cargo.lock index f21af487..a076a7af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,98 +2,59 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "abnf" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33741baa462d86e43fdec5e8ffca7c6ac82847ad06cbfb382c1bdbf527de9e6b" -dependencies = [ - "abnf-core", - "nom", -] - -[[package]] -name = "abnf-core" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c44e09c43ae1c368fb91a03a566472d0087c26cf7e1b9e8e289c14ede681dd7d" -dependencies = [ - "nom", -] - -[[package]] -name = "abnf_to_pest" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "939d59666dd9a7964a3a5312b9d24c9c107630752ee64f2dd5038189a23fe331" -dependencies = [ - "abnf", - "indexmap 1.9.3", - "itertools 0.10.5", - "pretty 0.11.3", -] - [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] -[[package]] -name = "annotate-snippets" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccaf7e9dfbb6ab22c82e473cd1a8a7bd313c19a5b7e40970f3d89ef5a5c9e81e" -dependencies = [ - "unicode-width", -] - [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys", @@ -101,9 +62,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" [[package]] name = "arbitrary" @@ -128,9 +89,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "beef" @@ -165,8 +126,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d9672209df1714ee804b1f4d4f68c8eb2a90b1f7a07acf472f88ce198ef1fed" dependencies = [ "either", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] @@ -187,24 +148,9 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[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.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" - -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array", -] +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "block-buffer" @@ -234,7 +180,7 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "candid" -version = "0.10.7" +version = "0.10.8" dependencies = [ "anyhow", "bincode", @@ -247,7 +193,7 @@ dependencies = [ "num-bigint", "num-traits", "paste", - "pretty 0.12.3", + "pretty", "rand", "serde", "serde_bytes", @@ -262,14 +208,14 @@ name = "candid_derive" version = "0.6.6" dependencies = [ "lazy_static", - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.52", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] name = "candid_parser" -version = "0.1.4" +version = "0.2.0-beta.0" dependencies = [ "anyhow", "arbitrary", @@ -281,25 +227,26 @@ dependencies = [ "dialoguer", "fake", "goldenfile", + "handlebars", "hex", "lalrpop", "lalrpop-util", "logos", "num-bigint", "num-traits", - "pretty 0.12.3", + "pretty", "rand", "serde", - "serde_dhall", "test-generator", "thiserror", + "toml", ] [[package]] name = "cc" -version = "1.0.90" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" [[package]] name = "cfg-if" @@ -307,11 +254,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "clap" -version = "4.5.2" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -331,14 +284,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ "heck", - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.52", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -359,9 +312,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "console" @@ -421,9 +374,9 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.4.2" +version = "3.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b" +checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345" dependencies = [ "nix", "windows-sys", @@ -431,51 +384,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "deunicode" -version = "1.4.3" +version = "1.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6e854126756c496b8c81dec88f9a706b15b875c5849d4097a3854476b9fdf94" - -[[package]] -name = "dhall" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9093ee48621ca9db16cd4948c7acf24a8ecc9af41cc9e226e39ea719df06d8b5" -dependencies = [ - "abnf_to_pest", - "annotate-snippets", - "elsa", - "hex", - "home", - "itertools 0.9.0", - "lazy_static", - "once_cell", - "percent-encoding", - "pest", - "pest_consume", - "pest_generator", - "quote 1.0.35", - "serde", - "serde_cbor", - "sha2 0.9.9", - "url", -] - -[[package]] -name = "dhall_proc_macros" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df7c81d16870879ef530b07cef32bc6088f98937ab4168106cc8e382a05146bf" -dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 1.0.109", -] +checksum = "322ef0094744e63628e6f0eb2295517f79276a5b342a4c2ff3042566ca181d4e" [[package]] name = "dialoguer" @@ -491,7 +408,7 @@ dependencies = [ [[package]] name = "didc" -version = "0.3.7" +version = "0.4.0" dependencies = [ "anyhow", "candid_parser", @@ -501,22 +418,13 @@ dependencies = [ "rand", ] -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] - [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.4", + "block-buffer", "crypto-common", ] @@ -541,26 +449,11 @@ dependencies = [ "winapi", ] -[[package]] -name = "doc-comment" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" - [[package]] name = "either" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" - -[[package]] -name = "elsa" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98e71ae4df57d214182a2e5cb90230c0192c6ddfcaa05c36453d46a54713e10" -dependencies = [ - "stable_deref_trait", -] +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" [[package]] name = "ena" @@ -605,9 +498,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fixedbitset" @@ -621,15 +514,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -642,9 +526,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "libc", @@ -659,9 +543,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "goldenfile" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a67453a3b358bd8213aedafd4feed75eecab9fb04bed26ba6fdf94694be560" +checksum = "a0d5c44265baec620ea19c97b4ce9f068e28f8c3d7faccc483f02968b5e3c587" dependencies = [ "scopeguard", "similar-asserts", @@ -676,22 +560,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" [[package]] -name = "hashbrown" -version = "0.12.3" +name = "handlebars" +version = "5.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "d08485b96a0e6393e9e4d1b8d48cf74ad6c063cd905eb33f42c1ce3f0377539b" +dependencies = [ + "log", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror", +] [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" @@ -699,15 +591,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "home" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" -dependencies = [ - "windows-sys", -] - [[package]] name = "ic_principal" version = "0.1.1" @@ -720,20 +603,10 @@ dependencies = [ "serde_cbor", "serde_json", "serde_test", - "sha2 0.10.8", + "sha2", "thiserror", ] -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "impls" version = "1.0.3" @@ -742,41 +615,19 @@ checksum = "7a46645bbd70538861a90d0f26c31537cdf1e44aae99a794fb75a664b70951bc" [[package]] name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - -[[package]] -name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown", ] [[package]] -name = "itertools" -version = "0.9.0" +name = "is_terminal_polyfill" +version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" [[package]] name = "itertools" @@ -789,9 +640,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "lalrpop" @@ -802,12 +653,12 @@ dependencies = [ "ascii-canvas", "bit-set", "ena", - "itertools 0.11.0", + "itertools", "lalrpop-util", "petgraph", "pico-args", "regex", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", "string_cache", "term", "tiny-keccak", @@ -838,19 +689,18 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libredox" -version = "0.0.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.4.2", + "bitflags", "libc", - "redox_syscall", ] [[package]] @@ -861,9 +711,9 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -892,10 +742,10 @@ checksum = "dc487311295e0002e452025d6b580b77bb17286de87b57138f3b5db711cded68" dependencies = [ "beef", "fnv", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "regex-syntax 0.6.29", - "syn 2.0.52", + "syn 2.0.60", ] [[package]] @@ -909,43 +759,28 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "new_debug_unreachable" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nix" -version = "0.27.1" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.4.2", + "bitflags", "cfg-if", + "cfg_aliases", "libc", ] -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "num-bigint" version = "0.4.4" @@ -982,17 +817,11 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", @@ -1000,15 +829,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.5", + "windows-targets", ] [[package]] @@ -1017,50 +846,22 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - [[package]] name = "pest" -version = "2.7.8" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f8023d0fb78c8e03784ea1c7f3fa36e68a723138990b8d5a47d916b651e7a8" +checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" dependencies = [ "memchr", "thiserror", "ucd-trie", ] -[[package]] -name = "pest_consume" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79447402d15d18e7142e14c72f2e63fa3d155be1bc5b70b3ccbb610ac55f536b" -dependencies = [ - "pest", - "pest_consume_macros", - "pest_derive", -] - -[[package]] -name = "pest_consume_macros" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d8630a7a899cb344ec1c16ba0a6b24240029af34bdc0a21f84e411d7f793f29" -dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 1.0.109", -] - [[package]] name = "pest_derive" -version = "2.7.8" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0d24f72393fd16ab6ac5738bc33cdb6a9aa73f8b902e8fe29cf4e67d7dd1026" +checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459" dependencies = [ "pest", "pest_generator", @@ -1068,26 +869,26 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.8" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc17e2a6c7d0a492f0158d7a4bd66cc17280308bbaff78d5bef566dca35ab80" +checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" dependencies = [ "pest", "pest_meta", - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.52", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] name = "pest_meta" -version = "2.7.8" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934cd7631c050f4674352a6e835d5f6711ffbfb9345c2fc0107155ac495ae293" +checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" dependencies = [ "once_cell", "pest", - "sha2 0.10.8", + "sha2", ] [[package]] @@ -1097,7 +898,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.2.5", + "indexmap", ] [[package]] @@ -1127,18 +928,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" -[[package]] -name = "pretty" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83f3aa1e3ca87d3b124db7461265ac176b40c277f37e503eaa29c9c75c037846" -dependencies = [ - "arrayvec", - "log", - "typed-arena", - "unicode-segmentation", -] - [[package]] name = "pretty" version = "0.12.3" @@ -1167,9 +956,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] @@ -1194,11 +983,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ - "proc-macro2 1.0.78", + "proc-macro2 1.0.81", ] [[package]] @@ -1233,18 +1022,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] name = "redox_users" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ "getrandom", "libredox", @@ -1253,14 +1042,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", "regex-automata 0.4.6", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -1277,7 +1066,7 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -1288,17 +1077,17 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.2", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -1307,9 +1096,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" [[package]] name = "ryu" @@ -1334,9 +1123,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] @@ -1362,36 +1151,32 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.52", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] -name = "serde_dhall" -version = "0.11.2" +name = "serde_json" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c01a6b1d6f9f33bb1ad5652249e938297e43a1f43418236f7b72e64837e347" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ - "dhall", - "dhall_proc_macros", - "doc-comment", + "itoa", + "ryu", "serde", - "url", ] [[package]] -name = "serde_json" -version = "1.0.114" +name = "serde_spanned" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" dependencies = [ - "itoa", - "ryu", "serde", ] @@ -1404,19 +1189,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "sha2" version = "0.10.8" @@ -1425,7 +1197,7 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.7", + "digest", ] [[package]] @@ -1436,9 +1208,9 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "similar" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" +checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" dependencies = [ "bstr", "unicode-segmentation", @@ -1462,15 +1234,9 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "stacker" @@ -1500,9 +1266,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" @@ -1521,19 +1287,19 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.52" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "unicode-ident", ] @@ -1583,22 +1349,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.52", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -1611,19 +1377,38 @@ dependencies = [ ] [[package]] -name = "tinyvec" -version = "1.6.0" +name = "toml" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" dependencies = [ - "tinyvec_macros", + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", ] [[package]] -name = "tinyvec_macros" -version = "0.1.1" +name = "toml_datetime" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] [[package]] name = "typed-arena" @@ -1643,27 +1428,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" -[[package]] -name = "unicode-normalization" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] - [[package]] name = "unicode-segmentation" version = "1.11.0" @@ -1672,9 +1442,9 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" [[package]] name = "unicode-xid" @@ -1688,17 +1458,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" -[[package]] -name = "url" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - [[package]] name = "utf8parse" version = "0.2.1" @@ -1745,11 +1504,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys", ] [[package]] @@ -1764,125 +1523,84 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets", ] [[package]] name = "windows-targets" -version = "0.48.5" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" -dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" [[package]] -name = "windows_i686_gnu" -version = "0.52.4" +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" -version = "0.48.5" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] -name = "windows_x86_64_msvc" -version = "0.52.4" +name = "winnow" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "14b9415ee827af173ebb3f15f9083df5a122eb93572ec28741fb153356ea2578" +dependencies = [ + "memchr", +] [[package]] name = "yansi" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c2861d76f58ec8fc95708b9b1e417f7b12fd72ad33c01fa6886707092dea0d3" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" diff --git a/Changelog.md b/Changelog.md index e8dcf97c..ddca135f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,14 +1,32 @@ # Changelog +## 2024-05-03 + +### candid_parser 0.2.0-beta.0 + +* Breaking changes: + + Rewrite `configs` and `random` modules, adapting TOML format as the parser. `configs` module is no longer under a feature flag, and no longer depend on dhall. + + Rewrite Rust bindgen to use the new `configs` module. Use `emit_bindgen` to generate type bindings, and use `output_handlebar` to provide a handlebar template for the generated module. `compile` function provides a default template. The generated file without config is exactly the same as before. + +* TO-DOs + + Spec for path and matching semantics + + Warning for unused path + + Rust bindgen: + * Generate `use_type` tests. How to handle recursive types? + * Threading state through `nominalize` + * When the path starts with method, duplicate type definition when necessary. + * Number label renaming + ## 2024-04-11 -### Candid 0.10.7 -- 0.10.5 +### Candid 0.10.5 -- 0.10.8 * Switch `HashMap` to `BTreeMap` in serialization and `T::ty()`. This leads to around 20% perf improvement for serializing complicated types. * Disable memoization for unrolled types in serialization to save cycle cost. In some cases, type table can get slightly larger, but it's worth the trade off. * Fix bug in `text_size` * Fix decoding cost calculation overflow +* Fix length check in decoding principal type ## 2024-02-27 diff --git a/rust/candid/Cargo.toml b/rust/candid/Cargo.toml index 93949bae..6a61a33b 100644 --- a/rust/candid/Cargo.toml +++ b/rust/candid/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "candid" -version = "0.10.7" +version = "0.10.8" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] diff --git a/rust/candid/src/ser.rs b/rust/candid/src/ser.rs index 002c6717..15750555 100644 --- a/rust/candid/src/ser.rs +++ b/rust/candid/src/ser.rs @@ -169,9 +169,9 @@ impl<'a> types::Serializer for &'a mut ValueSerializer { self.serialize_principal(blob)?; self.serialize_text(meth) } - fn serialize_option(self, v: Option<&T>) -> Result<()> + fn serialize_option(self, v: Option<&T>) -> Result<()> where - T: super::CandidType, + T: super::CandidType + ?Sized, { match v { None => { @@ -207,9 +207,9 @@ pub struct Compound<'a> { } impl<'a> types::Compound for Compound<'a> { type Error = Error; - fn serialize_element(&mut self, value: &T) -> Result<()> + fn serialize_element(&mut self, value: &T) -> Result<()> where - T: types::CandidType, + T: types::CandidType + ?Sized, { value.idl_serialize(&mut *self.ser)?; Ok(()) diff --git a/rust/candid/src/types/mod.rs b/rust/candid/src/types/mod.rs index f844ab72..aab4554a 100644 --- a/rust/candid/src/types/mod.rs +++ b/rust/candid/src/types/mod.rs @@ -81,9 +81,9 @@ pub trait Serializer: Sized { fn serialize_text(self, v: &str) -> Result<(), Self::Error>; fn serialize_null(self, v: ()) -> Result<(), Self::Error>; fn serialize_empty(self) -> Result<(), Self::Error>; - fn serialize_option(self, v: Option<&T>) -> Result<(), Self::Error> + fn serialize_option(self, v: Option<&T>) -> Result<(), Self::Error> where - T: CandidType; + T: CandidType + ?Sized; fn serialize_struct(self) -> Result; fn serialize_vec(self, len: usize) -> Result; fn serialize_blob(self, v: &[u8]) -> Result<(), Self::Error>; @@ -94,9 +94,9 @@ pub trait Serializer: Sized { pub trait Compound { type Error; - fn serialize_element(&mut self, v: &T) -> Result<(), Self::Error> + fn serialize_element(&mut self, v: &T) -> Result<(), Self::Error> where - T: CandidType; + T: CandidType + ?Sized; // Used for simulating serde(with = "serde_bytes"). We can remove this when specialization is stable in Rust, // or generalize this function to take a closure for with. #[doc(hidden)] diff --git a/rust/candid_parser/Cargo.toml b/rust/candid_parser/Cargo.toml index 503322ed..2630d651 100644 --- a/rust/candid_parser/Cargo.toml +++ b/rust/candid_parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "candid_parser" -version = "0.1.4" +version = "0.2.0-beta.0" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] @@ -26,18 +26,18 @@ num-bigint.workspace = true pretty.workspace = true thiserror.workspace = true anyhow.workspace = true +serde.workspace = true lalrpop-util = "0.20.0" logos = "0.13" convert_case = "0.6" +handlebars = "5.1" +toml = { version = "0.8", default-features = false, features = ["parse"] } arbitrary = { workspace = true, optional = true } -# Don't upgrade serde_dhall. It will introduce dependency with invalid license. -serde_dhall = { version = "0.11", default-features = false, optional = true } fake = { version = "2.4", optional = true } rand = { version = "0.8", optional = true } num-traits = { workspace = true, optional = true } -serde = { workspace = true, optional = true } dialoguer = { version = "0.11", default-features = false, features = ["editor", "completion"], optional = true } console = { version = "0.15", optional = true } ctrlc = { version = "3.4", optional = true } @@ -48,8 +48,7 @@ test-generator = "0.3.0" rand.workspace = true [features] -configs = ["serde_dhall"] -random = ["configs", "arbitrary", "fake", "rand", "num-traits", "serde"] +random = ["dep:arbitrary", "dep:fake", "dep:rand", "dep:num-traits"] assist = ["dep:dialoguer", "dep:console", "dep:ctrlc"] all = ["random", "assist"] diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index ee1b1b8c..afeb98ff 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -1,63 +1,51 @@ use super::analysis::{chase_actor, infer_rec}; +use crate::{ + configs::{ConfigState, ConfigTree, Configs, StateElem}, + Deserialize, +}; use candid::pretty::utils::*; use candid::types::{Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInner}; use convert_case::{Case, Casing}; use pretty::RcDoc; -use std::collections::BTreeSet; +use serde::Serialize; +use std::collections::{BTreeMap, BTreeSet}; -#[derive(Clone)] -pub enum Target { - CanisterCall, - Agent, - CanisterStub, +#[derive(Default, Deserialize, Clone, Debug)] +pub struct BindingConfig { + name: Option, + use_type: Option, + attributes: Option, + visibility: Option, } - -#[derive(Clone)] -pub struct Config { - candid_crate: String, - type_attributes: String, - canister_id: Option, - service_name: String, - target: Target, -} -impl Config { - pub fn new() -> Self { - Config { - candid_crate: "candid".to_string(), - type_attributes: "".to_string(), - canister_id: None, - service_name: "service".to_string(), - target: Target::CanisterCall, +impl ConfigState for BindingConfig { + fn merge_config(&mut self, config: &Self, elem: Option<&StateElem>, _is_recursive: bool) { + self.name.clone_from(&config.name); + // match use_type can survive across types, so that label.use_type works + if !matches!(elem, Some(StateElem::Label(_))) { + if let Some(use_type) = &config.use_type { + self.use_type = Some(use_type.clone()); + } + } else { + self.use_type.clone_from(&config.use_type); + } + // matched attributes can survive across labels, so that record.attributes works + if matches!(elem, Some(StateElem::Label(_))) { + if let Some(attr) = &config.attributes { + self.attributes = Some(attr.clone()); + } + } else { + self.attributes.clone_from(&config.attributes); + } + if config.visibility.is_some() { + self.visibility.clone_from(&config.visibility); } } - pub fn set_candid_crate(&mut self, name: String) -> &mut Self { - self.candid_crate = name; - self - } - /// Applies to all types for now - pub fn set_type_attributes(&mut self, attr: String) -> &mut Self { - self.type_attributes = attr; - self - } - /// Only generates SERVICE struct if canister_id is not provided - pub fn set_canister_id(&mut self, id: candid::Principal) -> &mut Self { - self.canister_id = Some(id); - self - } - /// Service name when canister id is provided - pub fn set_service_name(&mut self, name: String) -> &mut Self { - self.service_name = name; - self - } - pub fn set_target(&mut self, name: Target) -> &mut Self { - self.target = name; - self - } + fn update_state(&mut self, _elem: &StateElem) {} + fn restore_state(&mut self, _elem: &StateElem) {} } -impl Default for Config { - fn default() -> Self { - Self::new() - } +pub struct State<'a> { + state: crate::configs::State<'a, BindingConfig>, + recs: RecPoints<'a>, } type RecPoints<'a> = BTreeSet<&'a str>; @@ -101,363 +89,408 @@ fn ident_(id: &str, case: Option) -> (RcDoc, bool) { fn ident(id: &str, case: Option) -> RcDoc { ident_(id, case).0 } - -fn pp_ty<'a>(ty: &'a Type, recs: &RecPoints) -> RcDoc<'a> { - use TypeInner::*; - match ty.as_ref() { - Null => str("()"), - Bool => str("bool"), - Nat => str("candid::Nat"), - Int => str("candid::Int"), - Nat8 => str("u8"), - Nat16 => str("u16"), - Nat32 => str("u32"), - Nat64 => str("u64"), - Int8 => str("i8"), - Int16 => str("i16"), - Int32 => str("i32"), - Int64 => str("i64"), - Float32 => str("f32"), - Float64 => str("f64"), - Text => str("String"), - Reserved => str("candid::Reserved"), - Empty => str("candid::Empty"), - Var(ref id) => { - let name = ident(id, Some(Case::Pascal)); - if recs.contains(id.as_str()) { - str("Box<").append(name).append(">") - } else { - name - } - } - Principal => str("Principal"), - Opt(ref t) => str("Option").append(enclose("<", pp_ty(t, recs), ">")), - // It's a bit tricky to use `deserialize_with = "serde_bytes"`. It's not working for `type t = blob` - Vec(ref t) if matches!(t.as_ref(), Nat8) => str("serde_bytes::ByteBuf"), - Vec(ref t) => str("Vec").append(enclose("<", pp_ty(t, recs), ">")), - Record(ref fs) => pp_record_fields(fs, recs, ""), - Variant(_) => unreachable!(), // not possible after rewriting - Func(_) => unreachable!(), // not possible after rewriting - Service(_) => unreachable!(), // not possible after rewriting - Class(_, _) => unreachable!(), - Knot(_) | Unknown | Future => unreachable!(), +fn pp_vis<'a>(vis: &Option) -> RcDoc<'a> { + match vis { + Some(vis) if vis.is_empty() => RcDoc::nil(), + Some(vis) => RcDoc::text(vis.clone()).append(" "), + None => RcDoc::text("pub "), } } - -fn pp_label<'a>(id: &'a SharedLabel, is_variant: bool, vis: &'a str) -> RcDoc<'a> { - let vis = if vis.is_empty() { - RcDoc::nil() - } else { - kwd(vis) - }; - match &**id { - Label::Named(id) => { - let case = if is_variant { Some(Case::Pascal) } else { None }; - let (doc, is_rename) = ident_(id, case); - if is_rename { - str("#[serde(rename=\"") - .append(id.escape_debug().to_string()) - .append("\")]") - .append(RcDoc::line()) - .append(vis) - .append(doc) - } else { - vis.append(doc) +impl<'a> State<'a> { + fn pp_ty<'b>(&mut self, ty: &'b Type, is_ref: bool) -> RcDoc<'b> { + use TypeInner::*; + let elem = StateElem::Type(ty); + let old = self.state.push_state(&elem); + let res = if let Some(t) = &self.state.config.use_type { + RcDoc::text(t.clone()) + } else { + match ty.as_ref() { + Null => str("()"), + Bool => str("bool"), + Nat => str("candid::Nat"), + Int => str("candid::Int"), + Nat8 => str("u8"), + Nat16 => str("u16"), + Nat32 => str("u32"), + Nat64 => str("u64"), + Int8 => str("i8"), + Int16 => str("i16"), + Int32 => str("i32"), + Int64 => str("i64"), + Float32 => str("f32"), + Float64 => str("f64"), + Text => str("String"), + Reserved => str("candid::Reserved"), + Empty => str("candid::Empty"), + Var(ref id) => { + let name = if let Some(name) = &self.state.config.name { + RcDoc::text(name.clone()) + } else { + ident(id, Some(Case::Pascal)) + }; + if !is_ref && self.recs.contains(id.as_str()) { + str("Box<").append(name).append(">") + } else { + name + } + } + Principal => str("Principal"), + Opt(ref t) => str("Option").append(enclose("<", self.pp_ty(t, is_ref), ">")), + // It's a bit tricky to use `deserialize_with = "serde_bytes"`. It's not working for `type t = blob` + Vec(ref t) if matches!(t.as_ref(), Nat8) => str("serde_bytes::ByteBuf"), + Vec(ref t) => str("Vec").append(enclose("<", self.pp_ty(t, is_ref), ">")), + Record(ref fs) => self.pp_record_fields(fs, false, is_ref), + Variant(_) => unreachable!(), // not possible after rewriting + Func(_) => unreachable!(), // not possible after rewriting + Service(_) => unreachable!(), // not possible after rewriting + Class(_, _) => unreachable!(), + Knot(_) | Unknown | Future => unreachable!(), } - } - Label::Id(n) | Label::Unnamed(n) => vis.append("_").append(RcDoc::as_string(n)).append("_"), + }; + self.state.pop_state(old, elem); + res } -} - -fn pp_record_field<'a>(field: &'a Field, recs: &RecPoints, vis: &'a str) -> RcDoc<'a> { - pp_label(&field.id, false, vis) - .append(kwd(":")) - .append(pp_ty(&field.ty, recs)) -} - -fn pp_record_fields<'a>(fs: &'a [Field], recs: &RecPoints, vis: &'a str) -> RcDoc<'a> { - if is_tuple(fs) { - let vis = if vis.is_empty() { + fn pp_label<'b>(&mut self, id: &'b SharedLabel, is_variant: bool, need_vis: bool) -> RcDoc<'b> { + let vis = if need_vis { + pp_vis(&self.state.config.visibility) + } else { RcDoc::nil() + }; + let attr = self + .state + .config + .attributes + .clone() + .map(|s| RcDoc::text(s).append(RcDoc::line())) + .unwrap_or(RcDoc::nil()); + match &**id { + Label::Named(id) => { + let (doc, is_rename) = if let Some(name) = &self.state.config.name { + (RcDoc::text(name.clone()), true) + } else { + let case = if is_variant { Some(Case::Pascal) } else { None }; + ident_(id, case) + }; + let attr = if is_rename { + attr.append("#[serde(rename=\"") + .append(id.escape_debug().to_string()) + .append("\")]") + .append(RcDoc::line()) + } else { + attr + }; + attr.append(vis).append(doc) + } + Label::Id(n) | Label::Unnamed(n) => { + // TODO rename + vis.append("_").append(RcDoc::as_string(n)).append("_") + } + } + } + fn pp_tuple<'b>(&mut self, fs: &'b [Field], need_vis: bool, is_ref: bool) -> RcDoc<'b> { + let tuple = fs.iter().enumerate().map(|(i, f)| { + let lab = i.to_string(); + let old = self.state.push_state(&StateElem::Label(&lab)); + let vis = if need_vis { + pp_vis(&self.state.config.visibility) + } else { + RcDoc::nil() + }; + let res = vis.append(self.pp_ty(&f.ty, is_ref)).append(","); + self.state.pop_state(old, StateElem::Label(&lab)); + res + }); + enclose("(", RcDoc::concat(tuple), ")") + } + fn pp_record_field<'b>(&mut self, field: &'b Field, need_vis: bool, is_ref: bool) -> RcDoc<'b> { + let lab = field.id.to_string(); + let old = self.state.push_state(&StateElem::Label(&lab)); + let res = self + .pp_label(&field.id, false, need_vis) + .append(kwd(":")) + .append(self.pp_ty(&field.ty, is_ref)); + self.state.pop_state(old, StateElem::Label(&lab)); + res + } + fn pp_record_fields<'b>(&mut self, fs: &'b [Field], need_vis: bool, is_ref: bool) -> RcDoc<'b> { + let old = self.state.push_state(&StateElem::TypeStr("record")); + let res = if is_tuple(fs) { + // TODO check if there is no name/attr in the label subtree + self.pp_tuple(fs, need_vis, is_ref) } else { - kwd(vis) + let fields: Vec<_> = fs + .iter() + .map(|f| self.pp_record_field(f, need_vis, is_ref)) + .collect(); + let fields = concat(fields.into_iter(), ","); + enclose_space("{", fields, "}") }; - let tuple = RcDoc::concat( - fs.iter() - .map(|f| vis.clone().append(pp_ty(&f.ty, recs)).append(",")), - ); - enclose("(", tuple, ")") - } else { - let fields = concat(fs.iter().map(|f| pp_record_field(f, recs, vis)), ","); - enclose_space("{", fields, "}") + self.state.pop_state(old, StateElem::TypeStr("record")); + res } -} - -fn pp_variant_field<'a>(field: &'a Field, recs: &RecPoints) -> RcDoc<'a> { - match field.ty.as_ref() { - TypeInner::Null => pp_label(&field.id, true, ""), - TypeInner::Record(fs) => { - pp_label(&field.id, true, "").append(pp_record_fields(fs, recs, "")) - } - _ => pp_label(&field.id, true, "").append(enclose("(", pp_ty(&field.ty, recs), ")")), + fn pp_variant_field<'b>(&mut self, field: &'b Field) -> RcDoc<'b> { + let lab = field.id.to_string(); + let old = self.state.push_state(&StateElem::Label(&lab)); + let res = match field.ty.as_ref() { + TypeInner::Null => self.pp_label(&field.id, true, false), + TypeInner::Record(fs) => self + .pp_label(&field.id, true, false) + .append(self.pp_record_fields(fs, false, false)), + _ => self.pp_label(&field.id, true, false).append(enclose( + "(", + self.pp_ty(&field.ty, false), + ")", + )), + }; + self.state.pop_state(old, StateElem::Label(&lab)); + res } -} - -fn pp_variant_fields<'a>(fs: &'a [Field], recs: &RecPoints) -> RcDoc<'a> { - let fields = concat(fs.iter().map(|f| pp_variant_field(f, recs)), ","); - enclose_space("{", fields, "}") -} - -fn pp_defs<'a>( - config: &'a Config, - env: &'a TypeEnv, - def_list: &'a [&'a str], - recs: &'a RecPoints, -) -> RcDoc<'a> { - let derive = if config.type_attributes.is_empty() { - "#[derive(CandidType, Deserialize)]" - } else { - &config.type_attributes - }; - lines(def_list.iter().map(|id| { - let ty = env.find_type(id).unwrap(); - let name = ident(id, Some(Case::Pascal)).append(" "); - let vis = "pub "; - match ty.as_ref() { - TypeInner::Record(fs) => { - let separator = if is_tuple(fs) { - RcDoc::text(";") - } else { - RcDoc::nil() - }; - str(derive) - .append(RcDoc::line()) - .append(vis) - .append("struct ") - .append(name) - .append(pp_record_fields(fs, recs, "pub")) - .append(separator) - .append(RcDoc::hardline()) + fn pp_variant_fields<'b>(&mut self, fs: &'b [Field]) -> RcDoc<'b> { + let old = self.state.push_state(&StateElem::TypeStr("variant")); + let fields: Vec<_> = fs.iter().map(|f| self.pp_variant_field(f)).collect(); + let fields = concat(fields.into_iter(), ","); + let res = enclose_space("{", fields, "}"); + self.state.pop_state(old, StateElem::TypeStr("variant")); + res + } + fn pp_defs(&mut self, def_list: &'a [&'a str]) -> RcDoc<'a> { + let mut res = Vec::with_capacity(def_list.len()); + for id in def_list { + let old = self.state.push_state(&StateElem::Label(id)); + if self.state.config.use_type.is_some() { + self.state.pop_state(old, StateElem::Label(id)); + continue; } - TypeInner::Variant(fs) => str(derive) - .append(RcDoc::line()) - .append(vis) - .append("enum ") - .append(name) - .append(pp_variant_fields(fs, recs)) - .append(RcDoc::hardline()), - TypeInner::Func(func) => str("candid::define_function!(") - .append(vis) - .append(name) - .append(": ") - .append(pp_ty_func(func)) - .append(");"), - TypeInner::Service(serv) => str("candid::define_service!(") - .append(vis) - .append(name) - .append(": ") - .append(pp_ty_service(serv)) - .append(");"), - _ => { - if recs.contains(id) { - str(derive) + let ty = self.state.env.find_type(id).unwrap(); + let name = self + .state + .config + .name + .clone() + .map(RcDoc::text) + .unwrap_or_else(|| ident(id, Some(Case::Pascal))); + let vis = pp_vis(&self.state.config.visibility); + let derive = self + .state + .config + .attributes + .clone() + .map(RcDoc::text) + .unwrap_or(RcDoc::text("#[derive(CandidType, Deserialize)]")); + let line = match ty.as_ref() { + TypeInner::Record(fs) => { + let separator = if is_tuple(fs) { + RcDoc::text(";") + } else { + RcDoc::nil() + }; + derive .append(RcDoc::line()) .append(vis) .append("struct ") - .append(ident(id, Some(Case::Pascal))) - .append(enclose("(", pp_ty(ty, recs), ")")) - .append(";") - .append(RcDoc::hardline()) - } else { - str(vis) - .append(kwd("type")) .append(name) - .append("= ") - .append(pp_ty(ty, recs)) - .append(";") + .append(" ") + .append(self.pp_record_fields(fs, true, false)) + .append(separator) } - } + TypeInner::Variant(fs) => derive + .append(RcDoc::line()) + .append(vis) + .append("enum ") + .append(name) + .append(" ") + .append(self.pp_variant_fields(fs)), + TypeInner::Func(func) => str("candid::define_function!(") + .append(vis) + .append(name) + .append(" : ") + .append(self.pp_ty_func(func)) + .append(");"), + TypeInner::Service(serv) => str("candid::define_service!(") + .append(vis) + .append(name) + .append(" : ") + .append(self.pp_ty_service(serv)) + .append(");"), + _ => { + if self.recs.contains(id) { + derive + .append(RcDoc::line()) + .append(vis) + .append("struct ") + .append(name) + .append(enclose("(", self.pp_ty(ty, false), ")")) + .append(";") + } else { + vis.append(kwd("type")) + .append(name) + .append(" = ") + .append(self.pp_ty(ty, false)) + .append(";") + } + } + }; + self.state.pop_state(old, StateElem::Label(id)); + res.push(line) } - })) -} - -fn pp_args(args: &[Type]) -> RcDoc { - let empty = RecPoints::default(); - let doc = concat(args.iter().map(|t| pp_ty(t, &empty)), ","); - enclose("(", doc, ")") -} -fn pp_ty_func(f: &Function) -> RcDoc { - let args = pp_args(&f.args); - let rets = pp_args(&f.rets); - let modes = candid::pretty::candid::pp_modes(&f.modes); - args.append(" ->") - .append(RcDoc::space()) - .append(rets.append(modes)) - .nest(INDENT_SPACE) -} -fn pp_ty_service(serv: &[(String, Type)]) -> RcDoc { - let doc = concat( - serv.iter().map(|(id, func)| { + lines(res.into_iter()) + } + fn pp_args<'b>(&mut self, args: &'b [Type], prefix: &'b str) -> RcDoc<'b> { + let doc: Vec<_> = args + .iter() + .enumerate() + .map(|(i, t)| { + let lab = format!("{prefix}{i}"); + let old = self.state.push_state(&StateElem::Label(&lab)); + let res = self.pp_ty(t, true); + self.state.pop_state(old, StateElem::Label(&lab)); + res + }) + .collect(); + let doc = concat(doc.into_iter(), ","); + enclose("(", doc, ")") + } + fn pp_ty_func<'b>(&mut self, f: &'b Function) -> RcDoc<'b> { + let lab = StateElem::TypeStr("func"); + let old = self.state.push_state(&lab); + let args = self.pp_args(&f.args, "arg"); + let rets = self.pp_args(&f.rets, "ret"); + let modes = candid::pretty::candid::pp_modes(&f.modes); + let res = args + .append(" ->") + .append(RcDoc::space()) + .append(rets.append(modes)) + .nest(INDENT_SPACE); + self.state.pop_state(old, lab); + res + } + fn pp_ty_service<'b>(&mut self, serv: &'b [(String, Type)]) -> RcDoc<'b> { + let lab = StateElem::TypeStr("service"); + let old = self.state.push_state(&lab); + let mut list = Vec::new(); + for (id, func) in serv.iter() { let func_doc = match func.as_ref() { - TypeInner::Func(ref f) => enclose("candid::func!(", pp_ty_func(f), ")"), - TypeInner::Var(_) => pp_ty(func, &RecPoints::default()).append("::ty()"), + TypeInner::Func(ref f) => enclose("candid::func!(", self.pp_ty_func(f), ")"), + TypeInner::Var(_) => self.pp_ty(func, true).append("::ty()"), _ => unreachable!(), }; - RcDoc::text("\"") - .append(id) - .append(kwd("\" :")) - .append(func_doc) - }), - ";", - ); - enclose_space("{", doc, "}") -} - -fn pp_function<'a>(config: &Config, id: &'a str, func: &'a Function) -> RcDoc<'a> { - let name = ident(id, Some(Case::Snake)); - let empty = BTreeSet::new(); - let arg_prefix = str(match config.target { - Target::CanisterCall => "&self", - Target::Agent => "&self", - Target::CanisterStub => unimplemented!(), - }); - let args = concat( - std::iter::once(arg_prefix).chain( - func.args - .iter() - .enumerate() - .map(|(i, ty)| RcDoc::as_string(format!("arg{i}: ")).append(pp_ty(ty, &empty))), - ), - ",", - ); - let rets = match config.target { - Target::CanisterCall => enclose( - "(", - RcDoc::concat(func.rets.iter().map(|ty| pp_ty(ty, &empty).append(","))), - ")", - ), - Target::Agent => match func.rets.len() { - 0 => str("()"), - 1 => pp_ty(&func.rets[0], &empty), - _ => enclose( - "(", - RcDoc::intersperse( - func.rets.iter().map(|ty| pp_ty(ty, &empty)), - RcDoc::text(", "), - ), - ")", - ), - }, - Target::CanisterStub => unimplemented!(), - }; - let sig = kwd("pub async fn") - .append(name) - .append(enclose("(", args, ")")) - .append(kwd(" ->")) - .append(enclose("Result<", rets, "> ")); - let method = id.escape_debug().to_string(); - let body = match config.target { - Target::CanisterCall => { - let args = RcDoc::concat((0..func.args.len()).map(|i| RcDoc::text(format!("arg{i},")))); - str("ic_cdk::call(self.0, \"") - .append(method) - .append("\", ") - .append(enclose("(", args, ")")) - .append(").await") - } - Target::Agent => { - let is_query = func.is_query(); - let builder_method = if is_query { "query" } else { "update" }; - let call = if is_query { "call" } else { "call_and_wait" }; - let args = RcDoc::intersperse( - (0..func.args.len()).map(|i| RcDoc::text(format!("&arg{i}"))), - RcDoc::text(", "), - ); - let blob = str("Encode!").append(enclose("(", args, ")?;")); - let rets = RcDoc::concat( - func.rets - .iter() - .map(|ty| str(", ").append(pp_ty(ty, &empty))), + list.push( + RcDoc::text("\"") + .append(id) + .append(kwd("\" :")) + .append(func_doc), ); - str("let args = ").append(blob).append(RcDoc::hardline()) - .append(format!("let bytes = self.1.{builder_method}(&self.0, \"{method}\").with_arg(args).{call}().await?;")) - .append(RcDoc::hardline()) - .append("Ok(Decode!(&bytes").append(rets).append(")?)") } - Target::CanisterStub => unimplemented!(), - }; - sig.append(enclose_space("{", body, "}")) -} - -fn pp_actor<'a>(config: &'a Config, env: &'a TypeEnv, actor: &'a Type) -> RcDoc<'a> { - // TODO trace to service before we figure out what canister means in Rust - let serv = env.as_service(actor).unwrap(); - let body = RcDoc::intersperse( - serv.iter().map(|(id, func)| { - let func = env.as_func(func).unwrap(); - pp_function(config, id, func) - }), - RcDoc::hardline(), - ); - let struct_name = config.service_name.to_case(Case::Pascal); - let service_def = match config.target { - Target::CanisterCall => format!("pub struct {}(pub Principal);", struct_name), - Target::Agent => format!( - "pub struct {}<'a>(pub Principal, pub &'a ic_agent::Agent);", - struct_name - ), - Target::CanisterStub => unimplemented!(), - }; - let service_impl = match config.target { - Target::CanisterCall => format!("impl {} ", struct_name), - Target::Agent => format!("impl<'a> {}<'a> ", struct_name), - Target::CanisterStub => unimplemented!(), - }; - let res = RcDoc::text(service_def) - .append(RcDoc::hardline()) - .append(service_impl) - .append(enclose_space("{", body, "}")) - .append(RcDoc::hardline()); - if let Some(cid) = config.canister_id { - let slice = cid - .as_slice() + let doc = concat(list.into_iter(), ";"); + let res = enclose_space("{", doc, "}"); + self.state.pop_state(old, lab); + res + } + fn pp_function(&mut self, id: &str, func: &Function) -> Method { + let old = self.state.push_state(&StateElem::Label(id)); + let name = self + .state + .config + .name + .clone() + .unwrap_or_else(|| ident(id, Some(Case::Snake)).pretty(LINE_WIDTH).to_string()); + let args: Vec<_> = func + .args .iter() - .map(|b| b.to_string()) - .collect::>() - .join(", "); - let id = RcDoc::text(format!( - "pub const CANISTER_ID : Principal = Principal::from_slice(&[{}]); // {}", - slice, cid - )); - let instance = match config.target { - Target::CanisterCall => format!( - "pub const {} : {} = {}(CANISTER_ID);", - config.service_name, struct_name, struct_name - ), - Target::Agent => "".to_string(), - Target::CanisterStub => unimplemented!(), + .enumerate() + .map(|(i, ty)| { + let lab = format!("arg{i}"); + let old = self.state.push_state(&StateElem::Label(&lab)); + let name = self + .state + .config + .name + .clone() + .unwrap_or_else(|| lab.clone()); + let res = self.pp_ty(ty, true); + self.state.pop_state(old, StateElem::Label(&lab)); + (name, res) + }) + .collect(); + let rets: Vec<_> = func + .rets + .iter() + .enumerate() + .map(|(i, ty)| { + let lab = format!("ret{i}"); + let old = self.state.push_state(&StateElem::Label(&lab)); + let res = self.pp_ty(ty, true); + self.state.pop_state(old, StateElem::Label(&lab)); + res + }) + .collect(); + let mode = if func.is_query() { "query" } else { "update" }.to_string(); + let res = Method { + name, + original_name: id.to_string(), + args: args + .into_iter() + .map(|(id, t)| (id, t.pretty(LINE_WIDTH).to_string())) + .collect(), + rets: rets + .into_iter() + .map(|x| x.pretty(LINE_WIDTH).to_string()) + .collect(), + mode, }; - res.append(id).append(RcDoc::hardline()).append(instance) - } else { + self.state.pop_state(old, StateElem::Label(id)); res } -} - -pub fn compile(config: &Config, env: &TypeEnv, actor: &Option) -> String { - let header = format!( - r#"// This is an experimental feature to generate Rust binding from Candid. -// You may want to manually adjust some of the types. -#![allow(dead_code, unused_imports)] -use {}::{{self, CandidType, Deserialize, Principal, Encode, Decode}}; -"#, - config.candid_crate - ); - let header = header - + match &config.target { - Target::CanisterCall => "use ic_cdk::api::call::CallResult as Result;\n", - Target::Agent => "type Result = std::result::Result;\n", - Target::CanisterStub => "", + fn pp_actor(&mut self, actor: &Type) -> (Vec, Option>) { + let actor = self.state.env.trace_type(actor).unwrap(); + let init = if let TypeInner::Class(args, _) = actor.as_ref() { + let old = self.state.push_state(&StateElem::Label("init")); + let args: Vec<_> = args + .iter() + .enumerate() + .map(|(i, ty)| { + let lab = format!("arg{i}"); + let old = self.state.push_state(&StateElem::Label(&lab)); + let name = self + .state + .config + .name + .clone() + .unwrap_or_else(|| lab.clone()); + let res = self.pp_ty(ty, true); + self.state.pop_state(old, StateElem::Label(&lab)); + (name, res.pretty(LINE_WIDTH).to_string()) + }) + .collect(); + self.state.pop_state(old, StateElem::Label("init")); + Some(args) + } else { + None }; + let serv = self.state.env.as_service(&actor).unwrap(); + let mut res = Vec::new(); + for (id, func) in serv.iter() { + let func = self.state.env.as_func(func).unwrap(); + res.push(self.pp_function(id, func)); + } + (res, init) + } +} +#[derive(Serialize, Debug)] +pub struct Output { + type_defs: String, + methods: Vec, + init_args: Option>, +} +#[derive(Serialize, Debug)] +pub struct Method { + name: String, + original_name: String, + args: Vec<(String, String)>, + rets: Vec, + mode: String, +} +pub fn emit_bindgen(tree: &Config, env: &TypeEnv, actor: &Option) -> Output { let (env, actor) = nominalize_all(env, actor); let def_list: Vec<_> = if let Some(actor) = &actor { chase_actor(&env, actor).unwrap() @@ -465,16 +498,72 @@ use {}::{{self, CandidType, Deserialize, Principal, Encode, Decode}}; env.0.iter().map(|pair| pair.0.as_ref()).collect() }; let recs = infer_rec(&env, &def_list).unwrap(); - let defs = pp_defs(config, &env, &def_list, &recs); - let doc = match &actor { - None => defs, - Some(actor) => { - let actor = pp_actor(config, &env, actor); - defs.append(actor) - } + let mut state = State { + state: crate::configs::State::new(&tree.0, &env), + recs, + }; + let defs = state.pp_defs(&def_list); + let (methods, init_args) = if let Some(actor) = &actor { + state.pp_actor(actor) + } else { + (Vec::new(), None) + }; + Output { + type_defs: defs.pretty(LINE_WIDTH).to_string(), + methods, + init_args, + } +} +pub fn output_handlebar(output: Output, config: ExternalConfig, template: &str) -> String { + let hbs = get_hbs(); + #[derive(Serialize)] + struct HBOutput { + #[serde(flatten)] + external: BTreeMap, + type_defs: String, + methods: Vec, + } + let data = HBOutput { + type_defs: output.type_defs, + methods: output.methods, + external: config.0, + }; + hbs.render_template(template, &data).unwrap() +} +pub struct Config(ConfigTree); +impl Config { + pub fn new(configs: Configs) -> Self { + Self(ConfigTree::from_configs("rust", configs).unwrap()) + } +} +pub struct ExternalConfig(pub BTreeMap); +impl Default for ExternalConfig { + fn default() -> Self { + Self( + [ + ("candid_crate", "candid"), + ("service_name", "service"), + ("target", "canister_call"), + ] + .into_iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(), + ) + } +} +pub fn compile( + tree: &Config, + env: &TypeEnv, + actor: &Option, + external: ExternalConfig, +) -> String { + let source = match external.0.get("target").map(|s| s.as_str()) { + Some("canister_call") | None => include_str!("rust_call.hbs"), + Some("agent") => include_str!("rust_agent.hbs"), + _ => unimplemented!(), }; - let doc = RcDoc::text(header).append(RcDoc::line()).append(doc); - doc.pretty(LINE_WIDTH).to_string() + let output = emit_bindgen(tree, env, actor); + output_handlebar(output, external, source) } pub enum TypePath { @@ -665,3 +754,104 @@ fn nominalize_all(env: &TypeEnv, actor: &Option) -> (TypeEnv, Option .map(|ty| nominalize(&mut res, &mut vec![], ty)); (res, actor) } + +fn get_hbs() -> handlebars::Handlebars<'static> { + use handlebars::*; + let mut hbs = Handlebars::new(); + hbs.register_escape_fn(handlebars::no_escape); + hbs.set_strict_mode(true); + hbs.register_helper( + "escape_debug", + Box::new( + |h: &Helper, + _: &Handlebars, + _: &Context, + _: &mut RenderContext, + out: &mut dyn Output| + -> HelperResult { + let s = h.param(0).unwrap().value().as_str().unwrap(); + out.write(&s.escape_debug().to_string())?; + Ok(()) + }, + ), + ); + hbs.register_helper( + "snake_case", + Box::new( + |h: &Helper, + _: &Handlebars, + _: &Context, + _: &mut RenderContext, + out: &mut dyn Output| + -> HelperResult { + let s = h.param(0).unwrap().value().as_str().unwrap(); + out.write(s.to_case(Case::Snake).as_ref())?; + Ok(()) + }, + ), + ); + hbs.register_helper( + "PascalCase", + Box::new( + |h: &Helper, + _: &Handlebars, + _: &Context, + _: &mut RenderContext, + out: &mut dyn Output| + -> HelperResult { + let s = h.param(0).unwrap().value().as_str().unwrap(); + out.write(s.to_case(Case::Pascal).as_ref())?; + Ok(()) + }, + ), + ); + hbs.register_helper( + "vec_to_arity", + Box::new( + |h: &Helper, + _: &Handlebars, + _: &Context, + _: &mut RenderContext, + out: &mut dyn Output| + -> HelperResult { + let vec: Vec<_> = h + .param(0) + .unwrap() + .value() + .as_array() + .unwrap() + .iter() + .map(|v| v.as_str().unwrap()) + .collect(); + match vec.len() { + 1 => out.write(vec[0])?, + _ => out.write(&format!("({})", vec.join(", ")))?, + } + Ok(()) + }, + ), + ); + hbs.register_helper( + "principal_slice", + Box::new( + |h: &Helper, + _: &Handlebars, + _: &Context, + _: &mut RenderContext, + out: &mut dyn Output| + -> HelperResult { + let s = h.param(0).unwrap().value().as_str().unwrap(); + let id = crate::Principal::from_text(s).unwrap(); + let slice = id + .as_slice() + .iter() + .map(|b| b.to_string()) + .collect::>() + .join(", "); + out.write(slice.as_str())?; + Ok(()) + }, + ), + ); + hbs +} diff --git a/rust/candid_parser/src/bindings/rust_agent.hbs b/rust/candid_parser/src/bindings/rust_agent.hbs new file mode 100644 index 00000000..f48fefc6 --- /dev/null +++ b/rust/candid_parser/src/bindings/rust_agent.hbs @@ -0,0 +1,22 @@ +// This is an experimental feature to generate Rust binding from Candid. +// You may want to manually adjust some of the types. +#![allow(dead_code, unused_imports)] +use {{candid_crate}}::{self, CandidType, Deserialize, Principal, Encode, Decode}; +type Result = std::result::Result; + +{{type_defs}} +{{#if methods}} +pub struct {{PascalCase service_name}}<'a>(pub Principal, pub &'a ic_agent::Agent); +impl<'a> {{PascalCase service_name}}<'a> { + {{#each methods}} + pub async fn {{this.name}}(&self{{#each this.args}}, {{this.0}}: {{this.1}}{{/each}}) -> Result<{{vec_to_arity this.rets}}> { + let args = Encode!({{#each this.args}}&{{this.0}}{{#unless @last}},{{/unless}}{{/each}})?; + let bytes = self.1.{{this.mode}}(&self.0, "{{escape_debug this.original_name}}").with_arg(args).{{#if (eq this.mode "query")}}call{{else}}call_and_wait(){{/if}}.await?; + Ok(Decode!(&bytes{{#each this.rets}}, {{this}}{{/each}})?) + } + {{/each}} +} +{{#if canister_id}} +pub const CANISTER_ID : Principal = Principal::from_slice(&[{{principal_slice canister_id}}]); // {{canister_id}} +{{/if}} +{{/if}} diff --git a/rust/candid_parser/src/bindings/rust_call.hbs b/rust/candid_parser/src/bindings/rust_call.hbs new file mode 100644 index 00000000..6be0fe4b --- /dev/null +++ b/rust/candid_parser/src/bindings/rust_call.hbs @@ -0,0 +1,21 @@ +// This is an experimental feature to generate Rust binding from Candid. +// You may want to manually adjust some of the types. +#![allow(dead_code, unused_imports)] +use {{candid_crate}}::{self, CandidType, Deserialize, Principal, Encode, Decode}; +use ic_cdk::api::call::CallResult as Result; + +{{type_defs}} +{{#if methods}} +pub struct {{PascalCase service_name}}(pub Principal); +impl {{PascalCase service_name}} { + {{#each methods}} + pub async fn {{this.name}}(&self{{#each this.args}}, {{this.0}}: {{this.1}}{{/each}}) -> Result<({{#each this.rets}}{{this}},{{/each}})> { + ic_cdk::call(self.0, "{{escape_debug this.original_name}}", ({{#each this.args}}{{this.0}},{{/each}})).await + } + {{/each}} +} +{{#if canister_id}} +pub const CANISTER_ID : Principal = Principal::from_slice(&[{{principal_slice canister_id}}]); // {{canister_id}} +pub const {{snake_case service_name}} : {{PascalCase service_name}} = {{PascalCase service_name}}(CANISTER_ID); +{{/if}} +{{/if}} diff --git a/rust/candid_parser/src/configs.rs b/rust/candid_parser/src/configs.rs index 7acceb41..c711e4fd 100644 --- a/rust/candid_parser/src/configs.rs +++ b/rust/candid_parser/src/configs.rs @@ -1,82 +1,421 @@ -use crate::Result; -use candid::types::Type; +use anyhow::Result; +use candid::types::{Type, TypeEnv, TypeInner}; use serde::de::DeserializeOwned; -use serde_dhall::{from_simple_value, SimpleValue}; +use std::collections::BTreeMap; +use toml::{Table, Value}; -pub struct Configs(SimpleValue); +pub struct State<'a, T: ConfigState> { + tree: &'a ConfigTree, + open_tree: Option<&'a ConfigTree>, + path: Vec, + pub config: T, + pub env: &'a TypeEnv, +} +#[derive(Debug)] +pub enum StateElem<'a> { + Type(&'a Type), + TypeStr(&'a str), + Label(&'a str), +} +#[derive(Debug)] +pub struct Scope<'a> { + pub method: &'a str, + pub position: Option, +} +#[derive(Debug)] +pub enum ScopePos { + Arg, + Ret, +} -impl Configs { - pub fn from_dhall(v: &str) -> Result { - let v = serde_dhall::from_str(v).parse::()?; - Ok(Configs(v)) +impl<'a, T: ConfigState> State<'a, T> { + pub fn new(tree: &'a ConfigTree, env: &'a TypeEnv) -> Self { + let mut config = T::default(); + if let Some(state) = &tree.state { + config.merge_config(state, None, false); + } + Self { + tree, + open_tree: None, + path: Vec::new(), + config, + env, + } } - pub fn with_method(&self, method: &str) -> Self { - let path = format!("[{method}]"); - let mut res = self.0.clone(); - if let SimpleValue::Record(ref mut map) = res { - if let Some(SimpleValue::Record(mut subtree)) = map.remove(&path) { - map.append(&mut subtree); + /// Match paths in the scope first. If `scope` is None, clear the scope. + pub fn with_scope(&mut self, scope: &Option, idx: usize) { + match scope { + None => self.open_tree = None, + Some(scope) => { + let mut path = vec![format!("method:{}", scope.method)]; + match self.tree.with_prefix(&path) { + Some(tree) => { + match scope.position { + Some(ScopePos::Arg) => path.push(format!("arg:{}", idx)), + Some(ScopePos::Ret) => path.push(format!("ret:{}", idx)), + None => (), + } + self.open_tree = self.tree.with_prefix(&path).or(Some(tree)); + if let Some(state) = self.open_tree.unwrap().state.as_ref() { + self.config.merge_config(state, None, false); + } + } + None => self.open_tree = None, + } } - Configs(res) + } + } + /// Update config based on the new elem in the path. Return the old state AFTER `update_state`. + pub fn push_state(&mut self, elem: &StateElem) -> T { + self.config.update_state(elem); + let old_config = self.config.clone(); + self.path.push(elem.to_string()); + let new_state = if let Some(subtree) = self.open_tree { + subtree + .get_config(&self.path) + .or_else(|| self.tree.get_config(&self.path)) } else { - unreachable!() + self.tree.get_config(&self.path) + }; + if let Some((state, is_recursive)) = new_state { + self.config.merge_config(state, Some(elem), is_recursive); + //eprintln!("match path: {:?}, state: {:?}", self.path, self.config); + } else { + self.config + .merge_config(&T::unmatched_config(), Some(elem), false); + //eprintln!("path: {:?}, state: {:?}", self.path, self.config); } + old_config } - fn get_helper(&self, path: &[String]) -> Option<&SimpleValue> { - let mut result = &self.0; - for elem in path.iter() { - if let SimpleValue::Record(map) = result { - result = map.get(elem)?; + pub fn pop_state(&mut self, old_config: T, elem: StateElem) { + self.config = old_config; + assert_eq!(self.path.pop(), Some(elem.to_string())); + self.config.restore_state(&elem); + } +} + +pub trait ConfigState: DeserializeOwned + Default + Clone + std::fmt::Debug { + fn merge_config(&mut self, config: &Self, elem: Option<&StateElem>, is_recursive: bool); + fn update_state(&mut self, elem: &StateElem); + fn restore_state(&mut self, elem: &StateElem); + fn unmatched_config() -> Self { + Self::default() + } +} +#[derive(Debug)] +pub struct ConfigTree { + state: Option, + subtree: BTreeMap>, + // max_depth is only here to optimize the performance of `get_config` + max_depth: u8, +} +impl ConfigTree { + pub fn from_configs(kind: &str, configs: Configs) -> Result { + let mut map = configs.0; + if let Some(v) = map.remove(kind) { + generate_state_tree(v) + } else { + generate_state_tree(Value::Table(map)) + } + } + /// Return the subtree starting with prefix + pub fn with_prefix(&self, prefix: &[String]) -> Option<&Self> { + let mut tree = self; + for elem in prefix.iter() { + tree = tree.subtree.get(elem)?; + } + Some(tree) + } + pub fn add_config(&mut self, path: &[String], config: T) { + let n = path.len(); + let mut tree: &Self = self; + let mut i = 0; + while i < n { + if let Some(subtree) = tree.subtree.get(&path[i]) { + tree = subtree; + i += 1; } else { - unreachable!() + break; + } + } + let mut node = Self { + state: Some(config.clone()), + subtree: BTreeMap::default(), + max_depth: 0, + }; + for k in (i + 1..n).rev() { + node = Self { + state: None, + max_depth: node.max_depth + 1, + subtree: [(path[k].clone(), node)].into_iter().collect(), } } - if has_leaf(result) { - Some(result) + let mut tree = self; + let mut d = n as u8; + #[allow(clippy::needless_range_loop)] + for k in 0..i { + tree.max_depth = std::cmp::max(d, tree.max_depth); + tree = tree.subtree.get_mut(&path[k]).unwrap(); + d -= 1; + } + if i == n { + tree.state = Some(config); } else { - None + tree.subtree.insert(path[i].clone(), node); + tree.max_depth = std::cmp::max(d, tree.max_depth); } } - /// Get config that starts somewhere in the path and ends at the end of the path. - /// The second return bool is whether the matched path appears earlier in the path (inside a recursion). - /// Empty path returns the top-level config. - pub fn get(&self, path: &[String]) -> Option<(T, bool)> { - if path.is_empty() { - return Some((from_simple_value::(self.0.clone()).ok()?, false)); - } - for i in (0..path.len()).rev() { - let (_, tail) = path.split_at(i); - match self.get_helper(tail) { - Some(v) => { - let parsed_config = from_simple_value::(v.clone()).ok()?; - return Some((parsed_config, is_repeated(path, tail))); - } + pub fn get_config(&self, path: &[String]) -> Option<(&T, bool)> { + let len = path.len(); + assert!(len > 0); + let start = len.saturating_sub(self.max_depth as usize); + for i in (start..len).rev() { + let (path, tail) = path.split_at(i); + match self.match_exact_path(tail) { + Some(v) => return Some((v, is_repeated(path, tail))), None => continue, } } None } + fn match_exact_path(&self, path: &[String]) -> Option<&T> { + let mut result = self; + for elem in path.iter() { + result = result.subtree.get(elem)?; + } + result.state.as_ref() + } +} + +pub struct Configs(Table); + +impl std::str::FromStr for Configs { + type Err = crate::Error; + fn from_str(v: &str) -> Result { + let v = v.parse::()?; + Ok(Configs(v)) + } +} +impl<'a> std::fmt::Display for StateElem<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + StateElem::Type(t) => write!(f, "{}", path_name(t)), + StateElem::Label(l) => write!(f, "{}", l), + StateElem::TypeStr(s) => write!(f, "{}", s), + } + } } fn is_repeated(path: &[String], matched: &[String]) -> bool { - let (test, _) = path.split_at(path.len() - matched.len()); - test.join(".").contains(&matched.join(".")) + let iter = path.as_ref().windows(matched.len()); + for slice in iter { + if slice == matched { + return true; + } + } + false +} + +fn special_key(key: &str) -> bool { + key.starts_with("method:") || key.starts_with("arg:") || key.starts_with("ret:") } -fn has_leaf(v: &SimpleValue) -> bool { - if let SimpleValue::Record(map) = v { - for (_, val) in map.iter() { - match val { - SimpleValue::Record(_) => continue, - _ => return true, +fn generate_state_tree(v: Value) -> Result> { + let mut subtree = BTreeMap::new(); + let mut leaves = toml::Table::new(); + let mut depth = 0; + if let Value::Table(map) = v { + for (k, v) in map.into_iter() { + match v { + Value::Table(_) => { + let v = generate_state_tree(v)?; + let dep = if special_key(&k) { + v.max_depth + } else { + v.max_depth + 1 + }; + depth = std::cmp::max(depth, dep); + subtree.insert(k, v); + } + v => drop(leaves.insert(k, v)), } } - false + let state = if !leaves.is_empty() { + Some(leaves.try_into::()?) + } else { + None + }; + Ok(ConfigTree { + state, + subtree, + max_depth: depth, + }) } else { - false + Err(anyhow::anyhow!("Expected a table")) } } pub fn path_name(t: &Type) -> String { - t.to_string().split(' ').next().unwrap().to_string() + match t.as_ref() { + TypeInner::Null => "null", + TypeInner::Bool => "bool", + TypeInner::Nat => "nat", + TypeInner::Int => "int", + TypeInner::Nat8 => "nat8", + TypeInner::Nat16 => "nat16", + TypeInner::Nat32 => "nat32", + TypeInner::Nat64 => "nat64", + TypeInner::Int8 => "int8", + TypeInner::Int16 => "int16", + TypeInner::Int32 => "int32", + TypeInner::Int64 => "int64", + TypeInner::Float32 => "float32", + TypeInner::Float64 => "float64", + TypeInner::Text => "text", + TypeInner::Reserved => "reserved", + TypeInner::Empty => "empty", + TypeInner::Var(id) => id, + TypeInner::Knot(id) => id.name, + TypeInner::Principal => "principal", + TypeInner::Opt(_) => "opt", + TypeInner::Vec(t) if matches!(t.as_ref(), TypeInner::Nat8) => "blob", + TypeInner::Vec(_) => "vec", + TypeInner::Record(_) => "record", + TypeInner::Variant(_) => "variant", + TypeInner::Func(_) => "func", + TypeInner::Service(_) => "service", + TypeInner::Future => "future", + TypeInner::Class(..) | TypeInner::Unknown => unreachable!(), + } + .to_string() +} + +#[test] +fn parse() { + use serde::Deserialize; + #[derive(Debug, Deserialize, Clone, PartialEq, Default)] + struct T { + depth: Option, + size: Option, + text: Option, + } + impl ConfigState for T { + fn merge_config(&mut self, config: &Self, _elem: Option<&StateElem>, is_recursive: bool) { + *self = config.clone(); + if is_recursive { + self.size = Some(0); + } + } + fn update_state(&mut self, _elem: &StateElem) { + self.size = self.size.map(|s| s + 1); + } + fn restore_state(&mut self, _elem: &StateElem) { + self.size = self.size.map(|s| s - 1); + } + } + let toml = r#" +[random] +list = { depth = 20, size = 50 } +val.text = "42" +left.list = { depth = 1 } +vec.nat8.text = "blob" +Vec = { width = 2, size = 10 } +"method:f"."arg:0".list = { depth = 2, size = 20 } +"method:f".list = { depth = 3, size = 30 } + "#; + let configs = toml.parse::().unwrap(); + let mut tree: ConfigTree = ConfigTree::from_configs("random", configs).unwrap(); + assert_eq!(tree.state, None); + assert_eq!(tree.subtree.len(), 6); + assert_eq!(tree.max_depth, 2); + assert_eq!( + tree.get_config(&["list".to_string()]).unwrap().0.depth, + Some(20) + ); + let t = T { + text: None, + depth: Some(100), + size: None, + }; + tree.add_config(&[], t.clone()); + assert_eq!(tree.state, Some(t.clone())); + tree.add_config(&["left".to_string(), "list".to_string()], t.clone()); + assert_eq!( + tree.match_exact_path(&["left".to_string(), "list".to_string()]) + .unwrap() + .depth, + Some(100) + ); + assert_eq!( + tree.match_exact_path(&["left".to_string(), "a".to_string()]), + None + ); + tree.add_config(&["left".to_string(), "a".to_string()], t.clone()); + assert_eq!( + tree.match_exact_path(&["left".to_string(), "a".to_string()]) + .unwrap() + .depth, + Some(100) + ); + assert_eq!(tree.max_depth, 2); + tree.add_config( + &["a".to_string(), "b".to_string(), "c".to_string()], + t.clone(), + ); + assert_eq!( + tree.match_exact_path(&["a".to_string(), "b".to_string(), "c".to_string()]) + .unwrap() + .depth, + Some(100) + ); + assert_eq!(tree.max_depth, 3); + tree.add_config( + &["a".to_string(), "b".to_string(), "d".to_string()], + t.clone(), + ); + assert_eq!(tree.max_depth, 3); + tree.add_config( + &[ + "a".to_string(), + "b".to_string(), + "c".to_string(), + "d".to_string(), + ], + t.clone(), + ); + assert_eq!(tree.max_depth, 4); + let env = TypeEnv::default(); + let mut state = State::new(&tree, &env); + state.with_scope( + &Some(Scope { + method: "f", + position: Some(ScopePos::Arg), + }), + 0, + ); + let old = state.push_state(&StateElem::Label("list")); + assert_eq!(state.config.depth, Some(2)); + assert_eq!(state.config.size, Some(20)); + assert_eq!(state.config.text, None); + assert_eq!(old.size, None); + state.push_state(&StateElem::Label("val")); + assert_eq!(state.config.text, Some("42".to_string())); + state.with_scope( + &Some(Scope { + method: "f", + position: None, + }), + 0, + ); + state.push_state(&StateElem::Label("list")); + assert_eq!(state.config.depth, Some(3)); + assert_eq!(state.config.size, Some(0)); + state.with_scope(&None, 0); + let old = state.push_state(&StateElem::Label("list")); + assert_eq!(state.config.depth, Some(20)); + assert_eq!(state.config.size, Some(0)); + assert_eq!(old.size, Some(1)); + state.pop_state(old, StateElem::Label("list")); + assert_eq!(state.config.size, Some(0)); + assert_eq!(state.config.depth, Some(3)); } diff --git a/rust/candid_parser/src/error.rs b/rust/candid_parser/src/error.rs index 1bceb030..75b46e3e 100644 --- a/rust/candid_parser/src/error.rs +++ b/rust/candid_parser/src/error.rs @@ -99,11 +99,9 @@ impl From for Error { } } -#[cfg_attr(docsrs, doc(cfg(feature = "configs")))] -#[cfg(feature = "configs")] -impl From for Error { - fn from(e: serde_dhall::Error) -> Error { - Error::msg(format!("dhall error: {e}")) +impl From for Error { + fn from(e: toml::de::Error) -> Error { + Error::msg(format!("toml error: {e}")) } } diff --git a/rust/candid_parser/src/lib.rs b/rust/candid_parser/src/lib.rs index dc034d5b..9853c3d0 100644 --- a/rust/candid_parser/src/lib.rs +++ b/rust/candid_parser/src/lib.rs @@ -136,8 +136,6 @@ pub use candid::*; #[cfg_attr(docsrs, doc(cfg(feature = "assist")))] #[cfg(feature = "assist")] pub mod assist; -#[cfg_attr(docsrs, doc(cfg(feature = "configs")))] -#[cfg(feature = "configs")] pub mod configs; #[cfg_attr(docsrs, doc(cfg(feature = "random")))] #[cfg(feature = "random")] diff --git a/rust/candid_parser/src/random.rs b/rust/candid_parser/src/random.rs index de8bd428..0fdc81bf 100644 --- a/rust/candid_parser/src/random.rs +++ b/rust/candid_parser/src/random.rs @@ -1,4 +1,5 @@ -use super::configs::{path_name, Configs}; +use super::configs::{ConfigState, Configs, Scope, State}; +use crate::configs::StateElem; use crate::{Error, Result}; use arbitrary::{unstructured::Int, Arbitrary, Unstructured}; use candid::types::value::{IDLArgs, IDLField, IDLValue, VariantValue}; @@ -30,124 +31,91 @@ impl Default for GenConfig { } } } -impl GenConfig { - pub fn update(&self, config: GenConfig, recursive: bool) -> GenConfig { - // TODO support removing properties - let old = self.clone(); - GenConfig { - range: config.range.or(old.range), - text: config.text.or(old.text), - value: config.value.or(old.value), - width: config.width.or(old.width), - // These properties only update when it's not inside a recursion. - depth: if recursive { None } else { config.depth }, - size: if recursive { None } else { config.size }, +impl ConfigState for GenConfig { + fn merge_config(&mut self, config: &Self, _elem: Option<&StateElem>, is_recursive: bool) { + self.range = config.range.or(self.range); + if config.text.is_some() { + self.text.clone_from(&config.text); } - } -} - -pub struct GenState<'a> { - tree: &'a Configs, - env: &'a TypeEnv, - // state - path: Vec, - config: GenConfig, - depth: isize, - size: isize, -} -impl<'a> GenState<'a> { - fn new(tree: &'a Configs, env: &'a TypeEnv) -> Self { - let mut config = GenConfig::default(); - if let Some((global_config, _)) = tree.get::(&[]) { - config = config.update(global_config, false); + self.width = config.width.or(self.width); + if config.value.is_some() { + self.value.clone_from(&config.value); } - GenState { - depth: config.depth.take().unwrap_or(5), - size: config.size.take().unwrap_or(50), - tree, - config, - env, - path: Vec::new(), + if !is_recursive { + self.depth = config.depth.or(self.depth); + self.size = config.size.or(self.size); } } - // Update state and return the old config state. Label and var type are cost free. - fn push_state(&mut self, ty: &Type, label: Option) -> GenConfig { - let elem = if let Some(lab) = label { - lab - } else { - match ty.as_ref() { - TypeInner::Var(_) => (), - _ => { - self.depth -= 1; - self.size -= 1; - } + fn update_state(&mut self, elem: &StateElem) { + if let StateElem::Type(t) = elem { + if !matches!(t.as_ref(), TypeInner::Var(_)) { + self.depth = self.depth.map(|d| d - 1); + self.size = self.size.map(|s| s - 1); } - path_name(ty) - }; - let mut old_config = self.config.clone(); - self.path.push(elem); - if let Some((config, recursive)) = self.tree.get::(&self.path) { - self.config = self.config.update(config, recursive); } - // Current depth and size are stored in old_config for pop_state to restore states. - old_config.depth = Some(self.depth); - old_config.size = Some(self.size); - self.depth = self.config.depth.take().unwrap_or(self.depth); - self.size = self.config.size.take().unwrap_or(self.size); - old_config } - fn pop_state(&mut self, old_config: GenConfig, ty: &Type, is_label: bool) { - if !is_label { - match ty.as_ref() { - TypeInner::Var(_) => (), - _ => { - self.depth += 1; - } + fn restore_state(&mut self, elem: &StateElem) { + if let StateElem::Type(t) = elem { + if !matches!(t.as_ref(), TypeInner::Var(_)) { + self.depth = self.depth.map(|d| d + 1); } } - self.path.pop(); - self.config = old_config; - self.depth = self.config.depth.take().unwrap(); - self.size = self.config.size.take().unwrap(); } + fn unmatched_config() -> Self { + GenConfig { + range: None, + text: None, + width: None, + value: None, + depth: None, + size: None, + } + } +} + +pub struct RandState<'a>(State<'a, GenConfig>); +impl<'a> RandState<'a> { pub fn any(&mut self, u: &mut Unstructured, ty: &Type) -> Result { - let old_config = self.push_state(ty, None); - assert!(self.config.depth.is_none()); - if let Some(vec) = &self.config.value { + let old_config = self.0.push_state(&StateElem::Type(ty)); + if let Some(vec) = &self.0.config.value { let v = u.choose(vec)?; let v: IDLValue = super::parse_idl_value(v)?; - let v = v.annotate_type(true, self.env, ty)?; - self.pop_state(old_config, ty, false); + let v = v.annotate_type(true, self.0.env, ty)?; + self.0.pop_state(old_config, StateElem::Type(ty)); return Ok(v); } let res = Ok(match ty.as_ref() { TypeInner::Var(id) => { - let ty = self.env.rec_find_type(id)?; + let ty = self.0.env.rec_find_type(id)?; self.any(u, ty)? } TypeInner::Null => IDLValue::Null, TypeInner::Reserved => IDLValue::Reserved, TypeInner::Bool => IDLValue::Bool(u.arbitrary()?), - TypeInner::Int => IDLValue::Int(arbitrary_num::(u, self.config.range)?.into()), - TypeInner::Nat => IDLValue::Nat(arbitrary_num::(u, self.config.range)?.into()), - TypeInner::Nat8 => IDLValue::Nat8(arbitrary_num(u, self.config.range)?), - TypeInner::Nat16 => IDLValue::Nat16(arbitrary_num(u, self.config.range)?), - TypeInner::Nat32 => IDLValue::Nat32(arbitrary_num(u, self.config.range)?), - TypeInner::Nat64 => IDLValue::Nat64(arbitrary_num(u, self.config.range)?), - TypeInner::Int8 => IDLValue::Int8(arbitrary_num(u, self.config.range)?), - TypeInner::Int16 => IDLValue::Int16(arbitrary_num(u, self.config.range)?), - TypeInner::Int32 => IDLValue::Int32(arbitrary_num(u, self.config.range)?), - TypeInner::Int64 => IDLValue::Int64(arbitrary_num(u, self.config.range)?), + TypeInner::Int => IDLValue::Int(arbitrary_num::(u, self.0.config.range)?.into()), + TypeInner::Nat => IDLValue::Nat(arbitrary_num::(u, self.0.config.range)?.into()), + TypeInner::Nat8 => IDLValue::Nat8(arbitrary_num(u, self.0.config.range)?), + TypeInner::Nat16 => IDLValue::Nat16(arbitrary_num(u, self.0.config.range)?), + TypeInner::Nat32 => IDLValue::Nat32(arbitrary_num(u, self.0.config.range)?), + TypeInner::Nat64 => IDLValue::Nat64(arbitrary_num(u, self.0.config.range)?), + TypeInner::Int8 => IDLValue::Int8(arbitrary_num(u, self.0.config.range)?), + TypeInner::Int16 => IDLValue::Int16(arbitrary_num(u, self.0.config.range)?), + TypeInner::Int32 => IDLValue::Int32(arbitrary_num(u, self.0.config.range)?), + TypeInner::Int64 => IDLValue::Int64(arbitrary_num(u, self.0.config.range)?), TypeInner::Float32 => IDLValue::Float32(u.arbitrary()?), TypeInner::Float64 => IDLValue::Float64(u.arbitrary()?), - TypeInner::Text => { - IDLValue::Text(arbitrary_text(u, &self.config.text, &self.config.width)?) - } + TypeInner::Text => IDLValue::Text(arbitrary_text( + u, + &self.0.config.text, + &self.0.config.width, + )?), TypeInner::Opt(t) => { - let depths = if self.depth <= 0 || self.size <= 0 { + let depths = if self.0.config.depth.is_some_and(|d| d <= 0) + || self.0.config.size.is_some_and(|s| s <= 0) + { [1, 0] } else { - [1, size(self.env, t).unwrap_or(MAX_DEPTH)] + [1, size(self.0.env, t).unwrap_or(MAX_DEPTH)] }; let idx = arbitrary_variant(u, &depths)?; if idx == 0 { @@ -157,9 +125,9 @@ impl<'a> GenState<'a> { } } TypeInner::Vec(t) => { - let width = self.config.width.or_else(|| { - let elem_size = size(self.env, t).unwrap_or(MAX_DEPTH); - Some(std::cmp::max(0, self.size) as usize / elem_size) + let width = self.0.config.width.or_else(|| { + let elem_size = size(self.0.env, t).unwrap_or(MAX_DEPTH); + Some(std::cmp::max(0, self.0.config.size.unwrap_or(0)) as usize / elem_size) }); let len = arbitrary_len(u, width)?; let mut vec = Vec::with_capacity(len); @@ -172,9 +140,11 @@ impl<'a> GenState<'a> { TypeInner::Record(fs) => { let mut res = Vec::new(); for Field { id, ty } in fs.iter() { - let old_config = self.push_state(ty, Some(id.to_string())); + let lab_str = id.to_string(); + let elem = StateElem::Label(&lab_str); + let old_config = self.0.push_state(&elem); let val = self.any(u, ty)?; - self.pop_state(old_config, ty, true); + self.0.pop_state(old_config, elem); res.push(IDLField { id: id.as_ref().clone(), val, @@ -185,8 +155,10 @@ impl<'a> GenState<'a> { TypeInner::Variant(fs) => { let choices = fs .iter() - .map(|Field { ty, .. }| size(self.env, ty).unwrap_or(MAX_DEPTH)); - let sizes: Vec<_> = if self.depth <= 0 || self.size <= 0 { + .map(|Field { ty, .. }| size(self.0.env, ty).unwrap_or(MAX_DEPTH)); + let sizes: Vec<_> = if self.0.config.depth.is_some_and(|d| d <= 0) + || self.0.config.size.is_some_and(|s| s <= 0) + { let min = choices.clone().min().unwrap_or(0); choices.map(|d| if d > min { 0 } else { d }).collect() } else { @@ -194,9 +166,11 @@ impl<'a> GenState<'a> { }; let idx = arbitrary_variant(u, &sizes)?; let Field { id, ty } = &fs[idx]; - let old_config = self.push_state(ty, Some(id.to_string())); + let lab_str = id.to_string(); + let elem = StateElem::Label(&lab_str); + let old_config = self.0.push_state(&elem); let val = self.any(u, ty)?; - self.pop_state(old_config, ty, true); + self.0.pop_state(old_config, elem); let field = IDLField { id: id.as_ref().clone(), val, @@ -206,22 +180,31 @@ impl<'a> GenState<'a> { TypeInner::Principal => IDLValue::Principal(crate::Principal::arbitrary(u)?), TypeInner::Func(_) => IDLValue::Func( crate::Principal::arbitrary(u)?, - arbitrary_text(u, &self.config.text, &self.config.width)?, + arbitrary_text(u, &self.0.config.text, &self.0.config.width)?, ), TypeInner::Service(_) => IDLValue::Service(crate::Principal::arbitrary(u)?), _ => unimplemented!(), }); - self.pop_state(old_config, ty, false); + self.0.pop_state(old_config, StateElem::Type(ty)); res } } -pub fn any(seed: &[u8], tree: &Configs, env: &TypeEnv, types: &[Type]) -> Result { +pub fn any( + seed: &[u8], + configs: Configs, + env: &TypeEnv, + types: &[Type], + scope: &Option, +) -> Result { let mut u = arbitrary::Unstructured::new(seed); + let tree = super::configs::ConfigTree::from_configs("random", configs)?; let mut args = Vec::new(); for (i, t) in types.iter().enumerate() { - let tree = tree.with_method(&i.to_string()); - let mut state = GenState::new(&tree, env); + let mut state = State::new(&tree, env); + state.with_scope(scope, i); + let mut state = RandState(state); + state.0.push_state(&StateElem::Label(&i.to_string())); let v = state.any(&mut u, t)?; args.push(v); } diff --git a/rust/candid_parser/tests/assets/example.toml b/rust/candid_parser/tests/assets/example.toml new file mode 100644 index 00000000..90d9f14a --- /dev/null +++ b/rust/candid_parser/tests/assets/example.toml @@ -0,0 +1,11 @@ +[rust] +attributes = "#[derive(CandidType, Deserialize, Debug)]" +visibility = "pub(crate)" +List.name = "MyList" +Nested41.variant.A = { name = "AAA", attributes = "#[serde(skip_deserializing)]" } +ListInner.attributes = "#derive[CandidType, Deserialize, Clone]" +ListInner.record = { visibility = "", head.name = "HEAD", attributes = "#[serde(skip_deserializing)]", tail.use_type = "Arc" } +my_type = { visibility = "", name = "CanisterId" } +nat.use_type = "u128" +BrokerFindRet = { name = "BrokerReturn", visibility = "pub" } +g1 = { name = "G11", arg0.name = "id", arg1.name = "list", arg2.name = "is_okay", ret0.use_type = "i128" } diff --git a/rust/candid_parser/tests/assets/ok/actor.rs b/rust/candid_parser/tests/assets/ok/actor.rs index 2f2dc6ad..79138a42 100644 --- a/rust/candid_parser/tests/assets/ok/actor.rs +++ b/rust/candid_parser/tests/assets/ok/actor.rs @@ -27,3 +27,4 @@ impl Service { } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/class.rs b/rust/candid_parser/tests/assets/ok/class.rs index 17a8df14..2198ab57 100644 --- a/rust/candid_parser/tests/assets/ok/class.rs +++ b/rust/candid_parser/tests/assets/ok/class.rs @@ -18,3 +18,4 @@ impl Service { } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/comment.rs b/rust/candid_parser/tests/assets/ok/comment.rs index 4839a3f7..c819f2f1 100644 --- a/rust/candid_parser/tests/assets/ok/comment.rs +++ b/rust/candid_parser/tests/assets/ok/comment.rs @@ -6,3 +6,4 @@ use ic_cdk::api::call::CallResult as Result; pub type Id = u8; + diff --git a/rust/candid_parser/tests/assets/ok/cyclic.rs b/rust/candid_parser/tests/assets/ok/cyclic.rs index cb7c2821..f3fae3f3 100644 --- a/rust/candid_parser/tests/assets/ok/cyclic.rs +++ b/rust/candid_parser/tests/assets/ok/cyclic.rs @@ -8,23 +8,16 @@ pub type C = Box; pub type B = Option; #[derive(CandidType, Deserialize)] pub struct A(Option); - pub type Z = Box; pub type Y = Z; pub type X = Y; + pub struct Service(pub Principal); impl Service { - pub async fn f( - &self, - arg0: A, - arg1: B, - arg2: C, - arg3: X, - arg4: Y, - arg5: Z, - ) -> Result<()> { + pub async fn f(&self, arg0: A, arg1: B, arg2: C, arg3: X, arg4: Y, arg5: Z) -> Result<()> { ic_cdk::call(self.0, "f", (arg0,arg1,arg2,arg3,arg4,arg5,)).await } } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/empty.rs b/rust/candid_parser/tests/assets/ok/empty.rs index ee1eed61..64d70472 100644 --- a/rust/candid_parser/tests/assets/ok/empty.rs +++ b/rust/candid_parser/tests/assets/ok/empty.rs @@ -6,16 +6,12 @@ use ic_cdk::api::call::CallResult as Result; #[derive(CandidType, Deserialize)] pub struct FArg {} - #[derive(CandidType, Deserialize)] pub enum FRet {} - #[derive(CandidType, Deserialize)] pub struct T (pub Box,); - #[derive(CandidType, Deserialize)] pub enum GRet { #[serde(rename="a")] A(Box) } - #[derive(CandidType, Deserialize)] pub enum HRet { #[serde(rename="a")] A(Box), #[serde(rename="b")] B{} } @@ -33,3 +29,4 @@ impl Service { } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/escape.rs b/rust/candid_parser/tests/assets/ok/escape.rs index 6325fdc9..c6b66fef 100644 --- a/rust/candid_parser/tests/assets/ok/escape.rs +++ b/rust/candid_parser/tests/assets/ok/escape.rs @@ -24,3 +24,4 @@ impl Service { } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/example.rs b/rust/candid_parser/tests/assets/ok/example.rs index 180f01f6..18891eb8 100644 --- a/rust/candid_parser/tests/assets/ok/example.rs +++ b/rust/candid_parser/tests/assets/ok/example.rs @@ -4,83 +4,88 @@ use candid::{self, CandidType, Deserialize, Principal, Encode, Decode}; use ic_cdk::api::call::CallResult as Result; -#[derive(CandidType, Deserialize)] -pub struct B (pub candid::Int,pub candid::Nat,); - -#[derive(CandidType, Deserialize)] -pub struct Node { pub head: candid::Nat, pub tail: Box } - -#[derive(CandidType, Deserialize)] -pub struct List(Option); - -pub type A = Box; -#[derive(CandidType, Deserialize)] -pub struct B(Option); - -#[derive(CandidType, Deserialize)] -pub enum Tree { +#[derive(CandidType, Deserialize, Debug)] +pub(crate) struct B (pub(crate) candid::Int,pub(crate) u128,); +#[derive(CandidType, Deserialize, Debug)] +pub(crate) struct Node { pub(crate) head: u128, pub(crate) tail: Box } +#[derive(CandidType, Deserialize, Debug)] +pub(crate) struct List(Option); +pub(crate) type A = Box; +#[derive(CandidType, Deserialize, Debug)] +pub(crate) struct B(Option); +#[derive(CandidType, Deserialize, Debug)] +pub(crate) enum Tree { #[serde(rename="branch")] Branch{ val: candid::Int, left: Box, right: Box }, #[serde(rename="leaf")] Leaf(candid::Int), } - -candid::define_function!(pub StreamInnerNext : () -> (Stream) query); -#[derive(CandidType, Deserialize)] -pub struct StreamInner { pub head: candid::Nat, pub next: StreamInnerNext } - -#[derive(CandidType, Deserialize)] -pub struct Stream(Option); - -candid::define_service!(pub S : { +candid::define_function!(pub(crate) StreamInnerNext : () -> (Stream) query); +#[derive(CandidType, Deserialize, Debug)] +pub(crate) struct StreamInner { + pub(crate) head: u128, + pub(crate) next: StreamInnerNext, +} +#[derive(CandidType, Deserialize, Debug)] +pub(crate) struct Stream(Option); +candid::define_service!(pub(crate) S : { "f" : T::ty(); "g" : candid::func!((List) -> (B, Tree, Stream)); }); -candid::define_function!(pub T : (S) -> ()); -pub type MyType = Principal; -#[derive(CandidType, Deserialize)] -pub struct ListInner { pub head: candid::Int, pub tail: Box } - -#[derive(CandidType, Deserialize)] -pub struct List(Option); - -#[derive(CandidType, Deserialize)] -pub struct Nested3 { pub _0_: candid::Nat, pub _42_: candid::Nat, pub _43_: u8 } - -#[derive(CandidType, Deserialize)] -pub enum Nested41 { _42_, A, B, C } - -#[derive(CandidType, Deserialize)] -pub struct Nested { - pub _0_: candid::Nat, - pub _1_: candid::Nat, - pub _2_: (candid::Nat,candid::Int,), - pub _3_: Nested3, - pub _40_: candid::Nat, - pub _41_: Nested41, - pub _42_: candid::Nat, +candid::define_function!(pub(crate) T : (S) -> ()); +type CanisterId = Principal; +#derive[CandidType, Deserialize, Clone] +pub(crate) struct ListInner { + #[serde(skip_deserializing)] + #[serde(rename="head")] + HEAD: candid::Int, + #[serde(skip_deserializing)] + tail: Arc, } - -candid::define_service!(pub BrokerFindRet : { +#[derive(CandidType, Deserialize, Debug)] +pub(crate) struct MyList(Option); +#[derive(CandidType, Deserialize, Debug)] +pub(crate) struct Nested3 { + pub(crate) _0_: u128, + pub(crate) _42_: u128, + pub(crate) _43_: u8, +} +#[derive(CandidType, Deserialize, Debug)] +pub(crate) enum Nested41 { + _42_, + #[serde(skip_deserializing)] + #[serde(rename="A")] + AAA, + B, + C, +} +#[derive(CandidType, Deserialize, Debug)] +pub(crate) struct Nested { + pub(crate) _0_: u128, + pub(crate) _1_: u128, + pub(crate) _2_: (u128,candid::Int,), + pub(crate) _3_: Nested3, + pub(crate) _40_: u128, + pub(crate) _41_: Nested41, + pub(crate) _42_: u128, +} +candid::define_service!(pub BrokerReturn : { "current" : candid::func!(() -> (u32)); "up" : candid::func!(() -> ()); }); -candid::define_service!(pub Broker : { - "find" : candid::func!((String) -> (BrokerFindRet)); +candid::define_service!(pub(crate) Broker : { + "find" : candid::func!((String) -> (BrokerReturn)); }); -#[derive(CandidType, Deserialize)] -pub enum HArg1 { A(candid::Nat), B(Option) } - -#[derive(CandidType, Deserialize)] -pub struct HRet42 {} - -#[derive(CandidType, Deserialize)] -pub struct HRet { pub _42_: HRet42, pub id: candid::Nat } - -candid::define_function!(pub FArg1 : (i32) -> (i64)); -candid::define_function!(pub F : (List, FArg1) -> (Option)); -#[derive(CandidType, Deserialize)] -pub enum A { #[serde(rename="a")] A, #[serde(rename="b")] B(B) } +#[derive(CandidType, Deserialize, Debug)] +pub(crate) enum HArg1 { A(u128), B(Option) } +#[derive(CandidType, Deserialize, Debug)] +pub(crate) struct HRet42 {} +#[derive(CandidType, Deserialize, Debug)] +pub(crate) struct HRet { pub(crate) _42_: HRet42, pub(crate) id: u128 } +candid::define_function!(pub(crate) FArg1 : (i32) -> (i64)); +candid::define_function!(pub(crate) F : (MyList, FArg1) -> (Option)); +#[derive(CandidType, Deserialize, Debug)] +pub(crate) enum A { #[serde(rename="a")] A, #[serde(rename="b")] B(B) } pub struct Service(pub Principal); impl Service { @@ -90,31 +95,19 @@ impl Service { pub async fn f(&self, arg0: S) -> Result<()> { ic_cdk::call(self.0, "f", (arg0,)).await } - pub async fn f_1( - &self, - arg0: List, - arg1: serde_bytes::ByteBuf, - arg2: Option, - ) -> Result<()> { ic_cdk::call(self.0, "f1", (arg0,arg1,arg2,)).await } + pub async fn f_1(&self, arg0: List, arg1: serde_bytes::ByteBuf, arg2: Option) -> Result<()> { + ic_cdk::call(self.0, "f1", (arg0,arg1,arg2,)).await + } pub async fn g(&self, arg0: List) -> Result<(B,Tree,Stream,)> { ic_cdk::call(self.0, "g", (arg0,)).await } - pub async fn g_1( - &self, - arg0: MyType, - arg1: List, - arg2: Option, - arg3: Nested, - ) -> Result<(candid::Int,Broker,)> { - ic_cdk::call(self.0, "g1", (arg0,arg1,arg2,arg3,)).await + pub async fn G11(&self, id: CanisterId, list: MyList, is_okay: Option, arg3: Nested) -> Result<(i128,Broker,)> { + ic_cdk::call(self.0, "g1", (id,list,is_okay,arg3,)).await } - pub async fn h( - &self, - arg0: Vec>, - arg1: HArg1, - arg2: Option, - ) -> Result<(HRet,)> { ic_cdk::call(self.0, "h", (arg0,arg1,arg2,)).await } - pub async fn i(&self, arg0: List, arg1: FArg1) -> Result<(Option,)> { + pub async fn h(&self, arg0: Vec>, arg1: HArg1, arg2: Option) -> Result<(HRet,)> { + ic_cdk::call(self.0, "h", (arg0,arg1,arg2,)).await + } + pub async fn i(&self, arg0: MyList, arg1: FArg1) -> Result<(Option,)> { ic_cdk::call(self.0, "i", (arg0,arg1,)).await } pub async fn x(&self, arg0: A, arg1: B) -> Result<(Option,Option,)> { @@ -123,3 +116,4 @@ impl Service { } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/fieldnat.rs b/rust/candid_parser/tests/assets/ok/fieldnat.rs index a42fec7c..8cbba81a 100644 --- a/rust/candid_parser/tests/assets/ok/fieldnat.rs +++ b/rust/candid_parser/tests/assets/ok/fieldnat.rs @@ -6,32 +6,24 @@ use ic_cdk::api::call::CallResult as Result; #[derive(CandidType, Deserialize)] pub struct BarArg { #[serde(rename="2")] pub _50_: candid::Int } - #[derive(CandidType, Deserialize)] pub enum BarRet { #[serde(rename="e20")] E20, #[serde(rename="e30")] E30 } - #[derive(CandidType, Deserialize)] pub struct BazArg { pub _2_: candid::Int, #[serde(rename="2")] pub _50_: candid::Nat, } - #[derive(CandidType, Deserialize)] pub struct BazRet {} - #[derive(CandidType, Deserialize)] pub struct Tuple (pub String,pub String,); - #[derive(CandidType, Deserialize)] pub struct NonTuple { pub _1_: String, pub _2_: String } - #[derive(CandidType, Deserialize)] pub enum BibRet { _0_(candid::Int) } - #[derive(CandidType, Deserialize)] pub struct FooArg { pub _2_: candid::Int } - #[derive(CandidType, Deserialize)] pub struct FooRet { pub _2_: candid::Int, pub _2: candid::Int } @@ -43,9 +35,9 @@ impl Service { pub async fn bar(&self, arg0: BarArg) -> Result<(BarRet,)> { ic_cdk::call(self.0, "bar", (arg0,)).await } - pub async fn bas(&self, arg0: (candid::Int,candid::Int,)) -> Result< - ((String,candid::Nat,),) - > { ic_cdk::call(self.0, "bas", (arg0,)).await } + pub async fn bas(&self, arg0: (candid::Int,candid::Int,)) -> Result<((String,candid::Nat,),)> { + ic_cdk::call(self.0, "bas", (arg0,)).await + } pub async fn baz(&self, arg0: BazArg) -> Result<(BazRet,)> { ic_cdk::call(self.0, "baz", (arg0,)).await } @@ -61,3 +53,4 @@ impl Service { } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/keyword.rs b/rust/candid_parser/tests/assets/ok/keyword.rs index 0010e120..fd6ce914 100644 --- a/rust/candid_parser/tests/assets/ok/keyword.rs +++ b/rust/candid_parser/tests/assets/ok/keyword.rs @@ -6,26 +6,20 @@ use ic_cdk::api::call::CallResult as Result; #[derive(CandidType, Deserialize)] pub struct O(Option>); - #[derive(CandidType, Deserialize)] pub struct FieldArg { pub test: u16, pub _1291438163_: u8 } - #[derive(CandidType, Deserialize)] pub struct FieldRet {} - #[derive(CandidType, Deserialize)] pub struct FieldnatArg { pub _2_: candid::Int, #[serde(rename="2")] pub _50_: candid::Nat, } - #[derive(CandidType, Deserialize)] pub struct Node { pub head: candid::Nat, pub tail: Box } - #[derive(CandidType, Deserialize)] pub struct List(Option); - #[derive(CandidType, Deserialize)] pub enum If { #[serde(rename="branch")] @@ -33,14 +27,11 @@ pub enum If { #[serde(rename="leaf")] Leaf(candid::Int), } - candid::define_function!(pub StreamInnerNext : () -> (Stream) query); #[derive(CandidType, Deserialize)] pub struct StreamInner { pub head: candid::Nat, pub next: StreamInnerNext } - #[derive(CandidType, Deserialize)] pub struct Stream(Option); - candid::define_service!(pub Return : { "f" : T::ty(); "g" : candid::func!((List) -> (If, Stream)); @@ -69,19 +60,16 @@ impl Service { pub async fn oneway(&self, arg0: u8) -> Result<()> { ic_cdk::call(self.0, "oneway_", (arg0,)).await } - pub async fn query(&self, arg0: serde_bytes::ByteBuf) -> Result< - (serde_bytes::ByteBuf,) - > { ic_cdk::call(self.0, "query", (arg0,)).await } + pub async fn query(&self, arg0: serde_bytes::ByteBuf) -> Result<(serde_bytes::ByteBuf,)> { + ic_cdk::call(self.0, "query", (arg0,)).await + } pub async fn r#return(&self, arg0: O) -> Result<(O,)> { ic_cdk::call(self.0, "return", (arg0,)).await } pub async fn service(&self, arg0: Return) -> Result<()> { ic_cdk::call(self.0, "service", (arg0,)).await } - pub async fn tuple( - &self, - arg0: (candid::Int,serde_bytes::ByteBuf,String,), - ) -> Result<((candid::Int,u8,),)> { + pub async fn tuple(&self, arg0: (candid::Int,serde_bytes::ByteBuf,String,)) -> Result<((candid::Int,u8,),)> { ic_cdk::call(self.0, "tuple", (arg0,)).await } pub async fn variant(&self, arg0: VariantArg) -> Result<()> { @@ -90,3 +78,4 @@ impl Service { } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/management.rs b/rust/candid_parser/tests/assets/ok/management.rs index a74b2a2b..a3715bbe 100644 --- a/rust/candid_parser/tests/assets/ok/management.rs +++ b/rust/candid_parser/tests/assets/ok/management.rs @@ -11,7 +11,6 @@ pub enum BitcoinNetwork { #[serde(rename="testnet")] Testnet, } - pub type BitcoinAddress = String; #[derive(CandidType, Deserialize)] pub struct GetBalanceRequest { @@ -19,11 +18,9 @@ pub struct GetBalanceRequest { pub address: BitcoinAddress, pub min_confirmations: Option, } - pub type Satoshi = u64; #[derive(CandidType, Deserialize)] pub struct GetCurrentFeePercentilesRequest { pub network: BitcoinNetwork } - pub type MillisatoshiPerByte = u64; #[derive(CandidType, Deserialize)] pub enum GetUtxosRequestFilterInner { @@ -32,21 +29,17 @@ pub enum GetUtxosRequestFilterInner { #[serde(rename="min_confirmations")] MinConfirmations(u32), } - #[derive(CandidType, Deserialize)] pub struct GetUtxosRequest { pub network: BitcoinNetwork, pub filter: Option, pub address: BitcoinAddress, } - pub type BlockHash = serde_bytes::ByteBuf; #[derive(CandidType, Deserialize)] pub struct Outpoint { pub txid: serde_bytes::ByteBuf, pub vout: u32 } - #[derive(CandidType, Deserialize)] pub struct Utxo { pub height: u32, pub value: Satoshi, pub outpoint: Outpoint } - #[derive(CandidType, Deserialize)] pub struct GetUtxosResponse { pub next_page: Option, @@ -54,17 +47,14 @@ pub struct GetUtxosResponse { pub tip_block_hash: BlockHash, pub utxos: Vec, } - #[derive(CandidType, Deserialize)] pub struct SendTransactionRequest { pub transaction: serde_bytes::ByteBuf, pub network: BitcoinNetwork, } - pub type CanisterId = Principal; #[derive(CandidType, Deserialize)] pub struct CanisterStatusArg { pub canister_id: CanisterId } - #[derive(CandidType, Deserialize)] pub enum CanisterStatusRetStatus { #[serde(rename="stopped")] @@ -74,7 +64,6 @@ pub enum CanisterStatusRetStatus { #[serde(rename="running")] Running, } - #[derive(CandidType, Deserialize)] pub struct DefiniteCanisterSettings { pub freezing_threshold: candid::Nat, @@ -82,7 +71,6 @@ pub struct DefiniteCanisterSettings { pub memory_allocation: candid::Nat, pub compute_allocation: candid::Nat, } - #[derive(CandidType, Deserialize)] pub struct CanisterStatusRet { pub status: CanisterStatusRetStatus, @@ -92,7 +80,6 @@ pub struct CanisterStatusRet { pub idle_cycles_burned_per_day: candid::Nat, pub module_hash: Option, } - #[derive(CandidType, Deserialize)] pub struct CanisterSettings { pub freezing_threshold: Option, @@ -100,38 +87,29 @@ pub struct CanisterSettings { pub memory_allocation: Option, pub compute_allocation: Option, } - #[derive(CandidType, Deserialize)] pub struct CreateCanisterArg { pub settings: Option } - #[derive(CandidType, Deserialize)] pub struct CreateCanisterRet { pub canister_id: CanisterId } - #[derive(CandidType, Deserialize)] pub struct DeleteCanisterArg { pub canister_id: CanisterId } - #[derive(CandidType, Deserialize)] pub struct DepositCyclesArg { pub canister_id: CanisterId } - #[derive(CandidType, Deserialize)] pub enum EcdsaCurve { #[serde(rename="secp256k1")] Secp256K1 } - #[derive(CandidType, Deserialize)] pub struct EcdsaPublicKeyArgKeyId { pub name: String, pub curve: EcdsaCurve } - #[derive(CandidType, Deserialize)] pub struct EcdsaPublicKeyArg { pub key_id: EcdsaPublicKeyArgKeyId, pub canister_id: Option, pub derivation_path: Vec, } - #[derive(CandidType, Deserialize)] pub struct EcdsaPublicKeyRet { pub public_key: serde_bytes::ByteBuf, pub chain_code: serde_bytes::ByteBuf, } - #[derive(CandidType, Deserialize)] pub enum HttpRequestArgMethod { #[serde(rename="get")] @@ -141,23 +119,19 @@ pub enum HttpRequestArgMethod { #[serde(rename="post")] Post, } - #[derive(CandidType, Deserialize)] pub struct HttpHeader { pub value: String, pub name: String } - #[derive(CandidType, Deserialize)] pub struct HttpResponse { pub status: candid::Nat, pub body: serde_bytes::ByteBuf, pub headers: Vec, } - #[derive(CandidType, Deserialize)] pub struct HttpRequestArgTransformInnerFunctionArg { pub context: serde_bytes::ByteBuf, pub response: HttpResponse, } - candid::define_function!(pub HttpRequestArgTransformInnerFunction : ( HttpRequestArgTransformInnerFunctionArg, ) -> (HttpResponse) query); @@ -166,7 +140,6 @@ pub struct HttpRequestArgTransformInner { pub function: HttpRequestArgTransformInnerFunction, pub context: serde_bytes::ByteBuf, } - #[derive(CandidType, Deserialize)] pub struct HttpRequestArg { pub url: String, @@ -176,7 +149,6 @@ pub struct HttpRequestArg { pub transform: Option, pub headers: Vec, } - pub type WasmModule = serde_bytes::ByteBuf; #[derive(CandidType, Deserialize)] pub enum InstallCodeArgMode { @@ -187,7 +159,6 @@ pub enum InstallCodeArgMode { #[serde(rename="install")] Install, } - #[derive(CandidType, Deserialize)] pub struct InstallCodeArg { pub arg: serde_bytes::ByteBuf, @@ -195,47 +166,37 @@ pub struct InstallCodeArg { pub mode: InstallCodeArgMode, pub canister_id: CanisterId, } - #[derive(CandidType, Deserialize)] pub struct ProvisionalCreateCanisterWithCyclesArg { pub settings: Option, pub specified_id: Option, pub amount: Option, } - #[derive(CandidType, Deserialize)] pub struct ProvisionalCreateCanisterWithCyclesRet { pub canister_id: CanisterId, } - #[derive(CandidType, Deserialize)] pub struct ProvisionalTopUpCanisterArg { pub canister_id: CanisterId, pub amount: candid::Nat, } - #[derive(CandidType, Deserialize)] pub struct SignWithEcdsaArgKeyId { pub name: String, pub curve: EcdsaCurve } - #[derive(CandidType, Deserialize)] pub struct SignWithEcdsaArg { pub key_id: SignWithEcdsaArgKeyId, pub derivation_path: Vec, pub message_hash: serde_bytes::ByteBuf, } - #[derive(CandidType, Deserialize)] pub struct SignWithEcdsaRet { pub signature: serde_bytes::ByteBuf } - #[derive(CandidType, Deserialize)] pub struct StartCanisterArg { pub canister_id: CanisterId } - #[derive(CandidType, Deserialize)] pub struct StopCanisterArg { pub canister_id: CanisterId } - #[derive(CandidType, Deserialize)] pub struct UninstallCodeArg { pub canister_id: CanisterId } - #[derive(CandidType, Deserialize)] pub struct UpdateSettingsArg { pub canister_id: Principal, @@ -244,46 +205,32 @@ pub struct UpdateSettingsArg { pub struct Service<'a>(pub Principal, pub &'a ic_agent::Agent); impl<'a> Service<'a> { - pub async fn bitcoin_get_balance(&self, arg0: GetBalanceRequest) -> Result< - Satoshi - > { + pub async fn bitcoin_get_balance(&self, arg0: GetBalanceRequest) -> Result { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "bitcoin_get_balance").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes, Satoshi)?) } - pub async fn bitcoin_get_current_fee_percentiles( - &self, - arg0: GetCurrentFeePercentilesRequest, - ) -> Result> { + pub async fn bitcoin_get_current_fee_percentiles(&self, arg0: GetCurrentFeePercentilesRequest) -> Result> { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "bitcoin_get_current_fee_percentiles").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes, Vec)?) } - pub async fn bitcoin_get_utxos(&self, arg0: GetUtxosRequest) -> Result< - GetUtxosResponse - > { + pub async fn bitcoin_get_utxos(&self, arg0: GetUtxosRequest) -> Result { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "bitcoin_get_utxos").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes, GetUtxosResponse)?) } - pub async fn bitcoin_send_transaction( - &self, - arg0: SendTransactionRequest, - ) -> Result<()> { + pub async fn bitcoin_send_transaction(&self, arg0: SendTransactionRequest) -> Result<()> { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "bitcoin_send_transaction").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes)?) } - pub async fn canister_status(&self, arg0: CanisterStatusArg) -> Result< - CanisterStatusRet - > { + pub async fn canister_status(&self, arg0: CanisterStatusArg) -> Result { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "canister_status").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes, CanisterStatusRet)?) } - pub async fn create_canister(&self, arg0: CreateCanisterArg) -> Result< - CreateCanisterRet - > { + pub async fn create_canister(&self, arg0: CreateCanisterArg) -> Result { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "create_canister").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes, CreateCanisterRet)?) @@ -298,16 +245,12 @@ impl<'a> Service<'a> { let bytes = self.1.update(&self.0, "deposit_cycles").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes)?) } - pub async fn ecdsa_public_key(&self, arg0: EcdsaPublicKeyArg) -> Result< - EcdsaPublicKeyRet - > { + pub async fn ecdsa_public_key(&self, arg0: EcdsaPublicKeyArg) -> Result { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "ecdsa_public_key").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes, EcdsaPublicKeyRet)?) } - pub async fn http_request(&self, arg0: HttpRequestArg) -> Result< - HttpResponse - > { + pub async fn http_request(&self, arg0: HttpRequestArg) -> Result { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "http_request").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes, HttpResponse)?) @@ -317,18 +260,12 @@ impl<'a> Service<'a> { let bytes = self.1.update(&self.0, "install_code").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes)?) } - pub async fn provisional_create_canister_with_cycles( - &self, - arg0: ProvisionalCreateCanisterWithCyclesArg, - ) -> Result { + pub async fn provisional_create_canister_with_cycles(&self, arg0: ProvisionalCreateCanisterWithCyclesArg) -> Result { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "provisional_create_canister_with_cycles").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes, ProvisionalCreateCanisterWithCyclesRet)?) } - pub async fn provisional_top_up_canister( - &self, - arg0: ProvisionalTopUpCanisterArg, - ) -> Result<()> { + pub async fn provisional_top_up_canister(&self, arg0: ProvisionalTopUpCanisterArg) -> Result<()> { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "provisional_top_up_canister").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes)?) @@ -338,9 +275,7 @@ impl<'a> Service<'a> { let bytes = self.1.update(&self.0, "raw_rand").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes, serde_bytes::ByteBuf)?) } - pub async fn sign_with_ecdsa(&self, arg0: SignWithEcdsaArg) -> Result< - SignWithEcdsaRet - > { + pub async fn sign_with_ecdsa(&self, arg0: SignWithEcdsaArg) -> Result { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "sign_with_ecdsa").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes, SignWithEcdsaRet)?) diff --git a/rust/candid_parser/tests/assets/ok/recursion.rs b/rust/candid_parser/tests/assets/ok/recursion.rs index 25095093..939a8378 100644 --- a/rust/candid_parser/tests/assets/ok/recursion.rs +++ b/rust/candid_parser/tests/assets/ok/recursion.rs @@ -7,14 +7,11 @@ use ic_cdk::api::call::CallResult as Result; candid::define_function!(pub T : (S) -> ()); #[derive(CandidType, Deserialize)] pub struct Node { pub head: candid::Nat, pub tail: Box } - #[derive(CandidType, Deserialize)] pub struct List(Option); - pub type A = Box; #[derive(CandidType, Deserialize)] pub struct B(Option); - #[derive(CandidType, Deserialize)] pub enum Tree { #[serde(rename="branch")] @@ -22,18 +19,16 @@ pub enum Tree { #[serde(rename="leaf")] Leaf(candid::Int), } - candid::define_function!(pub StreamInnerNext : () -> (Stream) query); #[derive(CandidType, Deserialize)] pub struct StreamInner { pub head: candid::Nat, pub next: StreamInnerNext } - #[derive(CandidType, Deserialize)] pub struct Stream(Option); - candid::define_service!(pub S : { "f" : T::ty(); "g" : candid::func!((List) -> (B, Tree, Stream)); }); + pub struct Service(pub Principal); impl Service { pub async fn f(&self, arg0: S) -> Result<()> { @@ -45,3 +40,4 @@ impl Service { } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/recursive_class.rs b/rust/candid_parser/tests/assets/ok/recursive_class.rs index 3edd2249..eda0084e 100644 --- a/rust/candid_parser/tests/assets/ok/recursive_class.rs +++ b/rust/candid_parser/tests/assets/ok/recursive_class.rs @@ -5,6 +5,7 @@ use candid::{self, CandidType, Deserialize, Principal, Encode, Decode}; use ic_cdk::api::call::CallResult as Result; candid::define_service!(pub S : { "next" : candid::func!(() -> (S)) }); + pub struct Service(pub Principal); impl Service { pub async fn next(&self) -> Result<(S,)> { @@ -13,3 +14,4 @@ impl Service { } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/service.rs b/rust/candid_parser/tests/assets/ok/service.rs index d4ac89b4..bdf308f9 100644 --- a/rust/candid_parser/tests/assets/ok/service.rs +++ b/rust/candid_parser/tests/assets/ok/service.rs @@ -32,3 +32,4 @@ impl Service { } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/unicode.rs b/rust/candid_parser/tests/assets/ok/unicode.rs index 6bb2c33f..ca077654 100644 --- a/rust/candid_parser/tests/assets/ok/unicode.rs +++ b/rust/candid_parser/tests/assets/ok/unicode.rs @@ -15,7 +15,6 @@ pub struct A { #[serde(rename="字 段 名2")] pub _3133479156_: candid::Nat, } - #[derive(CandidType, Deserialize)] pub enum B { #[serde(rename="")] @@ -39,9 +38,10 @@ impl Service { pub async fn _3300066460_(&self, arg0: A) -> Result<(B,)> { ic_cdk::call(self.0, "函数名", (arg0,)).await } - pub async fn _2669435454_(&self, arg0: candid::Nat) -> Result< - (candid::Nat,) - > { ic_cdk::call(self.0, "👀", (arg0,)).await } + pub async fn _2669435454_(&self, arg0: candid::Nat) -> Result<(candid::Nat,)> { + ic_cdk::call(self.0, "👀", (arg0,)).await + } } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/parse_type.rs b/rust/candid_parser/tests/parse_type.rs index 7dfb2fb6..2d7bb219 100644 --- a/rust/candid_parser/tests/parse_type.rs +++ b/rust/candid_parser/tests/parse_type.rs @@ -1,6 +1,7 @@ use candid::pretty::candid::compile; use candid::types::TypeEnv; use candid_parser::bindings::{javascript, motoko, rust, typescript}; +use candid_parser::configs::Configs; use candid_parser::types::IDLProg; use candid_parser::typing::{check_file, check_prog}; use goldenfile::Mint; @@ -62,13 +63,28 @@ fn compiler_test(resource: &str) { } } { - let mut config = rust::Config::new(); - config.set_canister_id(candid::Principal::from_text("aaaaa-aa").unwrap()); - if filename.file_name().unwrap().to_str().unwrap() == "management.did" { - config.set_target(rust::Target::Agent); + use rust::{Config, ExternalConfig}; + use std::str::FromStr; + let mut config = Config::new(Configs::from_str("").unwrap()); + let mut external = ExternalConfig::default(); + external + .0 + .insert("canister_id".to_string(), "aaaaa-aa".to_string()); + match filename.file_name().unwrap().to_str().unwrap() { + "management.did" => { + drop(external.0.insert("target".to_string(), "agent".to_string())) + } + "example.did" => { + let configs = std::fs::read_to_string(base_path.join("example.toml")) + .unwrap() + .parse::() + .unwrap(); + config = Config::new(configs); + } + _ => (), } let mut output = mint.new_goldenfile(filename.with_extension("rs")).unwrap(); - let content = rust::compile(&config, &env, &actor); + let content = rust::compile(&config, &env, &actor, external); writeln!(output, "{content}").unwrap(); } { diff --git a/tools/didc/Cargo.toml b/tools/didc/Cargo.toml index b1117a5f..7243aa19 100644 --- a/tools/didc/Cargo.toml +++ b/tools/didc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "didc" -version = "0.3.7" +version = "0.4.0" authors = ["DFINITY Team"] edition = "2021" diff --git a/tools/didc/random.dhall b/tools/didc/random.dhall deleted file mode 100644 index 81a6ca90..00000000 --- a/tools/didc/random.dhall +++ /dev/null @@ -1,30 +0,0 @@ -let default = - { range = None (List Natural) - , text = Some "emoji" - , width = Some 10 - , depth = Some 5 - , size = Some 100 - , value = None (List Text) - } - -in default - ∧ { `[h]` = { `[0]`.a.range = Some [ 42, 42 ], b.range = Some [ 0, 1 ] } - , list = { depth = Some 20, size = Some 50 } - , val.value = Some [ "42", "-1" ] - , left = { depth = Some 1, range = Some [ -200, -100 ] } - , right.tree = { depth = Some 5, range = Some [ 100, 200 ] } - , vec.nat8.range = Some [ 65, 90 ] - , Vec = { width = Some 2, size = Some 10 } - , profile.record - = - { name.text = Some "name" - , age.range = Some [ 18, 65 ] - , company.text = Some "company" - , country.text = Some "country" - , file.text = Some "path" - , description.text = Some "bs" - } - , principal.value - = Some - [ "principal \"aaaaa-aa\"", "principal \"2ibo7-dia\"" ] - } diff --git a/tools/didc/random.toml b/tools/didc/random.toml new file mode 100644 index 00000000..1b4f7f28 --- /dev/null +++ b/tools/didc/random.toml @@ -0,0 +1,24 @@ + +[random] +list = { depth = 20, size = 50 } +val.value = [ "42", "-1" ] +left = { depth = 1, range = [-200, -100] } +right.tree = { depth = 5, range = [100, 200] } +vec.nat8.range = [ 65, 90 ] +Vec = { width = 2, size = 10 } +text = "company" + +[random."method:h"] +"arg:0".a.range = [ 42, 42 ] +b.range = [ 0, 1 ] + +[random.profile.record] +name.text = "name" +age.range = [ 18, 65 ] +company.text = "company" +country.text = "country" +file.text = "path" +description = "bs" + +[random.principal] +value = [ "principal \"aaaaa-aa\"", "principal \"2ibo7-dia\"" ] diff --git a/tools/didc/src/main.rs b/tools/didc/src/main.rs index 9c5378f4..daeef0ca 100644 --- a/tools/didc/src/main.rs +++ b/tools/didc/src/main.rs @@ -1,6 +1,7 @@ use anyhow::{bail, Result}; use candid_parser::candid::types::{subtype, Type}; use candid_parser::{ + configs::Configs, parse_idl_args, parse_idl_value, pretty_check_file, pretty_parse, pretty_wrap, types::{IDLType, IDLTypes}, typing::ast_to_type, @@ -10,6 +11,7 @@ use clap::Parser; use std::collections::HashSet; use std::io; use std::path::PathBuf; +use std::str::FromStr; #[derive(Parser)] #[clap(version, author)] @@ -31,6 +33,9 @@ enum Command { #[clap(short, long, value_parser = ["js", "ts", "did", "mo", "rs", "rs-agent"])] /// Specifies target language target: String, + #[clap(short, long)] + /// Specifies binding generation config in TOML syntax + config: Option, }, /// Generate test suites for different languages Test { @@ -74,12 +79,9 @@ enum Command { Random { #[clap(flatten)] annotate: TypeAnnotation, - #[clap(short, long, conflicts_with("file"))] - /// Specifies random value generation config in Dhall syntax - config: Option, #[clap(short, long)] - /// Load random value generation config from file - file: Option, + /// Specifies random value generation config in TOML syntax + config: Option, #[clap(short, long, value_parser = ["did", "js"], default_value = "did")] /// Specifies target language lang: String, @@ -157,6 +159,13 @@ fn parse_args(str: &str) -> Result { fn parse_types(str: &str) -> Result { pretty_parse("type annotations", str) } +fn load_config(input: &Option) -> Result { + match input { + None => Configs::from_str(""), + Some(str) if str.ends_with(".toml") => Configs::from_str(&std::fs::read_to_string(str)?), + Some(str) => Configs::from_str(str), + } +} fn main() -> Result<()> { match Command::parse() { @@ -194,7 +203,12 @@ fn main() -> Result<()> { let ty2 = ast_to_type(&env, &ty2)?; subtype::subtype(&mut HashSet::new(), &env, &ty1, &ty2)?; } - Command::Bind { input, target } => { + Command::Bind { + input, + target, + config, + } => { + let configs = load_config(&config)?; let (env, actor) = pretty_check_file(&input)?; let content = match target.as_str() { "js" => candid_parser::bindings::javascript::compile(&env, &actor), @@ -202,13 +216,16 @@ fn main() -> Result<()> { "did" => candid_parser::pretty::candid::compile(&env, &actor), "mo" => candid_parser::bindings::motoko::compile(&env, &actor), "rs" => { - let config = candid_parser::bindings::rust::Config::new(); - candid_parser::bindings::rust::compile(&config, &env, &actor) + use candid_parser::bindings::rust::{compile, Config, ExternalConfig}; + let config = Config::new(configs); + compile(&config, &env, &actor, ExternalConfig::default()) } "rs-agent" => { - let mut config = candid_parser::bindings::rust::Config::new(); - config.set_target(candid_parser::bindings::rust::Target::Agent); - candid_parser::bindings::rust::compile(&config, &env, &actor) + use candid_parser::bindings::rust::{compile, Config, ExternalConfig}; + let config = Config::new(configs); + let mut external = ExternalConfig::default(); + external.0.insert("target".to_string(), "agent".to_string()); + compile(&config, &env, &actor, external) } _ => unreachable!(), }; @@ -305,31 +322,16 @@ fn main() -> Result<()> { annotate, lang, config, - file, args, } => { - use candid_parser::configs::Configs; + use candid_parser::configs::{Scope, ScopePos}; use rand::Rng; let (env, types) = if args.is_some() { annotate.get_types(Mode::Decode)? } else { annotate.get_types(Mode::Encode)? }; - let config = match (config, file) { - (None, None) => Configs::from_dhall("{=}")?, - (Some(str), None) => Configs::from_dhall(&str)?, - (None, Some(file)) => { - let content = std::fs::read_to_string(&file) - .map_err(|_| Error::msg(format!("could not read {file}")))?; - Configs::from_dhall(&content)? - } - _ => unreachable!(), - }; - let config = if let Some(ref method) = annotate.method { - config.with_method(method) - } else { - config - }; + let config = load_config(&config)?; // TODO figure out how many bytes of entropy we need let seed: Vec = if let Some(ref args) = args { let (env, types) = annotate.get_types(Mode::Encode)?; @@ -339,7 +341,15 @@ fn main() -> Result<()> { let mut rng = rand::thread_rng(); (0..2048).map(|_| rng.gen::()).collect() }; - let args = candid_parser::random::any(&seed, &config, &env, &types)?; + let scope = annotate.method.as_ref().map(|method| { + let position = Some(if args.is_some() { + ScopePos::Ret + } else { + ScopePos::Arg + }); + Scope { position, method } + }); + let args = candid_parser::random::any(&seed, config, &env, &types, &scope)?; match lang.as_str() { "did" => println!("{args}"), "js" => println!(