From aef7dcdc2c7f742c0bea0f13f0a72467ed2db5cb Mon Sep 17 00:00:00 2001 From: Linwei Shang Date: Mon, 23 Oct 2023 18:33:46 -0400 Subject: [PATCH] feat!: split candid crate (#471) Currently, the `candid` crate mainly consists of two parts: * Candid data encoding/decoding * Parser and derived functionality (bindgen) The second part evolves fast which forces us to bump major version frequently. Such version bumps propagated to the downstream projects (agent-rs, cdk-rs, sdk, ic monorepo, etc). By splitting out `candid_parser`, we expect to have a stable `candid` crate. Also, I move `Principal` into a separate `ic_principal` crate. Therefore, we can provide a lightweight dependency for those who only need `Principal`. ## Notable changes * Some private or crate public methods are now public since they are called cross-crate * Add `parse_idl_args()` and `parse_idl_value()` because in `candid_parser` we cannot implement `FromStr` for `IDLArgs` and `IDLValue` which are defined in `candid`. * `TypeEnv` is defined in `candid`, so we can't implement type method `ast_to_type` in `candid_parser`. Instead I turned it into a function which takes `TypeEnv` as the first argument. Similarly, `size_helper()` and `size()` are changed. --- Cargo.lock | 338 ++++++++-------- Cargo.toml | 2 + Changelog.md | 7 + rust/candid/Cargo.toml | 59 +-- rust/candid/src/bindings/mod.rs | 20 - rust/candid/src/error.rs | 125 +----- rust/candid/src/lib.rs | 124 +----- rust/candid/src/parser/grammar.rs | 2 - rust/candid/src/parser/mod.rs | 36 -- .../{bindings/candid.rs => pretty_printer.rs} | 13 +- rust/candid/src/types/internal.rs | 24 +- rust/candid/src/types/number.rs | 2 +- rust/candid/src/types/principal.rs | 354 +---------------- rust/candid/src/types/subtype.rs | 2 +- rust/candid/src/types/value.rs | 40 +- rust/candid/src/utils.rs | 97 ----- rust/candid_derive/src/func.rs | 2 +- rust/candid_parser/Cargo.toml | 61 +++ rust/candid_parser/LICENSE | 1 + rust/{candid => candid_parser}/build.rs | 3 +- .../src/bindings/analysis.rs | 2 +- .../src/bindings/javascript.rs | 26 +- rust/candid_parser/src/bindings/mod.rs | 8 + .../src/bindings/motoko.rs | 12 +- .../src/bindings/rust.rs | 10 +- .../src/bindings/typescript.rs | 4 +- .../parser => candid_parser/src}/configs.rs | 2 +- rust/candid_parser/src/error.rs | 126 ++++++ .../src}/grammar.lalrpop | 10 +- rust/candid_parser/src/grammar.rs | 2 + rust/candid_parser/src/lib.rs | 147 +++++++ .../parser => candid_parser/src}/random.rs | 112 +++--- .../src/parser => candid_parser/src}/test.rs | 10 +- .../src/parser => candid_parser/src}/token.rs | 0 .../src/parser => candid_parser/src}/types.rs | 2 +- .../parser => candid_parser/src}/typing.rs | 20 +- rust/candid_parser/src/utils.rs | 82 ++++ .../tests/assets/actor.did | 0 .../tests/assets/bad_comment.did | 0 .../tests/assets/bad_import.did | 0 .../tests/assets/class.did | 0 .../tests/assets/codegen/basic/invalid_id.did | 0 .../tests/assets/codegen/basic/invalid_id.rs | 0 .../tests/assets/codegen/basic/prim_types.did | 0 .../tests/assets/codegen/basic/prim_types.rs | 0 .../tests/assets/codegen/basic/recursive.did | 0 .../tests/assets/codegen/basic/recursive.rs | 0 .../assets/codegen/examples/linkedup.did | 0 .../tests/assets/codegen/examples/linkedup.rs | 0 .../tests/assets/collision_fields.did | 0 .../tests/assets/collision_fields2.did | 0 .../tests/assets/comment.did | 0 .../tests/assets/cyclic.did | 0 .../tests/assets/escape.did | 0 .../tests/assets/example.did | 0 .../tests/assets/fieldnat.did | 0 .../tests/assets/import/a.did | 0 .../tests/assets/import/b/b.did | 0 .../tests/assets/invalid_cyclic.did | 0 .../tests/assets/keyword.did | 0 .../tests/assets/management.did | 0 .../tests/assets/not_func.did | 0 .../tests/assets/not_serv.did | 0 .../tests/assets/ok/actor.d.ts | 0 .../tests/assets/ok/actor.did | 0 .../tests/assets/ok/actor.js | 0 .../tests/assets/ok/actor.mo | 0 .../tests/assets/ok/actor.rs | 0 .../tests/assets/ok/bad_comment.fail | 0 .../tests/assets/ok/bad_import.fail | 0 .../tests/assets/ok/class.d.ts | 0 .../tests/assets/ok/class.did | 0 .../tests/assets/ok/class.js | 0 .../tests/assets/ok/class.mo | 0 .../tests/assets/ok/class.rs | 0 .../tests/assets/ok/collision_fields.fail | 0 .../tests/assets/ok/collision_fields2.fail | 0 .../tests/assets/ok/comment.d.ts | 0 .../tests/assets/ok/comment.did | 0 .../tests/assets/ok/comment.js | 0 .../tests/assets/ok/comment.mo | 0 .../tests/assets/ok/comment.rs | 0 .../tests/assets/ok/cyclic.d.ts | 0 .../tests/assets/ok/cyclic.did | 0 .../tests/assets/ok/cyclic.js | 0 .../tests/assets/ok/cyclic.mo | 0 .../tests/assets/ok/cyclic.rs | 0 .../tests/assets/ok/escape.d.ts | 0 .../tests/assets/ok/escape.did | 0 .../tests/assets/ok/escape.js | 0 .../tests/assets/ok/escape.rs | 0 .../tests/assets/ok/example.d.ts | 0 .../tests/assets/ok/example.did | 0 .../tests/assets/ok/example.js | 0 .../tests/assets/ok/example.mo | 0 .../tests/assets/ok/example.rs | 0 .../tests/assets/ok/fieldnat.d.ts | 0 .../tests/assets/ok/fieldnat.did | 0 .../tests/assets/ok/fieldnat.js | 0 .../tests/assets/ok/fieldnat.mo | 0 .../tests/assets/ok/fieldnat.rs | 0 .../tests/assets/ok/invalid_cyclic.fail | 0 .../tests/assets/ok/keyword.d.ts | 0 .../tests/assets/ok/keyword.did | 0 .../tests/assets/ok/keyword.js | 0 .../tests/assets/ok/keyword.mo | 0 .../tests/assets/ok/keyword.rs | 0 .../tests/assets/ok/management.d.ts | 0 .../tests/assets/ok/management.did | 0 .../tests/assets/ok/management.js | 0 .../tests/assets/ok/management.mo | 0 .../tests/assets/ok/management.rs | 0 .../tests/assets/ok/not_func.fail | 0 .../tests/assets/ok/not_serv.fail | 0 .../tests/assets/ok/oneway.fail | 0 .../tests/assets/ok/recursion.d.ts | 0 .../tests/assets/ok/recursion.did | 0 .../tests/assets/ok/recursion.js | 0 .../tests/assets/ok/recursion.mo | 0 .../tests/assets/ok/recursion.rs | 0 .../tests/assets/ok/recursive_class.d.ts | 0 .../tests/assets/ok/recursive_class.did | 0 .../tests/assets/ok/recursive_class.js | 0 .../tests/assets/ok/recursive_class.mo | 0 .../tests/assets/ok/recursive_class.rs | 0 .../tests/assets/ok/service.d.ts | 0 .../tests/assets/ok/service.did | 0 .../tests/assets/ok/service.js | 0 .../tests/assets/ok/service.mo | 0 .../tests/assets/ok/service.rs | 0 .../tests/assets/ok/surrogate.fail | 0 .../tests/assets/ok/undefine.fail | 0 .../tests/assets/ok/unicode.d.ts | 0 .../tests/assets/ok/unicode.did | 0 .../tests/assets/ok/unicode.js | 0 .../tests/assets/ok/unicode.rs | 0 .../tests/assets/oneway.did | 0 .../tests/assets/recursion.did | 0 .../tests/assets/recursive_class.did | 0 .../tests/assets/service.did | 0 .../tests/assets/surrogate.did | 0 .../tests/assets/undefine.did | 0 .../tests/assets/unicode.did | 0 .../tests/parse_type.rs | 11 +- .../tests/parse_value.rs | 11 +- .../tests/test_suite.rs | 4 +- rust/{candid => candid_parser}/tests/value.rs | 10 +- rust/ic_principal/Cargo.toml | 39 ++ rust/ic_principal/LICENSE | 1 + rust/ic_principal/README.md | 5 + rust/ic_principal/src/lib.rs | 360 ++++++++++++++++++ .../tests/principal.rs | 5 +- tools/didc/Cargo.toml | 3 +- tools/didc/src/main.rs | 63 +-- 154 files changed, 1236 insertions(+), 1163 deletions(-) delete mode 100644 rust/candid/src/bindings/mod.rs delete mode 100644 rust/candid/src/parser/grammar.rs delete mode 100644 rust/candid/src/parser/mod.rs rename rust/candid/src/{bindings/candid.rs => pretty_printer.rs} (97%) create mode 100644 rust/candid_parser/Cargo.toml create mode 120000 rust/candid_parser/LICENSE rename rust/{candid => candid_parser}/build.rs (62%) rename rust/{candid => candid_parser}/src/bindings/analysis.rs (98%) rename rust/{candid => candid_parser}/src/bindings/javascript.rs (95%) create mode 100644 rust/candid_parser/src/bindings/mod.rs rename rust/{candid => candid_parser}/src/bindings/motoko.rs (95%) rename rust/{candid => candid_parser}/src/bindings/rust.rs (98%) rename rust/{candid => candid_parser}/src/bindings/typescript.rs (98%) rename rust/{candid/src/parser => candid_parser/src}/configs.rs (99%) create mode 100644 rust/candid_parser/src/error.rs rename rust/{candid/src/parser => candid_parser/src}/grammar.lalrpop (97%) create mode 100644 rust/candid_parser/src/grammar.rs create mode 100644 rust/candid_parser/src/lib.rs rename rust/{candid/src/parser => candid_parser/src}/random.rs (82%) rename rust/{candid/src/parser => candid_parser/src}/test.rs (95%) rename rust/{candid/src/parser => candid_parser/src}/token.rs (100%) rename rust/{candid/src/parser => candid_parser/src}/types.rs (98%) rename rust/{candid/src/parser => candid_parser/src}/typing.rs (95%) create mode 100644 rust/candid_parser/src/utils.rs rename rust/{candid => candid_parser}/tests/assets/actor.did (100%) rename rust/{candid => candid_parser}/tests/assets/bad_comment.did (100%) rename rust/{candid => candid_parser}/tests/assets/bad_import.did (100%) rename rust/{candid => candid_parser}/tests/assets/class.did (100%) rename rust/{candid => candid_parser}/tests/assets/codegen/basic/invalid_id.did (100%) rename rust/{candid => candid_parser}/tests/assets/codegen/basic/invalid_id.rs (100%) rename rust/{candid => candid_parser}/tests/assets/codegen/basic/prim_types.did (100%) rename rust/{candid => candid_parser}/tests/assets/codegen/basic/prim_types.rs (100%) rename rust/{candid => candid_parser}/tests/assets/codegen/basic/recursive.did (100%) rename rust/{candid => candid_parser}/tests/assets/codegen/basic/recursive.rs (100%) rename rust/{candid => candid_parser}/tests/assets/codegen/examples/linkedup.did (100%) rename rust/{candid => candid_parser}/tests/assets/codegen/examples/linkedup.rs (100%) rename rust/{candid => candid_parser}/tests/assets/collision_fields.did (100%) rename rust/{candid => candid_parser}/tests/assets/collision_fields2.did (100%) rename rust/{candid => candid_parser}/tests/assets/comment.did (100%) rename rust/{candid => candid_parser}/tests/assets/cyclic.did (100%) rename rust/{candid => candid_parser}/tests/assets/escape.did (100%) rename rust/{candid => candid_parser}/tests/assets/example.did (100%) rename rust/{candid => candid_parser}/tests/assets/fieldnat.did (100%) rename rust/{candid => candid_parser}/tests/assets/import/a.did (100%) rename rust/{candid => candid_parser}/tests/assets/import/b/b.did (100%) rename rust/{candid => candid_parser}/tests/assets/invalid_cyclic.did (100%) rename rust/{candid => candid_parser}/tests/assets/keyword.did (100%) rename rust/{candid => candid_parser}/tests/assets/management.did (100%) rename rust/{candid => candid_parser}/tests/assets/not_func.did (100%) rename rust/{candid => candid_parser}/tests/assets/not_serv.did (100%) rename rust/{candid => candid_parser}/tests/assets/ok/actor.d.ts (100%) rename rust/{candid => candid_parser}/tests/assets/ok/actor.did (100%) rename rust/{candid => candid_parser}/tests/assets/ok/actor.js (100%) rename rust/{candid => candid_parser}/tests/assets/ok/actor.mo (100%) rename rust/{candid => candid_parser}/tests/assets/ok/actor.rs (100%) rename rust/{candid => candid_parser}/tests/assets/ok/bad_comment.fail (100%) rename rust/{candid => candid_parser}/tests/assets/ok/bad_import.fail (100%) rename rust/{candid => candid_parser}/tests/assets/ok/class.d.ts (100%) rename rust/{candid => candid_parser}/tests/assets/ok/class.did (100%) rename rust/{candid => candid_parser}/tests/assets/ok/class.js (100%) rename rust/{candid => candid_parser}/tests/assets/ok/class.mo (100%) rename rust/{candid => candid_parser}/tests/assets/ok/class.rs (100%) rename rust/{candid => candid_parser}/tests/assets/ok/collision_fields.fail (100%) rename rust/{candid => candid_parser}/tests/assets/ok/collision_fields2.fail (100%) rename rust/{candid => candid_parser}/tests/assets/ok/comment.d.ts (100%) rename rust/{candid => candid_parser}/tests/assets/ok/comment.did (100%) rename rust/{candid => candid_parser}/tests/assets/ok/comment.js (100%) rename rust/{candid => candid_parser}/tests/assets/ok/comment.mo (100%) rename rust/{candid => candid_parser}/tests/assets/ok/comment.rs (100%) rename rust/{candid => candid_parser}/tests/assets/ok/cyclic.d.ts (100%) rename rust/{candid => candid_parser}/tests/assets/ok/cyclic.did (100%) rename rust/{candid => candid_parser}/tests/assets/ok/cyclic.js (100%) rename rust/{candid => candid_parser}/tests/assets/ok/cyclic.mo (100%) rename rust/{candid => candid_parser}/tests/assets/ok/cyclic.rs (100%) rename rust/{candid => candid_parser}/tests/assets/ok/escape.d.ts (100%) rename rust/{candid => candid_parser}/tests/assets/ok/escape.did (100%) rename rust/{candid => candid_parser}/tests/assets/ok/escape.js (100%) rename rust/{candid => candid_parser}/tests/assets/ok/escape.rs (100%) rename rust/{candid => candid_parser}/tests/assets/ok/example.d.ts (100%) rename rust/{candid => candid_parser}/tests/assets/ok/example.did (100%) rename rust/{candid => candid_parser}/tests/assets/ok/example.js (100%) rename rust/{candid => candid_parser}/tests/assets/ok/example.mo (100%) rename rust/{candid => candid_parser}/tests/assets/ok/example.rs (100%) rename rust/{candid => candid_parser}/tests/assets/ok/fieldnat.d.ts (100%) rename rust/{candid => candid_parser}/tests/assets/ok/fieldnat.did (100%) rename rust/{candid => candid_parser}/tests/assets/ok/fieldnat.js (100%) rename rust/{candid => candid_parser}/tests/assets/ok/fieldnat.mo (100%) rename rust/{candid => candid_parser}/tests/assets/ok/fieldnat.rs (100%) rename rust/{candid => candid_parser}/tests/assets/ok/invalid_cyclic.fail (100%) rename rust/{candid => candid_parser}/tests/assets/ok/keyword.d.ts (100%) rename rust/{candid => candid_parser}/tests/assets/ok/keyword.did (100%) rename rust/{candid => candid_parser}/tests/assets/ok/keyword.js (100%) rename rust/{candid => candid_parser}/tests/assets/ok/keyword.mo (100%) rename rust/{candid => candid_parser}/tests/assets/ok/keyword.rs (100%) rename rust/{candid => candid_parser}/tests/assets/ok/management.d.ts (100%) rename rust/{candid => candid_parser}/tests/assets/ok/management.did (100%) rename rust/{candid => candid_parser}/tests/assets/ok/management.js (100%) rename rust/{candid => candid_parser}/tests/assets/ok/management.mo (100%) rename rust/{candid => candid_parser}/tests/assets/ok/management.rs (100%) rename rust/{candid => candid_parser}/tests/assets/ok/not_func.fail (100%) rename rust/{candid => candid_parser}/tests/assets/ok/not_serv.fail (100%) rename rust/{candid => candid_parser}/tests/assets/ok/oneway.fail (100%) rename rust/{candid => candid_parser}/tests/assets/ok/recursion.d.ts (100%) rename rust/{candid => candid_parser}/tests/assets/ok/recursion.did (100%) rename rust/{candid => candid_parser}/tests/assets/ok/recursion.js (100%) rename rust/{candid => candid_parser}/tests/assets/ok/recursion.mo (100%) rename rust/{candid => candid_parser}/tests/assets/ok/recursion.rs (100%) rename rust/{candid => candid_parser}/tests/assets/ok/recursive_class.d.ts (100%) rename rust/{candid => candid_parser}/tests/assets/ok/recursive_class.did (100%) rename rust/{candid => candid_parser}/tests/assets/ok/recursive_class.js (100%) rename rust/{candid => candid_parser}/tests/assets/ok/recursive_class.mo (100%) rename rust/{candid => candid_parser}/tests/assets/ok/recursive_class.rs (100%) rename rust/{candid => candid_parser}/tests/assets/ok/service.d.ts (100%) rename rust/{candid => candid_parser}/tests/assets/ok/service.did (100%) rename rust/{candid => candid_parser}/tests/assets/ok/service.js (100%) rename rust/{candid => candid_parser}/tests/assets/ok/service.mo (100%) rename rust/{candid => candid_parser}/tests/assets/ok/service.rs (100%) rename rust/{candid => candid_parser}/tests/assets/ok/surrogate.fail (100%) rename rust/{candid => candid_parser}/tests/assets/ok/undefine.fail (100%) rename rust/{candid => candid_parser}/tests/assets/ok/unicode.d.ts (100%) rename rust/{candid => candid_parser}/tests/assets/ok/unicode.did (100%) rename rust/{candid => candid_parser}/tests/assets/ok/unicode.js (100%) rename rust/{candid => candid_parser}/tests/assets/ok/unicode.rs (100%) rename rust/{candid => candid_parser}/tests/assets/oneway.did (100%) rename rust/{candid => candid_parser}/tests/assets/recursion.did (100%) rename rust/{candid => candid_parser}/tests/assets/recursive_class.did (100%) rename rust/{candid => candid_parser}/tests/assets/service.did (100%) rename rust/{candid => candid_parser}/tests/assets/surrogate.did (100%) rename rust/{candid => candid_parser}/tests/assets/undefine.did (100%) rename rust/{candid => candid_parser}/tests/assets/unicode.did (100%) rename rust/{candid => candid_parser}/tests/parse_type.rs (91%) rename rust/{candid => candid_parser}/tests/parse_value.rs (97%) rename rust/{candid => candid_parser}/tests/test_suite.rs (85%) rename rust/{candid => candid_parser}/tests/value.rs (95%) create mode 100644 rust/ic_principal/Cargo.toml create mode 120000 rust/ic_principal/LICENSE create mode 100644 rust/ic_principal/README.md create mode 100644 rust/ic_principal/src/lib.rs rename rust/{candid => ic_principal}/tests/principal.rs (98%) diff --git a/Cargo.lock b/Cargo.lock index 899e13ab..8b07d2e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,9 +35,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -59,9 +59,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.5.0" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" dependencies = [ "anstyle", "anstyle-parse", @@ -73,15 +73,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" dependencies = [ "utf8parse", ] @@ -97,9 +97,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "2.1.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -113,9 +113,9 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "arbitrary" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" +checksum = "a2e1373abdaa212b704512ec2bd8b26bd0b7d5c3f70117411a5d9a451383c859" [[package]] name = "arrayvec" @@ -138,7 +138,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi 0.1.19", + "hermit-abi", "libc", "winapi", ] @@ -182,7 +182,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d9672209df1714ee804b1f4d4f68c8eb2a90b1f7a07acf472f88ce198ef1fed" dependencies = [ "either", - "proc-macro2 1.0.67", + "proc-macro2 1.0.69", "quote 1.0.33", "syn 1.0.109", ] @@ -210,9 +210,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "block-buffer" @@ -251,33 +251,24 @@ checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "candid" -version = "0.9.11" +version = "0.10.0" dependencies = [ "anyhow", - "arbitrary", "bincode", "binread", "byteorder", "candid_derive", "codespan-reporting", - "convert_case", - "crc32fast", "criterion", - "data-encoding", - "fake", - "goldenfile", "hex", - "impls", - "lalrpop", - "lalrpop-util", + "ic_principal", "leb128", - "logos", "num-bigint", "num-traits", "num_enum", @@ -287,12 +278,8 @@ dependencies = [ "serde", "serde_bytes", "serde_cbor", - "serde_dhall", "serde_json", - "serde_test", - "sha2 0.10.8", "stacker", - "test-generator", "thiserror", ] @@ -301,9 +288,37 @@ name = "candid_derive" version = "0.6.4" dependencies = [ "lazy_static", - "proc-macro2 1.0.67", + "proc-macro2 1.0.69", "quote 1.0.33", - "syn 2.0.37", + "syn 2.0.38", +] + +[[package]] +name = "candid_parser" +version = "0.1.0" +dependencies = [ + "anyhow", + "arbitrary", + "candid", + "codespan-reporting", + "convert_case", + "fake", + "goldenfile", + "hex", + "lalrpop", + "lalrpop-util", + "logos", + "num-bigint", + "num-traits", + "pretty 0.12.3", + "rand", + "serde", + "serde_bytes", + "serde_dhall", + "sha2 0.10.8", + "stacker", + "test-generator", + "thiserror", ] [[package]] @@ -368,9 +383,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.5" +version = "4.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824956d0dca8334758a5b7f7e50518d66ea319330cbceedcf76905c2f6ab30e3" +checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" dependencies = [ "clap_builder", "clap_derive", @@ -378,9 +393,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.5" +version = "4.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122ec64120a49b4563ccaedcbea7818d069ed8e9aa6d829b82d8a4128936b2ab" +checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" dependencies = [ "anstream", "anstyle", @@ -395,9 +410,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" dependencies = [ "heck", - "proc-macro2 1.0.67", + "proc-macro2 1.0.69", "quote 1.0.33", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -454,9 +469,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "3fbc60abd742b35f2492f808e1abbb83d45f72db402e14c55057edc9c7b1e9e4" dependencies = [ "libc", ] @@ -592,7 +607,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df7c81d16870879ef530b07cef32bc6088f98937ab4168106cc8e382a05146bf" dependencies = [ - "proc-macro2 1.0.67", + "proc-macro2 1.0.69", "quote 1.0.33", "syn 1.0.109", ] @@ -603,18 +618,13 @@ version = "0.3.5" dependencies = [ "anyhow", "candid", - "clap 4.4.5", + "candid_parser", + "clap 4.4.6", "hex", "pretty-hex", "rand", ] -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - [[package]] name = "digest" version = "0.9.0" @@ -699,25 +709,14 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" dependencies = [ - "errno-dragonfly", "libc", "windows-sys 0.48.0", ] -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "fake" version = "2.8.0" @@ -806,9 +805,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" [[package]] name = "heck" @@ -825,12 +824,6 @@ dependencies = [ "libc", ] -[[package]] -name = "hermit-abi" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" - [[package]] name = "hex" version = "0.4.3" @@ -846,6 +839,23 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ic_principal" +version = "0.1.0" +dependencies = [ + "crc32fast", + "data-encoding", + "hex", + "impls", + "serde", + "serde_bytes", + "serde_cbor", + "serde_json", + "serde_test", + "sha2 0.10.8", + "thiserror", +] + [[package]] name = "idna" version = "0.4.0" @@ -874,39 +884,37 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad227c3af19d4914570ad36d30409928b75967c298feb9ea1969db3a610bb14e" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.14.2", ] [[package]] -name = "is-terminal" -version = "0.4.9" +name = "itertools" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" dependencies = [ - "hermit-abi 0.3.3", - "rustix", - "windows-sys 0.48.0", + "either", ] [[package]] name = "itertools" -version = "0.9.0" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itertools" -version = "0.10.5" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] @@ -928,16 +936,14 @@ dependencies = [ [[package]] name = "lalrpop" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da4081d44f4611b66c6dd725e6de3169f9f63905421e8626fcb86b6a898998b8" +checksum = "83be602e051ada38d90c7841092adabeb585197afe9dabb20e4f8375cc87846e" dependencies = [ "ascii-canvas", "bit-set", - "diff", "ena", - "is-terminal", - "itertools 0.10.5", + "itertools 0.11.0", "lalrpop-util", "petgraph", "pico-args", @@ -951,11 +957,11 @@ dependencies = [ [[package]] name = "lalrpop-util" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f35c735096c0293d313e8f2a641627472b83d01b937177fe76e5e2708d31e0d" +checksum = "365d88f9d803538a06641e6736f21d95ecf9226dc1693421212e62c405cdd199" dependencies = [ - "regex", + "regex-automata 0.3.9", ] [[package]] @@ -972,9 +978,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "linux-raw-sys" @@ -984,9 +990,9 @@ checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -1015,10 +1021,10 @@ checksum = "dc487311295e0002e452025d6b580b77bb17286de87b57138f3b5db711cded68" dependencies = [ "beef", "fnv", - "proc-macro2 1.0.67", + "proc-macro2 1.0.69", "quote 1.0.33", "regex-syntax 0.6.29", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1032,9 +1038,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.6.3" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memoffset" @@ -1091,9 +1097,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] @@ -1114,9 +1120,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" dependencies = [ "proc-macro-crate", - "proc-macro2 1.0.67", + "proc-macro2 1.0.69", "quote 1.0.33", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1139,9 +1145,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "os_str_bytes" -version = "6.5.1" +version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" [[package]] name = "parking_lot" @@ -1155,13 +1161,13 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", + "redox_syscall 0.4.1", "smallvec", "windows-targets 0.48.5", ] @@ -1206,7 +1212,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d8630a7a899cb344ec1c16ba0a6b24240029af34bdc0a21f84e411d7f793f29" dependencies = [ - "proc-macro2 1.0.67", + "proc-macro2 1.0.69", "quote 1.0.33", "syn 1.0.109", ] @@ -1229,9 +1235,9 @@ checksum = "bc9fc1b9e7057baba189b5c626e2d6f40681ae5b6eb064dc7c7834101ec8123a" dependencies = [ "pest", "pest_meta", - "proc-macro2 1.0.67", + "proc-macro2 1.0.69", "quote 1.0.33", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1252,7 +1258,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.0.1", + "indexmap 2.0.2", ] [[package]] @@ -1360,9 +1366,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] @@ -1391,7 +1397,7 @@ version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ - "proc-macro2 1.0.67", + "proc-macro2 1.0.69", ] [[package]] @@ -1462,6 +1468,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_users" version = "0.4.3" @@ -1475,14 +1490,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.5" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.8", - "regex-syntax 0.7.5", + "regex-automata 0.4.3", + "regex-syntax 0.8.2", ] [[package]] @@ -1493,15 +1508,26 @@ checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-automata" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" dependencies = [ "aho-corasick", "memchr", "regex-syntax 0.7.5", ] +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.2", +] + [[package]] name = "regex-syntax" version = "0.6.29" @@ -1514,13 +1540,19 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + [[package]] name = "rustix" -version = "0.38.19" +version = "0.38.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed" +checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", @@ -1556,9 +1588,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" dependencies = [ "serde_derive", ] @@ -1584,13 +1616,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" dependencies = [ - "proc-macro2 1.0.67", + "proc-macro2 1.0.69", "quote 1.0.33", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1652,9 +1684,9 @@ dependencies = [ [[package]] name = "similar" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" +checksum = "2aeaf503862c419d66959f5d7ca015337d864e9c49485d771b732e2a20453597" dependencies = [ "bstr", "unicode-segmentation", @@ -1737,18 +1769,18 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.67", + "proc-macro2 1.0.69", "quote 1.0.33", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.37" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ - "proc-macro2 1.0.67", + "proc-macro2 1.0.69", "quote 1.0.33", "unicode-ident", ] @@ -1806,22 +1838,22 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.49" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.49" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ - "proc-macro2 1.0.67", + "proc-macro2 1.0.69", "quote 1.0.33", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1860,9 +1892,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" [[package]] name = "toml_edit" @@ -1870,7 +1902,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.0.1", + "indexmap 2.0.2", "toml_datetime", "winnow", ] @@ -2002,9 +2034,9 @@ dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.67", + "proc-macro2 1.0.69", "quote 1.0.33", - "syn 2.0.37", + "syn 2.0.38", "wasm-bindgen-shared", ] @@ -2024,9 +2056,9 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ - "proc-macro2 1.0.67", + "proc-macro2 1.0.69", "quote 1.0.33", - "syn 2.0.37", + "syn 2.0.38", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2212,9 +2244,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.5.15" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +checksum = "a3b801d0e0a6726477cc207f60162da452f3a95adb368399bef20a946e06f65c" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 352d28c5..eecbc858 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,9 @@ [workspace] members = [ "rust/candid", + "rust/candid_parser", "rust/candid_derive", + "rust/ic_principal", "tools/didc", ] resolver = "2" diff --git a/Changelog.md b/Changelog.md index 51a20b77..d8acf7a6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,13 @@ # Changelog +## Rust 0.10.0 + +* The original `candid` crate is split into three crates: + * `candid`: mainly for Candid data (de-)serialization. + * `candid_parser`: used to be the `parser` and `bindings` module in `candid` crate. + * `ic_principal`: only for `Principal` and `PrincipalError`. + ## Rust 0.9.9 * Set different config values for `full_error_message` and `zero_sized_values` for Wasm and non-Wasm target. diff --git a/rust/candid/Cargo.toml b/rust/candid/Cargo.toml index 9580da64..ffb2a6b8 100644 --- a/rust/candid/Cargo.toml +++ b/rust/candid/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "candid" -version = "0.9.11" +version = "0.10.0" edition = "2021" authors = ["DFINITY Team"] description = "Candid is an interface description language (IDL) for interacting with canisters running on the Internet Computer." @@ -9,22 +9,16 @@ documentation = "https://docs.rs/candid" repository = "https://github.com/dfinity/candid" license = "Apache-2.0" readme = "README.md" - -categories = ["encoding", "parsing", "wasm"] -keywords = ["internet-computer", "idl", "candid", "dfinity", "parser"] -include = ["src", "Cargo.toml", "build.rs", "LICENSE", "README.md"] -build = "build.rs" - -[build-dependencies] -lalrpop = { version = "0.20.0", optional = true } +categories = ["encoding", "wasm"] +keywords = ["internet-computer", "idl", "candid", "dfinity"] +include = ["src", "Cargo.toml", "LICENSE", "README.md"] [dependencies] byteorder = "1.4.3" candid_derive = { path = "../candid_derive", version = "=0.6.4" } codespan-reporting = "0.11" -crc32fast = "1.3.0" -data-encoding = "2.4.0" hex = "0.4.2" +ic_principal = { path = "../ic_principal", version = "0.1.0" } leb128 = "0.2.4" num_enum = "0.6.1" num-bigint = { version = "0.4.2", features = ["serde"] } @@ -33,33 +27,18 @@ paste = "1.0.0" pretty = "0.12.0" serde = { version = "1.0.118", features = ["derive"] } serde_bytes = "0.11" -sha2 = "0.10.1" thiserror = "1.0.20" anyhow = "1.0" binread = { version = "2.1", features = ["debug_template"] } -lalrpop-util = { version = "0.20.0", optional = true } -logos = { version = "0.13", optional = true } -convert_case = { version = "0.6", optional = true } - -arbitrary = { version = "1.0", 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 } - [target.'cfg(not(target_arch = "wasm32"))'.dependencies] stacker = "0.1" [dev-dependencies] -goldenfile = "1.1.0" -test-generator = "0.3.0" rand = "0.8" criterion = "0.4" serde_cbor = "0.11.2" serde_json = "1.0.74" -serde_test = "1.0.137" -impls = "1" bincode = "1.3.3" [[bench]] @@ -67,33 +46,5 @@ name = "benchmark" harness = false path = "benches/benchmark.rs" -[[test]] -name = "test_suite" -path = "tests/test_suite.rs" -required-features = ["parser"] -[[test]] -name = "value" -path = "tests/value.rs" -required-features = ["parser"] -[[test]] -name = "parse_value" -path = "tests/parse_value.rs" -required-features = ["parser"] -[[test]] -name = "parse_type" -path = "tests/parse_type.rs" -required-features = ["parser"] - [features] -configs = ["serde_dhall"] -random = ["parser", "configs", "arbitrary", "fake", "rand"] -parser = ["lalrpop", "lalrpop-util", "logos", "convert_case"] -all = ["random"] mute_warnings = [] - -# docs.rs-specific configuration -# To test locally: RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --features all -[package.metadata.docs.rs] -features = ["all"] -# defines the configuration attribute `docsrs` -rustdoc-args = ["--cfg", "docsrs"] diff --git a/rust/candid/src/bindings/mod.rs b/rust/candid/src/bindings/mod.rs deleted file mode 100644 index 89d86ab3..00000000 --- a/rust/candid/src/bindings/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! Candid bindings for different languages. -// This module assumes the input are type checked, it is safe to use unwrap. - -pub mod candid; - -#[cfg_attr(docsrs, doc(cfg(feature = "parser")))] -#[cfg(feature = "parser")] -pub mod analysis; -#[cfg_attr(docsrs, doc(cfg(feature = "parser")))] -#[cfg(feature = "parser")] -pub mod javascript; -#[cfg_attr(docsrs, doc(cfg(feature = "parser")))] -#[cfg(feature = "parser")] -pub mod motoko; -#[cfg_attr(docsrs, doc(cfg(feature = "parser")))] -#[cfg(feature = "parser")] -pub mod rust; -#[cfg_attr(docsrs, doc(cfg(feature = "parser")))] -#[cfg(feature = "parser")] -pub mod typescript; diff --git a/rust/candid/src/error.rs b/rust/candid/src/error.rs index 9181d4a0..095b8208 100644 --- a/rust/candid/src/error.rs +++ b/rust/candid/src/error.rs @@ -2,27 +2,13 @@ use codespan_reporting::diagnostic::Label; use serde::{de, ser}; -use std::io; +use std::{io, num::ParseIntError}; use thiserror::Error; -#[cfg(feature = "parser")] -use crate::parser::token; -#[cfg(feature = "parser")] -use codespan_reporting::{ - diagnostic::Diagnostic, - files::{Error as ReportError, SimpleFile}, - term::{self, termcolor::StandardStream}, -}; - pub type Result = std::result::Result; #[derive(Debug, Error)] pub enum Error { - #[cfg_attr(docsrs, doc(cfg(feature = "parser")))] - #[cfg(feature = "parser")] - #[error("Candid parser error: {0}")] - Parse(#[from] token::ParserError), - #[error("binary parser error: {}", .0.get(0).map(|f| format!("{} at byte offset {}", f.message, f.range.start/2)).unwrap_or_else(|| "io error".to_string()))] Binread(Vec>), @@ -40,42 +26,6 @@ impl Error { pub fn subtype(msg: T) -> Self { Error::Subtype(msg.to_string()) } - #[cfg_attr(docsrs, doc(cfg(feature = "parser")))] - #[cfg(feature = "parser")] - pub fn report(&self) -> Diagnostic<()> { - match self { - Error::Parse(e) => { - use lalrpop_util::ParseError::*; - let mut diag = Diagnostic::error().with_message("parser error"); - let label = match e { - User { error } => { - Label::primary((), error.span.clone()).with_message(&error.err) - } - InvalidToken { location } => { - Label::primary((), *location..location + 1).with_message("Invalid token") - } - UnrecognizedEof { location, expected } => { - diag = diag.with_notes(report_expected(expected)); - Label::primary((), *location..location + 1).with_message("Unexpected EOF") - } - UnrecognizedToken { token, expected } => { - diag = diag.with_notes(report_expected(expected)); - Label::primary((), token.0..token.2).with_message("Unexpected token") - } - ExtraToken { token } => { - Label::primary((), token.0..token.2).with_message("Extra token") - } - }; - diag.with_labels(vec![label]) - } - Error::Binread(labels) => { - let diag = Diagnostic::error().with_message("decoding error"); - diag.with_labels(labels.to_vec()) - } - Error::Subtype(e) => Diagnostic::error().with_message(e), - Error::Custom(e) => Diagnostic::error().with_message(e.to_string()), - } - } } fn get_binread_labels(e: &binread::Error) -> Vec> { @@ -123,26 +73,6 @@ fn get_binread_labels(e: &binread::Error) -> Vec> { } } -#[cfg_attr(docsrs, doc(cfg(feature = "parser")))] -#[cfg(feature = "parser")] -fn report_expected(expected: &[String]) -> Vec { - if expected.is_empty() { - return Vec::new(); - } - use pretty::RcDoc; - let doc: RcDoc<()> = RcDoc::intersperse( - expected.iter().map(RcDoc::text), - RcDoc::text(",").append(RcDoc::softline()), - ); - let header = if expected.len() == 1 { - "Expects" - } else { - "Expects one of" - }; - let doc = RcDoc::text(header).append(RcDoc::softline().append(doc)); - vec![doc.pretty(70).to_string()] -} - impl ser::Error for Error { fn custom(msg: T) -> Self { Error::msg(format!("Serialize error: {msg}")) @@ -174,56 +104,9 @@ impl From for Error { Error::Binread(get_binread_labels(&e)) } } -#[cfg_attr(docsrs, doc(cfg(feature = "parser")))] -#[cfg(feature = "parser")] -impl From for Error { - fn from(e: ReportError) -> Error { - Error::msg(e) - } -} -#[cfg_attr(docsrs, doc(cfg(feature = "random")))] -#[cfg(feature = "random")] -impl From for Error { - fn from(e: arbitrary::Error) -> Error { - Error::msg(format!("arbitrary error: {e}")) - } -} -#[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: ParseIntError) -> Error { + Error::msg(format!("ParseIntError: {e}")) } } - -#[cfg_attr(docsrs, doc(cfg(feature = "parser")))] -#[cfg(feature = "parser")] -pub fn pretty_parse(name: &str, str: &str) -> Result -where - T: std::str::FromStr, -{ - str.parse::().or_else(|e| { - let writer = StandardStream::stderr(term::termcolor::ColorChoice::Auto); - let config = term::Config::default(); - let file = SimpleFile::new(name, str); - term::emit(&mut writer.lock(), &config, &file, &e.report())?; - Err(e) - }) -} -#[cfg_attr(docsrs, doc(cfg(feature = "parser")))] -#[cfg(feature = "parser")] -pub fn pretty_read(reader: &mut std::io::Cursor<&[u8]>) -> Result -where - T: binread::BinRead, -{ - T::read(reader).or_else(|e| { - let e = Error::from(e); - let writer = StandardStream::stderr(term::termcolor::ColorChoice::Auto); - let config = term::Config::default(); - let str = hex::encode(reader.get_ref()); - let file = SimpleFile::new("binary", &str); - term::emit(&mut writer.lock(), &config, &file, &e.report())?; - Err(e) - }) -} diff --git a/rust/candid/src/lib.rs b/rust/candid/src/lib.rs index 446735af..c4d5217a 100644 --- a/rust/candid/src/lib.rs +++ b/rust/candid/src/lib.rs @@ -170,111 +170,6 @@ //! # Ok::<(), candid::Error>(()) //! ``` //! -//! We provide a data structure [`candid::IDLArgs`](parser/value/struct.IDLArgs.html) to represent a sequence of `IDLValue`s, -//! and use `to_bytes()` and `from_bytes()` to encode and decode Candid messages. -//! We also provide a parser to parse Candid values in text format. -//! -//! ``` -//! #[cfg(feature = "parser")] -//! # fn f() -> Result<(), candid::Error> { -//! use candid::{IDLArgs, TypeEnv}; -//! // Candid values represented in text format -//! let text_value = r#" -//! (42, opt true, vec {1;2;3}, -//! opt record {label="text"; 42="haha"}) -//! "#; -//! -//! // Parse text format into IDLArgs for serialization -//! let args: IDLArgs = text_value.parse()?; -//! let encoded: Vec = args.to_bytes()?; -//! -//! // Deserialize into IDLArgs -//! let decoded: IDLArgs = IDLArgs::from_bytes(&encoded)?; -//! assert_eq!(encoded, decoded.to_bytes()?); -//! -//! // Convert IDLArgs to text format -//! let output: String = decoded.to_string(); -//! let parsed_args: IDLArgs = output.parse()?; -//! let annotated_args = args.annotate_types(true, &TypeEnv::new(), &parsed_args.get_types())?; -//! assert_eq!(annotated_args, parsed_args); -//! # Ok(()) -//! # } -//! ``` -//! Note that when parsing Candid values, we assume the number literals are always of type `Int`. -//! This can be changed by providing the type of the method arguments, which can usually be obtained -//! by parsing a Candid file in the following section. -//! -//! ## Operating on Candid AST -//! We provide a parser and type checker for Candid files specifying the service interface. -//! -//! ``` -//! #[cfg(feature = "parser")] -//! # fn f() -> Result<(), candid::Error> { -//! use candid::{IDLProg, TypeEnv, check_prog, types::{Type, TypeInner}}; -//! let did_file = r#" -//! type List = opt record { head: int; tail: List }; -//! type byte = nat8; -//! service : { -//! f : (byte, int, nat, int8) -> (List); -//! g : (List) -> (int) query; -//! } -//! "#; -//! -//! // Parse did file into an AST -//! let ast: IDLProg = did_file.parse()?; -//! -//! // Type checking a given .did file -//! // let (env, opt_actor) = check_file("a.did")?; -//! // Or alternatively, use check_prog to check in-memory did file -//! // Note that file import is ignored by check_prog. -//! let mut env = TypeEnv::new(); -//! let actor: Type = check_prog(&mut env, &ast)?.unwrap(); -//! -//! let method = env.get_method(&actor, "g").unwrap(); -//! assert_eq!(method.is_query(), true); -//! assert_eq!(method.args, vec![TypeInner::Var("List".to_string()).into()]); -//! # Ok(()) -//! # } -//! ``` -//! -//! ## Serializing untyped Candid values with type annotations. -//! With type signatures from the Candid file, [`candid::IDLArgs`](parser/value/struct.IDLArgs.html) -//! uses `to_bytes_with_types` function to serialize arguments directed by the Candid types. -//! This is useful when serializing different number types and recursive types. -//! There is no need to use types for deserialization as the types are available in the Candid message. -//! -//! ``` -//! #[cfg(feature = "parser")] -//! # fn f() -> Result<(), candid::Error> { -//! use candid::{IDLArgs, types::value::IDLValue}; -//! # use candid::{IDLProg, TypeEnv, check_prog}; -//! # let did_file = r#" -//! # type List = opt record { head: int; tail: List }; -//! # type byte = nat8; -//! # service : { -//! # f : (byte, int, nat, int8) -> (List); -//! # g : (List) -> (int) query; -//! # } -//! # "#; -//! # let ast = did_file.parse::()?; -//! # let mut env = TypeEnv::new(); -//! # let actor = check_prog(&mut env, &ast)?.unwrap(); -//! // Get method type f : (byte, int, nat, int8) -> (List) -//! let method = env.get_method(&actor, "f").unwrap(); -//! let args = "(42, 42, 42, 42)".parse::()?; -//! // Serialize arguments with candid types -//! let encoded = args.to_bytes_with_types(&env, &method.args)?; -//! let decoded = IDLArgs::from_bytes(&encoded)?; -//! assert_eq!(decoded.args, -//! vec![IDLValue::Nat8(42), -//! IDLValue::Int(42.into()), -//! IDLValue::Nat(42.into()), -//! IDLValue::Int8(42) -//! ]); -//! # Ok(()) -//! # } -//! ``` -//! //! ## Building the library as a JS/Wasm package //! With the help of `wasm-bindgen` and `wasm-pack`, we can build the library as a Wasm binary and run in the browser. //! This is useful for client-side UIs and parsing did files in JavaScript. @@ -287,6 +182,7 @@ //! [dependencies] //! wasm-bindgen = "0.2" //! candid = "0.9.0" +//! candid_parser = "0.1.0" //! //! [profile.release] //! lto = true @@ -294,14 +190,15 @@ //! ``` //! Expose the methods in `lib.rs` //! ```ignore -//! use candid::{check_prog, IDLProg, TypeEnv}; +//! use candid::TypeEnv; +//! use candid_parser::{check_prog, IDLProg}; //! use wasm_bindgen::prelude::*; //! #[wasm_bindgen] //! pub fn did_to_js(prog: String) -> Option { //! let ast = prog.parse::().ok()?; //! let mut env = TypeEnv::new(); //! let actor = check_prog(&mut env, &ast).ok()?; -//! Some(candid::bindings::javascript::compile(&env, &actor)) +//! Some(candid_parser::bindings::javascript::compile(&env, &actor)) //! } //! ``` //! ### Building @@ -353,18 +250,7 @@ pub mod utils; pub use utils::{decode_args, decode_one, encode_args, encode_one, write_args}; pub mod pretty; -#[cfg_attr(docsrs, doc(cfg(feature = "parser")))] -#[cfg(feature = "parser")] -pub mod parser; -#[cfg(feature = "parser")] -pub use error::{pretty_parse, pretty_read}; -#[cfg(feature = "parser")] -pub use parser::{ - types::IDLProg, - typing::{check_file, check_prog, pretty_check_file}, -}; - -pub mod bindings; +pub mod pretty_printer; // Candid hash function comes from // https://caml.inria.fr/pub/papers/garrigue-polymorphic_variants-ml98.pdf diff --git a/rust/candid/src/parser/grammar.rs b/rust/candid/src/parser/grammar.rs deleted file mode 100644 index 2f39e7f0..00000000 --- a/rust/candid/src/parser/grammar.rs +++ /dev/null @@ -1,2 +0,0 @@ -#![allow(clippy::all)] -include!(concat!(env!("OUT_DIR"), "/parser/grammar.rs")); diff --git a/rust/candid/src/parser/mod.rs b/rust/candid/src/parser/mod.rs deleted file mode 100644 index 6c4c399c..00000000 --- a/rust/candid/src/parser/mod.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! Provides parser for Candid type and value. -//! * `str.parse::()` parses the Candid signature file to Candid AST. -//! * `str.parse::()` parses the Candid value in text format to a struct `IDLArg` that can be used for serialization and deserialization between Candid and an enum type `IDLValue` in Rust. - -pub mod grammar; - -pub mod token; -pub mod types; - -pub mod typing; - -#[cfg_attr(docsrs, doc(cfg(feature = "configs")))] -#[cfg(feature = "configs")] -pub mod configs; -#[cfg_attr(docsrs, doc(cfg(feature = "random")))] -#[cfg(feature = "random")] -pub mod random; -pub mod test; - -pub use crate::types::value::{IDLArgs, IDLValue}; - -impl std::str::FromStr for IDLArgs { - type Err = crate::Error; - fn from_str(str: &str) -> std::result::Result { - let lexer = token::Tokenizer::new(str); - Ok(grammar::ArgsParser::new().parse(lexer)?) - } -} - -impl std::str::FromStr for IDLValue { - type Err = crate::Error; - fn from_str(str: &str) -> std::result::Result { - let lexer = token::Tokenizer::new(str); - Ok(grammar::ArgParser::new().parse(lexer)?) - } -} diff --git a/rust/candid/src/bindings/candid.rs b/rust/candid/src/pretty_printer.rs similarity index 97% rename from rust/candid/src/bindings/candid.rs rename to rust/candid/src/pretty_printer.rs index ffe4d8d3..fd513436 100644 --- a/rust/candid/src/bindings/candid.rs +++ b/rust/candid/src/pretty_printer.rs @@ -39,7 +39,7 @@ fn is_keyword(id: &str) -> bool { KEYWORDS.contains(&id) } -pub(crate) fn is_valid_as_id(id: &str) -> bool { +pub fn is_valid_as_id(id: &str) -> bool { if id.is_empty() || !id.is_ascii() { return false; } @@ -161,7 +161,7 @@ pub fn pp_args(args: &[Type]) -> RcDoc { enclose("(", doc, ")") } -pub(crate) fn pp_modes(modes: &[crate::types::FuncMode]) -> RcDoc { +pub fn pp_modes(modes: &[crate::types::FuncMode]) -> RcDoc { RcDoc::concat(modes.iter().map(|m| RcDoc::space().append(m.to_doc()))) } @@ -211,7 +211,7 @@ pub fn compile(env: &TypeEnv, actor: &Option) -> String { } pub mod value { - use crate::bindings::candid::{ident_string, pp_text}; + use super::{ident_string, pp_text}; use crate::pretty::*; use crate::types::value::{IDLArgs, IDLField, IDLValue}; use crate::types::{number::pp_num_str, Label}; @@ -325,12 +325,7 @@ pub mod value { Reserved => write!(f, "null : reserved"), Principal(id) => write!(f, "principal \"{id}\""), Service(id) => write!(f, "service \"{id}\""), - Func(id, meth) => write!( - f, - "func \"{}\".{}", - id, - crate::bindings::candid::ident_string(meth) - ), + Func(id, meth) => write!(f, "func \"{}\".{}", id, ident_string(meth)), Opt(v) if has_type_annotation(v) => write!(f, "opt ({v:?})"), Opt(v) => write!(f, "opt {v:?}"), Vec(vs) => { diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index 47676e07..9c296d36 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -283,16 +283,12 @@ impl Type { } impl fmt::Display for Type { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", crate::bindings::candid::pp_ty(self).pretty(80)) + write!(f, "{}", crate::pretty_printer::pp_ty(self).pretty(80)) } } impl fmt::Display for TypeInner { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - crate::bindings::candid::pp_ty_inner(self).pretty(80) - ) + write!(f, "{}", crate::pretty_printer::pp_ty_inner(self).pretty(80)) } } pub(crate) fn text_size(t: &Type, limit: i32) -> Result { @@ -410,7 +406,7 @@ impl fmt::Display for Field { write!( f, "{}", - crate::bindings::candid::pp_field(self, false).pretty(80) + crate::pretty_printer::pp_field(self, false).pretty(80) ) } } @@ -462,7 +458,7 @@ pub enum FuncMode { CompositeQuery, } impl FuncMode { - pub(crate) fn to_doc(&self) -> pretty::RcDoc { + pub fn to_doc(&self) -> pretty::RcDoc { match self { FuncMode::Oneway => pretty::RcDoc::text("oneway"), FuncMode::Query => pretty::RcDoc::text("query"), @@ -478,11 +474,7 @@ pub struct Function { } impl fmt::Display for Function { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - crate::bindings::candid::pp_function(self).pretty(80) - ) + write!(f, "{}", crate::pretty_printer::pp_function(self).pretty(80)) } } impl Function { @@ -528,7 +520,7 @@ macro_rules! service { #[derive(Debug, PartialEq, TryFromPrimitive)] #[repr(i64)] -pub(crate) enum Opcode { +pub enum Opcode { Null = -1, Bool = -2, Nat = -3, @@ -607,7 +599,7 @@ thread_local! { static NAME: RefCell = RefCell::new(Default::default()); } -pub(crate) fn find_type(id: &TypeId) -> Option { +pub fn find_type(id: &TypeId) -> Option { ENV.with(|e| e.borrow().get(id).cloned()) } @@ -620,7 +612,7 @@ pub(crate) fn show_env() { pub(crate) fn env_add(id: TypeId, t: Type) { ENV.with(|e| drop(e.borrow_mut().insert(id, t))); } -pub(crate) fn env_clear() { +pub fn env_clear() { ENV.with(|e| e.borrow_mut().clear()); } diff --git a/rust/candid/src/types/number.rs b/rust/candid/src/types/number.rs index ef550bea..75ca3de3 100644 --- a/rust/candid/src/types/number.rs +++ b/rust/candid/src/types/number.rs @@ -88,7 +88,7 @@ impl std::str::FromStr for Nat { } } -pub(crate) fn pp_num_str(s: &str) -> String { +pub fn pp_num_str(s: &str) -> String { let mut groups = Vec::new(); for chunk in s.as_bytes().rchunks(3) { let str = String::from_utf8_lossy(chunk); diff --git a/rust/candid/src/types/principal.rs b/rust/candid/src/types/principal.rs index f5c8d776..7cca8fbd 100644 --- a/rust/candid/src/types/principal.rs +++ b/rust/candid/src/types/principal.rs @@ -1,95 +1,6 @@ use super::{CandidType, Serializer, Type, TypeInner}; -use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha224}; -use std::convert::TryFrom; -use std::fmt::Write; -use thiserror::Error; -/// An error happened while encoding, decoding or serializing a [`Principal`]. -#[derive(Error, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub enum PrincipalError { - #[error("Bytes is longer than 29 bytes.")] - BytesTooLong(), - - #[error("Text must be in valid Base32 encoding.")] - InvalidBase32(), - - #[error("Text is too short.")] - TextTooShort(), - - #[error("Text is too long.")] - TextTooLong(), - - #[error("CRC32 check sequence doesn't match with calculated from Principal bytes.")] - CheckSequenceNotMatch(), - - #[error(r#"Text should be separated by - (dash) every 5 characters: expected "{0}""#)] - AbnormalGrouped(Principal), -} - -/// Generic ID on Internet Computer. -/// -/// Principals are generic identifiers for canisters, users -/// and possibly other concepts in the future. -/// As far as most uses of the IC are concerned they are -/// opaque binary blobs with a length between 0 and 29 bytes, -/// and there is intentionally no mechanism to tell canister ids and user ids apart. -/// -/// Note a principal is not necessarily tied with a public key-pair, -/// yet we need at least a key-pair of a related principal to sign -/// requests. -/// -/// A Principal can be serialized to a byte array ([`Vec`]) or a text -/// representation, but the inner structure of the byte representation -/// is kept private. -/// -/// Example of using a Principal object: -/// ``` -/// # use candid::Principal; -/// let text = "aaaaa-aa"; // The management canister ID. -/// let principal = Principal::from_text(text).expect("Could not decode the principal."); -/// assert_eq!(principal.as_slice(), &[] as &[u8]); -/// assert_eq!(principal.to_text(), text); -/// ``` -/// -/// Similarly, serialization using serde has two versions: -/// serilizing to a byte bufer for non-human readable serializer, and a string version for human -/// readable serializers. -/// -/// ``` -/// # use candid::Principal; -/// use serde::{Deserialize, Serialize}; -/// use std::str::FromStr; -/// -/// #[derive(Serialize)] -/// struct Data { -/// id: Principal, -/// } -/// -/// let id = Principal::from_str("2chl6-4hpzw-vqaaa-aaaaa-c").unwrap(); -/// -/// // JSON is human readable, so this will serialize to a textual -/// // representation of the Principal. -/// assert_eq!( -/// serde_json::to_string(&Data { id: id.clone() }).unwrap(), -/// r#"{"id":"2chl6-4hpzw-vqaaa-aaaaa-c"}"# -/// ); -/// -/// // CBOR is not human readable, so will serialize to bytes. -/// assert_eq!( -/// serde_cbor::to_vec(&Data { id: id.clone() }).unwrap(), -/// &[161, 98, 105, 100, 73, 239, 205, 171, 0, 0, 0, 0, 0, 1], -/// ); -/// ``` -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Principal { - /// Length. - len: u8, - - /// The content buffer. When returning slices this should always be sized according to - /// `len`. - bytes: [u8; Self::MAX_LENGTH_IN_BYTES], -} +pub use ic_principal::{Principal, PrincipalError}; impl CandidType for Principal { fn _ty() -> Type { @@ -102,266 +13,3 @@ impl CandidType for Principal { serializer.serialize_principal(self.as_slice()) } } - -impl Principal { - const MAX_LENGTH_IN_BYTES: usize = 29; - const CRC_LENGTH_IN_BYTES: usize = 4; - - const SELF_AUTHENTICATING_TAG: u8 = 2; - const ANONYMOUS_TAG: u8 = 4; - - /// Construct a [`Principal`] of the IC management canister - pub const fn management_canister() -> Self { - Self { - len: 0, - bytes: [0; Self::MAX_LENGTH_IN_BYTES], - } - } - - /// Construct a self-authenticating ID from public key - pub fn self_authenticating>(public_key: P) -> Self { - let public_key = public_key.as_ref(); - let hash = Sha224::digest(public_key); - let mut bytes = [0; Self::MAX_LENGTH_IN_BYTES]; - bytes[..Self::MAX_LENGTH_IN_BYTES - 1].copy_from_slice(hash.as_slice()); - bytes[Self::MAX_LENGTH_IN_BYTES - 1] = Self::SELF_AUTHENTICATING_TAG; - - Self { - len: Self::MAX_LENGTH_IN_BYTES as u8, - bytes, - } - } - - /// Construct an anonymous ID. - pub const fn anonymous() -> Self { - let mut bytes = [0; Self::MAX_LENGTH_IN_BYTES]; - bytes[0] = Self::ANONYMOUS_TAG; - Self { len: 1, bytes } - } - - /// Construct a [`Principal`] from a slice of bytes. - /// - /// # Panics - /// - /// Panics if the slice is longer than 29 bytes. - pub const fn from_slice(slice: &[u8]) -> Self { - match Self::try_from_slice(slice) { - Ok(v) => v, - _ => panic!("slice length exceeds capacity"), - } - } - - /// Construct a [`Principal`] from a slice of bytes. - pub const fn try_from_slice(slice: &[u8]) -> Result { - const MAX_LENGTH_IN_BYTES: usize = Principal::MAX_LENGTH_IN_BYTES; - match slice.len() { - len @ 0..=MAX_LENGTH_IN_BYTES => { - let mut bytes = [0; MAX_LENGTH_IN_BYTES]; - let mut i = 0; - while i < len { - bytes[i] = slice[i]; - i += 1; - } - Ok(Self { - len: len as u8, - bytes, - }) - } - _ => Err(PrincipalError::BytesTooLong()), - } - } - - /// Parse a [`Principal`] from text representation. - pub fn from_text>(text: S) -> Result { - // Strategy: Parse very liberally, then pretty-print and compare output - // This is both simpler and yields better error messages - - let mut s = text.as_ref().to_string(); - s.make_ascii_uppercase(); - s.retain(|c| c != '-'); - match data_encoding::BASE32_NOPAD.decode(s.as_bytes()) { - Ok(bytes) => { - if bytes.len() < Self::CRC_LENGTH_IN_BYTES { - return Err(PrincipalError::TextTooShort()); - } - - let crc_bytes = &bytes[..Self::CRC_LENGTH_IN_BYTES]; - let data_bytes = &bytes[Self::CRC_LENGTH_IN_BYTES..]; - if data_bytes.len() > Self::MAX_LENGTH_IN_BYTES { - return Err(PrincipalError::TextTooLong()); - } - - if crc32fast::hash(data_bytes).to_be_bytes() != crc_bytes { - return Err(PrincipalError::CheckSequenceNotMatch()); - } - - // Already checked data_bytes.len() <= MAX_LENGTH_IN_BYTES - // safe to unwrap here - let result = Self::try_from_slice(data_bytes).unwrap(); - let expected = format!("{result}"); - - // In the Spec: - // The textual representation is conventionally printed with lower case letters, - // but parsed case-insensitively. - if text.as_ref().to_ascii_lowercase() != expected { - return Err(PrincipalError::AbnormalGrouped(result)); - } - Ok(result) - } - _ => Err(PrincipalError::InvalidBase32()), - } - } - - /// Convert [`Principal`] to text representation. - pub fn to_text(&self) -> String { - format!("{self}") - } - - /// Return the [`Principal`]'s underlying slice of bytes. - #[inline] - pub fn as_slice(&self) -> &[u8] { - &self.bytes[..self.len as usize] - } -} - -impl std::fmt::Display for Principal { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let blob: &[u8] = self.as_slice(); - - // calc checksum - let checksum = crc32fast::hash(blob); - - // combine blobs - let mut bytes = vec![]; - bytes.extend_from_slice(&checksum.to_be_bytes()); - bytes.extend_from_slice(blob); - - // base32 - let mut s = data_encoding::BASE32_NOPAD.encode(&bytes); - s.make_ascii_lowercase(); - - // write out string with dashes - let mut s = s.as_str(); - while s.len() > 5 { - f.write_str(&s[..5])?; - f.write_char('-')?; - s = &s[5..]; - } - f.write_str(s) - } -} - -impl std::str::FromStr for Principal { - type Err = PrincipalError; - - fn from_str(s: &str) -> Result { - Principal::from_text(s) - } -} - -impl TryFrom<&str> for Principal { - type Error = PrincipalError; - - fn try_from(s: &str) -> Result { - Principal::from_text(s) - } -} - -impl TryFrom> for Principal { - type Error = PrincipalError; - - fn try_from(bytes: Vec) -> Result { - Self::try_from(bytes.as_slice()) - } -} - -impl TryFrom<&Vec> for Principal { - type Error = PrincipalError; - - fn try_from(bytes: &Vec) -> Result { - Self::try_from(bytes.as_slice()) - } -} - -impl TryFrom<&[u8]> for Principal { - type Error = PrincipalError; - - fn try_from(bytes: &[u8]) -> Result { - Self::try_from_slice(bytes) - } -} - -impl AsRef<[u8]> for Principal { - fn as_ref(&self) -> &[u8] { - self.as_slice() - } -} - -// Serialization -impl serde::Serialize for Principal { - fn serialize(&self, serializer: S) -> Result { - if serializer.is_human_readable() { - self.to_text().serialize(serializer) - } else { - serializer.serialize_bytes(self.as_slice()) - } - } -} - -// Deserialization -mod deserialize { - use super::Principal; - use std::convert::TryFrom; - - // Simple visitor for deserialization from bytes. We don't support other number types - // as there's no need for it. - pub(super) struct PrincipalVisitor; - - impl<'de> serde::de::Visitor<'de> for PrincipalVisitor { - type Value = super::Principal; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("bytes or string") - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - Principal::from_text(v).map_err(E::custom) - } - - fn visit_bytes(self, value: &[u8]) -> Result - where - E: serde::de::Error, - { - Principal::try_from(value).map_err(E::custom) - } - /// This visitor should only be used by the Candid crate. - fn visit_byte_buf(self, v: Vec) -> Result - where - E: serde::de::Error, - { - if v.is_empty() || v[0] != 2u8 { - Err(E::custom("Not called by Candid")) - } else { - Principal::try_from(&v[1..]).map_err(E::custom) - } - } - } -} - -impl<'de> serde::Deserialize<'de> for Principal { - fn deserialize>(deserializer: D) -> Result { - use serde::de::Error; - if deserializer.is_human_readable() { - deserializer - .deserialize_str(deserialize::PrincipalVisitor) - .map_err(D::Error::custom) - } else { - deserializer - .deserialize_bytes(deserialize::PrincipalVisitor) - .map_err(D::Error::custom) - } - } -} diff --git a/rust/candid/src/types/subtype.rs b/rust/candid/src/types/subtype.rs index 552c9b85..85c6e334 100644 --- a/rust/candid/src/types/subtype.rs +++ b/rust/candid/src/types/subtype.rs @@ -1,5 +1,5 @@ use super::internal::{find_type, Field, Label, Type, TypeInner}; -use crate::bindings::candid::pp_args; +use crate::pretty_printer::pp_args; use crate::types::TypeEnv; use crate::{Error, Result}; use anyhow::Context; diff --git a/rust/candid/src/types/value.rs b/rust/candid/src/types/value.rs index a2676218..ebfb0f5c 100644 --- a/rust/candid/src/types/value.rs +++ b/rust/candid/src/types/value.rs @@ -134,10 +134,6 @@ impl IDLValue { /// string, we need to set `from_parser` to true to enable converting numbers to the expected /// types, and disable the opt rules. pub fn annotate_type(&self, from_parser: bool, env: &TypeEnv, t: &Type) -> Result { - #[cfg(not(feature = "parser"))] - if from_parser { - panic!("Please enable \"parser\" feature"); - } Ok(match (self, t.as_ref()) { (_, TypeInner::Var(id)) => { let ty = env.rec_find_type(id)?; @@ -239,27 +235,23 @@ impl IDLValue { (IDLValue::Principal(id), TypeInner::Principal) => IDLValue::Principal(*id), (IDLValue::Service(_), TypeInner::Service(_)) => self.clone(), (IDLValue::Func(_, _), TypeInner::Func(_)) => self.clone(), - #[cfg(feature = "parser")] - (IDLValue::Number(str), _) if from_parser => { - use crate::parser::token::error; - match t.as_ref() { - TypeInner::Int => IDLValue::Int(str.parse::()?), - TypeInner::Nat => IDLValue::Nat(str.parse::()?), - TypeInner::Nat8 => IDLValue::Nat8(str.parse::().map_err(error)?), - TypeInner::Nat16 => IDLValue::Nat16(str.parse::().map_err(error)?), - TypeInner::Nat32 => IDLValue::Nat32(str.parse::().map_err(error)?), - TypeInner::Nat64 => IDLValue::Nat64(str.parse::().map_err(error)?), - TypeInner::Int8 => IDLValue::Int8(str.parse::().map_err(error)?), - TypeInner::Int16 => IDLValue::Int16(str.parse::().map_err(error)?), - TypeInner::Int32 => IDLValue::Int32(str.parse::().map_err(error)?), - TypeInner::Int64 => IDLValue::Int64(str.parse::().map_err(error)?), - _ => { - return Err(Error::msg(format!( - "type mismatch: {self} can not be of type {t}" - ))) - } + (IDLValue::Number(str), _) if from_parser => match t.as_ref() { + TypeInner::Int => IDLValue::Int(str.parse::()?), + TypeInner::Nat => IDLValue::Nat(str.parse::()?), + TypeInner::Nat8 => IDLValue::Nat8(str.parse::()?), + TypeInner::Nat16 => IDLValue::Nat16(str.parse::()?), + TypeInner::Nat32 => IDLValue::Nat32(str.parse::()?), + TypeInner::Nat64 => IDLValue::Nat64(str.parse::()?), + TypeInner::Int8 => IDLValue::Int8(str.parse::()?), + TypeInner::Int16 => IDLValue::Int16(str.parse::()?), + TypeInner::Int32 => IDLValue::Int32(str.parse::()?), + TypeInner::Int64 => IDLValue::Int64(str.parse::()?), + _ => { + return Err(Error::msg(format!( + "type mismatch: {self} can not be of type {t}" + ))) } - } + }, _ => { return Err(Error::msg(format!( "type mismatch: {self} cannot be of type {t}" diff --git a/rust/candid/src/utils.rs b/rust/candid/src/utils.rs index 64bee0a2..be57f900 100644 --- a/rust/candid/src/utils.rs +++ b/rust/candid/src/utils.rs @@ -3,13 +3,6 @@ use crate::ser::IDLBuilder; use crate::{CandidType, Error, Result}; use serde::de::Deserialize; -#[cfg(feature = "parser")] -use crate::{check_prog, pretty_check_file}; -#[cfg(feature = "parser")] -use crate::{pretty_parse, types::Type, TypeEnv}; -#[cfg(feature = "parser")] -use std::path::Path; - pub fn check_unique<'a, I, T>(sorted: I) -> Result<()> where T: 'a + PartialEq + std::fmt::Display, @@ -29,96 +22,6 @@ where Ok(()) } -#[cfg_attr(docsrs, doc(cfg(feature = "parser")))] -#[cfg(feature = "parser")] -pub enum CandidSource<'a> { - File(&'a Path), - Text(&'a str), -} -#[cfg_attr(docsrs, doc(cfg(feature = "parser")))] -#[cfg(feature = "parser")] -impl<'a> CandidSource<'a> { - pub fn load(&self) -> Result<(TypeEnv, Option)> { - Ok(match self { - CandidSource::File(path) => pretty_check_file(path)?, - CandidSource::Text(str) => { - let ast = pretty_parse("", str)?; - let mut env = TypeEnv::new(); - let actor = check_prog(&mut env, &ast)?; - (env, actor) - } - }) - } -} - -#[cfg_attr(docsrs, doc(cfg(feature = "parser")))] -#[cfg(feature = "parser")] -/// Check compatibility of two service types -pub fn service_compatible(new: CandidSource, old: CandidSource) -> Result<()> { - let (mut env, t1) = new.load()?; - let t1 = t1.ok_or_else(|| Error::msg("new interface has no main service type"))?; - let (env2, t2) = old.load()?; - let t2 = t2.ok_or_else(|| Error::msg("old interface has no main service type"))?; - let mut gamma = std::collections::HashSet::new(); - let t2 = env.merge_type(env2, t2); - crate::types::subtype::subtype(&mut gamma, &env, &t1, &t2)?; - Ok(()) -} - -#[cfg_attr(docsrs, doc(cfg(feature = "parser")))] -#[cfg(feature = "parser")] -/// Check structural equality of two service types -pub fn service_equal(left: CandidSource, right: CandidSource) -> Result<()> { - let (mut env, t1) = left.load()?; - let t1 = t1.ok_or_else(|| Error::msg("left interface has no main service type"))?; - let (env2, t2) = right.load()?; - let t2 = t2.ok_or_else(|| Error::msg("right interface has no main service type"))?; - let mut gamma = std::collections::HashSet::new(); - let t2 = env.merge_type(env2, t2); - crate::types::subtype::equal(&mut gamma, &env, &t1, &t2)?; - Ok(()) -} - -#[cfg_attr(docsrs, doc(cfg(feature = "parser")))] -#[cfg(feature = "parser")] -/// Take a did file and outputs the init args and the service type (without init args). -/// If the original did file contains imports, the output flattens the type definitions. -/// For now, the comments from the original did file is omitted. -pub fn instantiate_candid(candid: CandidSource) -> Result<(Vec, (TypeEnv, Type))> { - use crate::types::TypeInner; - let (env, serv) = candid.load()?; - let serv = serv.ok_or_else(|| Error::msg("the Candid interface has no main service type"))?; - let serv = env.trace_type(&serv)?; - Ok(match serv.as_ref() { - TypeInner::Class(args, ty) => (args.clone(), (env, ty.clone())), - TypeInner::Service(_) => (vec![], (env, serv)), - _ => unreachable!(), - }) -} - -/// Merge canister metadata candid:args and candid:service into a service constructor. -/// If candid:service already contains init args, returns the original did file. -#[cfg_attr(docsrs, doc(cfg(feature = "parser")))] -#[cfg(feature = "parser")] -pub fn merge_init_args(candid: &str, init: &str) -> Result<(TypeEnv, Type)> { - use crate::parser::{types::IDLInitArgs, typing::check_init_args}; - use crate::types::TypeInner; - let candid = CandidSource::Text(candid); - let (env, serv) = candid.load()?; - let serv = serv.ok_or_else(|| Error::msg("the Candid interface has no main service type"))?; - let serv = env.trace_type(&serv)?; - match serv.as_ref() { - TypeInner::Class(_, _) => Ok((env, serv)), - TypeInner::Service(_) => { - let prog = init.parse::()?; - let mut env2 = TypeEnv::new(); - let args = check_init_args(&mut env2, &env, &prog)?; - Ok((env2, TypeInner::Class(args, serv).into())) - } - _ => unreachable!(), - } -} - /// Encode sequence of Rust values into Candid message of type `candid::Result>`. #[macro_export] macro_rules! Encode { diff --git a/rust/candid_derive/src/func.rs b/rust/candid_derive/src/func.rs index a8da2fac..dfbde3dd 100644 --- a/rust/candid_derive/src/func.rs +++ b/rust/candid_derive/src/func.rs @@ -135,7 +135,7 @@ pub(crate) fn export_service(path: Option) -> TokenStream { fn __export_service() -> String { #service #actor - let result = #candid::bindings::candid::compile(&env.env, &actor); + let result = #candid::pretty_printer::compile(&env.env, &actor); format!("{}", result) } }; diff --git a/rust/candid_parser/Cargo.toml b/rust/candid_parser/Cargo.toml new file mode 100644 index 00000000..eac40b32 --- /dev/null +++ b/rust/candid_parser/Cargo.toml @@ -0,0 +1,61 @@ +[package] +name = "candid_parser" +version = "0.1.0" +edition = "2021" +authors = ["DFINITY Team"] +description = "Candid is an interface description language (IDL) for interacting with canisters running on the Internet Computer." +homepage = "https://internetcomputer.org/docs/current/developer-docs/build/candid/candid-concepts" +documentation = "https://docs.rs/candid_parser" +repository = "https://github.com/dfinity/candid" +license = "Apache-2.0" +readme = "README.md" +categories = ["encoding", "parsing", "wasm"] +keywords = ["internet-computer", "idl", "candid", "dfinity", "parser"] +include = ["src", "Cargo.toml", "build.rs", "LICENSE", "README.md"] +build = "build.rs" + +[build-dependencies] +lalrpop = "0.20.0" + +[dependencies] +candid = { path = "../candid", version = "0.10" } +codespan-reporting = "0.11" +hex = "0.4.2" +num-bigint = { version = "0.4.2", features = ["serde"] } +num-traits = "0.2.12" +pretty = "0.12.0" +serde = { version = "1.0.118", features = ["derive"] } +serde_bytes = "0.11" +sha2 = "0.10.1" +thiserror = "1.0.20" +anyhow = "1.0" + +lalrpop-util = "0.20.0" +logos = "0.13" +convert_case = "0.6" + +arbitrary = { version = "1.0", 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 } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +stacker = "0.1" + +[dev-dependencies] +goldenfile = "1.1.0" +test-generator = "0.3.0" +rand = "0.8" + +[features] +configs = ["serde_dhall"] +random = ["configs", "arbitrary", "fake", "rand"] +all = ["random"] + +# docs.rs-specific configuration +# To test locally: RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --features all +[package.metadata.docs.rs] +features = ["all"] +# defines the configuration attribute `docsrs` +rustdoc-args = ["--cfg", "docsrs"] diff --git a/rust/candid_parser/LICENSE b/rust/candid_parser/LICENSE new file mode 120000 index 00000000..30cff740 --- /dev/null +++ b/rust/candid_parser/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/rust/candid/build.rs b/rust/candid_parser/build.rs similarity index 62% rename from rust/candid/build.rs rename to rust/candid_parser/build.rs index e6396a04..d8dc3bac 100644 --- a/rust/candid/build.rs +++ b/rust/candid_parser/build.rs @@ -1,8 +1,7 @@ fn main() { - #[cfg(feature = "parser")] lalrpop::Configuration::new() .use_cargo_dir_conventions() .emit_rerun_directives(true) - .process_file("src/parser/grammar.lalrpop") + .process_file("src/grammar.lalrpop") .unwrap(); } diff --git a/rust/candid/src/bindings/analysis.rs b/rust/candid_parser/src/bindings/analysis.rs similarity index 98% rename from rust/candid/src/bindings/analysis.rs rename to rust/candid_parser/src/bindings/analysis.rs index a6cc6d22..6d8f717c 100644 --- a/rust/candid/src/bindings/analysis.rs +++ b/rust/candid_parser/src/bindings/analysis.rs @@ -1,5 +1,5 @@ -use crate::types::{Type, TypeEnv, TypeInner}; use crate::Result; +use candid::types::{Type, TypeEnv, TypeInner}; use std::collections::BTreeSet; /// Same as chase_actor, with seen set as part of the type. Used for chasing type names from type definitions. diff --git a/rust/candid/src/bindings/javascript.rs b/rust/candid_parser/src/bindings/javascript.rs similarity index 95% rename from rust/candid/src/bindings/javascript.rs rename to rust/candid_parser/src/bindings/javascript.rs index 739fba25..6b3ecf74 100644 --- a/rust/candid/src/bindings/javascript.rs +++ b/rust/candid_parser/src/bindings/javascript.rs @@ -1,6 +1,6 @@ use super::analysis::{chase_actor, chase_types, infer_rec}; -use crate::pretty::*; -use crate::types::{Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInner}; +use candid::pretty::*; +use candid::types::{Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInner}; use pretty::RcDoc; use std::collections::BTreeSet; @@ -170,7 +170,7 @@ fn pp_args(args: &[Type]) -> RcDoc { enclose("[", doc, "]") } -fn pp_modes(modes: &[crate::types::FuncMode]) -> RcDoc { +fn pp_modes(modes: &[candid::types::FuncMode]) -> RcDoc { let doc = concat( modes .iter() @@ -267,10 +267,10 @@ pub fn compile(env: &TypeEnv, actor: &Option) -> String { } pub mod value { - use super::super::candid::value::number_to_string; - use crate::pretty::*; - use crate::types::value::{IDLArgs, IDLField, IDLValue}; - use crate::types::Label; + use candid::pretty::*; + use candid::pretty_printer::value::number_to_string; + use candid::types::value::{IDLArgs, IDLField, IDLValue}; + use candid::types::Label; use pretty::RcDoc; fn is_tuple(v: &IDLValue) -> bool { @@ -357,9 +357,9 @@ pub mod value { pub mod test { use super::value; - use crate::parser::test::{HostAssert, HostTest, Test}; - use crate::pretty::*; - use crate::TypeEnv; + use crate::test::{HostAssert, HostTest, Test}; + use candid::pretty::*; + use candid::TypeEnv; use pretty::RcDoc; fn pp_hex(bytes: &[u8]) -> RcDoc { @@ -367,7 +367,7 @@ pub mod test { .append(RcDoc::as_string(hex::encode(bytes))) .append("', 'hex')") } - fn pp_encode<'a>(args: &'a crate::IDLArgs, tys: &'a [crate::types::Type]) -> RcDoc<'a> { + fn pp_encode<'a>(args: &'a candid::IDLArgs, tys: &'a [candid::types::Type]) -> RcDoc<'a> { let vals = value::pp_args(args); let tys = super::pp_args(tys); let items = [tys, vals]; @@ -375,7 +375,7 @@ pub mod test { str("IDL.encode").append(enclose("(", params, ")")) } - fn pp_decode<'a>(bytes: &'a [u8], tys: &'a [crate::types::Type]) -> RcDoc<'a> { + fn pp_decode<'a>(bytes: &'a [u8], tys: &'a [candid::types::Type]) -> RcDoc<'a> { let hex = pp_hex(bytes); let tys = super::pp_args(tys); let items = [tys, hex]; @@ -404,7 +404,7 @@ import { Principal } from './principal'; for (i, assert) in test.asserts.iter().enumerate() { let mut types = Vec::new(); for ty in assert.typ.iter() { - types.push(env.ast_to_type(ty).unwrap()); + types.push(crate::typing::ast_to_type(&env, ty).unwrap()); } let host = HostTest::from_assert(assert, &env, &types); let mut expects = Vec::new(); diff --git a/rust/candid_parser/src/bindings/mod.rs b/rust/candid_parser/src/bindings/mod.rs new file mode 100644 index 00000000..ae2a95b4 --- /dev/null +++ b/rust/candid_parser/src/bindings/mod.rs @@ -0,0 +1,8 @@ +//! Candid bindings for different languages. +// This module assumes the input are type checked, it is safe to use unwrap. + +pub mod analysis; +pub mod javascript; +pub mod motoko; +pub mod rust; +pub mod typescript; diff --git a/rust/candid/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs similarity index 95% rename from rust/candid/src/bindings/motoko.rs rename to rust/candid_parser/src/bindings/motoko.rs index 48148e68..ef4079b5 100644 --- a/rust/candid/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -1,10 +1,10 @@ // This module implements the Candid to Motoko binding as specified in // https://github.com/dfinity/motoko/blob/master/design/IDL-Motoko.md -use super::candid::is_valid_as_id; -use crate::pretty::*; -use crate::types::FuncMode; -use crate::types::{Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInner}; +use candid::pretty::*; +use candid::pretty_printer::is_valid_as_id; +use candid::types::FuncMode; +use candid::types::{Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInner}; use pretty::RcDoc; // The definition of tuple is language specific. @@ -84,7 +84,9 @@ fn escape(id: &str, is_method: bool) -> RcDoc { str(id) } } else if !is_method { - str("_").append(crate::idl_hash(id).to_string()).append("_") + str("_") + .append(candid::idl_hash(id).to_string()) + .append("_") } else { panic!("Candid method {id} is not a valid Motoko id"); } diff --git a/rust/candid/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs similarity index 98% rename from rust/candid/src/bindings/rust.rs rename to rust/candid_parser/src/bindings/rust.rs index fe399dbb..256d2ef8 100644 --- a/rust/candid/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -1,6 +1,6 @@ use super::analysis::{chase_actor, infer_rec}; -use crate::pretty::*; -use crate::types::{Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInner}; +use candid::pretty::*; +use candid::types::{Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInner}; use convert_case::{Case, Casing}; use pretty::RcDoc; use std::collections::BTreeSet; @@ -18,7 +18,7 @@ pub struct Config { /// Applies to all types for now pub type_attributes: String, /// Only generates SERVICE struct if canister_id is not provided - pub canister_id: Option, + pub canister_id: Option, /// Service name when canister id is provided pub service_name: String, pub target: Target, @@ -62,7 +62,7 @@ fn ident_(id: &str, case: Option) -> (RcDoc, bool) { || id.starts_with(|c: char| !c.is_ascii_alphabetic() && c != '_') || id.chars().any(|c| !c.is_ascii_alphanumeric() && c != '_') { - return (RcDoc::text(format!("_{}_", crate::idl_hash(id))), true); + return (RcDoc::text(format!("_{}_", candid::idl_hash(id))), true); } let (is_rename, id) = if let Some(case) = case { let new_id = id.to_case(case); @@ -269,7 +269,7 @@ fn pp_args(args: &[Type]) -> RcDoc { fn pp_ty_func(f: &Function) -> RcDoc { let args = pp_args(&f.args); let rets = pp_args(&f.rets); - let modes = super::candid::pp_modes(&f.modes); + let modes = candid::pretty_printer::pp_modes(&f.modes); args.append(" ->") .append(RcDoc::space()) .append(rets.append(modes)) diff --git a/rust/candid/src/bindings/typescript.rs b/rust/candid_parser/src/bindings/typescript.rs similarity index 98% rename from rust/candid/src/bindings/typescript.rs rename to rust/candid_parser/src/bindings/typescript.rs index 9d9bc783..ddb4451b 100644 --- a/rust/candid/src/bindings/typescript.rs +++ b/rust/candid_parser/src/bindings/typescript.rs @@ -1,6 +1,6 @@ use super::javascript::{ident, is_tuple}; -use crate::pretty::*; -use crate::types::{Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInner}; +use candid::pretty::*; +use candid::types::{Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInner}; use pretty::RcDoc; fn pp_ty<'a>(env: &'a TypeEnv, ty: &'a Type, is_ref: bool) -> RcDoc<'a> { diff --git a/rust/candid/src/parser/configs.rs b/rust/candid_parser/src/configs.rs similarity index 99% rename from rust/candid/src/parser/configs.rs rename to rust/candid_parser/src/configs.rs index 39371c86..7acceb41 100644 --- a/rust/candid/src/parser/configs.rs +++ b/rust/candid_parser/src/configs.rs @@ -1,5 +1,5 @@ -use crate::types::Type; use crate::Result; +use candid::types::Type; use serde::de::DeserializeOwned; use serde_dhall::{from_simple_value, SimpleValue}; diff --git a/rust/candid_parser/src/error.rs b/rust/candid_parser/src/error.rs new file mode 100644 index 00000000..db8e86d3 --- /dev/null +++ b/rust/candid_parser/src/error.rs @@ -0,0 +1,126 @@ +//! When serializing or deserializing Candid goes wrong. + +use codespan_reporting::diagnostic::Label; +use std::io; +use thiserror::Error; + +use crate::token; +use codespan_reporting::{ + diagnostic::Diagnostic, + files::{Error as ReportError, SimpleFile}, + term::{self, termcolor::StandardStream}, +}; + +pub type Result = std::result::Result; + +#[derive(Debug, Error)] +pub enum Error { + #[error("Candid parser error: {0}")] + Parse(#[from] token::ParserError), + + #[error(transparent)] + Custom(#[from] anyhow::Error), + + #[error(transparent)] + CandidError(#[from] ::candid::Error), +} + +impl Error { + pub fn msg(msg: T) -> Self { + Error::Custom(anyhow::anyhow!(msg.to_string())) + } + + pub fn report(&self) -> Diagnostic<()> { + match self { + Error::Parse(e) => { + use lalrpop_util::ParseError::*; + let mut diag = Diagnostic::error().with_message("parser error"); + let label = match e { + User { error } => { + Label::primary((), error.span.clone()).with_message(&error.err) + } + InvalidToken { location } => { + Label::primary((), *location..location + 1).with_message("Invalid token") + } + UnrecognizedEof { location, expected } => { + diag = diag.with_notes(report_expected(expected)); + Label::primary((), *location..location + 1).with_message("Unexpected EOF") + } + UnrecognizedToken { token, expected } => { + diag = diag.with_notes(report_expected(expected)); + Label::primary((), token.0..token.2).with_message("Unexpected token") + } + ExtraToken { token } => { + Label::primary((), token.0..token.2).with_message("Extra token") + } + }; + diag.with_labels(vec![label]) + } + Error::Custom(e) => Diagnostic::error().with_message(e.to_string()), + Error::CandidError(e) => Diagnostic::error().with_message(e.to_string()), + } + } +} + +fn report_expected(expected: &[String]) -> Vec { + if expected.is_empty() { + return Vec::new(); + } + use pretty::RcDoc; + let doc: RcDoc<()> = RcDoc::intersperse( + expected.iter().map(RcDoc::text), + RcDoc::text(",").append(RcDoc::softline()), + ); + let header = if expected.len() == 1 { + "Expects" + } else { + "Expects one of" + }; + let doc = RcDoc::text(header).append(RcDoc::softline().append(doc)); + vec![doc.pretty(70).to_string()] +} + +impl From for Error { + fn from(e: io::Error) -> Error { + Error::msg(format!("io error: {e}")) + } +} + +impl From for Error { + fn from(e: ReportError) -> Error { + Error::msg(e) + } +} +#[cfg_attr(docsrs, doc(cfg(feature = "random")))] +#[cfg(feature = "random")] +impl From for Error { + fn from(e: arbitrary::Error) -> Error { + Error::msg(format!("arbitrary error: {e}")) + } +} + +#[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}")) + } +} + +pub fn pretty_parse(name: &str, str: &str) -> Result +where + T: std::str::FromStr, +{ + str.parse::().or_else(|e| { + pretty_diagnose(name, str, &e)?; + Err(e) + }) +} + +pub fn pretty_diagnose(file_name: &str, source: &str, e: &Error) -> Result<()> { + let writer = StandardStream::stderr(term::termcolor::ColorChoice::Auto); + let config = term::Config::default(); + let file = SimpleFile::new(file_name, source); + term::emit(&mut writer.lock(), &config, &file, &e.report())?; + Ok(()) +} diff --git a/rust/candid/src/parser/grammar.lalrpop b/rust/candid_parser/src/grammar.lalrpop similarity index 97% rename from rust/candid/src/parser/grammar.lalrpop rename to rust/candid_parser/src/grammar.lalrpop index 6f0e2356..153924f0 100644 --- a/rust/candid/src/parser/grammar.lalrpop +++ b/rust/candid_parser/src/grammar.lalrpop @@ -1,10 +1,10 @@ -use crate::types::value::{IDLField, IDLValue, IDLArgs, VariantValue}; -use crate::types::{TypeEnv, FuncMode}; -use crate::utils::check_unique; use super::types::{IDLType, PrimType, TypeField, FuncType, Binding, Dec, IDLProg, IDLTypes, IDLInitArgs}; use super::test::{Assert, Input, Test}; use super::token::{Token, error2, LexicalError, Span}; -use crate::{Principal, types::Label}; +use candid::{Principal, types::Label}; +use candid::types::value::{IDLField, IDLValue, IDLArgs, VariantValue}; +use candid::types::{TypeEnv, FuncMode}; +use candid::utils::check_unique; grammar; @@ -116,7 +116,7 @@ AnnVal: IDLValue = { => <>, > ":" > =>? { let env = TypeEnv::new(); - let typ = env.ast_to_type(&typ.0).map_err(|e| error2(e, typ.1))?; + let typ = crate::typing::ast_to_type(&env, &typ.0).map_err(|e| error2(e, typ.1))?; arg.0.annotate_type(true, &env, &typ).map_err(|e| error2(e, arg.1)) } } diff --git a/rust/candid_parser/src/grammar.rs b/rust/candid_parser/src/grammar.rs new file mode 100644 index 00000000..8fa4f72f --- /dev/null +++ b/rust/candid_parser/src/grammar.rs @@ -0,0 +1,2 @@ +#![allow(clippy::all)] +include!(concat!(env!("OUT_DIR"), "/grammar.rs")); diff --git a/rust/candid_parser/src/lib.rs b/rust/candid_parser/src/lib.rs new file mode 100644 index 00000000..1cbc0e46 --- /dev/null +++ b/rust/candid_parser/src/lib.rs @@ -0,0 +1,147 @@ +//! # Candid Parser +//! +//! Provides parser for Candid type and value. +//! * `str.parse::()` parses the Candid signature file to Candid AST. +//! * `parse_idl_args()` parses the Candid value in text format to a struct `IDLArg` that can be used for serialization and deserialization between Candid and an enum type `IDLValue` in Rust. + +//! ## Parse [`candid::IDLArgs`] +//! +//! We provide a parser to parse Candid values in text format. +//! +//! ``` +//! # fn f() -> anyhow::Result<()> { +//! use candid::{IDLArgs, TypeEnv}; +//! use candid_parser::parse_idl_args; +//! // Candid values represented in text format +//! let text_value = r#" +//! (42, opt true, vec {1;2;3}, +//! opt record {label="text"; 42="haha"}) +//! "#; +//! +//! // Parse text format into IDLArgs for serialization +//! let args: IDLArgs = parse_idl_args(text_value)?; +//! let encoded: Vec = args.to_bytes()?; +//! +//! // Deserialize into IDLArgs +//! let decoded: IDLArgs = IDLArgs::from_bytes(&encoded)?; +//! assert_eq!(encoded, decoded.to_bytes()?); +//! +//! // Convert IDLArgs to text format +//! let output: String = decoded.to_string(); +//! let parsed_args: IDLArgs = parse_idl_args(&output)?; +//! let annotated_args = args.annotate_types(true, &TypeEnv::new(), &parsed_args.get_types())?; +//! assert_eq!(annotated_args, parsed_args); +//! # Ok(()) +//! # } +//! ``` +//! Note that when parsing Candid values, we assume the number literals are always of type `Int`. +//! This can be changed by providing the type of the method arguments, which can usually be obtained +//! by parsing a Candid file in the following section. +//! +//! ## Operating on Candid AST +//! +//! We provide a parser and type checker for Candid files specifying the service interface. +//! +//! ``` +//! # fn f() -> anyhow::Result<()> { +//! use candid::{TypeEnv, types::{Type, TypeInner}}; +//! use candid_parser::{IDLProg, check_prog}; +//! let did_file = r#" +//! type List = opt record { head: int; tail: List }; +//! type byte = nat8; +//! service : { +//! f : (byte, int, nat, int8) -> (List); +//! g : (List) -> (int) query; +//! } +//! "#; +//! +//! // Parse did file into an AST +//! let ast: IDLProg = did_file.parse()?; +//! +//! // Type checking a given .did file +//! // let (env, opt_actor) = check_file("a.did")?; +//! // Or alternatively, use check_prog to check in-memory did file +//! // Note that file import is ignored by check_prog. +//! let mut env = TypeEnv::new(); +//! let actor: Type = check_prog(&mut env, &ast)?.unwrap(); +//! +//! let method = env.get_method(&actor, "g").unwrap(); +//! assert_eq!(method.is_query(), true); +//! assert_eq!(method.args, vec![TypeInner::Var("List".to_string()).into()]); +//! # Ok(()) +//! # } +//! ``` +//! +//! ## Serializing untyped Candid values with type annotations. +//! +//! With type signatures from the Candid file, [`candid::IDLArgs`](parser/value/struct.IDLArgs.html) +//! uses `to_bytes_with_types` function to serialize arguments directed by the Candid types. +//! This is useful when serializing different number types and recursive types. +//! There is no need to use types for deserialization as the types are available in the Candid message. +//! +//! ``` +//! # fn f() -> anyhow::Result<()> { +//! use candid::{IDLArgs, types::value::IDLValue}; +//! use candid_parser::parse_idl_args; +//! # use candid::TypeEnv; +//! # use candid_parser::{IDLProg, check_prog}; +//! # let did_file = r#" +//! # type List = opt record { head: int; tail: List }; +//! # type byte = nat8; +//! # service : { +//! # f : (byte, int, nat, int8) -> (List); +//! # g : (List) -> (int) query; +//! # } +//! # "#; +//! # let ast = did_file.parse::()?; +//! # let mut env = TypeEnv::new(); +//! # let actor = check_prog(&mut env, &ast)?.unwrap(); +//! // Get method type f : (byte, int, nat, int8) -> (List) +//! let method = env.get_method(&actor, "f").unwrap(); +//! let args = parse_idl_args("(42, 42, 42, 42)")?; +//! // Serialize arguments with candid types +//! let encoded = args.to_bytes_with_types(&env, &method.args)?; +//! let decoded = IDLArgs::from_bytes(&encoded)?; +//! assert_eq!(decoded.args, +//! vec![IDLValue::Nat8(42), +//! IDLValue::Int(42.into()), +//! IDLValue::Nat(42.into()), +//! IDLValue::Int8(42) +//! ]); +//! # Ok(()) +//! # } +//! ``` + +// only enables the `doc_cfg` feature when +// the `docsrs` configuration attribute is defined +#![cfg_attr(docsrs, feature(doc_cfg))] + +pub mod error; +pub use error::{pretty_parse, Error, Result}; + +pub mod bindings; +pub mod grammar; +pub mod token; +pub mod types; +pub mod utils; +pub use types::IDLProg; +pub mod typing; +pub use typing::{check_file, check_prog, pretty_check_file}; + +#[cfg_attr(docsrs, doc(cfg(feature = "configs")))] +#[cfg(feature = "configs")] +pub mod configs; +#[cfg_attr(docsrs, doc(cfg(feature = "random")))] +#[cfg(feature = "random")] +pub mod random; +pub mod test; + +pub fn parse_idl_args(s: &str) -> crate::Result { + let lexer = token::Tokenizer::new(s); + Ok(grammar::ArgsParser::new().parse(lexer)?) +} + +pub fn parse_idl_value(s: &str) -> crate::Result { + let lexer = token::Tokenizer::new(s); + Ok(grammar::ArgParser::new().parse(lexer)?) +} diff --git a/rust/candid/src/parser/random.rs b/rust/candid_parser/src/random.rs similarity index 82% rename from rust/candid/src/parser/random.rs rename to rust/candid_parser/src/random.rs index 79ce9019..b35fe6a1 100644 --- a/rust/candid/src/parser/random.rs +++ b/rust/candid_parser/src/random.rs @@ -1,9 +1,9 @@ use super::configs::{path_name, Configs}; -use crate::types::value::{IDLArgs, IDLField, IDLValue, VariantValue}; -use crate::types::{Field, Type, TypeEnv, TypeInner}; -use crate::Deserialize; use crate::{Error, Result}; use arbitrary::{unstructured::Int, Arbitrary, Unstructured}; +use candid::types::value::{IDLArgs, IDLField, IDLValue, VariantValue}; +use candid::types::{Field, Type, TypeEnv, TypeInner}; +use candid::Deserialize; use std::collections::HashSet; use std::convert::TryFrom; @@ -115,7 +115,7 @@ impl<'a> GenState<'a> { assert!(self.config.depth.is_none()); if let Some(vec) = &self.config.value { let v = u.choose(vec)?; - let v = v.parse::()?; + let v: IDLValue = super::parse_idl_value(v)?; let v = v.annotate_type(true, self.env, ty)?; self.pop_state(old_config, ty, false); return Ok(v); @@ -140,7 +140,7 @@ impl<'a> GenState<'a> { TypeInner::Int64 => IDLValue::Int64(arbitrary_num(u, self.config.range)?), TypeInner::Float32 => IDLValue::Float32(u.arbitrary()?), TypeInner::Float64 => IDLValue::Float64(u.arbitrary()?), - TypeInner::Principal => IDLValue::Principal(crate::Principal::anonymous()), + TypeInner::Principal => IDLValue::Principal(candid::Principal::anonymous()), TypeInner::Text => { IDLValue::Text(arbitrary_text(u, &self.config.text, &self.config.width)?) } @@ -148,7 +148,7 @@ impl<'a> GenState<'a> { let depths = if self.depth <= 0 || self.size <= 0 { [1, 0] } else { - [1, self.env.size(t).unwrap_or(MAX_DEPTH)] + [1, size(self.env, t).unwrap_or(MAX_DEPTH)] }; let idx = arbitrary_variant(u, &depths)?; if idx == 0 { @@ -159,7 +159,7 @@ impl<'a> GenState<'a> { } TypeInner::Vec(t) => { let width = self.config.width.or_else(|| { - let elem_size = self.env.size(t).unwrap_or(MAX_DEPTH); + let elem_size = size(self.env, t).unwrap_or(MAX_DEPTH); Some(std::cmp::max(0, self.size) as usize / elem_size) }); let len = arbitrary_len(u, width)?; @@ -186,7 +186,7 @@ impl<'a> GenState<'a> { TypeInner::Variant(fs) => { let choices = fs .iter() - .map(|Field { ty, .. }| self.env.size(ty).unwrap_or(MAX_DEPTH)); + .map(|Field { ty, .. }| size(self.env, ty).unwrap_or(MAX_DEPTH)); let sizes: Vec<_> = if self.depth <= 0 || self.size <= 0 { let min = choices.clone().min().unwrap_or(0); choices.map(|d| if d > min { 0 } else { d }).collect() @@ -211,62 +211,58 @@ impl<'a> GenState<'a> { } } -impl IDLArgs { - pub fn any(seed: &[u8], tree: &Configs, env: &TypeEnv, types: &[Type]) -> Result { - let mut u = arbitrary::Unstructured::new(seed); - 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 v = state.any(&mut u, t)?; - args.push(v); - } - Ok(IDLArgs { args }) +pub fn any(seed: &[u8], tree: &Configs, env: &TypeEnv, types: &[Type]) -> Result { + let mut u = arbitrary::Unstructured::new(seed); + 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 v = state.any(&mut u, t)?; + args.push(v); } + Ok(IDLArgs { args }) } -impl TypeEnv { - /// Approxiamte upper bound for IDLValue size of type t. Returns None if infinite. - fn size_helper(&self, seen: &mut HashSet, t: &Type) -> Option { - use TypeInner::*; - Some(match t.as_ref() { - Var(id) => { - if seen.insert(id.to_string()) { - let ty = self.rec_find_type(id).unwrap(); - let res = self.size_helper(seen, ty)?; - seen.remove(id); - res - } else { - return None; - } +fn size_helper(env: &TypeEnv, seen: &mut HashSet, t: &Type) -> Option { + use TypeInner::*; + Some(match t.as_ref() { + Var(id) => { + if seen.insert(id.to_string()) { + let ty = env.rec_find_type(id).unwrap(); + let res = size_helper(env, seen, ty)?; + seen.remove(id); + res + } else { + return None; } - Empty => 0, - Opt(t) => 1 + self.size_helper(seen, t)?, - Vec(t) => 1 + self.size_helper(seen, t)? * 2, - Record(fs) => { - let mut sum = 0; - for Field { ty, .. } in fs.iter() { - sum += self.size_helper(seen, ty)?; - } - 1 + sum + } + Empty => 0, + Opt(t) => 1 + size_helper(env, seen, t)?, + Vec(t) => 1 + size_helper(env, seen, t)? * 2, + Record(fs) => { + let mut sum = 0; + for Field { ty, .. } in fs.iter() { + sum += size_helper(env, seen, ty)?; } - Variant(fs) => { - let mut max = 0; - for Field { ty, .. } in fs.iter() { - let s = self.size_helper(seen, ty)?; - if s > max { - max = s; - }; - } - 1 + max + 1 + sum + } + Variant(fs) => { + let mut max = 0; + for Field { ty, .. } in fs.iter() { + let s = size_helper(env, seen, ty)?; + if s > max { + max = s; + }; } - _ => 1, - }) - } - fn size(&self, t: &Type) -> Option { - let mut seen = HashSet::new(); - self.size_helper(&mut seen, t) - } + 1 + max + } + _ => 1, + }) +} + +fn size(env: &TypeEnv, t: &Type) -> Option { + let mut seen = HashSet::new(); + size_helper(env, &mut seen, t) } fn choose_range(u: &mut Unstructured, ranges: &[std::ops::RangeInclusive]) -> Result { diff --git a/rust/candid/src/parser/test.rs b/rust/candid_parser/src/test.rs similarity index 95% rename from rust/candid/src/parser/test.rs rename to rust/candid_parser/src/test.rs index 7fefd6d1..f48c2ac4 100644 --- a/rust/candid/src/parser/test.rs +++ b/rust/candid_parser/src/test.rs @@ -1,8 +1,8 @@ use super::types::{Dec, IDLProg, IDLType}; use super::typing::check_prog; -use crate::types::value::IDLArgs; -use crate::types::{Type, TypeEnv}; use crate::{Error, Result}; +use candid::types::value::IDLArgs; +use candid::types::{Type, TypeEnv}; type TupType = Vec; @@ -50,7 +50,7 @@ impl Assert { impl Input { pub fn parse(&self, env: &TypeEnv, types: &[Type]) -> Result { match self { - Input::Text(ref s) => s.parse::()?.annotate_types(true, env, types), + Input::Text(ref s) => Ok(super::parse_idl_args(s)?.annotate_types(true, env, types)?), Input::Blob(ref bytes) => Ok(IDLArgs::from_bytes_with_types(bytes, env, types)?), } } @@ -82,7 +82,7 @@ impl HostTest { Input::Text(s) => { // Without type annotation, numbers are all of type int. // Assertion may not pass. - let parsed = s.parse::(); + let parsed = crate::parse_idl_args(s); if parsed.is_err() { let desc = format!("(skip) {}", assert.desc()); return HostTest { desc, asserts }; @@ -145,7 +145,7 @@ pub fn check(test: Test) -> Result<()> { print!("Checking {} {}...", i + 1, assert.desc()); let mut types = Vec::new(); for ty in assert.typ.iter() { - types.push(env.ast_to_type(ty)?); + types.push(super::typing::ast_to_type(&env, ty)?); } let input = assert.left.parse(&env, &types); let pass = if let Some(assert_right) = &assert.right { diff --git a/rust/candid/src/parser/token.rs b/rust/candid_parser/src/token.rs similarity index 100% rename from rust/candid/src/parser/token.rs rename to rust/candid_parser/src/token.rs diff --git a/rust/candid/src/parser/types.rs b/rust/candid_parser/src/types.rs similarity index 98% rename from rust/candid/src/parser/types.rs rename to rust/candid_parser/src/types.rs index 5c9794f6..70861704 100644 --- a/rust/candid/src/parser/types.rs +++ b/rust/candid_parser/src/types.rs @@ -1,5 +1,5 @@ -use crate::types::{FuncMode, Label}; use crate::Result; +use candid::types::{FuncMode, Label}; #[derive(Debug, Clone)] pub enum IDLType { diff --git a/rust/candid/src/parser/typing.rs b/rust/candid_parser/src/typing.rs similarity index 95% rename from rust/candid/src/parser/typing.rs rename to rust/candid_parser/src/typing.rs index 1b126d59..60bd26ba 100644 --- a/rust/candid/src/parser/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -1,6 +1,6 @@ use super::types::*; -use crate::types::{Field, Function, Type, TypeEnv, TypeInner}; use crate::{pretty_parse, Error, Result}; +use candid::types::{Field, Function, Type, TypeEnv, TypeInner}; use std::collections::BTreeSet; use std::path::{Path, PathBuf}; @@ -9,15 +9,13 @@ pub struct Env<'a> { pub pre: bool, } -impl TypeEnv { - /// Convert candid AST to internal Type - pub fn ast_to_type(&self, ast: &super::types::IDLType) -> Result { - let env = Env { - te: &mut self.clone(), - pre: false, - }; - check_type(&env, ast) - } +/// Convert candid AST to internal Type +pub fn ast_to_type(env: &TypeEnv, ast: &super::types::IDLType) -> Result { + let env = Env { + te: &mut env.clone(), + pre: false, + }; + check_type(&env, ast) } fn check_prim(prim: &PrimType) -> Type { @@ -80,7 +78,7 @@ pub fn check_type(env: &Env, t: &IDLType) -> Result { return Err(Error::msg("cannot have more than one mode")); } if func.modes.len() == 1 - && func.modes[0] == crate::types::FuncMode::Oneway + && func.modes[0] == candid::types::FuncMode::Oneway && !t2.is_empty() { return Err(Error::msg("oneway function has non-unit return type")); diff --git a/rust/candid_parser/src/utils.rs b/rust/candid_parser/src/utils.rs new file mode 100644 index 00000000..82a1b32c --- /dev/null +++ b/rust/candid_parser/src/utils.rs @@ -0,0 +1,82 @@ +use crate::{check_prog, pretty_check_file, pretty_parse, Error, Result}; +use candid::{types::Type, TypeEnv}; +use std::path::Path; + +pub enum CandidSource<'a> { + File(&'a Path), + Text(&'a str), +} + +impl<'a> CandidSource<'a> { + pub fn load(&self) -> Result<(TypeEnv, Option)> { + Ok(match self { + CandidSource::File(path) => pretty_check_file(path)?, + CandidSource::Text(str) => { + let ast = pretty_parse("", str)?; + let mut env = TypeEnv::new(); + let actor = check_prog(&mut env, &ast)?; + (env, actor) + } + }) + } +} + +/// Check compatibility of two service types +pub fn service_compatible(new: CandidSource, old: CandidSource) -> Result<()> { + let (mut env, t1) = new.load()?; + let t1 = t1.ok_or_else(|| Error::msg("new interface has no main service type"))?; + let (env2, t2) = old.load()?; + let t2 = t2.ok_or_else(|| Error::msg("old interface has no main service type"))?; + let mut gamma = std::collections::HashSet::new(); + let t2 = env.merge_type(env2, t2); + candid::types::subtype::subtype(&mut gamma, &env, &t1, &t2)?; + Ok(()) +} + +/// Check structural equality of two service types +pub fn service_equal(left: CandidSource, right: CandidSource) -> Result<()> { + let (mut env, t1) = left.load()?; + let t1 = t1.ok_or_else(|| Error::msg("left interface has no main service type"))?; + let (env2, t2) = right.load()?; + let t2 = t2.ok_or_else(|| Error::msg("right interface has no main service type"))?; + let mut gamma = std::collections::HashSet::new(); + let t2 = env.merge_type(env2, t2); + candid::types::subtype::equal(&mut gamma, &env, &t1, &t2)?; + Ok(()) +} + +/// Take a did file and outputs the init args and the service type (without init args). +/// If the original did file contains imports, the output flattens the type definitions. +/// For now, the comments from the original did file is omitted. +pub fn instantiate_candid(candid: CandidSource) -> Result<(Vec, (TypeEnv, Type))> { + use candid::types::TypeInner; + let (env, serv) = candid.load()?; + let serv = serv.ok_or_else(|| Error::msg("the Candid interface has no main service type"))?; + let serv = env.trace_type(&serv)?; + Ok(match serv.as_ref() { + TypeInner::Class(args, ty) => (args.clone(), (env, ty.clone())), + TypeInner::Service(_) => (vec![], (env, serv)), + _ => unreachable!(), + }) +} + +/// Merge canister metadata candid:args and candid:service into a service constructor. +/// If candid:service already contains init args, returns the original did file. +pub fn merge_init_args(candid: &str, init: &str) -> Result<(TypeEnv, Type)> { + use crate::{types::IDLInitArgs, typing::check_init_args}; + use candid::types::TypeInner; + let candid = CandidSource::Text(candid); + let (env, serv) = candid.load()?; + let serv = serv.ok_or_else(|| Error::msg("the Candid interface has no main service type"))?; + let serv = env.trace_type(&serv)?; + match serv.as_ref() { + TypeInner::Class(_, _) => Ok((env, serv)), + TypeInner::Service(_) => { + let prog = init.parse::()?; + let mut env2 = TypeEnv::new(); + let args = check_init_args(&mut env2, &env, &prog)?; + Ok((env2, TypeInner::Class(args, serv).into())) + } + _ => unreachable!(), + } +} diff --git a/rust/candid/tests/assets/actor.did b/rust/candid_parser/tests/assets/actor.did similarity index 100% rename from rust/candid/tests/assets/actor.did rename to rust/candid_parser/tests/assets/actor.did diff --git a/rust/candid/tests/assets/bad_comment.did b/rust/candid_parser/tests/assets/bad_comment.did similarity index 100% rename from rust/candid/tests/assets/bad_comment.did rename to rust/candid_parser/tests/assets/bad_comment.did diff --git a/rust/candid/tests/assets/bad_import.did b/rust/candid_parser/tests/assets/bad_import.did similarity index 100% rename from rust/candid/tests/assets/bad_import.did rename to rust/candid_parser/tests/assets/bad_import.did diff --git a/rust/candid/tests/assets/class.did b/rust/candid_parser/tests/assets/class.did similarity index 100% rename from rust/candid/tests/assets/class.did rename to rust/candid_parser/tests/assets/class.did diff --git a/rust/candid/tests/assets/codegen/basic/invalid_id.did b/rust/candid_parser/tests/assets/codegen/basic/invalid_id.did similarity index 100% rename from rust/candid/tests/assets/codegen/basic/invalid_id.did rename to rust/candid_parser/tests/assets/codegen/basic/invalid_id.did diff --git a/rust/candid/tests/assets/codegen/basic/invalid_id.rs b/rust/candid_parser/tests/assets/codegen/basic/invalid_id.rs similarity index 100% rename from rust/candid/tests/assets/codegen/basic/invalid_id.rs rename to rust/candid_parser/tests/assets/codegen/basic/invalid_id.rs diff --git a/rust/candid/tests/assets/codegen/basic/prim_types.did b/rust/candid_parser/tests/assets/codegen/basic/prim_types.did similarity index 100% rename from rust/candid/tests/assets/codegen/basic/prim_types.did rename to rust/candid_parser/tests/assets/codegen/basic/prim_types.did diff --git a/rust/candid/tests/assets/codegen/basic/prim_types.rs b/rust/candid_parser/tests/assets/codegen/basic/prim_types.rs similarity index 100% rename from rust/candid/tests/assets/codegen/basic/prim_types.rs rename to rust/candid_parser/tests/assets/codegen/basic/prim_types.rs diff --git a/rust/candid/tests/assets/codegen/basic/recursive.did b/rust/candid_parser/tests/assets/codegen/basic/recursive.did similarity index 100% rename from rust/candid/tests/assets/codegen/basic/recursive.did rename to rust/candid_parser/tests/assets/codegen/basic/recursive.did diff --git a/rust/candid/tests/assets/codegen/basic/recursive.rs b/rust/candid_parser/tests/assets/codegen/basic/recursive.rs similarity index 100% rename from rust/candid/tests/assets/codegen/basic/recursive.rs rename to rust/candid_parser/tests/assets/codegen/basic/recursive.rs diff --git a/rust/candid/tests/assets/codegen/examples/linkedup.did b/rust/candid_parser/tests/assets/codegen/examples/linkedup.did similarity index 100% rename from rust/candid/tests/assets/codegen/examples/linkedup.did rename to rust/candid_parser/tests/assets/codegen/examples/linkedup.did diff --git a/rust/candid/tests/assets/codegen/examples/linkedup.rs b/rust/candid_parser/tests/assets/codegen/examples/linkedup.rs similarity index 100% rename from rust/candid/tests/assets/codegen/examples/linkedup.rs rename to rust/candid_parser/tests/assets/codegen/examples/linkedup.rs diff --git a/rust/candid/tests/assets/collision_fields.did b/rust/candid_parser/tests/assets/collision_fields.did similarity index 100% rename from rust/candid/tests/assets/collision_fields.did rename to rust/candid_parser/tests/assets/collision_fields.did diff --git a/rust/candid/tests/assets/collision_fields2.did b/rust/candid_parser/tests/assets/collision_fields2.did similarity index 100% rename from rust/candid/tests/assets/collision_fields2.did rename to rust/candid_parser/tests/assets/collision_fields2.did diff --git a/rust/candid/tests/assets/comment.did b/rust/candid_parser/tests/assets/comment.did similarity index 100% rename from rust/candid/tests/assets/comment.did rename to rust/candid_parser/tests/assets/comment.did diff --git a/rust/candid/tests/assets/cyclic.did b/rust/candid_parser/tests/assets/cyclic.did similarity index 100% rename from rust/candid/tests/assets/cyclic.did rename to rust/candid_parser/tests/assets/cyclic.did diff --git a/rust/candid/tests/assets/escape.did b/rust/candid_parser/tests/assets/escape.did similarity index 100% rename from rust/candid/tests/assets/escape.did rename to rust/candid_parser/tests/assets/escape.did diff --git a/rust/candid/tests/assets/example.did b/rust/candid_parser/tests/assets/example.did similarity index 100% rename from rust/candid/tests/assets/example.did rename to rust/candid_parser/tests/assets/example.did diff --git a/rust/candid/tests/assets/fieldnat.did b/rust/candid_parser/tests/assets/fieldnat.did similarity index 100% rename from rust/candid/tests/assets/fieldnat.did rename to rust/candid_parser/tests/assets/fieldnat.did diff --git a/rust/candid/tests/assets/import/a.did b/rust/candid_parser/tests/assets/import/a.did similarity index 100% rename from rust/candid/tests/assets/import/a.did rename to rust/candid_parser/tests/assets/import/a.did diff --git a/rust/candid/tests/assets/import/b/b.did b/rust/candid_parser/tests/assets/import/b/b.did similarity index 100% rename from rust/candid/tests/assets/import/b/b.did rename to rust/candid_parser/tests/assets/import/b/b.did diff --git a/rust/candid/tests/assets/invalid_cyclic.did b/rust/candid_parser/tests/assets/invalid_cyclic.did similarity index 100% rename from rust/candid/tests/assets/invalid_cyclic.did rename to rust/candid_parser/tests/assets/invalid_cyclic.did diff --git a/rust/candid/tests/assets/keyword.did b/rust/candid_parser/tests/assets/keyword.did similarity index 100% rename from rust/candid/tests/assets/keyword.did rename to rust/candid_parser/tests/assets/keyword.did diff --git a/rust/candid/tests/assets/management.did b/rust/candid_parser/tests/assets/management.did similarity index 100% rename from rust/candid/tests/assets/management.did rename to rust/candid_parser/tests/assets/management.did diff --git a/rust/candid/tests/assets/not_func.did b/rust/candid_parser/tests/assets/not_func.did similarity index 100% rename from rust/candid/tests/assets/not_func.did rename to rust/candid_parser/tests/assets/not_func.did diff --git a/rust/candid/tests/assets/not_serv.did b/rust/candid_parser/tests/assets/not_serv.did similarity index 100% rename from rust/candid/tests/assets/not_serv.did rename to rust/candid_parser/tests/assets/not_serv.did diff --git a/rust/candid/tests/assets/ok/actor.d.ts b/rust/candid_parser/tests/assets/ok/actor.d.ts similarity index 100% rename from rust/candid/tests/assets/ok/actor.d.ts rename to rust/candid_parser/tests/assets/ok/actor.d.ts diff --git a/rust/candid/tests/assets/ok/actor.did b/rust/candid_parser/tests/assets/ok/actor.did similarity index 100% rename from rust/candid/tests/assets/ok/actor.did rename to rust/candid_parser/tests/assets/ok/actor.did diff --git a/rust/candid/tests/assets/ok/actor.js b/rust/candid_parser/tests/assets/ok/actor.js similarity index 100% rename from rust/candid/tests/assets/ok/actor.js rename to rust/candid_parser/tests/assets/ok/actor.js diff --git a/rust/candid/tests/assets/ok/actor.mo b/rust/candid_parser/tests/assets/ok/actor.mo similarity index 100% rename from rust/candid/tests/assets/ok/actor.mo rename to rust/candid_parser/tests/assets/ok/actor.mo diff --git a/rust/candid/tests/assets/ok/actor.rs b/rust/candid_parser/tests/assets/ok/actor.rs similarity index 100% rename from rust/candid/tests/assets/ok/actor.rs rename to rust/candid_parser/tests/assets/ok/actor.rs diff --git a/rust/candid/tests/assets/ok/bad_comment.fail b/rust/candid_parser/tests/assets/ok/bad_comment.fail similarity index 100% rename from rust/candid/tests/assets/ok/bad_comment.fail rename to rust/candid_parser/tests/assets/ok/bad_comment.fail diff --git a/rust/candid/tests/assets/ok/bad_import.fail b/rust/candid_parser/tests/assets/ok/bad_import.fail similarity index 100% rename from rust/candid/tests/assets/ok/bad_import.fail rename to rust/candid_parser/tests/assets/ok/bad_import.fail diff --git a/rust/candid/tests/assets/ok/class.d.ts b/rust/candid_parser/tests/assets/ok/class.d.ts similarity index 100% rename from rust/candid/tests/assets/ok/class.d.ts rename to rust/candid_parser/tests/assets/ok/class.d.ts diff --git a/rust/candid/tests/assets/ok/class.did b/rust/candid_parser/tests/assets/ok/class.did similarity index 100% rename from rust/candid/tests/assets/ok/class.did rename to rust/candid_parser/tests/assets/ok/class.did diff --git a/rust/candid/tests/assets/ok/class.js b/rust/candid_parser/tests/assets/ok/class.js similarity index 100% rename from rust/candid/tests/assets/ok/class.js rename to rust/candid_parser/tests/assets/ok/class.js diff --git a/rust/candid/tests/assets/ok/class.mo b/rust/candid_parser/tests/assets/ok/class.mo similarity index 100% rename from rust/candid/tests/assets/ok/class.mo rename to rust/candid_parser/tests/assets/ok/class.mo diff --git a/rust/candid/tests/assets/ok/class.rs b/rust/candid_parser/tests/assets/ok/class.rs similarity index 100% rename from rust/candid/tests/assets/ok/class.rs rename to rust/candid_parser/tests/assets/ok/class.rs diff --git a/rust/candid/tests/assets/ok/collision_fields.fail b/rust/candid_parser/tests/assets/ok/collision_fields.fail similarity index 100% rename from rust/candid/tests/assets/ok/collision_fields.fail rename to rust/candid_parser/tests/assets/ok/collision_fields.fail diff --git a/rust/candid/tests/assets/ok/collision_fields2.fail b/rust/candid_parser/tests/assets/ok/collision_fields2.fail similarity index 100% rename from rust/candid/tests/assets/ok/collision_fields2.fail rename to rust/candid_parser/tests/assets/ok/collision_fields2.fail diff --git a/rust/candid/tests/assets/ok/comment.d.ts b/rust/candid_parser/tests/assets/ok/comment.d.ts similarity index 100% rename from rust/candid/tests/assets/ok/comment.d.ts rename to rust/candid_parser/tests/assets/ok/comment.d.ts diff --git a/rust/candid/tests/assets/ok/comment.did b/rust/candid_parser/tests/assets/ok/comment.did similarity index 100% rename from rust/candid/tests/assets/ok/comment.did rename to rust/candid_parser/tests/assets/ok/comment.did diff --git a/rust/candid/tests/assets/ok/comment.js b/rust/candid_parser/tests/assets/ok/comment.js similarity index 100% rename from rust/candid/tests/assets/ok/comment.js rename to rust/candid_parser/tests/assets/ok/comment.js diff --git a/rust/candid/tests/assets/ok/comment.mo b/rust/candid_parser/tests/assets/ok/comment.mo similarity index 100% rename from rust/candid/tests/assets/ok/comment.mo rename to rust/candid_parser/tests/assets/ok/comment.mo diff --git a/rust/candid/tests/assets/ok/comment.rs b/rust/candid_parser/tests/assets/ok/comment.rs similarity index 100% rename from rust/candid/tests/assets/ok/comment.rs rename to rust/candid_parser/tests/assets/ok/comment.rs diff --git a/rust/candid/tests/assets/ok/cyclic.d.ts b/rust/candid_parser/tests/assets/ok/cyclic.d.ts similarity index 100% rename from rust/candid/tests/assets/ok/cyclic.d.ts rename to rust/candid_parser/tests/assets/ok/cyclic.d.ts diff --git a/rust/candid/tests/assets/ok/cyclic.did b/rust/candid_parser/tests/assets/ok/cyclic.did similarity index 100% rename from rust/candid/tests/assets/ok/cyclic.did rename to rust/candid_parser/tests/assets/ok/cyclic.did diff --git a/rust/candid/tests/assets/ok/cyclic.js b/rust/candid_parser/tests/assets/ok/cyclic.js similarity index 100% rename from rust/candid/tests/assets/ok/cyclic.js rename to rust/candid_parser/tests/assets/ok/cyclic.js diff --git a/rust/candid/tests/assets/ok/cyclic.mo b/rust/candid_parser/tests/assets/ok/cyclic.mo similarity index 100% rename from rust/candid/tests/assets/ok/cyclic.mo rename to rust/candid_parser/tests/assets/ok/cyclic.mo diff --git a/rust/candid/tests/assets/ok/cyclic.rs b/rust/candid_parser/tests/assets/ok/cyclic.rs similarity index 100% rename from rust/candid/tests/assets/ok/cyclic.rs rename to rust/candid_parser/tests/assets/ok/cyclic.rs diff --git a/rust/candid/tests/assets/ok/escape.d.ts b/rust/candid_parser/tests/assets/ok/escape.d.ts similarity index 100% rename from rust/candid/tests/assets/ok/escape.d.ts rename to rust/candid_parser/tests/assets/ok/escape.d.ts diff --git a/rust/candid/tests/assets/ok/escape.did b/rust/candid_parser/tests/assets/ok/escape.did similarity index 100% rename from rust/candid/tests/assets/ok/escape.did rename to rust/candid_parser/tests/assets/ok/escape.did diff --git a/rust/candid/tests/assets/ok/escape.js b/rust/candid_parser/tests/assets/ok/escape.js similarity index 100% rename from rust/candid/tests/assets/ok/escape.js rename to rust/candid_parser/tests/assets/ok/escape.js diff --git a/rust/candid/tests/assets/ok/escape.rs b/rust/candid_parser/tests/assets/ok/escape.rs similarity index 100% rename from rust/candid/tests/assets/ok/escape.rs rename to rust/candid_parser/tests/assets/ok/escape.rs diff --git a/rust/candid/tests/assets/ok/example.d.ts b/rust/candid_parser/tests/assets/ok/example.d.ts similarity index 100% rename from rust/candid/tests/assets/ok/example.d.ts rename to rust/candid_parser/tests/assets/ok/example.d.ts diff --git a/rust/candid/tests/assets/ok/example.did b/rust/candid_parser/tests/assets/ok/example.did similarity index 100% rename from rust/candid/tests/assets/ok/example.did rename to rust/candid_parser/tests/assets/ok/example.did diff --git a/rust/candid/tests/assets/ok/example.js b/rust/candid_parser/tests/assets/ok/example.js similarity index 100% rename from rust/candid/tests/assets/ok/example.js rename to rust/candid_parser/tests/assets/ok/example.js diff --git a/rust/candid/tests/assets/ok/example.mo b/rust/candid_parser/tests/assets/ok/example.mo similarity index 100% rename from rust/candid/tests/assets/ok/example.mo rename to rust/candid_parser/tests/assets/ok/example.mo diff --git a/rust/candid/tests/assets/ok/example.rs b/rust/candid_parser/tests/assets/ok/example.rs similarity index 100% rename from rust/candid/tests/assets/ok/example.rs rename to rust/candid_parser/tests/assets/ok/example.rs diff --git a/rust/candid/tests/assets/ok/fieldnat.d.ts b/rust/candid_parser/tests/assets/ok/fieldnat.d.ts similarity index 100% rename from rust/candid/tests/assets/ok/fieldnat.d.ts rename to rust/candid_parser/tests/assets/ok/fieldnat.d.ts diff --git a/rust/candid/tests/assets/ok/fieldnat.did b/rust/candid_parser/tests/assets/ok/fieldnat.did similarity index 100% rename from rust/candid/tests/assets/ok/fieldnat.did rename to rust/candid_parser/tests/assets/ok/fieldnat.did diff --git a/rust/candid/tests/assets/ok/fieldnat.js b/rust/candid_parser/tests/assets/ok/fieldnat.js similarity index 100% rename from rust/candid/tests/assets/ok/fieldnat.js rename to rust/candid_parser/tests/assets/ok/fieldnat.js diff --git a/rust/candid/tests/assets/ok/fieldnat.mo b/rust/candid_parser/tests/assets/ok/fieldnat.mo similarity index 100% rename from rust/candid/tests/assets/ok/fieldnat.mo rename to rust/candid_parser/tests/assets/ok/fieldnat.mo diff --git a/rust/candid/tests/assets/ok/fieldnat.rs b/rust/candid_parser/tests/assets/ok/fieldnat.rs similarity index 100% rename from rust/candid/tests/assets/ok/fieldnat.rs rename to rust/candid_parser/tests/assets/ok/fieldnat.rs diff --git a/rust/candid/tests/assets/ok/invalid_cyclic.fail b/rust/candid_parser/tests/assets/ok/invalid_cyclic.fail similarity index 100% rename from rust/candid/tests/assets/ok/invalid_cyclic.fail rename to rust/candid_parser/tests/assets/ok/invalid_cyclic.fail diff --git a/rust/candid/tests/assets/ok/keyword.d.ts b/rust/candid_parser/tests/assets/ok/keyword.d.ts similarity index 100% rename from rust/candid/tests/assets/ok/keyword.d.ts rename to rust/candid_parser/tests/assets/ok/keyword.d.ts diff --git a/rust/candid/tests/assets/ok/keyword.did b/rust/candid_parser/tests/assets/ok/keyword.did similarity index 100% rename from rust/candid/tests/assets/ok/keyword.did rename to rust/candid_parser/tests/assets/ok/keyword.did diff --git a/rust/candid/tests/assets/ok/keyword.js b/rust/candid_parser/tests/assets/ok/keyword.js similarity index 100% rename from rust/candid/tests/assets/ok/keyword.js rename to rust/candid_parser/tests/assets/ok/keyword.js diff --git a/rust/candid/tests/assets/ok/keyword.mo b/rust/candid_parser/tests/assets/ok/keyword.mo similarity index 100% rename from rust/candid/tests/assets/ok/keyword.mo rename to rust/candid_parser/tests/assets/ok/keyword.mo diff --git a/rust/candid/tests/assets/ok/keyword.rs b/rust/candid_parser/tests/assets/ok/keyword.rs similarity index 100% rename from rust/candid/tests/assets/ok/keyword.rs rename to rust/candid_parser/tests/assets/ok/keyword.rs diff --git a/rust/candid/tests/assets/ok/management.d.ts b/rust/candid_parser/tests/assets/ok/management.d.ts similarity index 100% rename from rust/candid/tests/assets/ok/management.d.ts rename to rust/candid_parser/tests/assets/ok/management.d.ts diff --git a/rust/candid/tests/assets/ok/management.did b/rust/candid_parser/tests/assets/ok/management.did similarity index 100% rename from rust/candid/tests/assets/ok/management.did rename to rust/candid_parser/tests/assets/ok/management.did diff --git a/rust/candid/tests/assets/ok/management.js b/rust/candid_parser/tests/assets/ok/management.js similarity index 100% rename from rust/candid/tests/assets/ok/management.js rename to rust/candid_parser/tests/assets/ok/management.js diff --git a/rust/candid/tests/assets/ok/management.mo b/rust/candid_parser/tests/assets/ok/management.mo similarity index 100% rename from rust/candid/tests/assets/ok/management.mo rename to rust/candid_parser/tests/assets/ok/management.mo diff --git a/rust/candid/tests/assets/ok/management.rs b/rust/candid_parser/tests/assets/ok/management.rs similarity index 100% rename from rust/candid/tests/assets/ok/management.rs rename to rust/candid_parser/tests/assets/ok/management.rs diff --git a/rust/candid/tests/assets/ok/not_func.fail b/rust/candid_parser/tests/assets/ok/not_func.fail similarity index 100% rename from rust/candid/tests/assets/ok/not_func.fail rename to rust/candid_parser/tests/assets/ok/not_func.fail diff --git a/rust/candid/tests/assets/ok/not_serv.fail b/rust/candid_parser/tests/assets/ok/not_serv.fail similarity index 100% rename from rust/candid/tests/assets/ok/not_serv.fail rename to rust/candid_parser/tests/assets/ok/not_serv.fail diff --git a/rust/candid/tests/assets/ok/oneway.fail b/rust/candid_parser/tests/assets/ok/oneway.fail similarity index 100% rename from rust/candid/tests/assets/ok/oneway.fail rename to rust/candid_parser/tests/assets/ok/oneway.fail diff --git a/rust/candid/tests/assets/ok/recursion.d.ts b/rust/candid_parser/tests/assets/ok/recursion.d.ts similarity index 100% rename from rust/candid/tests/assets/ok/recursion.d.ts rename to rust/candid_parser/tests/assets/ok/recursion.d.ts diff --git a/rust/candid/tests/assets/ok/recursion.did b/rust/candid_parser/tests/assets/ok/recursion.did similarity index 100% rename from rust/candid/tests/assets/ok/recursion.did rename to rust/candid_parser/tests/assets/ok/recursion.did diff --git a/rust/candid/tests/assets/ok/recursion.js b/rust/candid_parser/tests/assets/ok/recursion.js similarity index 100% rename from rust/candid/tests/assets/ok/recursion.js rename to rust/candid_parser/tests/assets/ok/recursion.js diff --git a/rust/candid/tests/assets/ok/recursion.mo b/rust/candid_parser/tests/assets/ok/recursion.mo similarity index 100% rename from rust/candid/tests/assets/ok/recursion.mo rename to rust/candid_parser/tests/assets/ok/recursion.mo diff --git a/rust/candid/tests/assets/ok/recursion.rs b/rust/candid_parser/tests/assets/ok/recursion.rs similarity index 100% rename from rust/candid/tests/assets/ok/recursion.rs rename to rust/candid_parser/tests/assets/ok/recursion.rs diff --git a/rust/candid/tests/assets/ok/recursive_class.d.ts b/rust/candid_parser/tests/assets/ok/recursive_class.d.ts similarity index 100% rename from rust/candid/tests/assets/ok/recursive_class.d.ts rename to rust/candid_parser/tests/assets/ok/recursive_class.d.ts diff --git a/rust/candid/tests/assets/ok/recursive_class.did b/rust/candid_parser/tests/assets/ok/recursive_class.did similarity index 100% rename from rust/candid/tests/assets/ok/recursive_class.did rename to rust/candid_parser/tests/assets/ok/recursive_class.did diff --git a/rust/candid/tests/assets/ok/recursive_class.js b/rust/candid_parser/tests/assets/ok/recursive_class.js similarity index 100% rename from rust/candid/tests/assets/ok/recursive_class.js rename to rust/candid_parser/tests/assets/ok/recursive_class.js diff --git a/rust/candid/tests/assets/ok/recursive_class.mo b/rust/candid_parser/tests/assets/ok/recursive_class.mo similarity index 100% rename from rust/candid/tests/assets/ok/recursive_class.mo rename to rust/candid_parser/tests/assets/ok/recursive_class.mo diff --git a/rust/candid/tests/assets/ok/recursive_class.rs b/rust/candid_parser/tests/assets/ok/recursive_class.rs similarity index 100% rename from rust/candid/tests/assets/ok/recursive_class.rs rename to rust/candid_parser/tests/assets/ok/recursive_class.rs diff --git a/rust/candid/tests/assets/ok/service.d.ts b/rust/candid_parser/tests/assets/ok/service.d.ts similarity index 100% rename from rust/candid/tests/assets/ok/service.d.ts rename to rust/candid_parser/tests/assets/ok/service.d.ts diff --git a/rust/candid/tests/assets/ok/service.did b/rust/candid_parser/tests/assets/ok/service.did similarity index 100% rename from rust/candid/tests/assets/ok/service.did rename to rust/candid_parser/tests/assets/ok/service.did diff --git a/rust/candid/tests/assets/ok/service.js b/rust/candid_parser/tests/assets/ok/service.js similarity index 100% rename from rust/candid/tests/assets/ok/service.js rename to rust/candid_parser/tests/assets/ok/service.js diff --git a/rust/candid/tests/assets/ok/service.mo b/rust/candid_parser/tests/assets/ok/service.mo similarity index 100% rename from rust/candid/tests/assets/ok/service.mo rename to rust/candid_parser/tests/assets/ok/service.mo diff --git a/rust/candid/tests/assets/ok/service.rs b/rust/candid_parser/tests/assets/ok/service.rs similarity index 100% rename from rust/candid/tests/assets/ok/service.rs rename to rust/candid_parser/tests/assets/ok/service.rs diff --git a/rust/candid/tests/assets/ok/surrogate.fail b/rust/candid_parser/tests/assets/ok/surrogate.fail similarity index 100% rename from rust/candid/tests/assets/ok/surrogate.fail rename to rust/candid_parser/tests/assets/ok/surrogate.fail diff --git a/rust/candid/tests/assets/ok/undefine.fail b/rust/candid_parser/tests/assets/ok/undefine.fail similarity index 100% rename from rust/candid/tests/assets/ok/undefine.fail rename to rust/candid_parser/tests/assets/ok/undefine.fail diff --git a/rust/candid/tests/assets/ok/unicode.d.ts b/rust/candid_parser/tests/assets/ok/unicode.d.ts similarity index 100% rename from rust/candid/tests/assets/ok/unicode.d.ts rename to rust/candid_parser/tests/assets/ok/unicode.d.ts diff --git a/rust/candid/tests/assets/ok/unicode.did b/rust/candid_parser/tests/assets/ok/unicode.did similarity index 100% rename from rust/candid/tests/assets/ok/unicode.did rename to rust/candid_parser/tests/assets/ok/unicode.did diff --git a/rust/candid/tests/assets/ok/unicode.js b/rust/candid_parser/tests/assets/ok/unicode.js similarity index 100% rename from rust/candid/tests/assets/ok/unicode.js rename to rust/candid_parser/tests/assets/ok/unicode.js diff --git a/rust/candid/tests/assets/ok/unicode.rs b/rust/candid_parser/tests/assets/ok/unicode.rs similarity index 100% rename from rust/candid/tests/assets/ok/unicode.rs rename to rust/candid_parser/tests/assets/ok/unicode.rs diff --git a/rust/candid/tests/assets/oneway.did b/rust/candid_parser/tests/assets/oneway.did similarity index 100% rename from rust/candid/tests/assets/oneway.did rename to rust/candid_parser/tests/assets/oneway.did diff --git a/rust/candid/tests/assets/recursion.did b/rust/candid_parser/tests/assets/recursion.did similarity index 100% rename from rust/candid/tests/assets/recursion.did rename to rust/candid_parser/tests/assets/recursion.did diff --git a/rust/candid/tests/assets/recursive_class.did b/rust/candid_parser/tests/assets/recursive_class.did similarity index 100% rename from rust/candid/tests/assets/recursive_class.did rename to rust/candid_parser/tests/assets/recursive_class.did diff --git a/rust/candid/tests/assets/service.did b/rust/candid_parser/tests/assets/service.did similarity index 100% rename from rust/candid/tests/assets/service.did rename to rust/candid_parser/tests/assets/service.did diff --git a/rust/candid/tests/assets/surrogate.did b/rust/candid_parser/tests/assets/surrogate.did similarity index 100% rename from rust/candid/tests/assets/surrogate.did rename to rust/candid_parser/tests/assets/surrogate.did diff --git a/rust/candid/tests/assets/undefine.did b/rust/candid_parser/tests/assets/undefine.did similarity index 100% rename from rust/candid/tests/assets/undefine.did rename to rust/candid_parser/tests/assets/undefine.did diff --git a/rust/candid/tests/assets/unicode.did b/rust/candid_parser/tests/assets/unicode.did similarity index 100% rename from rust/candid/tests/assets/unicode.did rename to rust/candid_parser/tests/assets/unicode.did diff --git a/rust/candid/tests/parse_type.rs b/rust/candid_parser/tests/parse_type.rs similarity index 91% rename from rust/candid/tests/parse_type.rs rename to rust/candid_parser/tests/parse_type.rs index 85ec1362..8608ced7 100644 --- a/rust/candid/tests/parse_type.rs +++ b/rust/candid_parser/tests/parse_type.rs @@ -1,7 +1,8 @@ -use candid::bindings::{candid as candid_export, javascript, motoko, rust, typescript}; -use candid::parser::types::IDLProg; -use candid::parser::typing::{check_file, check_prog}; +use candid::pretty_printer::compile; use candid::types::TypeEnv; +use candid_parser::bindings::{javascript, motoko, rust, typescript}; +use candid_parser::types::IDLProg; +use candid_parser::typing::{check_file, check_prog}; use goldenfile::Mint; use std::io::Write; use std::path::Path; @@ -29,7 +30,7 @@ service server : { prog.parse::().unwrap(); } -#[test_generator::test_resources("rust/candid/tests/assets/*.did")] +#[test_generator::test_resources("rust/candid_parser/tests/assets/*.did")] fn compiler_test(resource: &str) { let base_path = std::env::current_dir().unwrap().join("tests/assets"); let mut mint = Mint::new(base_path.join("ok")); @@ -41,7 +42,7 @@ fn compiler_test(resource: &str) { Ok((env, actor)) => { { let mut output = mint.new_goldenfile(filename.with_extension("did")).unwrap(); - let content = candid_export::compile(&env, &actor); + let content = compile(&env, &actor); // Type check output let ast = content.parse::().unwrap(); check_prog(&mut TypeEnv::new(), &ast).unwrap(); diff --git a/rust/candid/tests/parse_value.rs b/rust/candid_parser/tests/parse_value.rs similarity index 97% rename from rust/candid/tests/parse_value.rs rename to rust/candid_parser/tests/parse_value.rs index 027401db..fce5297d 100644 --- a/rust/candid/tests/parse_value.rs +++ b/rust/candid_parser/tests/parse_value.rs @@ -1,20 +1,21 @@ use candid::types::value::{IDLArgs, IDLField, IDLValue, VariantValue}; use candid::types::{Label, Type, TypeEnv, TypeInner}; use candid::{record, variant, CandidType, Nat}; +use candid_parser::parse_idl_args; fn parse_args(input: &str) -> IDLArgs { - input.parse().unwrap() + parse_idl_args(input).unwrap() } -fn parse_args_err(input: &str) -> candid::Result { - input.parse() +fn parse_args_err(input: &str) -> candid_parser::Result { + parse_idl_args(input) } fn parse_type(input: &str) -> Type { - use candid::parser::types::IDLType; + use candid_parser::types::IDLType; let env = TypeEnv::new(); let ast = input.parse::().unwrap(); - env.ast_to_type(&ast).unwrap() + candid_parser::typing::ast_to_type(&env, &ast).unwrap() } #[test] diff --git a/rust/candid/tests/test_suite.rs b/rust/candid_parser/tests/test_suite.rs similarity index 85% rename from rust/candid/tests/test_suite.rs rename to rust/candid_parser/tests/test_suite.rs index e3a68fa1..83a07133 100644 --- a/rust/candid/tests/test_suite.rs +++ b/rust/candid_parser/tests/test_suite.rs @@ -1,4 +1,4 @@ -use candid::parser::test::{check, Test}; +use candid_parser::test::{check, Test}; #[test_generator::test_resources("test/*.test.did")] fn decode_test(resource: &str) { @@ -13,7 +13,7 @@ fn decode_test(resource: &str) { #[test_generator::test_resources("test/*.test.did")] fn js_test(resource: &str) { - use candid::bindings::javascript::test::test_generate; + use candid_parser::bindings::javascript::test::test_generate; let path = std::env::current_dir() .unwrap() .join("../../") diff --git a/rust/candid/tests/value.rs b/rust/candid_parser/tests/value.rs similarity index 95% rename from rust/candid/tests/value.rs rename to rust/candid_parser/tests/value.rs index fd3f4f61..0e0f4748 100644 --- a/rust/candid/tests/value.rs +++ b/rust/candid_parser/tests/value.rs @@ -1,7 +1,7 @@ -use candid::parser::{types::IDLProg, typing::check_prog}; use candid::types::value::{IDLArgs, IDLField, IDLValue, VariantValue}; use candid::types::{Label, TypeEnv}; use candid::{decode_args, decode_one, Decode}; +use candid_parser::{parse_idl_args, types::IDLProg, typing::check_prog}; #[test] fn test_parser() { @@ -36,7 +36,7 @@ service : { let actor = check_prog(&mut env, &ast).unwrap().unwrap(); let method = env.get_method(&actor, "f").unwrap(); { - let args = "(42,42,42,42)".parse::().unwrap(); + let args = parse_idl_args("(42,42,42,42)").unwrap(); let encoded = args.to_bytes_with_types(&env, &method.args).unwrap(); let decoded = IDLArgs::from_bytes(&encoded).unwrap(); assert_eq!( @@ -51,7 +51,7 @@ service : { } { let str = "(opt record { head = 1000; tail = opt record {head = -2000; tail = null}}, variant {a = 42})"; - let args = str.parse::().unwrap(); + let args = parse_idl_args(str).unwrap(); let encoded = args.to_bytes_with_types(&env, &method.rets).unwrap(); let decoded = IDLArgs::from_bytes(&encoded).unwrap(); assert_eq!(decoded.to_string(), "(\n opt record {\n 1_158_359_328 = 1_000 : int16;\n 1_291_237_008 = opt record {\n 1_158_359_328 = -2_000 : int16;\n 1_291_237_008 = null;\n };\n },\n variant { 97 = 42 : nat },\n)"); @@ -120,11 +120,11 @@ fn test_variant() { } fn parse_check(str: &str) { - let args = str.parse::().unwrap(); + let args = parse_idl_args(str).unwrap(); let encoded = args.to_bytes().unwrap(); let decoded = IDLArgs::from_bytes(&encoded).unwrap(); let output = decoded.to_string(); - let back_args = output.parse::().unwrap(); + let back_args = parse_idl_args(&output).unwrap(); let annotated_args = args .annotate_types(true, &TypeEnv::new(), &back_args.get_types()) .unwrap(); diff --git a/rust/ic_principal/Cargo.toml b/rust/ic_principal/Cargo.toml new file mode 100644 index 00000000..8164b80d --- /dev/null +++ b/rust/ic_principal/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "ic_principal" +version = "0.1.0" +authors = ["DFINITY Stiftung "] +edition = "2021" +homepage = "https://internetcomputer.org/docs/current/references/id-encoding-spec" +documentation = "https://docs.rs/ic_principal" +repository="https://github.com/dfinity/candid" +license = "Apache-2.0" +readme = "README.md" +categories = ["data-structures", "no-std"] +keywords = ["internet-computer", "types", "dfinity"] +include = ["src", "Cargo.toml", "LICENSE", "README.md"] + +[dependencies] +crc32fast = "1.3.0" +data-encoding = "2.3.2" +hex = "0.4.3" +sha2 = "0.10.1" +thiserror = "1.0.30" + +[dev-dependencies] +serde_cbor = "0.11.2" +serde_json = "1.0.74" +serde_test = "1.0.137" +impls = "1" + +[dependencies.serde] +version = "1.0.115" +features = ["derive"] +optional = true + +[dependencies.serde_bytes] +version = "0.11.5" +optional = true + +[features] +# Default features include serde support. +default = ['serde', 'serde_bytes'] diff --git a/rust/ic_principal/LICENSE b/rust/ic_principal/LICENSE new file mode 120000 index 00000000..30cff740 --- /dev/null +++ b/rust/ic_principal/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/rust/ic_principal/README.md b/rust/ic_principal/README.md new file mode 100644 index 00000000..b32805eb --- /dev/null +++ b/rust/ic_principal/README.md @@ -0,0 +1,5 @@ +# Internet Computer Principal + +`Principal` is the data structure for generic identifiers on the Internet Computer. + +Check the [ID encoding specification](https://internetcomputer.org/docs/current/references/id-encoding-spec) for more information. diff --git a/rust/ic_principal/src/lib.rs b/rust/ic_principal/src/lib.rs new file mode 100644 index 00000000..b128ccce --- /dev/null +++ b/rust/ic_principal/src/lib.rs @@ -0,0 +1,360 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha224}; +use std::convert::TryFrom; +use std::fmt::Write; +use thiserror::Error; + +/// An error happened while encoding, decoding or serializing a [`Principal`]. +#[derive(Error, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum PrincipalError { + #[error("Bytes is longer than 29 bytes.")] + BytesTooLong(), + + #[error("Text must be in valid Base32 encoding.")] + InvalidBase32(), + + #[error("Text is too short.")] + TextTooShort(), + + #[error("Text is too long.")] + TextTooLong(), + + #[error("CRC32 check sequence doesn't match with calculated from Principal bytes.")] + CheckSequenceNotMatch(), + + #[error(r#"Text should be separated by - (dash) every 5 characters: expected "{0}""#)] + AbnormalGrouped(Principal), +} + +/// Generic ID on Internet Computer. +/// +/// Principals are generic identifiers for canisters, users +/// and possibly other concepts in the future. +/// As far as most uses of the IC are concerned they are +/// opaque binary blobs with a length between 0 and 29 bytes, +/// and there is intentionally no mechanism to tell canister ids and user ids apart. +/// +/// Note a principal is not necessarily tied with a public key-pair, +/// yet we need at least a key-pair of a related principal to sign +/// requests. +/// +/// A Principal can be serialized to a byte array ([`Vec`]) or a text +/// representation, but the inner structure of the byte representation +/// is kept private. +/// +/// Example of using a Principal object: +/// ``` +/// use ic_principal::Principal; +/// +/// let text = "aaaaa-aa"; // The management canister ID. +/// let principal = Principal::from_text(text).expect("Could not decode the principal."); +/// assert_eq!(principal.as_slice(), &[]); +/// assert_eq!(principal.to_text(), text); +/// ``` +/// +/// Serialization is enabled with the "serde" feature. It supports serializing +/// to a byte bufer for non-human readable serializer, and a string version for human +/// readable serializers. +/// +/// ``` +/// use ic_principal::Principal; +/// use serde::{Deserialize, Serialize}; +/// use std::str::FromStr; +/// +/// #[derive(Serialize)] +/// struct Data { +/// id: Principal, +/// } +/// +/// let id = Principal::from_str("2chl6-4hpzw-vqaaa-aaaaa-c").unwrap(); +/// +/// // JSON is human readable, so this will serialize to a textual +/// // representation of the Principal. +/// assert_eq!( +/// serde_json::to_string(&Data { id: id.clone() }).unwrap(), +/// r#"{"id":"2chl6-4hpzw-vqaaa-aaaaa-c"}"# +/// ); +/// +/// // CBOR is not human readable, so will serialize to bytes. +/// assert_eq!( +/// serde_cbor::to_vec(&Data { id: id.clone() }).unwrap(), +/// &[161, 98, 105, 100, 73, 239, 205, 171, 0, 0, 0, 0, 0, 1], +/// ); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Principal { + /// Length. + len: u8, + + /// The content buffer. When returning slices this should always be sized according to + /// `len`. + bytes: [u8; Self::MAX_LENGTH_IN_BYTES], +} + +impl Principal { + const MAX_LENGTH_IN_BYTES: usize = 29; + const CRC_LENGTH_IN_BYTES: usize = 4; + + const SELF_AUTHENTICATING_TAG: u8 = 2; + const ANONYMOUS_TAG: u8 = 4; + + /// Construct a [`Principal`] of the IC management canister + pub const fn management_canister() -> Self { + Self { + len: 0, + bytes: [0; Self::MAX_LENGTH_IN_BYTES], + } + } + + /// Construct a self-authenticating ID from public key + pub fn self_authenticating>(public_key: P) -> Self { + let public_key = public_key.as_ref(); + let hash = Sha224::digest(public_key); + let mut bytes = [0; Self::MAX_LENGTH_IN_BYTES]; + bytes[..Self::MAX_LENGTH_IN_BYTES - 1].copy_from_slice(hash.as_slice()); + bytes[Self::MAX_LENGTH_IN_BYTES - 1] = Self::SELF_AUTHENTICATING_TAG; + + Self { + len: Self::MAX_LENGTH_IN_BYTES as u8, + bytes, + } + } + + /// Construct an anonymous ID. + pub const fn anonymous() -> Self { + let mut bytes = [0; Self::MAX_LENGTH_IN_BYTES]; + bytes[0] = Self::ANONYMOUS_TAG; + Self { len: 1, bytes } + } + + /// Construct a [`Principal`] from a slice of bytes. + /// + /// # Panics + /// + /// Panics if the slice is longer than 29 bytes. + pub const fn from_slice(slice: &[u8]) -> Self { + match Self::try_from_slice(slice) { + Ok(v) => v, + _ => panic!("slice length exceeds capacity"), + } + } + + /// Construct a [`Principal`] from a slice of bytes. + pub const fn try_from_slice(slice: &[u8]) -> Result { + const MAX_LENGTH_IN_BYTES: usize = Principal::MAX_LENGTH_IN_BYTES; + match slice.len() { + len @ 0..=MAX_LENGTH_IN_BYTES => { + let mut bytes = [0; MAX_LENGTH_IN_BYTES]; + let mut i = 0; + while i < len { + bytes[i] = slice[i]; + i += 1; + } + Ok(Self { + len: len as u8, + bytes, + }) + } + _ => Err(PrincipalError::BytesTooLong()), + } + } + + /// Parse a [`Principal`] from text representation. + pub fn from_text>(text: S) -> Result { + // Strategy: Parse very liberally, then pretty-print and compare output + // This is both simpler and yields better error messages + + let mut s = text.as_ref().to_string(); + s.make_ascii_uppercase(); + s.retain(|c| c != '-'); + match data_encoding::BASE32_NOPAD.decode(s.as_bytes()) { + Ok(bytes) => { + if bytes.len() < Self::CRC_LENGTH_IN_BYTES { + return Err(PrincipalError::TextTooShort()); + } + + let crc_bytes = &bytes[..Self::CRC_LENGTH_IN_BYTES]; + let data_bytes = &bytes[Self::CRC_LENGTH_IN_BYTES..]; + if data_bytes.len() > Self::MAX_LENGTH_IN_BYTES { + return Err(PrincipalError::TextTooLong()); + } + + if crc32fast::hash(data_bytes).to_be_bytes() != crc_bytes { + return Err(PrincipalError::CheckSequenceNotMatch()); + } + + // Already checked data_bytes.len() <= MAX_LENGTH_IN_BYTES + // safe to unwrap here + let result = Self::try_from_slice(data_bytes).unwrap(); + let expected = format!("{}", result); + + // In the Spec: + // The textual representation is conventionally printed with lower case letters, + // but parsed case-insensitively. + if text.as_ref().to_ascii_lowercase() != expected { + return Err(PrincipalError::AbnormalGrouped(result)); + } + Ok(result) + } + _ => Err(PrincipalError::InvalidBase32()), + } + } + + /// Convert [`Principal`] to text representation. + pub fn to_text(&self) -> String { + format!("{}", self) + } + + /// Return the [`Principal`]'s underlying slice of bytes. + #[inline] + pub fn as_slice(&self) -> &[u8] { + &self.bytes[..self.len as usize] + } +} + +impl std::fmt::Display for Principal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let blob: &[u8] = self.as_slice(); + + // calc checksum + let checksum = crc32fast::hash(blob); + + // combine blobs + let mut bytes = vec![]; + bytes.extend_from_slice(&checksum.to_be_bytes()); + bytes.extend_from_slice(blob); + + // base32 + let mut s = data_encoding::BASE32_NOPAD.encode(&bytes); + s.make_ascii_lowercase(); + + // write out string with dashes + let mut s = s.as_str(); + while s.len() > 5 { + f.write_str(&s[..5])?; + f.write_char('-')?; + s = &s[5..]; + } + f.write_str(s) + } +} + +impl std::str::FromStr for Principal { + type Err = PrincipalError; + + fn from_str(s: &str) -> Result { + Principal::from_text(s) + } +} + +impl TryFrom<&str> for Principal { + type Error = PrincipalError; + + fn try_from(s: &str) -> Result { + Principal::from_text(s) + } +} + +impl TryFrom> for Principal { + type Error = PrincipalError; + + fn try_from(bytes: Vec) -> Result { + Self::try_from(bytes.as_slice()) + } +} + +impl TryFrom<&Vec> for Principal { + type Error = PrincipalError; + + fn try_from(bytes: &Vec) -> Result { + Self::try_from(bytes.as_slice()) + } +} + +impl TryFrom<&[u8]> for Principal { + type Error = PrincipalError; + + fn try_from(bytes: &[u8]) -> Result { + Self::try_from_slice(bytes) + } +} + +impl AsRef<[u8]> for Principal { + fn as_ref(&self) -> &[u8] { + self.as_slice() + } +} + +// Serialization +#[cfg(feature = "serde")] +impl serde::Serialize for Principal { + fn serialize(&self, serializer: S) -> Result { + if serializer.is_human_readable() { + self.to_text().serialize(serializer) + } else { + serializer.serialize_bytes(self.as_slice()) + } + } +} + +// Deserialization +#[cfg(feature = "serde")] +mod deserialize { + use super::Principal; + use std::convert::TryFrom; + + // Simple visitor for deserialization from bytes. We don't support other number types + // as there's no need for it. + pub(super) struct PrincipalVisitor; + + impl<'de> serde::de::Visitor<'de> for PrincipalVisitor { + type Value = super::Principal; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("bytes or string") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + Principal::from_text(v).map_err(E::custom) + } + + fn visit_bytes(self, value: &[u8]) -> Result + where + E: serde::de::Error, + { + Principal::try_from(value).map_err(E::custom) + } + /// This visitor should only be used by the Candid crate. + fn visit_byte_buf(self, v: Vec) -> Result + where + E: serde::de::Error, + { + if v.is_empty() || v[0] != 2u8 { + Err(E::custom("Not called by Candid")) + } else { + Principal::try_from(&v[1..]).map_err(E::custom) + } + } + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for Principal { + fn deserialize>(deserializer: D) -> Result { + use serde::de::Error; + if deserializer.is_human_readable() { + deserializer + .deserialize_str(deserialize::PrincipalVisitor) + .map_err(D::Error::custom) + } else { + deserializer + .deserialize_bytes(deserialize::PrincipalVisitor) + .map_err(D::Error::custom) + } + } +} diff --git a/rust/candid/tests/principal.rs b/rust/ic_principal/tests/principal.rs similarity index 98% rename from rust/candid/tests/principal.rs rename to rust/ic_principal/tests/principal.rs index ec402be6..25393889 100644 --- a/rust/candid/tests/principal.rs +++ b/rust/ic_principal/tests/principal.rs @@ -1,6 +1,7 @@ -#![allow(deprecated)] +// #![allow(deprecated)] -use candid::types::principal::{Principal, PrincipalError}; +use ic_principal::Principal; +use ic_principal::PrincipalError; const MANAGEMENT_CANISTER_BYTES: [u8; 0] = []; const MANAGEMENT_CANISTER_TEXT: &str = "aaaaa-aa"; diff --git a/tools/didc/Cargo.toml b/tools/didc/Cargo.toml index f1cf5e91..ff4399fd 100644 --- a/tools/didc/Cargo.toml +++ b/tools/didc/Cargo.toml @@ -5,7 +5,8 @@ authors = ["DFINITY Team"] edition = "2021" [dependencies] -candid = { path = "../../rust/candid", features = ["all"] } +candid = { path = "../../rust/candid" } +candid_parser = { path = "../../rust/candid_parser", features = ["all"] } clap = { version = "4.3", features = ["derive"] } pretty-hex = "0.2.1" hex = "0.4.2" diff --git a/tools/didc/src/main.rs b/tools/didc/src/main.rs index 9a349fab..a13d341d 100644 --- a/tools/didc/src/main.rs +++ b/tools/didc/src/main.rs @@ -1,9 +1,11 @@ use anyhow::{bail, Result}; -use candid::{ - parser::types::{IDLType, IDLTypes}, - pretty_check_file, pretty_parse, - types::Type, - Error, IDLArgs, TypeEnv, +use candid::{types::Type, IDLArgs, TypeEnv}; +use candid_parser::{ + error::pretty_diagnose, + parse_idl_args, parse_idl_value, pretty_check_file, pretty_parse, + types::{IDLType, IDLTypes}, + typing::ast_to_type, + Error, }; use clap::Parser; use std::collections::HashSet; @@ -78,6 +80,7 @@ enum Command { /// Specifies target language lang: String, #[clap(short, long, requires("method"))] + #[clap(value_parser = parse_args)] /// Specifies input arguments for a method call, mocking the return result args: Option, }, @@ -113,7 +116,7 @@ impl TypeAnnotation { fn is_empty(&self) -> bool { self.tys.is_none() && self.method.is_none() } - fn get_types(&self, mode: Mode) -> candid::Result<(TypeEnv, Vec)> { + fn get_types(&self, mode: Mode) -> candid_parser::Result<(TypeEnv, Vec)> { let (env, actor) = if let Some(ref file) = self.defs { pretty_check_file(file)? } else { @@ -124,7 +127,7 @@ impl TypeAnnotation { (Some(tys), None) => { let mut types = Vec::new(); for ty in tys.args.iter() { - types.push(env.ast_to_type(ty)?); + types.push(ast_to_type(&env, ty)?); } Ok((env, types)) } @@ -145,7 +148,10 @@ impl TypeAnnotation { } fn parse_args(str: &str) -> Result { - pretty_parse("candid arguments", str) + parse_idl_args(str).map_err(|e| { + let _ = pretty_diagnose("candid arguments", str, &e); + e + }) } fn parse_types(str: &str) -> Result { pretty_parse("type annotations", str) @@ -183,25 +189,25 @@ fn main() -> Result<()> { } else { (TypeEnv::new(), None) }; - let ty1 = env.ast_to_type(&ty1)?; - let ty2 = env.ast_to_type(&ty2)?; + let ty1 = ast_to_type(&env, &ty1)?; + let ty2 = ast_to_type(&env, &ty2)?; candid::types::subtype::subtype(&mut HashSet::new(), &env, &ty1, &ty2)?; } Command::Bind { input, target } => { let (env, actor) = pretty_check_file(&input)?; let content = match target.as_str() { - "js" => candid::bindings::javascript::compile(&env, &actor), - "ts" => candid::bindings::typescript::compile(&env, &actor), - "did" => candid::bindings::candid::compile(&env, &actor), - "mo" => candid::bindings::motoko::compile(&env, &actor), + "js" => candid_parser::bindings::javascript::compile(&env, &actor), + "ts" => candid_parser::bindings::typescript::compile(&env, &actor), + "did" => candid::pretty_printer::compile(&env, &actor), + "mo" => candid_parser::bindings::motoko::compile(&env, &actor), "rs" => { - let config = candid::bindings::rust::Config::new(); - candid::bindings::rust::compile(&config, &env, &actor) + let config = candid_parser::bindings::rust::Config::new(); + candid_parser::bindings::rust::compile(&config, &env, &actor) } "rs-agent" => { - let mut config = candid::bindings::rust::Config::new(); - config.target = candid::bindings::rust::Target::Agent; - candid::bindings::rust::compile(&config, &env, &actor) + let mut config = candid_parser::bindings::rust::Config::new(); + config.target = candid_parser::bindings::rust::Target::Agent; + candid_parser::bindings::rust::compile(&config, &env, &actor) } _ => unreachable!(), }; @@ -210,11 +216,11 @@ fn main() -> Result<()> { Command::Test { input, target } => { let test = std::fs::read_to_string(&input) .map_err(|_| Error::msg(format!("could not read file {}", input.display())))?; - let ast = pretty_parse::(input.to_str().unwrap(), &test)?; + let ast = pretty_parse::(input.to_str().unwrap(), &test)?; let content = match target.as_str() { - "js" => candid::bindings::javascript::test::test_generate(ast), + "js" => candid_parser::bindings::javascript::test::test_generate(ast), "did" => { - candid::parser::test::check(ast)?; + candid_parser::test::check(ast)?; "".to_string() } _ => unreachable!(), @@ -248,7 +254,7 @@ fn main() -> Result<()> { "blob" => { let mut res = String::new(); for ch in bytes.iter() { - res.push_str(&candid::bindings::candid::value::pp_char(*ch)); + res.push_str(&candid::pretty_printer::value::pp_char(*ch)); } format!("blob \"{res}\"") } @@ -273,7 +279,10 @@ fn main() -> Result<()> { )?, "blob" => { use candid::types::value::IDLValue; - match pretty_parse::("blob", &blob)? { + match parse_idl_value(&blob).map_err(|e| { + let _ = pretty_diagnose("blob", &blob, &e); + e + })? { IDLValue::Vec(vec) => vec .iter() .map(|v| { @@ -304,7 +313,7 @@ fn main() -> Result<()> { file, args, } => { - use candid::parser::configs::Configs; + use candid_parser::configs::Configs; use rand::Rng; let (env, types) = if args.is_some() { annotate.get_types(Mode::Decode)? @@ -335,12 +344,12 @@ fn main() -> Result<()> { let mut rng = rand::thread_rng(); (0..2048).map(|_| rng.gen::()).collect() }; - let args = IDLArgs::any(&seed, &config, &env, &types)?; + let args = candid_parser::random::any(&seed, &config, &env, &types)?; match lang.as_str() { "did" => println!("{args}"), "js" => println!( "{}", - candid::bindings::javascript::value::pp_args(&args).pretty(80) + candid_parser::bindings::javascript::value::pp_args(&args).pretty(80) ), _ => unreachable!(), }