cddl-0.9.4/.cargo_vcs_info.json0000644000000001360000000000100117540ustar { "git": { "sha1": "12b7cb0902a7b767427214cb8e5bb4e1e17477fb" }, "path_in_vcs": "" }cddl-0.9.4/.gitattributes000064400000000000000000000000161046102023000134340ustar 00000000000000* text eol=lf cddl-0.9.4/.gitignore000064400000000000000000000001141046102023000125300ustar 00000000000000/target **/*.rs.bk Cargo.lock .cargo/ .DS_STORE node_modules/ /www/dist/ cddl-0.9.4/.npmrc000064400000000000000000000000551046102023000116640ustar 00000000000000@anweiss:registry=https://npm.pkg.github.com cddl-0.9.4/.rustfmt.toml000064400000000000000000000000171046102023000132210ustar 00000000000000tab_spaces = 2 cddl-0.9.4/Cargo.lock0000644000001026220000000000100077320ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "abnf" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33741baa462d86e43fdec5e8ffca7c6ac82847ad06cbfb382c1bdbf527de9e6b" dependencies = [ "abnf-core", "nom", ] [[package]] name = "abnf-core" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c44e09c43ae1c368fb91a03a566472d0087c26cf7e1b9e8e289c14ede681dd7d" dependencies = [ "nom", ] [[package]] name = "abnf_to_pest" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "939d59666dd9a7964a3a5312b9d24c9c107630752ee64f2dd5038189a23fe331" dependencies = [ "abnf", "indexmap", "itertools 0.10.5", "pretty", ] [[package]] name = "aho-corasick" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "arrayvec" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", "winapi", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base16" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d27c3610c36aee21ce8ac510e6224498de4228ad772a171ed65643a24693a5a8" [[package]] name = "base64" version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "base64-url" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5efc028d778cd6fb4d1779ca001b3282717e34127b726593002506aa77ca08" dependencies = [ "base64", ] [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "bumpalo" version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "cc" version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "libc", ] [[package]] name = "cddl" version = "0.9.4" dependencies = [ "abnf_to_pest", "base16", "base64-url", "chrono", "ciborium", "clap", "codespan-reporting", "console_error_panic_hook", "crossterm", "data-encoding", "displaydoc", "hexf-parse", "indoc", "itertools 0.11.0", "lexical-core", "log", "pest_meta", "pest_vm", "pretty_assertions", "regex", "regex-syntax 0.7.5", "serde", "serde-wasm-bindgen", "serde_json", "simplelog", "uriparse", "wasm-bindgen", "wasm-bindgen-test", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", "windows-targets", ] [[package]] name = "ciborium" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" [[package]] name = "ciborium-ll" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" dependencies = [ "ciborium-io", "half", ] [[package]] name = "clap" version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "atty", "bitflags 1.3.2", "clap_derive", "clap_lex", "indexmap", "once_cell", "strsim", "termcolor", "textwrap", ] [[package]] name = "clap_derive" version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" dependencies = [ "heck", "proc-macro-error", "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "clap_lex" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" dependencies = [ "os_str_bytes", ] [[package]] name = "codespan-reporting" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ "termcolor", "unicode-width", ] [[package]] name = "console_error_panic_hook" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" dependencies = [ "cfg-if", "wasm-bindgen", ] [[package]] name = "core-foundation-sys" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] [[package]] name = "crossterm" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ "bitflags 2.4.1", "crossterm_winapi", "libc", "mio", "parking_lot", "signal-hook", "signal-hook-mio", "winapi", ] [[package]] name = "crossterm_winapi" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" dependencies = [ "winapi", ] [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "data-encoding" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[package]] name = "deranged" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" dependencies = [ "powerfmt", ] [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", ] [[package]] name = "displaydoc" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", "syn 2.0.41", ] [[package]] name = "either" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "half" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "hexf-parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] name = "iana-time-zone" version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "indexmap" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", ] [[package]] name = "indoc" version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" [[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itertools" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "lexical-core" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46" dependencies = [ "lexical-parse-float", "lexical-parse-integer", "lexical-util", "lexical-write-float", "lexical-write-integer", ] [[package]] name = "lexical-parse-float" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f" dependencies = [ "lexical-parse-integer", "lexical-util", "static_assertions", ] [[package]] name = "lexical-parse-integer" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" dependencies = [ "lexical-util", "static_assertions", ] [[package]] name = "lexical-util" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc" dependencies = [ "static_assertions", ] [[package]] name = "lexical-write-float" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accabaa1c4581f05a3923d1b4cfd124c329352288b7b9da09e766b0668116862" dependencies = [ "lexical-util", "lexical-write-integer", "static_assertions", ] [[package]] name = "lexical-write-integer" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1b6f3d1f4422866b68192d62f77bc5c700bee84f3069f2469d7bc8c77852446" dependencies = [ "lexical-util", "static_assertions", ] [[package]] name = "libc" version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "lock_api" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "log", "wasi", "windows-sys", ] [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "num-traits" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] [[package]] name = "num_threads" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" dependencies = [ "libc", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "os_str_bytes" version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" [[package]] name = "parking_lot" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets", ] [[package]] name = "pest" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" dependencies = [ "memchr", "thiserror", "ucd-trie", ] [[package]] name = "pest_meta" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6" dependencies = [ "once_cell", "pest", "sha2", ] [[package]] name = "pest_vm" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12d2b440b79b697ca2791334f1cae93409e398e04b206c92388b0ceaa0555453" dependencies = [ "pest", "pest_meta", ] [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "pretty" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83f3aa1e3ca87d3b124db7461265ac176b40c277f37e503eaa29c9c75c037846" dependencies = [ "arrayvec", "log", "typed-arena", "unicode-segmentation", ] [[package]] name = "pretty_assertions" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" dependencies = [ "diff", "yansi", ] [[package]] name = "proc-macro-error" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", "syn 1.0.109", "version_check", ] [[package]] name = "proc-macro-error-attr" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", "quote", "version_check", ] [[package]] name = "proc-macro2" version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "regex" version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax 0.8.2", ] [[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.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 = "ryu" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "scoped-tls" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde-wasm-bindgen" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" dependencies = [ "js-sys", "serde", "wasm-bindgen", ] [[package]] name = "serde_derive" version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", "syn 2.0.41", ] [[package]] name = "serde_json" version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "sha2" version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "signal-hook" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" dependencies = [ "libc", "signal-hook-registry", ] [[package]] name = "signal-hook-mio" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" dependencies = [ "libc", "mio", "signal-hook", ] [[package]] name = "signal-hook-registry" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] name = "simplelog" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acee08041c5de3d5048c8b3f6f13fafb3026b24ba43c6a695a0c76179b844369" dependencies = [ "log", "termcolor", "time", ] [[package]] name = "smallvec" version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "termcolor" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ "winapi-util", ] [[package]] name = "textwrap" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", "syn 2.0.41", ] [[package]] name = "time" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ "deranged", "itoa", "libc", "num_threads", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] [[package]] name = "typed-arena" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-segmentation" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "uriparse" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0200d0fc04d809396c2ad43f3c95da3582a2556eba8d453c1087f4120ee352ff" dependencies = [ "fnv", "lazy_static", ] [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn 2.0.41", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" dependencies = [ "cfg-if", "js-sys", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", "syn 2.0.41", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "wasm-bindgen-test" version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cf9242c0d27999b831eae4767b2a146feb0b27d332d553e605864acd2afd403" dependencies = [ "console_error_panic_hook", "js-sys", "scoped-tls", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test-macro", ] [[package]] name = "wasm-bindgen-test-macro" version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "794645f5408c9a039fd09f4d113cdfb2e7eba5ff1956b07bcf701cf4b394fe89" dependencies = [ "proc-macro2", "quote", "syn 2.0.41", ] [[package]] name = "web-sys" version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" cddl-0.9.4/Cargo.toml0000644000000072040000000000100077550ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "cddl" version = "0.9.4" authors = ["Andrew Weiss "] exclude = [ "cddl-lsp/**/*", "www/**/*", ".github/**/*", ".devcontainer/**/*", "pkg/**/*", ".dockerignore", "Dockerfile", "tests/**/*", ] description = "Parser for the Concise data definition language (CDDL)" homepage = "https://cddl.anweiss.tech" readme = "README.md" categories = [ "parser-implementations", "encoding", "development-tools", "wasm", ] license = "MIT" repository = "https://github.com/anweiss/cddl" [profile.release] opt-level = "s" lto = true [lib] crate-type = [ "cdylib", "rlib", ] [[bin]] name = "cddl" path = "src/bin/cli.rs" test = false required-features = [ "std", "json", "cbor", ] [dependencies.abnf_to_pest] version = "0.5.1" [dependencies.base16] version = "0.2.1" default-features = false [dependencies.base64-url] version = "2.0.0" optional = true [dependencies.chrono] version = "0.4.19" optional = true [dependencies.ciborium] version = "0.2.0" optional = true [dependencies.clap] version = "3.2.23" features = ["derive"] optional = true [dependencies.codespan-reporting] version = "0.11.1" [dependencies.data-encoding] version = "2.3.3" default-features = false [dependencies.displaydoc] version = "0.2.3" default-features = false [dependencies.hexf-parse] version = "0.2.1" [dependencies.itertools] version = "0.11.0" [dependencies.lexical-core] version = "0.8.3" [dependencies.log] version = "0.4.14" [dependencies.pest_meta] version = "2.1.3" [dependencies.pest_vm] version = "2.1.0" [dependencies.regex] version = "1.5.4" features = [ "std", "unicode-perl", ] default-features = false [dependencies.regex-syntax] version = "0.7.1" optional = true [dependencies.serde] version = "1.0.127" features = ["derive"] optional = true [dependencies.serde_json] version = "1.0.66" features = ["std"] optional = true default-features = false [dependencies.simplelog] version = "0.12.1" [dependencies.uriparse] version = "0.6.3" optional = true [dev-dependencies.indoc] version = "2.0.1" [dev-dependencies.pretty_assertions] version = "1.2.0" [features] additional-controls = [] ast-comments = [] ast-parent = [] ast-span = [] cbor = ["std"] default = [ "std", "ast-span", "ast-comments", "json", "cbor", "additional-controls", "ast-parent", ] json = ["std"] lsp = ["std"] std = [ "base16/alloc", "data-encoding/alloc", "serde_json", "ciborium", "serde", "chrono", "wasm-bindgen", "serde-wasm-bindgen", "clap", "crossterm", "uriparse", "base64-url", "regex-syntax", ] [target."cfg(not(target_arch = \"wasm32\"))".dependencies.crossterm] version = "0.27.0" optional = true [target."cfg(target_arch = \"wasm32\")".dependencies.console_error_panic_hook] version = "0.1.6" [target."cfg(target_arch = \"wasm32\")".dependencies.serde-wasm-bindgen] version = "0.5.0" optional = true [target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen] version = "0.2" optional = true [target."cfg(target_arch = \"wasm32\")".dev-dependencies.wasm-bindgen-test] version = "0.3.25" [badges.maintenance] status = "actively-developed" cddl-0.9.4/Cargo.toml.orig000064400000000000000000000054131046102023000134360ustar 00000000000000[package] name = "cddl" description = "Parser for the Concise data definition language (CDDL)" repository = "https://github.com/anweiss/cddl" homepage = "https://cddl.anweiss.tech" categories = ["parser-implementations", "encoding", "development-tools", "wasm"] license = "MIT" version = "0.9.4" authors = ["Andrew Weiss "] readme = "README.md" edition = "2018" exclude = [ "cddl-lsp/**/*", "www/**/*", ".github/**/*", ".devcontainer/**/*", "pkg/**/*", ".dockerignore", "Dockerfile", "tests/**/*", ] [lib] crate-type = ["cdylib", "rlib"] [dependencies] base16 = { version = "0.2.1", default-features = false } # base64 = { version = "0.21.0", default-features = false } data-encoding = { version = "2.3.3", default-features = false } chrono = { version = "0.4.19", optional = true } clap = { version = "3.2.23", optional = true, features = ["derive"] } codespan-reporting = "0.11.1" hexf-parse = "0.2.1" itertools = "0.11.0" lexical-core = "0.8.3" regex = { version = "1.5.4", default-features = false, features = [ "std", "unicode-perl", ] } regex-syntax = { version = "0.7.1", optional = true } serde = { version = "1.0.127", optional = true, features = ["derive"] } ciborium = { version = "0.2.0", optional = true } serde_json = { version = "1.0.66", optional = true, default-features = false, features = [ "std", ] } uriparse = { version = "0.6.3", optional = true } base64-url = { version = "2.0.0", optional = true } abnf_to_pest = "0.5.1" pest_meta = "2.1.3" pest_vm = "2.1.0" displaydoc = { version = "0.2.3", default-features = false } log = "0.4.14" simplelog = "0.12.1" [dev-dependencies] indoc = "2.0.1" pretty_assertions = "1.2.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] crossterm = { version = "0.27.0", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] console_error_panic_hook = "0.1.6" serde-wasm-bindgen = { version = "0.5.0", optional = true } wasm-bindgen = { version = "0.2", optional = true } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen-test = "0.3.25" [features] default = [ "std", "ast-span", "ast-comments", "json", "cbor", "additional-controls", "ast-parent", ] std = [ "base16/alloc", "data-encoding/alloc", "serde_json", "ciborium", "serde", "chrono", "wasm-bindgen", "serde-wasm-bindgen", "clap", "crossterm", "uriparse", "base64-url", "regex-syntax", ] lsp = ["std"] additional-controls = [] ast-span = [] ast-comments = [] ast-parent = [] json = ["std"] cbor = ["std"] [[bin]] name = "cddl" required-features = ["std", "json", "cbor"] path = "src/bin/cli.rs" test = false [profile.release] opt-level = "s" lto = true [badges] maintenance = { status = "actively-developed" } cddl-0.9.4/LICENSE000064400000000000000000000020541046102023000115520ustar 00000000000000MIT License Copyright (c) 2019 Andrew Weiss Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.cddl-0.9.4/README.md000064400000000000000000000616641046102023000120400ustar 00000000000000# cddl-rs [![crates.io](https://img.shields.io/crates/v/cddl.svg)](https://crates.io/crates/cddl) [![docs.rs](https://docs.rs/cddl/badge.svg)](https://docs.rs/cddl) [![Publish packages](https://github.com/anweiss/cddl/workflows/Publish%20packages/badge.svg?branch=0.9.3&event=release)](https://github.com/anweiss/cddl/actions?query=workflow%3A%22Publish+packages%22) [![Build and Test](https://github.com/anweiss/cddl/workflows/Build%20and%20Test/badge.svg)](https://github.com/anweiss/cddl/actions?query=workflow%3A%22Build+and+Test%22) [![Active Development](https://img.shields.io/badge/Maintenance%20Level-Actively%20Developed-brightgreen.svg)](https://gist.github.com/cheerfulstoic/d107229326a01ff0f333a1d3476e068d) > This crate was originally developed as a personal learning exercise for getting acquainted with Rust and parsing in general. There are likely more performant and stable libraries out there for parsing CDDL. While there are some examples of this crate being used in production, careful consideration should be made prior to using this crate as such. A Rust implementation of the Concise data definition language (CDDL). CDDL is an IETF standard that "proposes a notational convention to express CBOR and JSON data structures." As of 2019-06-12, it is published as RFC 8610 (Proposed Standard) at [https://tools.ietf.org/html/rfc8610](https://tools.ietf.org/html/rfc8610). This crate includes a handwritten parser and lexer for CDDL, and its development has been heavily inspired by the techniques outlined in Thorsten Ball's book ["Writing An Interpretor In Go"](https://interpreterbook.com/). The AST has been built to closely match the rules defined by the ABNF grammar in [Appendix B.](https://tools.ietf.org/html/rfc8610#appendix-B) of the spec. All CDDL must use UTF-8 for its encoding per the spec. This crate supports validation of both CBOR and JSON data structures. The minimum supported Rust version (MSRV) is 1.67.0. Also bundled into this repository is a basic language server implementation and extension for Visual Studio Code for editing CDDL. The implementation is backed by the compiled WebAssembly target included in this crate. ## Goals - [x] Parse CDDL documents into an AST - [x] Verify conformance of CDDL documents against RFC 8610 - [x] Validate CBOR data structures - [x] Validate JSON documents - [ ] Generate dummy JSON from conformant CDDL - [x] As close to zero-copy as possible - [x] Compile WebAssembly target for browser and Node.js - [x] `no_std` support (lexing and parsing only) - [x] Language server implementation and Visual Studio Code Extension ## Non-goals - Performance (if this crate gains enough traction, it may be prudent to conduct more formal profiling and/or explore using a parser-combinator framework like [nom](https://github.com/Geal/nom)) - Support CBOR diagnostic notation - I-JSON compatibility ## Why Rust? Rust is a systems programming language designed around safety and is ideally-suited for resource-constrained systems. CDDL and CBOR are designed around small code and message sizes and constrained nodes, scenarios for which Rust has also been designed. ## CLI A CLI is available for various platforms. The tool supports parsing of CDDL files for verifying conformance against RFC 8610. It can also be used to validate JSON documents and CBOR binary files against CDDL documents. Detailed information about the JSON and CBOR validation implementation can be found in the sections below. ### Installation #### GitHub Releases Binaries for Linux, macOS and Windows can be downloaded from GitHub [Releases](https://github.com/anweiss/cddl/releases). #### Cargo ```sh cargo install cddl ``` #### Docker ```sh docker pull ghcr.io/anweiss/cddl-cli:latest ``` ### CLI usage Instructions for using the tool can be viewed by executing the `help` subcommand: ```sh cddl help ``` If using Docker: > Replace `` with an appropriate [release](https://github.com/anweiss/cddl/releases) tag. Requires use of the `--volume` argument for mounting CDDL documents into the container when executing the command. JSON or CBOR files can either be included in the volume mount or passed into the command via STDIN. ```sh docker run -it --rm -v $PWD:/cddl -w /cddl ghcr.io/anweiss/cddl-cli: help ``` You can validate JSON documents and/or CBOR binary files: ```sh cddl validate [OPTIONS] --cddl <--stdin|--json ...|--cbor ...> ``` It also supports validating files from STDIN (if it detects the input as valid UTF-8, it will attempt to validate the input as JSON, otherwise it will treat it as CBOR): ```sh cat reputon.json | cddl validate --cddl reputon.cddl --stdin cat reputon.cbor | cddl validate --cddl reputon.cddl --stdin ``` or using Docker: ```sh docker run -i --rm -v $PWD:/data -w /data ghcr.io/anweiss/cddl-cli:0.9.3 validate --cddl reputon.cddl --stdin < reputon.json ``` ## Website You can also find a simple RFC 8610 conformance tool at [https://cddl.anweiss.tech](https://cddl.anweiss.tech). This same codebase has been compiled for use in the browser via WebAssembly. ## Visual Studio Code extension An extension for editing CDDL documents with Visual Studio Code has been published to the Marketplace [here](https://marketplace.visualstudio.com/items?itemName=anweiss.cddl-languageserver). You can find more information in the [README](cddl-lsp/README.md). ## Supported features - [x] maps - [x] structs - [x] tables - [x] cuts - [x] groups - [x] arrays - [x] values - [x] choices - [x] ranges - [x] enumeration (building a choice from a group) - [x] root type - [x] occurrence - [x] predefined types - [x] tags - [x] unwrapping - [x] controls - [x] socket/plug - [x] generics - [x] operator precedence - [x] comments - [x] numerical int/uint values - [x] numerical hexfloat values - [x] numerical values with exponents - [x] unprefixed byte strings - [x] prefixed byte strings ## Usage Simply add the dependency to `Cargo.toml`: ```toml [dependencies] cddl = "0.9.3" ``` Both JSON and CBOR validation require `std`. ### Feature flags A few convenience features have been included to make the AST more concise and for enabling additional functionality. You can build with `default-features = false` for a `no_std` build and selectively enable any of the features below. **`--feature ast-span`** Add the `Span` type to the AST for keeping track of the position of the lexer and parser. Enabled by default. **`--feature ast-comments`** Include comment strings in the AST. Enabled by default. **`--feature ast-parent`** Add the `ParentVisitor` implementation so that the AST can be traversed using parent pointers. Enabled by default. **`--feature json`** Enable JSON validation. Enabled by default. **`--feature cbor`** Enable CBOR validation. Enabled by default. **`--feature additional-controls`** Enable validation support for the additional control operators defined in [RFC 9165](https://datatracker.ietf.org/doc/html/rfc9165). Enabled by default. ### Parsing CDDL ```rust use cddl::parser::cddl_from_str; let input = r#"myrule = int"#; assert!(cddl_from_str(input, true).is_ok()) ``` ### Validating JSON ```rust use cddl::validate_json_from_str; let cddl = r#"person = { name: tstr, age: uint, address: tstr, }"#; let json = r#"{ "name": "John", "age": 50, "address": "1234 Lakeshore Dr" }"#; assert!(validate_json_from_str(cddl, json).is_ok()) ``` This crate uses the [Serde](https://serde.rs/) framework, and more specifically, the [serde_json](https://crates.io/crates/serde_json) crate, for parsing and validating JSON. Serde was chosen due to its maturity in the ecosystem and its support for serializing and deserializing CBOR via the [ciborium](https://crates.io/crates/ciborium) crate. As outlined in [Appendix E.](https://tools.ietf.org/html/rfc8610#appendix-E) of the standard, only the JSON data model subset of CBOR can be used for validation. The limited prelude from the spec has been included below for brevity: ```cddl any = # uint = #0 nint = #1 int = uint / nint tstr = #3 text = tstr number = int / float float16 = #7.25 float32 = #7.26 float64 = #7.27 float16-32 = float16 / float32 float32-64 = float32 / float64 float = float16-32 / float64 false = #7.20 true = #7.21 bool = false / true nil = #7.22 null = nil ``` Furthermore, the following data types from the standard prelude can be used for validating JSON strings and numbers: ```cddl tdate = #6.0(tstr) uri = #6.32(tstr) b64url = #6.33(tstr) time = #6.1(number) ``` The first non-group rule defined by a CDDL data structure definition determines the root type, which is subsequently used for validating the top-level JSON data type. #### Supported JSON validation features The following types and features of CDDL are supported by this crate for validating JSON: | CDDL | JSON | | ---------------------- | ----------------------------------------------------------- | | structs | objects | | arrays | arrays[1](#arrays) | | `text / tstr` | string | | `uri` | string (valid RFC3986 URI) | | `tdate` | string (valid RFC3339 date/time) | | `b64url` | string (base64url-encoded) | | `time` | number (valid UNIX timestamp integer in seconds) | | `number / int / float` | number[2](#number) | | `bool / true / false` | boolean | | `null / nil` | null | | `any` | any valid JSON | | byte strings | not yet implemented | | unwrap (`~`) | any JSON that matches unwrapped type from map, array or tag | CDDL groups, generics, sockets/plugs and group-to-choice enumerations can all be used when validating JSON. Since JSON objects only support keys whose types are JSON strings, when validating JSON, member keys defined in CDDL structs must use either the colon syntax (`mykey: tstr` or `"mykey": tstr`) or the double arrow syntax provided that the member key is either a text string value (`"mykey" => tstr`) or a bareword that resolves to either a string data type (`text` or `tstr`) or another text string value (`* tstr => any`). Occurrence indicators can be used to validate key/value pairs in a JSON object and the number of elements in a JSON array; depending on how the indicators are defined in a CDDL data definition. Below is the table of supported control operators: | Control operator | Supported | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `.pcre` | ✔️[3](#regex) | | `.regex` | ✔️[3](#regex) (alias for `.pcre`) | | `.size` | ✔️ | | `.bits` | Ignored when validating JSON | | `.cbor` | Ignored when validating JSON | | `.cborseq` | Ignored when validating JSON | | `.within` | ✔️ | | `.and` | ✔️ | | `.lt` | ✔️ | | `.le` | ✔️ | | `.gt` | ✔️ | | `.ge` | ✔️ | | `.eq` | ✔️ | | `.ne` | ✔️ | | `.default` | ✔️ | 1: When groups with multiple group entries are used to validate arrays, occurrence indicators are "greedy" in that only the first occurrence indicator that is come across is used in the validation. Subsequent entries with occurrence indicators are ignored due to complexities involved with processing these ambiguities. For proper JSON validation, avoid writing CDDL that looks like the following: `[ * a: int, b: tstr, ? c: int ]`. 2: While JSON itself does not distinguish between integers and floating-point numbers, this crate does provide the ability to validate numbers against a more specific numerical CBOR type, provided that its equivalent representation is allowed by JSON. Refer to [Appendix E.](https://tools.ietf.org/html/rfc8610#appendix-E) of the standard for more details on the implications of using CDDL with JSON numbers. 3: Due to Perl-Compatible Regular Expressions (PCREs) being more widely used than XSD regular expressions, this crate also provides support for the proposed `.pcre` control extension in place of the `.regexp` operator (see [Discussion](https://tools.ietf.org/html/rfc8610#section-3.8.3.2) and [CDDL-Freezer proposal](https://tools.ietf.org/html/draft-bormann-cbor-cddl-freezer-03#section-5.1)). Ensure that your regex string is properly JSON escaped when using this control. If you've enabled the `additional-controls` feature, the table of controls below is also available for use: | Control operator | Supported | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `.plus` | ✔️ | | `.cat` | ✔️ | | `.det` | ✔️ | | `.abnf` | ✔️ | | `.abnfb` | Ignored when validating JSON | | `.feature` | ✔️ | You can activate features during validation as follows: ```rust use cddl::validate_json_from_str; let cddl = r#" v = JC<"v", 2> JC = C .feature "cbor" / J .feature "json" "#; let json = r#""v""#; assert!(validate_json_from_str(cddl, json, Some(&["json"])).is_ok()) ``` #### Comparing with JSON schema and JSON schema language [CDDL](https://tools.ietf.org/html/rfc8610), [JSON schema](https://json-schema.org/) and [JSON schema language](https://tools.ietf.org/html/draft-json-schema-language-02) can all be used to define JSON data structures. However, the approaches taken to develop each of these are vastly different. A good place to find past discussions on the differences between these formats is the [IETF mail archive](https://mailarchive.ietf.org/arch/), specifically in the JSON and CBOR lists. The purpose of this crate is not to argue for the use of CDDL over any one of these formats, but simply to provide an example implementation in Rust. ### Validating CBOR ```rust use cddl::validate_cbor_from_slice; let cddl = r#"rule = false"#; let cbor = b"\xF4"; assert!(validate_cbor_from_slice(cddl, cbor).is_ok()) ``` This crate also uses [Serde](https://serde.rs/) and [ciborium](https://crates.io/crates/ciborium) for validating CBOR data structures. CBOR validation is done via the loosely typed [`ciborium::value::Value`](https://github.com/enarx/ciborium/blob/main/ciborium/src/value/mod.rs#L22) enum. In addition to all of the same features implemented by the JSON validator, this crate also supports validating CBOR tags (e.g. `#6.32(tstr)`), CBOR major types (e.g. `#1.2`), table types (e.g. `{ [ + tstr ] => int }`) and byte strings. The `.bits`, `.cbor` and `.cborseq` control operators are all supported as well. The following tags are supported when validating CBOR: | Tag | Supported | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `tdate = #6.0(tstr)` | ✔️ | | `time = #6.1(number)` | ✔️ | | `biguint = #6.2(bstr)` | ✔️ | | `bignint = #6.3(bstr)` | ✔️ | | `decfrac = #6.4([e10: int, m: integer])` | not yet implemented | | `bigfloat = #6.5([e2: int, m: integer])` | not yet implemented | | `eb64url = #6.21(any)` | ✔️ | | `eb64legacy = #6.22(any)` | ✔️ | | `eb16 = #6.23(any)` | ✔️ | | `encoded-cbor = #6.24(bstr)` | ✔️ | | `uri = #6.32(tstr)` | ✔️ | | `b64url = #6.33(tstr)` | ✔️ | | `b64legacy = #6.34(tstr)` | ✔️ | | `regexp = #6.35(tstr)` | ✔️ | | `mime-message = #6.36(tstr)` | ✔️ | | `cbor-any = #6.55799(any)` | ✔️ | If you've enabled the `additional-controls` feature, the table of controls below is also available for use: | Control operator | Supported | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `.plus` | ✔️ | | `.cat` | ✔️ | | `.det` | ✔️ | | `.abnf` | ✔️ | | `.abnfb` | ✔️ | | `.feature` | ✔️ | You can activate features during validation by passing a slice of feature strings as follows: ```rust use cddl::validate_cbor_from_slice; let cddl = r#" v = JC<"v", 2> JC = C .feature "cbor" / J .feature "json" "#; let cbor = b"\x02"; assert!(validate_cbor_from_slice(cddl, cbor, Some(&["cbor"])).is_ok()) ``` ## `no_std` support Only the lexer and parser can be used in a `no_std` context provided that a heap allocator is available. This can be enabled by opting out of the default features in your `Cargo.toml` file as follows: ```toml [dependencies] cddl = { version = "0.9.3", default-features = false } ``` Zero-copy parsing is implemented to the extent that is possible. Allocation is required for error handling and diagnostics. Both JSON and CBOR validation are dependent on their respective heap allocated `Value` types, but since these types aren't supported in a `no_std` context, they subsequently aren't supported by this crate in `no_std`. ## Projects using this crate Below are some known projects that leverage this crate: - [https://github.com/Emurgo/cddl-codegen](https://github.com/Emurgo/cddl-codegen) - [https://github.com/p2panda/p2panda](https://github.com/p2panda/p2panda) cddl-0.9.4/cddl.code-workspace000064400000000000000000000002651046102023000143050ustar 00000000000000{ "folders": [ { "path": "." }, { "path": "cddl-lsp" } ], "settings": { "rust-analyzer.cargo.allFeatures": false, "rust-analyzer.cargo.features": [ "std" ] } } cddl-0.9.4/src/ast/mod.rs000064400000000000000000002375551046102023000132700ustar 00000000000000/// Parent visitor implementation pub mod parent; #[cfg(target_arch = "wasm32")] extern crate console_error_panic_hook; use crate::token::{ByteValue, ControlOperator, RangeValue, SocketPlug, Token, Value}; use std::{ fmt::{self, Write}, marker::PhantomData, }; #[cfg(feature = "std")] use std::borrow::Cow; #[cfg(target_arch = "wasm32")] use serde::{self, Serialize}; #[cfg(not(feature = "std"))] use alloc::{ borrow::Cow, boxed::Box, string::{String, ToString}, vec::Vec, }; /// Starting index, ending index and line number #[cfg(feature = "ast-span")] pub type Span = (usize, usize, usize); #[derive(Clone, Debug, PartialEq)] #[doc(hidden)] pub enum CDDLType<'a, 'b: 'a> { CDDL(&'b CDDL<'a>), Rule(&'b Rule<'a>), TypeRule(&'b TypeRule<'a>), GroupRule(&'b GroupRule<'a>), Group(&'b Group<'a>), GroupChoice(&'b GroupChoice<'a>), GenericParams(&'b GenericParams<'a>), GenericParam(&'b GenericParam<'a>), GenericArgs(&'b GenericArgs<'a>), GenericArg(&'b GenericArg<'a>), GroupEntry(&'b GroupEntry<'a>), Identifier(&'b Identifier<'a>), Type(&'b Type<'a>), TypeChoice(&'b TypeChoice<'a>), Type1(&'b Type1<'a>), Type2(&'b Type2<'a>), Operator(&'b Operator<'a>), RangeCtlOp(&'b RangeCtlOp), ControlOperator(&'b ControlOperator), Occurrence(&'b Occurrence<'a>), Occur(Occur), Value(Value<'a>), ValueMemberKeyEntry(&'b ValueMemberKeyEntry<'a>), TypeGroupnameEntry(&'b TypeGroupnameEntry<'a>), MemberKey(&'b MemberKey<'a>), NonMemberKey(&'b NonMemberKey<'a>), } macro_rules! cddl_types_from_ast { ($($t:ty => $p:path),* $(,)?) => { $( impl<'a, 'b: 'a> From<$t> for CDDLType<'a, 'b> { fn from(value: $t) -> Self { $p(value) } } )* }; } cddl_types_from_ast! { &'b CDDL<'a> => CDDLType::CDDL, &'b Rule<'a> => CDDLType::Rule, &'b TypeRule<'a> => CDDLType::TypeRule, &'b GroupRule<'a> => CDDLType::GroupRule, &'b Group<'a> => CDDLType::Group, &'b GroupChoice<'a> => CDDLType::GroupChoice, &'b GenericParams<'a> => CDDLType::GenericParams, &'b GenericParam<'a> => CDDLType::GenericParam, &'b GenericArgs<'a> => CDDLType::GenericArgs, &'b GenericArg<'a> => CDDLType::GenericArg, &'b GroupEntry<'a> => CDDLType::GroupEntry, &'b Identifier<'a> => CDDLType::Identifier, &'b Type<'a> => CDDLType::Type, &'b TypeChoice<'a> => CDDLType::TypeChoice, &'b Type1<'a> => CDDLType::Type1, &'b Type2<'a> => CDDLType::Type2, &'b Operator<'a> => CDDLType::Operator, &'b RangeCtlOp => CDDLType::RangeCtlOp, &'b ControlOperator => CDDLType::ControlOperator, &'b Occurrence<'a> => CDDLType::Occurrence, &'b ValueMemberKeyEntry<'a> => CDDLType::ValueMemberKeyEntry, &'b TypeGroupnameEntry<'a> => CDDLType::TypeGroupnameEntry, &'b MemberKey<'a> => CDDLType::MemberKey, &'b NonMemberKey<'a> => CDDLType::NonMemberKey, Occur => CDDLType::Occur, Value<'a> => CDDLType::Value, } #[cfg(feature = "ast-comments")] #[derive(Default, Debug, PartialEq, Eq, Clone)] #[doc(hidden)] pub struct Comments<'a>(pub Vec<&'a str>); #[cfg(feature = "ast-comments")] impl<'a> Comments<'a> { fn any_non_newline(&self) -> bool { self.0.iter().any(|c| *c != "\n") } fn all_newline(&self) -> bool { self.0.iter().all(|c| *c == "\n") } } #[cfg(feature = "ast-comments")] impl<'a> fmt::Display for Comments<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.all_newline() { return write!(f, ""); } let mut comment_str = String::new(); for comment in &self.0 { if *comment == "\n" { comment_str.push('\n') } else { let _ = writeln!(comment_str, ";{}", comment); } } write!(f, "{}", comment_str) } } /// CDDL AST /// /// ```abnf /// cddl = S 1*(rule S) /// ``` #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Default, Debug, PartialEq, Clone)] pub struct CDDL<'a> { /// Zero or more production rules #[cfg_attr(target_arch = "wasm32", serde(borrow))] pub rules: Vec>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] pub comments: Option>, } impl<'a> fmt::Display for CDDL<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { #[cfg(target_arch = "wasm32")] console_error_panic_hook::set_once(); let mut cddl_output = String::new(); #[cfg(feature = "ast-comments")] if let Some(comments) = &self.comments { cddl_output.push_str(&comments.to_string()); } let mut previous_single_line_type = false; #[cfg(feature = "ast-comments")] let mut previous_comments_after_rule = false; #[cfg(feature = "ast-comments")] for (idx, rule) in self.rules.iter().enumerate() { if rule.has_comments_after_rule() { cddl_output.push_str(&rule.to_string()); previous_comments_after_rule = true; } else if idx == self.rules.len() - 1 || rule.has_single_line_type() { let _ = writeln!(cddl_output, "{}", rule.to_string().trim_end()); previous_single_line_type = true; previous_comments_after_rule = false; } else if previous_single_line_type && !previous_comments_after_rule { let _ = write!(cddl_output, "\n{}\n\n", rule.to_string().trim_end()); previous_single_line_type = false; previous_comments_after_rule = false; } else { let _ = write!(cddl_output, "{}\n\n", rule.to_string().trim_end()); previous_comments_after_rule = false; } } #[cfg(not(feature = "ast-comments"))] for (idx, rule) in self.rules.iter().enumerate() { if idx == self.rules.len() - 1 || rule.has_single_line_type() { let _ = writeln!(cddl_output, "{}", rule.to_string().trim_end()); previous_single_line_type = true; } else if previous_single_line_type { let _ = write!(cddl_output, "\n{}\n\n", rule.to_string().trim_end()); previous_single_line_type = false; } else { let _ = write!(cddl_output, "{}\n\n", rule.to_string().trim_end()); } } write!(f, "{}", cddl_output) } } /// Identifier for a type name, group name or bareword, with an optional socket /// /// ```abnf /// id = EALPHA *(*("-" / ".") (EALPHA / DIGIT)) /// ALPHA = %x41-5A / %x61-7A /// EALPHA = ALPHA / "@" / "_" / "$" /// DIGIT = %x30-39 /// ``` #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, Clone)] pub struct Identifier<'a> { /// Identifier pub ident: &'a str, /// Optional socket pub socket: Option, /// Span #[cfg(feature = "ast-span")] pub span: Span, } impl<'a> PartialEq for Identifier<'a> { fn eq(&self, other: &Self) -> bool { self.to_string() == other.to_string() } } impl<'a> Eq for Identifier<'a> {} impl<'a> fmt::Display for Identifier<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if let Some(sp) = &self.socket { return write!(f, "{}{}", sp, self.ident); } write!(f, "{}", self.ident) } } impl<'a> From<&'static str> for Identifier<'a> { fn from(ident: &'static str) -> Self { let mut socket = ident.chars().take(2); if let Some(c) = socket.next() { if c == '$' { if let Some(c) = socket.next() { if c == '$' { return Identifier { ident, socket: Some(SocketPlug::GROUP), #[cfg(feature = "ast-span")] span: (0, 0, 0), }; } } return Identifier { ident, socket: Some(SocketPlug::TYPE), #[cfg(feature = "ast-span")] span: (0, 0, 0), }; } } Identifier { ident, socket: None, #[cfg(feature = "ast-span")] span: (0, 0, 0), } } } impl<'a> From> for Identifier<'a> { fn from(token: Token) -> Self { let token = if let Some(token) = token.in_standard_prelude() { token } else { "" }; Identifier::from(token) } } /// Type or group expression /// /// ```abnf /// rule = typename [genericparm] S assignt S type /// / groupname [genericparm] S assigng S grpent /// ``` #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, PartialEq, Clone)] pub enum Rule<'a> { /// Type expression Type { /// Type rule #[cfg_attr(target_arch = "wasm32", serde(borrow))] rule: TypeRule<'a>, /// Span #[cfg(feature = "ast-span")] span: Span, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments_after_rule: Option>, }, /// Group expression Group { /// Group rule rule: Box>, /// Span #[cfg(feature = "ast-span")] span: Span, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments_after_rule: Option>, }, } impl<'a> Rule<'a> { /// Return `Span` for `Rule` #[cfg(feature = "ast-span")] pub fn span(&self) -> Span { match self { Rule::Type { span, .. } => *span, Rule::Group { span, .. } => *span, } } #[cfg(feature = "ast-comments")] fn has_comments_after_rule(&self) -> bool { matches!(self, Rule::Type { comments_after_rule: Some(comments), .. } | Rule::Group { comments_after_rule: Some(comments), .. } if comments.any_non_newline()) } fn has_single_line_type(&self) -> bool { if let Rule::Type { rule: TypeRule { value: Type { type_choices, .. }, .. }, .. } = self { let type_check = |tc: &TypeChoice| { matches!( tc.type1.type2, Type2::Typename { .. } | Type2::FloatValue { .. } | Type2::IntValue { .. } | Type2::UintValue { .. } | Type2::TextValue { .. } | Type2::B16ByteString { .. } | Type2::B64ByteString { .. } ) }; if type_choices.len() <= 2 && type_choices.iter().all(type_check) { return true; } } false } } impl<'a> fmt::Display for Rule<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Rule::Type { rule, #[cfg(feature = "ast-comments")] comments_after_rule, .. } => { let mut rule_str = String::new(); rule_str.push_str(&rule.to_string()); #[cfg(feature = "ast-comments")] if let Some(comments) = comments_after_rule { if comments.any_non_newline() { if let Some(&"\n") = comments.0.first() { rule_str.push_str(&comments.to_string()); } else { let _ = write!(rule_str, " {}", comments); } } } write!(f, "{}", rule_str) } Rule::Group { rule, #[cfg(feature = "ast-comments")] comments_after_rule, .. } => { let mut rule_str = String::new(); rule_str.push_str(&rule.to_string()); #[cfg(feature = "ast-comments")] if let Some(comments) = comments_after_rule { if comments.any_non_newline() { if let Some(&"\n") = comments.0.first() { rule_str.push_str(&comments.to_string()); } else { let _ = write!(rule_str, " {}", comments); } } } write!(f, "{}", rule_str) } } } } impl<'a> Rule<'a> { /// Returns the name id of a rule pub fn name(&self) -> String { match self { Rule::Type { rule, .. } => rule.name.to_string(), Rule::Group { rule, .. } => rule.name.to_string(), } } /// Returns whether or not a rule extends an existing type or group rule with /// additional choices pub fn is_choice_alternate(&self) -> bool { match self { Rule::Type { rule, .. } => rule.is_type_choice_alternate, Rule::Group { rule, .. } => rule.is_group_choice_alternate, } } } /// Type expression /// /// ```abnf /// typename [genericparm] S assignt S type /// ``` #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, PartialEq, Clone)] pub struct TypeRule<'a> { /// Type name identifier #[cfg_attr(target_arch = "wasm32", serde(borrow))] pub name: Identifier<'a>, /// Optional generic parameters pub generic_params: Option>, /// Extends an existing type choice pub is_type_choice_alternate: bool, /// Type value pub value: Type<'a>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] pub comments_before_assignt: Option>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] pub comments_after_assignt: Option>, } impl<'a> fmt::Display for TypeRule<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut tr_output = self.name.to_string(); if let Some(gp) = &self.generic_params { tr_output.push_str(&gp.to_string()); } #[cfg(feature = "ast-comments")] if let Some(comments) = &self.comments_before_assignt { tr_output.push_str(&comments.to_string()); } if self.is_type_choice_alternate { tr_output.push_str(" /= "); } else { tr_output.push_str(" = "); } #[cfg(feature = "ast-comments")] if let Some(comments) = &self.comments_after_assignt { tr_output.push_str(&comments.to_string()); } tr_output.push_str(&self.value.to_string()); write!(f, "{}", tr_output) } } /// Group expression /// /// ```abnf /// groupname [genericparm] S assigng S grpent /// ``` #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, PartialEq, Clone)] pub struct GroupRule<'a> { /// Group name identifier #[cfg_attr(target_arch = "wasm32", serde(borrow))] pub name: Identifier<'a>, /// Optional generic parameters pub generic_params: Option>, /// Extends an existing group choice pub is_group_choice_alternate: bool, /// Group entry pub entry: GroupEntry<'a>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] pub comments_before_assigng: Option>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] pub comments_after_assigng: Option>, } impl<'a> fmt::Display for GroupRule<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut gr_output = self.name.to_string(); if let Some(gp) = &self.generic_params { gr_output.push_str(&gp.to_string()); } #[cfg(feature = "ast-comments")] if let Some(comments) = &self.comments_before_assigng { gr_output.push_str(&comments.to_string()); } if self.is_group_choice_alternate { gr_output.push_str(" //= "); } else { gr_output.push_str(" = "); } gr_output.push_str(&self.entry.to_string()); #[cfg(feature = "ast-comments")] if let Some(comments) = &self.comments_after_assigng { gr_output.push_str(&comments.to_string()); } write!(f, "{}", gr_output) } } /// Generic parameters /// /// ```abnf /// genericparm = "<" S id S *("," S id S ) ">" /// ``` #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct GenericParams<'a> { /// List of generic parameters pub params: Vec>, /// Span #[cfg(feature = "ast-span")] pub span: Span, } /// Generic parameter #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, PartialEq, Eq, Clone)] pub struct GenericParam<'a> { /// Generic parameter pub param: Identifier<'a>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] pub comments_before_ident: Option>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] pub comments_after_ident: Option>, } impl<'a> fmt::Display for GenericParams<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut gp = String::from("<"); for (idx, parm) in self.params.iter().enumerate() { if idx != 0 { gp.push_str(", "); } #[cfg(feature = "ast-comments")] if let Some(comments) = &parm.comments_before_ident { gp.push_str(&comments.to_string()); } gp.push_str(&parm.param.to_string()); #[cfg(feature = "ast-comments")] if let Some(comments) = &parm.comments_after_ident { gp.push_str(&comments.to_string()); } } gp.push('>'); write!(f, "{}", gp) } } /// Generic arguments /// /// ```abnf /// genericarg = "<" S type1 S *("," S type1 S ) ">" /// ``` #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, Clone, PartialEq, Default)] pub struct GenericArgs<'a> { /// Generic arguments pub args: Vec>, /// Span #[cfg(feature = "ast-span")] pub span: Span, } /// Generic argument #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, Clone, PartialEq)] pub struct GenericArg<'a> { /// Generic argument pub arg: Box>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] pub comments_before_type: Option>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] pub comments_after_type: Option>, } impl<'a> fmt::Display for GenericArgs<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut ga = String::from("<"); for (idx, arg) in self.args.iter().enumerate() { if idx != 0 { ga.push_str(", "); } #[cfg(feature = "ast-comments")] if let Some(comments) = &arg.comments_before_type { ga.push_str(&comments.to_string()); } ga.push_str(&arg.arg.to_string()); #[cfg(feature = "ast-comments")] if let Some(comments) = &arg.comments_after_type { ga.push_str(&comments.to_string()); } } ga.push('>'); write!(f, "{}", ga) } } /// Type choices /// /// ```abnf /// type = type1 *(S "/" S type1) /// ``` #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, Clone, PartialEq)] pub struct Type<'a> { /// Type choices pub type_choices: Vec>, /// Span #[cfg(feature = "ast-span")] pub span: Span, } impl<'a> Type<'a> { /// take all the comments after a type /// this is useful if the type is consumed to build another type object #[cfg(feature = "ast-comments")] #[doc(hidden)] pub fn take_comments_after_type(&mut self) -> Option> { if let Some(TypeChoice { type1: Type1 { comments_after_type, .. }, .. }) = self.type_choices.last_mut() { if let Some(comments) = comments_after_type { if comments.any_non_newline() { return comments_after_type.take(); } } } None } } impl<'a> Type<'a> { /// leave the first comment after a type as part of its parent /// and subsequent comments are considered after the type #[cfg(feature = "ast-comments")] #[doc(hidden)] pub fn split_comments_after_type(&mut self) -> Option> { if let Some(TypeChoice { type1: Type1 { comments_after_type, .. }, .. }) = self.type_choices.last_mut() { return match comments_after_type.as_mut() { Some(comments) if comments.any_non_newline() && comments.0.len() > 1 => { Some(Comments(comments.0.drain(1..).collect())) } _ => None, }; } None } } /// Type choice #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, Clone, PartialEq)] pub struct TypeChoice<'a> { /// Type choice pub type1: Type1<'a>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] pub comments_before_type: Option>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] pub comments_after_type: Option>, } impl<'a> fmt::Display for Type<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut type_str = String::new(); for (idx, tc) in self.type_choices.iter().enumerate() { if idx == 0 { type_str.push_str(&tc.type1.to_string()); #[cfg(feature = "ast-comments")] if let Some(comments) = &tc.comments_after_type { type_str.push_str(comments.to_string().trim_end()); } continue; } #[cfg(feature = "ast-comments")] if let Some(comments) = &tc.comments_before_type { type_str.push_str(&comments.to_string()); } if self.type_choices.len() > 2 { let _ = write!(type_str, "\n\t/ {}", tc.type1); } else { let _ = write!(type_str, " / {}", tc.type1); } #[cfg(feature = "ast-comments")] if let Some(comments) = &tc.comments_after_type { type_str.push_str(&comments.to_string()); } } write!(f, "{}", type_str) } } impl<'a> Type<'a> { /// Used to delineate between grpent with `Type` and group entry with group /// name identifier `id` #[allow(clippy::type_complexity)] #[cfg(feature = "ast-span")] pub fn groupname_entry(&self) -> Option<(Identifier<'a>, Option>, Span)> { if self.type_choices.len() == 1 { if let Some(tc) = self.type_choices.first() { if tc.type1.operator.is_none() { if let Type2::Typename { ident, generic_args, span, } = &tc.type1.type2 { return Some((ident.clone(), generic_args.clone(), *span)); } } } } None } /// Used to delineate between grpent with `Type` and group entry with group /// name identifier `id` #[allow(clippy::type_complexity)] #[cfg(not(feature = "ast-span"))] pub fn groupname_entry(&self) -> Option<(Identifier<'a>, Option>)> { if self.type_choices.len() == 1 { if let Some(tc) = self.type_choices.first() { if tc.type1.operator.is_none() { if let Type2::Typename { ident, generic_args, } = &tc.type1.type2 { return Some((ident.clone(), generic_args.clone())); } } } } None } } /// Type with optional range or control operator /// /// ```abnf /// type1 = type2 [S (rangeop / ctlop) S type2] /// ``` #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, Clone, PartialEq)] pub struct Type1<'a> { /// Type pub type2: Type2<'a>, /// Range or control operator over a second type pub operator: Option>, /// Span #[cfg(feature = "ast-span")] pub span: Span, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] pub comments_after_type: Option>, } impl<'a> From> for Type1<'a> { fn from(value: Value<'a>) -> Self { #[cfg(feature = "ast-span")] let span = Span::default(); let type2 = match value { Value::TEXT(value) => Type2::TextValue { value, #[cfg(feature = "ast-span")] span, }, Value::INT(value) => Type2::IntValue { value, #[cfg(feature = "ast-span")] span, }, Value::FLOAT(value) => Type2::FloatValue { value, #[cfg(feature = "ast-span")] span, }, Value::UINT(value) => Type2::UintValue { value, #[cfg(feature = "ast-span")] span, }, Value::BYTE(ByteValue::B16(value)) => Type2::B16ByteString { value, #[cfg(feature = "ast-span")] span, }, Value::BYTE(ByteValue::B64(value)) => Type2::B64ByteString { value, #[cfg(feature = "ast-span")] span, }, Value::BYTE(ByteValue::UTF8(value)) => Type2::UTF8ByteString { value, #[cfg(feature = "ast-span")] span, }, }; Type1 { type2, #[cfg(feature = "ast-span")] span, operator: None, #[cfg(feature = "ast-comments")] comments_after_type: None, } } } /// Range or control operator #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, Clone, PartialEq)] pub struct Operator<'a> { /// Operator pub operator: RangeCtlOp, /// Type bound by range or control operator pub type2: Type2<'a>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] pub comments_before_operator: Option>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] pub comments_after_operator: Option>, } impl<'a> fmt::Display for Type1<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut t1_str = String::new(); t1_str.push_str(&self.type2.to_string()); if let Type2::Typename { .. } = self.type2 { if self.operator.is_some() { t1_str.push(' '); } } #[cfg(feature = "ast-comments")] if let Some(o) = &self.operator { if let Some(comments) = &o.comments_before_operator { t1_str.push_str(&comments.to_string()); } t1_str.push_str(&o.operator.to_string()); if let Some(comments) = &o.comments_after_operator { t1_str.push_str(&comments.to_string()); } if let Type2::Typename { .. } = self.type2 { t1_str.push(' '); } t1_str.push_str(&o.type2.to_string()); } else if let Some(comments) = &self.comments_after_type { if comments.any_non_newline() { let _ = write!(t1_str, " {}", comments); } } #[cfg(not(feature = "ast-comments"))] if let Some(o) = &self.operator { t1_str.push_str(&o.operator.to_string()); if let Type2::Typename { .. } = self.type2 { t1_str.push(' '); } t1_str.push_str(&o.type2.to_string()); } write!(f, "{}", t1_str) } } /// Range or control operator /// /// ```abnf /// rangeop = "..." / ".." /// ctlop = "." id /// ``` #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, PartialEq, Eq, Clone)] pub enum RangeCtlOp { /// Range operator RangeOp { /// Is inclusive is_inclusive: bool, /// Span #[cfg(feature = "ast-span")] span: Span, }, /// Control operator CtlOp { /// Control identifier ctrl: ControlOperator, /// Span #[cfg(feature = "ast-span")] span: Span, }, } impl fmt::Display for RangeCtlOp { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { RangeCtlOp::RangeOp { is_inclusive: false, .. } => write!(f, "..."), RangeCtlOp::RangeOp { is_inclusive: true, .. } => write!(f, ".."), RangeCtlOp::CtlOp { ctrl, .. } => write!(f, "{}", ctrl), } } } /// Type /// /// ```abnf /// type2 = value /// / typename [genericarg] /// / "(" S type S ")" /// / "{" S group S "}" /// / "[" S group S "]" /// / "~" S typename [genericarg] /// / "&" S "(" S group S ")" /// / "&" S groupname [genericarg] /// / "#" "6" ["." uint] "(" S type S ")" /// / "#" DIGIT ["." uint] ; major/ai /// / "#" ; any /// ``` #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, Clone, PartialEq)] pub enum Type2<'a> { /// Integer value IntValue { /// Value value: isize, /// Span #[cfg(feature = "ast-span")] span: Span, }, /// Unsigned integer value UintValue { /// Value value: usize, /// Span #[cfg(feature = "ast-span")] span: Span, }, /// Float value FloatValue { /// Value value: f64, /// Span #[cfg(feature = "ast-span")] span: Span, }, /// Text string value (enclosed by '"') TextValue { /// Value value: Cow<'a, str>, /// Span #[cfg(feature = "ast-span")] span: Span, }, /// UTF-8 encoded byte string (enclosed by '') UTF8ByteString { /// Value value: Cow<'a, [u8]>, /// Span #[cfg(feature = "ast-span")] span: Span, }, /// Base 16 encoded prefixed byte string B16ByteString { /// Value value: Cow<'a, [u8]>, /// Span #[cfg(feature = "ast-span")] span: Span, }, /// Base 64 encoded (URL safe) prefixed byte string B64ByteString { /// Value value: Cow<'a, [u8]>, /// Span #[cfg(feature = "ast-span")] span: Span, }, /// Type name identifier with optional generic arguments Typename { /// Identifier ident: Identifier<'a>, /// Generic arguments generic_args: Option>, /// Span #[cfg(feature = "ast-span")] span: Span, }, /// Parenthesized type expression (for operator precedence) ParenthesizedType { /// Type pt: Type<'a>, /// Span #[cfg(feature = "ast-span")] span: Span, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments_before_type: Option>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments_after_type: Option>, }, /// Map expression Map { /// Group group: Group<'a>, /// Span #[cfg(feature = "ast-span")] span: Span, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments_before_group: Option>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments_after_group: Option>, }, /// Array expression Array { /// Span group: Group<'a>, /// Span #[cfg(feature = "ast-span")] span: Span, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments_before_group: Option>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments_after_group: Option>, }, /// Unwrapped group Unwrap { /// Identifier ident: Identifier<'a>, /// Generic arguments generic_args: Option>, /// Span #[cfg(feature = "ast-span")] span: Span, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments: Option>, }, /// Enumeration expression over an inline group ChoiceFromInlineGroup { /// Group group: Group<'a>, /// Span #[cfg(feature = "ast-span")] span: Span, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments: Option>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments_before_group: Option>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments_after_group: Option>, }, /// Enumeration expression over previously defined group ChoiceFromGroup { /// Identifier ident: Identifier<'a>, /// Generic arguments generic_args: Option>, /// Span #[cfg(feature = "ast-span")] span: Span, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments: Option>, }, /// Tagged data item where the first element is an optional tag and the second /// is the type of the tagged value TaggedData { /// Tag tag: Option, /// Type t: Type<'a>, /// Span #[cfg(feature = "ast-span")] span: Span, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments_before_type: Option>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments_after_type: Option>, }, /// Data item of a major type with optional data constraint DataMajorType { /// Major type mt: u8, /// Constraint constraint: Option, /// Span #[cfg(feature = "ast-span")] span: Span, }, /// Any data item Any { /// Span #[cfg(feature = "ast-span")] span: Span, }, } #[allow(clippy::cognitive_complexity)] impl<'a> fmt::Display for Type2<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Type2::IntValue { value, .. } => write!(f, "{}", value), Type2::UintValue { value, .. } => write!(f, "{}", value), Type2::FloatValue { value, .. } => write!(f, "{}", value), Type2::TextValue { value, .. } => write!(f, "\"{}\"", value), Type2::UTF8ByteString { value, .. } => write!( f, "'{}'", std::str::from_utf8(value).map_err(|_| fmt::Error)? ), Type2::B16ByteString { value, .. } => { write!(f, "{}", std::str::from_utf8(value).map_err(|_| fmt::Error)?) } Type2::B64ByteString { value, .. } => { write!(f, "{}", std::str::from_utf8(value).map_err(|_| fmt::Error)?) } Type2::Typename { ident, generic_args, .. } => { if let Some(args) = generic_args { return write!(f, "{}{}", ident, args); } write!(f, "{}", ident) } Type2::ParenthesizedType { #[cfg(feature = "ast-comments")] comments_before_type, pt, #[cfg(feature = "ast-comments")] comments_after_type, .. } => { let mut pt_str = String::from("("); #[cfg(feature = "ast-comments")] if let Some(comments) = comments_before_type { if comments.any_non_newline() { let _ = write!(pt_str, " {}\t", comments); pt_str.push_str(pt.to_string().trim_start()); } else { pt_str.push_str(&pt.to_string()); } } else { pt_str.push_str(&pt.to_string()); } #[cfg(not(feature = "ast-comments"))] { pt_str.push_str(&pt.to_string()); } #[cfg(feature = "ast-comments")] if let Some(comments) = comments_after_type { pt_str.push_str(&comments.to_string()); } pt_str.push(')'); write!(f, "{}", pt_str) } Type2::Map { #[cfg(feature = "ast-comments")] comments_before_group, group, #[cfg(feature = "ast-comments")] comments_after_group, .. } => { let mut t2_str = String::from("{"); #[cfg(feature = "ast-comments")] let mut non_newline_comments_before_group = false; #[cfg(feature = "ast-comments")] if let Some(comments) = comments_before_group { if comments.any_non_newline() { non_newline_comments_before_group = true; let _ = write!(t2_str, " {}\t", comments); t2_str.push_str(group.to_string().trim_start()); } else { t2_str.push_str(&group.to_string()); } } else { t2_str.push_str(&group.to_string()); } #[cfg(not(feature = "ast-comments"))] { t2_str.push_str(&group.to_string()); } #[cfg(feature = "ast-comments")] if let Some(comments) = comments_after_group { t2_str.push_str(&comments.to_string()); } #[cfg(feature = "ast-comments")] if non_newline_comments_before_group && !group .group_choices .iter() .any(|gc| gc.has_entries_with_trailing_comments()) { t2_str.push('\n'); } #[cfg(not(feature = "ast-comments"))] { t2_str.push('\n'); } t2_str.push('}'); write!(f, "{}", t2_str) } Type2::Array { #[cfg(feature = "ast-comments")] comments_before_group, group, #[cfg(feature = "ast-comments")] comments_after_group, .. } => { let mut t2_str = String::from("["); #[cfg(feature = "ast-comments")] let mut non_newline_comments_before_group = false; #[cfg(feature = "ast-comments")] if let Some(comments) = comments_before_group { if comments.any_non_newline() { non_newline_comments_before_group = true; for (idx, comment) in comments.0.iter().enumerate() { if *comment != "\n" { if idx == 0 { let _ = write!(t2_str, " ;{}", comment); } else { let _ = writeln!(t2_str, "\t;{}", comment); } } else { t2_str.push('\n'); } } let _ = write!(t2_str, "\t{}", group.to_string().trim_start()); } else { t2_str.push_str(&group.to_string()); } } else { t2_str.push_str(&group.to_string()); } #[cfg(not(feature = "ast-comments"))] { t2_str.push_str(&group.to_string()); } #[cfg(feature = "ast-comments")] if let Some(comments) = comments_after_group { t2_str.push_str(&comments.to_string()); } #[cfg(feature = "ast-comments")] if non_newline_comments_before_group && !group .group_choices .iter() .any(|gc| gc.has_entries_with_trailing_comments()) { t2_str.push('\n'); } #[cfg(not(feature = "ast-comments"))] t2_str.push('\n'); t2_str.push(']'); write!(f, "{}", t2_str) } Type2::Unwrap { #[cfg(feature = "ast-comments")] comments, ident, generic_args, .. } => { let mut t2_str = String::new(); #[cfg(feature = "ast-comments")] if let Some(comments) = comments { t2_str.push_str(&comments.to_string()); } if let Some(args) = generic_args { let _ = write!(t2_str, "{}{}", ident, args); } else { t2_str.push_str(&ident.to_string()); } write!(f, "{}", t2_str) } Type2::ChoiceFromInlineGroup { #[cfg(feature = "ast-comments")] comments, #[cfg(feature = "ast-comments")] comments_before_group, group, #[cfg(feature = "ast-comments")] comments_after_group, .. } => { let mut t2_str = String::from("&"); #[cfg(feature = "ast-comments")] if let Some(comments) = comments { t2_str.push_str(&comments.to_string()); } t2_str.push('('); #[cfg(feature = "ast-comments")] if let Some(comments) = comments_before_group { t2_str.push_str(&comments.to_string()); } t2_str.push_str(&group.to_string()); #[cfg(feature = "ast-comments")] if let Some(comments) = comments_after_group { t2_str.push_str(&comments.to_string()); } if group.group_choices.len() == 1 && group.group_choices[0].group_entries.len() == 1 { t2_str.push_str(" )"); } else { t2_str.push(')'); } write!(f, "{}", t2_str) } Type2::ChoiceFromGroup { #[cfg(feature = "ast-comments")] comments, ident, generic_args, .. } => { let mut t2_str = String::from("&"); #[cfg(feature = "ast-comments")] if let Some(comments) = comments { t2_str.push_str(&comments.to_string()); } if let Some(ga) = generic_args { let _ = write!(t2_str, "{}{}", ident, ga); } else { t2_str.push_str(&ident.to_string()); } write!(f, "{}", t2_str) } Type2::TaggedData { tag, #[cfg(feature = "ast-comments")] comments_before_type, t, #[cfg(feature = "ast-comments")] comments_after_type, .. } => { let mut t2_str = String::from("#6"); if let Some(tag_uint) = tag { let _ = write!(t2_str, ".{}", tag_uint); } t2_str.push('('); #[cfg(feature = "ast-comments")] if let Some(comments) = comments_before_type { if comments.any_non_newline() { let _ = write!(t2_str, " {}", comments); } } t2_str.push_str(&t.to_string()); #[cfg(feature = "ast-comments")] if let Some(comments) = comments_after_type { if comments.any_non_newline() { let _ = write!(t2_str, " {}", comments); } } t2_str.push(')'); write!(f, "{}", t2_str) } Type2::DataMajorType { mt, constraint, .. } => { if let Some(c) = constraint { return write!(f, "{}.{}", mt, c); } write!(f, "{}", mt) } Type2::Any { .. } => write!(f, "#"), } } } impl<'a> From> for Type2<'a> { fn from(rv: RangeValue<'a>) -> Self { #[cfg(feature = "ast-span")] let span = (0, 0, 0); match rv { RangeValue::IDENT(ident, socket) => Type2::Typename { ident: Identifier { ident, socket, #[cfg(feature = "ast-span")] span, }, generic_args: None, #[cfg(feature = "ast-span")] span, }, RangeValue::INT(value) => Type2::IntValue { value, #[cfg(feature = "ast-span")] span, }, RangeValue::UINT(value) => Type2::UintValue { value, #[cfg(feature = "ast-span")] span, }, RangeValue::FLOAT(value) => Type2::FloatValue { value, #[cfg(feature = "ast-span")] span, }, } } } impl<'a> From> for Type2<'a> { fn from(type1: Type1<'a>) -> Self { Type2::ParenthesizedType { pt: Type { type_choices: vec![TypeChoice { type1, #[cfg(feature = "ast-comments")] comments_after_type: None, #[cfg(feature = "ast-comments")] comments_before_type: None, }], #[cfg(feature = "ast-span")] span: Span::default(), }, #[cfg(feature = "ast-comments")] comments_after_type: None, #[cfg(feature = "ast-comments")] comments_before_type: None, #[cfg(feature = "ast-span")] span: Span::default(), } } } impl<'a> From for Type2<'a> { fn from(value: usize) -> Self { Type2::UintValue { value, #[cfg(feature = "ast-span")] span: Span::default(), } } } impl<'a> From for Type2<'a> { fn from(value: isize) -> Self { Type2::IntValue { value, #[cfg(feature = "ast-span")] span: Span::default(), } } } impl<'a> From for Type2<'a> { fn from(value: f64) -> Self { Type2::FloatValue { value, #[cfg(feature = "ast-span")] span: Span::default(), } } } impl<'a> From for Type2<'a> { fn from(value: String) -> Self { Type2::TextValue { value: value.into(), #[cfg(feature = "ast-span")] span: Span::default(), } } } // Convenience method for testing impl<'a> From<&'a str> for Type2<'a> { fn from(value: &'a str) -> Self { Type2::UTF8ByteString { value: value.as_bytes().into(), #[cfg(feature = "ast-span")] span: Span::default(), } } } impl<'a> From> for Type2<'a> { fn from(value: ByteValue<'a>) -> Self { match value { ByteValue::UTF8(value) => Type2::UTF8ByteString { value, #[cfg(feature = "ast-span")] span: Span::default(), }, ByteValue::B16(value) => Type2::B16ByteString { value, #[cfg(feature = "ast-span")] span: Span::default(), }, ByteValue::B64(value) => Type2::B64ByteString { value, #[cfg(feature = "ast-span")] span: Span::default(), }, } } } /// Retrieve `Type2` from token if it is a tag type in the standard prelude pub fn tag_from_token<'a>(token: &Token) -> Option> { match token { Token::TDATE => Some(Type2::TaggedData { tag: Some(0), t: type_from_token(Token::TSTR), #[cfg(feature = "ast-comments")] comments_after_type: None, #[cfg(feature = "ast-comments")] comments_before_type: None, #[cfg(feature = "ast-span")] span: Span::default(), }), Token::TIME => Some(Type2::TaggedData { tag: Some(1), t: type_from_token(Token::NUMBER), #[cfg(feature = "ast-comments")] comments_before_type: None, #[cfg(feature = "ast-comments")] comments_after_type: None, #[cfg(feature = "ast-span")] span: Span::default(), }), Token::BIGUINT => Some(Type2::TaggedData { tag: Some(2), t: type_from_token(Token::BSTR), #[cfg(feature = "ast-comments")] comments_before_type: None, #[cfg(feature = "ast-comments")] comments_after_type: None, #[cfg(feature = "ast-span")] span: Span::default(), }), Token::BIGNINT => Some(Type2::TaggedData { tag: Some(3), t: type_from_token(Token::BSTR), #[cfg(feature = "ast-comments")] comments_before_type: None, #[cfg(feature = "ast-comments")] comments_after_type: None, #[cfg(feature = "ast-span")] span: Span::default(), }), Token::DECFRAC => unimplemented!(), Token::BIGFLOAT => unimplemented!(), Token::EB64URL => Some(Type2::TaggedData { tag: Some(21), t: type_from_token(Token::ANY), #[cfg(feature = "ast-comments")] comments_before_type: None, #[cfg(feature = "ast-comments")] comments_after_type: None, #[cfg(feature = "ast-span")] span: Span::default(), }), Token::EB64LEGACY => Some(Type2::TaggedData { tag: Some(22), t: type_from_token(Token::ANY), #[cfg(feature = "ast-comments")] comments_before_type: None, #[cfg(feature = "ast-comments")] comments_after_type: None, #[cfg(feature = "ast-span")] span: Span::default(), }), Token::EB16 => Some(Type2::TaggedData { tag: Some(23), t: type_from_token(Token::ANY), #[cfg(feature = "ast-comments")] comments_before_type: None, #[cfg(feature = "ast-comments")] comments_after_type: None, #[cfg(feature = "ast-span")] span: Span::default(), }), Token::ENCODEDCBOR => Some(Type2::TaggedData { tag: Some(24), t: type_from_token(Token::BSTR), #[cfg(feature = "ast-comments")] comments_before_type: None, #[cfg(feature = "ast-comments")] comments_after_type: None, #[cfg(feature = "ast-span")] span: Span::default(), }), Token::URI => Some(Type2::TaggedData { tag: Some(32), t: type_from_token(Token::TSTR), #[cfg(feature = "ast-comments")] comments_before_type: None, #[cfg(feature = "ast-comments")] comments_after_type: None, #[cfg(feature = "ast-span")] span: Span::default(), }), Token::B64URL => Some(Type2::TaggedData { tag: Some(33), t: type_from_token(Token::TSTR), #[cfg(feature = "ast-comments")] comments_before_type: None, #[cfg(feature = "ast-comments")] comments_after_type: None, #[cfg(feature = "ast-span")] span: Span::default(), }), Token::B64LEGACY => Some(Type2::TaggedData { tag: Some(34), t: type_from_token(Token::TSTR), #[cfg(feature = "ast-comments")] comments_before_type: None, #[cfg(feature = "ast-comments")] comments_after_type: None, #[cfg(feature = "ast-span")] span: Span::default(), }), Token::REGEXP => Some(Type2::TaggedData { tag: Some(35), t: type_from_token(Token::TSTR), #[cfg(feature = "ast-comments")] comments_before_type: None, #[cfg(feature = "ast-comments")] comments_after_type: None, #[cfg(feature = "ast-span")] span: Span::default(), }), Token::MIMEMESSAGE => Some(Type2::TaggedData { tag: Some(36), t: type_from_token(Token::TSTR), #[cfg(feature = "ast-comments")] comments_before_type: None, #[cfg(feature = "ast-comments")] comments_after_type: None, #[cfg(feature = "ast-span")] span: Span::default(), }), Token::CBORANY => Some(Type2::TaggedData { tag: Some(55799), t: type_from_token(Token::ANY), #[cfg(feature = "ast-comments")] comments_before_type: None, #[cfg(feature = "ast-comments")] comments_after_type: None, #[cfg(feature = "ast-span")] span: Span::default(), }), _ => None, } } /// New `Type` from a given `token::Token` pub fn type_from_token(token: Token) -> Type { Type { type_choices: vec![TypeChoice { type1: Type1 { #[cfg(feature = "ast-comments")] comments_after_type: None, operator: None, #[cfg(feature = "ast-span")] span: Span::default(), type2: Type2::Typename { ident: Identifier::from(token), generic_args: None, #[cfg(feature = "ast-span")] span: Span::default(), }, }, #[cfg(feature = "ast-comments")] comments_after_type: None, #[cfg(feature = "ast-comments")] comments_before_type: None, }], #[cfg(feature = "ast-span")] span: Span::default(), } } /// Group choices /// /// ```abnf /// group = grpchoice * (S "//" S grpchoice) /// ``` #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, Clone, PartialEq)] pub struct Group<'a> { /// Group choices #[cfg_attr(target_arch = "wasm32", serde(borrow))] pub group_choices: Vec>, /// Span #[cfg(feature = "ast-span")] pub span: Span, } impl<'a> From> for Group<'a> { fn from(ge: GroupEntry<'a>) -> Self { Group { group_choices: vec![GroupChoice::new(vec![ge])], #[cfg(feature = "ast-span")] span: Span::default(), } } } impl<'a> fmt::Display for Group<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut group_str = String::new(); for (idx, gc) in self.group_choices.iter().enumerate() { let mut gc_str = gc.to_string(); #[cfg(feature = "ast-comments")] if self.group_choices.len() > 2 && gc.group_entries.len() <= 3 && !gc.has_entries_with_comments_before_comma() { gc_str = gc_str.replace('\n', ""); } #[cfg(not(feature = "ast-comments"))] if self.group_choices.len() > 2 && gc.group_entries.len() <= 3 { gc_str = gc_str.replace('\n', ""); } if idx == 0 { if self.group_choices.len() > 2 && gc.group_entries.len() <= 3 { group_str.push_str("\n\t"); if gc_str.ends_with(' ') { gc_str.pop(); } #[cfg(feature = "ast-comments")] if self.group_choices.len() > 2 && gc.has_entries_with_comments_before_comma() { gc_str = gc_str.replace('\n', "\n\t\t"); group_str.push_str(gc_str.trim()); } else { group_str.push_str(gc_str.trim_start()); } #[cfg(not(feature = "ast-comments"))] if self.group_choices.len() > 2 { gc_str = gc_str.replace('\n', "\n\t\t"); group_str.push_str(gc_str.trim()); } else { group_str.push_str(gc_str.trim_start()); } } else { group_str.push_str(&gc.to_string()); } if self.group_choices.len() > 2 && gc.group_entries.len() <= 3 { group_str.push('\n'); } continue; } gc_str = gc_str.trim().to_string(); #[cfg(feature = "ast-comments")] if self.group_choices.len() > 2 && gc.has_entries_with_comments_before_comma() { gc_str = gc_str.replace('\n', "\n\t\t"); } #[cfg(not(feature = "ast-comments"))] if self.group_choices.len() > 2 { gc_str = gc_str.replace('\n', "\n\t\t"); } if self.group_choices.len() <= 2 { let _ = write!(group_str, "// {} ", gc_str); } else { let _ = writeln!(group_str, "\t// {}", gc_str); } } write!(f, "{}", group_str) } } /// Group entries /// /// ```abnf /// grpchoice = *(grpent optcom) /// ``` /// /// If tuple is true, then entry is marked by a trailing comma #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, Clone, PartialEq)] pub struct GroupChoice<'a> { /// Group entries where the second item in the tuple indicates where or not a /// trailing comma is present #[cfg_attr(target_arch = "wasm32", serde(borrow))] pub group_entries: Vec<(GroupEntry<'a>, OptionalComma<'a>)>, /// Span #[cfg(feature = "ast-span")] pub span: Span, // No trailing comments since these will be captured by the S ["," S] matching // rule #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] pub comments_before_grpchoice: Option>, } impl<'a> GroupChoice<'a> { /// Create new group choice from group entries pub fn new(group_entries: Vec>) -> Self { GroupChoice { group_entries: group_entries .iter() .cloned() .map(|ge| (ge, OptionalComma::default())) .collect::>(), #[cfg(feature = "ast-span")] span: Span::default(), #[cfg(feature = "ast-comments")] comments_before_grpchoice: None, } } #[cfg(feature = "ast-comments")] fn has_entries_with_comments_before_comma(&self) -> bool { for ge in self.group_entries.iter() { if let GroupEntry::ValueMemberKey { ge: vmke, .. } = &ge.0 { if vmke .entry_type .type_choices .iter() .any(|tc| tc.type1.comments_after_type.is_some()) && ge.1.optional_comma { return true; } } if let GroupEntry::TypeGroupname { trailing_comments, .. } = &ge.0 { if trailing_comments.is_some() && ge.1.optional_comma { return true; } } } false } #[cfg(feature = "ast-comments")] fn has_entries_with_trailing_comments(&self) -> bool { self .group_entries .iter() .any(|ge| ge.0.has_trailing_comments() || ge.1.has_trailing_comments()) } } impl<'a> fmt::Display for GroupChoice<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut gc_str = String::new(); if self.group_entries.len() == 1 { let _ = write!( gc_str, " {}{}", self.group_entries[0].0, self.group_entries[0].1 ); #[cfg(feature = "ast-comments")] if !self.group_entries[0].1.has_trailing_comments() { gc_str.push(' '); } return write!(f, "{}", gc_str); } #[cfg(feature = "ast-comments")] if let Some(comments) = &self.comments_before_grpchoice { gc_str.push_str(&comments.to_string()); } // Keep track of group entries with comments written before commas for // proper formatting #[cfg(feature = "ast-comments")] let mut entries_with_comment_before_comma: Vec<(usize, bool)> = Vec::new(); #[cfg(feature = "ast-comments")] for (idx, ge) in self.group_entries.iter().enumerate() { if let GroupEntry::ValueMemberKey { trailing_comments: Some(comments), .. } = &ge.0 { if comments.any_non_newline() && ge.1.optional_comma { entries_with_comment_before_comma.push((idx, true)); continue; } } if let GroupEntry::TypeGroupname { trailing_comments: Some(comments), .. } = &ge.0 { if comments.any_non_newline() && ge.1.optional_comma { entries_with_comment_before_comma.push((idx, true)); continue; } } entries_with_comment_before_comma.push((idx, false)); } #[cfg(feature = "ast-comments")] let has_trailing_comments_after_comma = self .group_entries .iter() .any(|ge| ge.1.has_trailing_comments()); #[cfg(feature = "ast-comments")] if self.group_entries.len() > 3 || (self.group_entries.len() <= 3 && has_trailing_comments_after_comma) { gc_str.push('\n'); } else { gc_str.push(' '); } #[cfg(not(feature = "ast-comments"))] if self.group_entries.len() > 3 { gc_str.push('\n'); } else { gc_str.push(' '); } for (idx, ge) in self.group_entries.iter().enumerate() { #[cfg(feature = "ast-comments")] if self.group_entries.len() > 3 || (self.group_entries.len() <= 3 && has_trailing_comments_after_comma) { gc_str.push('\t'); } #[cfg(not(feature = "ast-comments"))] if self.group_entries.len() > 3 { gc_str.push('\t'); } #[cfg(feature = "ast-comments")] if entries_with_comment_before_comma.iter().any(|e| e.1) { if idx == 0 { if entries_with_comment_before_comma[idx].1 { let _ = write!(gc_str, "{}", ge.0); } else { let _ = writeln!(gc_str, "{}", ge.0); } } else if entries_with_comment_before_comma[idx].1 { let _ = write!(gc_str, ", {}", ge.0); } else if idx != self.group_entries.len() - 1 { let _ = writeln!(gc_str, ", {}", ge.0.to_string().trim_end()); } else { let _ = write!(gc_str, ", {}", ge.0.to_string().trim_end()); } } else { let _ = write!( gc_str, "{}{}", ge.0.to_string().trim_end(), ge.1.to_string().trim_end() ); // if idx != self.group_entries.len() - 1 { // gc_str.push_str(","); // } if self.group_entries.len() <= 3 && !has_trailing_comments_after_comma { gc_str.push(' '); } } #[cfg(not(feature = "ast-comments"))] { let _ = write!( gc_str, "{}{}", ge.0.to_string().trim_end(), ge.1.to_string().trim_end() ); // if idx != self.group_entries.len() - 1 { // gc_str.push_str(","); // } if self.group_entries.len() <= 3 { gc_str.push(' '); } } if idx == self.group_entries.len() - 1 && self.group_entries.len() > 3 { gc_str.push('\n'); break; } #[cfg(feature = "ast-comments")] if self.group_entries.len() > 3 && entries_with_comment_before_comma.iter().all(|e| !e.1) { gc_str.push('\n'); } #[cfg(not(feature = "ast-comments"))] if self.group_entries.len() > 3 { gc_str.push('\n'); } } write!(f, "{}", gc_str) } } /// Group entry /// /// ```abnf /// grpent = [occur S] [memberkey S] type /// / [occur S] groupname [genericarg] ; preempted by above /// / [occur S] "(" S group S ")" /// ``` #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, Clone, PartialEq)] pub enum GroupEntry<'a> { /// Value group entry type ValueMemberKey { /// Group entry #[cfg_attr(target_arch = "wasm32", serde(borrow))] ge: Box>, /// Span #[cfg(feature = "ast-span")] span: Span, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] leading_comments: Option>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] trailing_comments: Option>, }, /// Group entry from a named group or type TypeGroupname { /// Group entry #[cfg_attr(target_arch = "wasm32", serde(borrow))] ge: TypeGroupnameEntry<'a>, /// span #[cfg(feature = "ast-span")] span: Span, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] leading_comments: Option>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] trailing_comments: Option>, }, /// Parenthesized group with optional occurrence indicator InlineGroup { /// Occurrence occur: Option>, /// Group group: Group<'a>, /// Span #[cfg(feature = "ast-span")] span: Span, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments_before_group: Option>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments_after_group: Option>, }, } impl<'a> GroupEntry<'a> { #[cfg(feature = "ast-comments")] fn has_trailing_comments(&self) -> bool { matches!(self, GroupEntry::ValueMemberKey { trailing_comments: Some(comments), .. } | GroupEntry::TypeGroupname { trailing_comments: Some(comments), .. } if comments.any_non_newline() ) } } /// Optional comma #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct OptionalComma<'a> { /// Optional comma pub optional_comma: bool, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] pub trailing_comments: Option>, #[doc(hidden)] pub _a: PhantomData<&'a ()>, } impl<'a> fmt::Display for OptionalComma<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut optcomma_str = String::new(); if self.optional_comma { optcomma_str.push(','); } #[cfg(feature = "ast-comments")] if let Some(comments) = &self.trailing_comments { if comments.any_non_newline() { if let Some(comment) = comments.0.first() { if *comment != "\n" && self.optional_comma { optcomma_str.push(' '); } } for (idx, &comment) in comments.0.iter().enumerate() { if idx == 0 && comment != "\n" { let _ = writeln!(optcomma_str, ";{}", comment); } else if idx == 0 { optcomma_str.push_str(comment); } else if comment != "\n" { let _ = writeln!(optcomma_str, "\t;{}", comment); } else { let _ = write!(optcomma_str, "\t{}", comment); } } } } write!(f, "{}", optcomma_str) } } impl<'a> OptionalComma<'a> { #[cfg(feature = "ast-comments")] fn has_trailing_comments(&self) -> bool { if let Some(comments) = &self.trailing_comments { return comments.any_non_newline(); } false } } impl<'a> fmt::Display for GroupEntry<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { GroupEntry::ValueMemberKey { ge, #[cfg(feature = "ast-comments")] leading_comments, #[cfg(feature = "ast-comments")] trailing_comments, .. } => { let mut ge_str = String::new(); #[cfg(feature = "ast-comments")] if let Some(comments) = leading_comments { ge_str.push_str(&comments.to_string()); } ge_str.push_str(&ge.to_string()); #[cfg(feature = "ast-comments")] if let Some(comments) = trailing_comments { if comments.any_non_newline() { let _ = write!(ge_str, " {}", comments); } } write!(f, "{}", ge_str) } GroupEntry::TypeGroupname { ge, #[cfg(feature = "ast-comments")] leading_comments, #[cfg(feature = "ast-comments")] trailing_comments, .. } => { let mut ge_str = String::new(); #[cfg(feature = "ast-comments")] if let Some(comments) = leading_comments { ge_str.push_str(&comments.to_string()); } ge_str.push_str(&ge.to_string()); #[cfg(feature = "ast-comments")] if let Some(comments) = trailing_comments { if comments.any_non_newline() { let _ = write!(ge_str, " {}", comments); } } write!(f, "{}", ge_str) } GroupEntry::InlineGroup { occur, group, #[cfg(feature = "ast-comments")] comments_before_group, #[cfg(feature = "ast-comments")] comments_after_group, .. } => { let mut ge_str = String::new(); if let Some(o) = occur { let _ = write!(ge_str, "{} ", o.occur); #[cfg(feature = "ast-comments")] if let Some(comments) = &o.comments { ge_str.push_str(&comments.to_string()); } } ge_str.push('('); #[cfg(feature = "ast-comments")] let mut non_newline_comments_before_group = false; #[cfg(feature = "ast-comments")] if let Some(comments) = comments_before_group { if comments.any_non_newline() { non_newline_comments_before_group = true; let _ = write!(ge_str, " {}", comments); if !group .group_choices .iter() .all(|gc| gc.group_entries.is_empty()) { let _ = write!(ge_str, "\t{}", group.to_string().trim_start()); } } else { ge_str.push_str(&group.to_string()); } } else { ge_str.push_str(&group.to_string()); } #[cfg(not(feature = "ast-comments"))] { ge_str.push_str(&group.to_string()); } #[cfg(feature = "ast-comments")] if let Some(comments) = comments_after_group { ge_str.push_str(&comments.to_string()); } #[cfg(feature = "ast-comments")] if non_newline_comments_before_group && !group .group_choices .iter() .any(|gc| gc.has_entries_with_trailing_comments()) { ge_str.push('\n'); } #[cfg(not(feature = "ast-comments"))] ge_str.push('\n'); ge_str.push(')'); write!(f, "{}", ge_str) } } } } /// Occurrence indicator #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, Clone, PartialEq, Eq)] pub struct Occurrence<'a> { /// Occurrence indicator pub occur: Occur, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] pub comments: Option>, #[doc(hidden)] pub _a: PhantomData<&'a ()>, } impl<'a> fmt::Display for Occurrence<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { #[cfg(feature = "ast-comments")] let mut occur_str = self.occur.to_string(); #[cfg(not(feature = "ast-comments"))] let occur_str = self.occur.to_string(); #[cfg(feature = "ast-comments")] if let Some(comments) = &self.comments { occur_str.push_str(&comments.to_string()); } write!(f, "{}", occur_str) } } /// Value group entry type with optional occurrence indicator and optional /// member key /// /// ```abnf /// [occur S] [memberkey S] type /// ``` #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, Clone, PartialEq)] pub struct ValueMemberKeyEntry<'a> { /// Optional occurrence indicator pub occur: Option>, /// Optional member key pub member_key: Option>, /// Entry type #[cfg_attr(target_arch = "wasm32", serde(borrow))] pub entry_type: Type<'a>, } impl<'a> fmt::Display for ValueMemberKeyEntry<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut vmke_str = String::new(); if let Some(o) = &self.occur { let _ = write!(vmke_str, "{} ", o); } if let Some(mk) = &self.member_key { let _ = write!(vmke_str, "{} ", mk); } vmke_str.push_str(&self.entry_type.to_string()); write!(f, "{}", vmke_str) } } /// Group entry from a named type or group #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, Clone, PartialEq)] pub struct TypeGroupnameEntry<'a> { /// Optional occurrence indicator pub occur: Option>, /// Type or group name identifier #[cfg_attr(target_arch = "wasm32", serde(borrow))] pub name: Identifier<'a>, /// Optional generic arguments pub generic_args: Option>, } impl<'a> fmt::Display for TypeGroupnameEntry<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut tge_str = String::new(); if let Some(o) = &self.occur { let _ = write!(tge_str, "{} ", o); } tge_str.push_str(&self.name.to_string()); if let Some(ga) = &self.generic_args { tge_str.push_str(&ga.to_string()); } write!(f, "{}", tge_str) } } /// Member key /// ```abnf /// memberkey = type1 S ["^" S] "=>" /// / bareword S ":" /// / value S ":" /// ``` #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, Clone, PartialEq)] pub enum MemberKey<'a> { /// Type expression Type1 { /// Type1 #[cfg_attr(target_arch = "wasm32", serde(borrow))] t1: Box>, /// Is cut indicator present is_cut: bool, /// Span #[cfg(feature = "ast-span")] span: Span, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments_before_cut: Option>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments_after_cut: Option>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments_after_arrowmap: Option>, }, /// Bareword string type Bareword { /// Identifier #[cfg_attr(target_arch = "wasm32", serde(borrow))] ident: Identifier<'a>, /// Span #[cfg(feature = "ast-span")] span: Span, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments: Option>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments_after_colon: Option>, }, /// Value type Value { /// Value #[cfg_attr(target_arch = "wasm32", serde(borrow))] value: Value<'a>, /// Span #[cfg(feature = "ast-span")] span: Span, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments: Option>, #[cfg(feature = "ast-comments")] #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] comments_after_colon: Option>, }, #[cfg_attr(target_arch = "wasm32", serde(skip))] #[doc(hidden)] NonMemberKey { non_member_key: NonMemberKey<'a>, #[cfg(feature = "ast-comments")] comments_before_type_or_group: Option>, #[cfg(feature = "ast-comments")] comments_after_type_or_group: Option>, }, } #[derive(Debug, Clone, PartialEq)] #[doc(hidden)] pub enum NonMemberKey<'a> { Group(Group<'a>), Type(Type<'a>), } impl<'a> fmt::Display for MemberKey<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { MemberKey::Type1 { t1, #[cfg(feature = "ast-comments")] comments_before_cut, is_cut, #[cfg(feature = "ast-comments")] comments_after_cut, #[cfg(feature = "ast-comments")] comments_after_arrowmap, .. } => { let mut mk_str = format!("{} ", t1); #[cfg(feature = "ast-comments")] if let Some(comments) = comments_before_cut { if comments.any_non_newline() { mk_str.push_str(&comments.to_string()); } } if *is_cut { mk_str.push_str("^ "); } #[cfg(feature = "ast-comments")] if let Some(comments) = comments_after_cut { if comments.any_non_newline() { mk_str.push_str(&comments.to_string()); } } mk_str.push_str("=>"); #[cfg(feature = "ast-comments")] if let Some(comments) = comments_after_arrowmap { if comments.any_non_newline() { let _ = write!(mk_str, " {}", comments); } } write!(f, "{}", mk_str) } MemberKey::Bareword { ident, #[cfg(feature = "ast-comments")] comments, #[cfg(feature = "ast-comments")] comments_after_colon, .. } => { let mut mk_str = format!("{}", ident); #[cfg(feature = "ast-comments")] if let Some(comments) = comments { if comments.any_non_newline() { let _ = write!(mk_str, " {}", comments); } } mk_str.push(':'); #[cfg(feature = "ast-comments")] if let Some(comments) = comments_after_colon { if comments.any_non_newline() { let _ = write!(mk_str, " {}", comments); } } write!(f, "{}", mk_str) } MemberKey::Value { value, #[cfg(feature = "ast-comments")] comments, #[cfg(feature = "ast-comments")] comments_after_colon, .. } => { let mut mk_str = format!("{}", value); #[cfg(feature = "ast-comments")] if let Some(comments) = comments { if comments.any_non_newline() { let _ = write!(mk_str, " {}", comments); } } mk_str.push(':'); #[cfg(feature = "ast-comments")] if let Some(comments) = comments_after_colon { if comments.any_non_newline() { let _ = write!(mk_str, " {}", comments); } } write!(f, "{}", mk_str) } MemberKey::NonMemberKey { non_member_key: NonMemberKey::Group(g), #[cfg(feature = "ast-comments")] comments_before_type_or_group, #[cfg(feature = "ast-comments")] comments_after_type_or_group, } => { let mut nmk_str = String::new(); #[cfg(feature = "ast-comments")] if let Some(comments) = comments_before_type_or_group { nmk_str.push_str(&comments.to_string()); } nmk_str.push_str(&g.to_string()); #[cfg(feature = "ast-comments")] if let Some(comments) = comments_after_type_or_group { nmk_str.push_str(&comments.to_string()); } write!(f, "{}", nmk_str) } MemberKey::NonMemberKey { non_member_key: NonMemberKey::Type(t), #[cfg(feature = "ast-comments")] comments_before_type_or_group, #[cfg(feature = "ast-comments")] comments_after_type_or_group, } => { let mut nmk_str = String::new(); #[cfg(feature = "ast-comments")] if let Some(comments) = comments_before_type_or_group { nmk_str.push_str(&comments.to_string()); } nmk_str.push_str(&t.to_string()); #[cfg(feature = "ast-comments")] if let Some(comments) = comments_after_type_or_group { nmk_str.push_str(&comments.to_string()); } write!(f, "{}", nmk_str) } } } } /// Occurrence indicator /// ```abnf /// occur = [uint] "*" [uint] /// / "+" /// / "?" /// ``` #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub enum Occur { /// Occurrence indicator in the form n*m, where n is an optional lower limit /// and m is an optional upper limit Exact { /// Lower bound lower: Option, /// Upper bound upper: Option, /// Span #[cfg(feature = "ast-span")] span: Span, }, /// Occurrence indicator in the form *, allowing zero or more occurrences ZeroOrMore { /// Span #[cfg(feature = "ast-span")] span: Span, }, /// Occurrence indicator in the form +, allowing one or more occurrences OneOrMore { /// Span #[cfg(feature = "ast-span")] span: Span, }, /// Occurrence indicator in the form ?, allowing an optional occurrence Optional { /// Span #[cfg(feature = "ast-span")] span: Span, }, } impl fmt::Display for Occur { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Occur::ZeroOrMore { .. } => write!(f, "*"), Occur::Exact { lower, upper, .. } => { if let Some(li) = lower { if let Some(ui) = upper { return write!(f, "{}*{}", li, ui); } return write!(f, "{}*", li); } if let Some(ui) = upper { return write!(f, "*{}", ui); } write!(f, "*") } Occur::OneOrMore { .. } => write!(f, "+"), Occur::Optional { .. } => write!(f, "?"), } } } #[cfg(test)] #[allow(unused_imports)] #[cfg(feature = "ast-comments")] mod tests { use super::*; use pretty_assertions::assert_eq; #[test] fn verify_groupentry_output() { assert_eq!( GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur: None, name: Identifier::from("entry1"), generic_args: None, }, leading_comments: None, trailing_comments: None, #[cfg(feature = "ast-span")] span: (0, 0, 0), } .to_string(), "entry1".to_string() ) } #[test] fn verify_group_output() { assert_eq!( Group { group_choices: vec![GroupChoice { group_entries: vec![ ( GroupEntry::ValueMemberKey { ge: Box::from(ValueMemberKeyEntry { occur: None, member_key: Some(MemberKey::Bareword { ident: "key1".into(), comments: None, comments_after_colon: None, #[cfg(feature = "ast-span")] span: (0, 0, 0), }), entry_type: Type { type_choices: vec![TypeChoice { type1: Type1 { type2: Type2::TextValue { value: "value1".into(), #[cfg(feature = "ast-span")] span: (0, 0, 0), }, operator: None, comments_after_type: None, #[cfg(feature = "ast-span")] span: (0, 0, 0), }, comments_before_type: None, comments_after_type: None, }], #[cfg(feature = "ast-span")] span: (0, 0, 0), }, }), leading_comments: None, trailing_comments: None, #[cfg(feature = "ast-span")] span: (0, 0, 0), }, OptionalComma { optional_comma: true, trailing_comments: None, _a: PhantomData, } ), ( GroupEntry::ValueMemberKey { ge: Box::from(ValueMemberKeyEntry { occur: None, member_key: Some(MemberKey::Bareword { ident: "key2".into(), comments: None, comments_after_colon: None, #[cfg(feature = "ast-span")] span: (0, 0, 0), }), entry_type: Type { type_choices: vec![TypeChoice { type1: Type1 { type2: Type2::TextValue { value: "value2".into(), #[cfg(feature = "ast-span")] span: (0, 0, 0), }, operator: None, comments_after_type: None, #[cfg(feature = "ast-span")] span: (0, 0, 0), }, comments_before_type: None, comments_after_type: None, }], #[cfg(feature = "ast-span")] span: (0, 0, 0), }, }), leading_comments: None, trailing_comments: None, #[cfg(feature = "ast-span")] span: (0, 0, 0), }, OptionalComma { optional_comma: true, trailing_comments: None, _a: PhantomData, } ), ], comments_before_grpchoice: None, #[cfg(feature = "ast-span")] span: (0, 0, 0), }], #[cfg(feature = "ast-span")] span: (0, 0, 0), } .to_string(), " key1: \"value1\", key2: \"value2\", ".to_string() ) } } cddl-0.9.4/src/ast/parent.rs000064400000000000000000000674761046102023000140050ustar 00000000000000#![cfg(feature = "ast-parent")] use crate::{ ast::*, token::{ByteValue, ControlOperator, Value}, visitor::{self, *}, }; use std::{borrow::Cow, fmt}; /// Parent visitor result pub type Result = std::result::Result; /// Parent visitor error #[derive(Debug)] pub enum Error { /// Tree overwrite error Overwrite, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::Overwrite => write!(f, "attempt to overwrite existing tree node"), } } } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } } /// Parent trait retrieving the implemented type's parent pub trait Parent<'a, 'b: 'a, T> { /// Returns the parent for the AST type fn parent(&'a self, parent_visitor: &'b ParentVisitor<'a, 'b>) -> Option<&T>; } macro_rules! impl_parent { ($($parent:ty => ([$($child:ty),+], $p:path)),* $(,)?) => { $( $( impl<'a, 'b: 'a> Parent<'a, 'b, $parent> for $child { fn parent(&'a self, parent_visitor: &'b ParentVisitor<'a, 'b>) -> Option<&$parent> { if let Some($p(value)) = CDDLType::from(self).parent(parent_visitor) { return Some(value); } None } } )* )* }; } impl_parent! { CDDL<'a> => ([Rule<'a>], CDDLType::CDDL), Rule<'a> => ([GroupRule<'a>, TypeRule<'a>], CDDLType::Rule), TypeRule<'a> => ([Identifier<'a>, GenericParams<'a>, Type<'a>], CDDLType::TypeRule), GroupRule<'a> => ([Identifier<'a>, GenericParams<'a>, GroupEntry<'a>], CDDLType::GroupRule), Type<'a> => ([TypeChoice<'a>], CDDLType::Type), TypeChoice<'a> => ([Type1<'a>], CDDLType::TypeChoice), Type1<'a> => ([Operator<'a>, Type2<'a>], CDDLType::Type1), Operator<'a> => ([Type2<'a>], CDDLType::Operator), RangeCtlOp => ([ControlOperator], CDDLType::RangeCtlOp), Type2<'a> => ([Identifier<'a>, GenericArgs<'a>, Type<'a>, Group<'a>], CDDLType::Type2), Group<'a> => ([GroupChoice<'a>, Occurrence<'a>], CDDLType::Group), GroupChoice<'a> => ([GroupEntry<'a>], CDDLType::GroupChoice), GroupEntry<'a> => ([ValueMemberKeyEntry<'a>, TypeGroupnameEntry<'a>, Occurrence<'a>, Group<'a>], CDDLType::GroupEntry), ValueMemberKeyEntry<'a> => ([Occurrence<'a>, MemberKey<'a>, Type<'a>], CDDLType::ValueMemberKeyEntry), TypeGroupnameEntry<'a> => ([Occurrence<'a>, GenericArgs<'a>, Identifier<'a>], CDDLType::TypeGroupnameEntry), MemberKey<'a> => ([Type1<'a>, Identifier<'a>, NonMemberKey<'a>], CDDLType::MemberKey), GenericArgs<'a> => ([GenericArg<'a>], CDDLType::GenericArgs), GenericArg<'a> => ([Type1<'a>], CDDLType::GenericArg), GenericParams<'a> => ([GenericParam<'a>], CDDLType::GenericParams), GenericParam<'a> => ([Identifier<'a>], CDDLType::GenericParam), NonMemberKey<'a> => ([Group<'a>, Type<'a>], CDDLType::NonMemberKey), } impl<'a, 'b: 'a> Parent<'a, 'b, ()> for CDDL<'a> { fn parent(&'a self, _parent_visitor: &'b ParentVisitor<'a, 'b>) -> Option<&()> { None } } impl<'a, 'b: 'a> Parent<'a, 'b, Type2<'a>> for Value<'a> { fn parent(&'a self, parent_visitor: &'b ParentVisitor<'a, 'b>) -> Option<&Type2<'a>> { if let Some(CDDLType::Type2(value)) = CDDLType::from(self.to_owned()).parent(parent_visitor) { return Some(value); } None } } impl<'a, 'b: 'a> Parent<'a, 'b, MemberKey<'a>> for Value<'a> { fn parent(&'a self, parent_visitor: &'b ParentVisitor<'a, 'b>) -> Option<&MemberKey<'a>> { if let Some(CDDLType::MemberKey(value)) = CDDLType::from(self.to_owned()).parent(parent_visitor) { return Some(value); } None } } impl<'a, 'b: 'a> Parent<'a, 'b, Occurrence<'a>> for Occur { fn parent(&self, parent_visitor: &'b ParentVisitor<'a, 'b>) -> Option<&Occurrence<'a>> { if let Some(CDDLType::Occurrence(value)) = CDDLType::from(*self).parent(parent_visitor) { return Some(value); } None } } #[derive(Debug, Default, Clone)] struct ArenaTree<'a, 'b: 'a> { arena: Vec>, } impl<'a, 'b: 'a> ArenaTree<'a, 'b> { fn node(&mut self, val: CDDLType<'a, 'b>) -> usize { for node in self.arena.iter() { if node.val == val { return node.idx; } } let idx = self.arena.len(); self.arena.push(Node::new(idx, val)); idx } } #[derive(Debug, Clone)] struct Node<'a, 'b: 'a> { idx: usize, val: CDDLType<'a, 'b>, parent: Option, children: Vec, } impl<'a, 'b: 'a> Node<'a, 'b> { fn new(idx: usize, val: CDDLType<'a, 'b>) -> Self { Self { idx, val, parent: None, children: vec![], } } } /// Parent visitor type // #[derive(Clone)] pub struct ParentVisitor<'a, 'b: 'a> { arena_tree: ArenaTree<'a, 'b>, } impl<'a, 'b: 'a> ParentVisitor<'a, 'b> { /// Creates a new parent visitor given a CDDL reference pub fn new(cddl: &'a CDDL<'a>) -> Result { let mut p = ParentVisitor { arena_tree: ArenaTree { arena: Vec::default(), }, }; p.visit_cddl(cddl)?; Ok(p) } } impl<'a, 'b: 'a> ParentVisitor<'a, 'b> { fn insert(&mut self, parent: usize, child: usize) -> Result<()> { if self.arena_tree.arena[child].parent.is_none() { self.arena_tree.arena[child].parent = Some(parent); } self.arena_tree.arena[parent].children.push(child); Ok(()) } } impl<'a, 'b: 'a> CDDLType<'a, 'b> { pub fn parent(&self, visitor: &'b ParentVisitor<'a, 'b>) -> Option<&'b CDDLType<'a, 'b>> { for node in visitor.arena_tree.arena.iter() { if self == &node.val { if let Some(parent_idx) = node.parent { if let Some(parent) = visitor.arena_tree.arena.get(parent_idx) { return Some(&parent.val); } } } } None } } impl<'a, 'b: 'a> Visitor<'a, 'b, Error> for ParentVisitor<'a, 'b> { fn visit_cddl(&mut self, cddl: &'b CDDL<'a>) -> visitor::Result { let parent = self.arena_tree.node(CDDLType::CDDL(cddl)); for rule in cddl.rules.iter() { let child = self.arena_tree.node(CDDLType::Rule(rule)); self.insert(parent, child)?; self.visit_rule(rule)?; } Ok(()) } fn visit_rule(&mut self, rule: &'b Rule<'a>) -> visitor::Result { let parent = self.arena_tree.node(CDDLType::Rule(rule)); match rule { Rule::Group { rule, .. } => { let child = self.arena_tree.node(CDDLType::GroupRule(rule)); self.insert(parent, child)?; self.visit_group_rule(rule)?; } Rule::Type { rule, .. } => { let child = self.arena_tree.node(CDDLType::TypeRule(rule)); self.insert(parent, child)?; self.visit_type_rule(rule)?; } } Ok(()) } fn visit_type_rule(&mut self, tr: &'b TypeRule<'a>) -> visitor::Result { let parent = self.arena_tree.node(CDDLType::TypeRule(tr)); let child = self.arena_tree.node(CDDLType::Identifier(&tr.name)); self.insert(parent, child)?; if let Some(params) = &tr.generic_params { let child = self.arena_tree.node(CDDLType::GenericParams(params)); self.insert(parent, child)?; self.visit_generic_params(params)?; } let child = self.arena_tree.node(CDDLType::Type(&tr.value)); self.insert(parent, child)?; self.visit_type(&tr.value) } fn visit_group_rule(&mut self, gr: &'b GroupRule<'a>) -> visitor::Result { let parent = self.arena_tree.node(CDDLType::GroupRule(gr)); let child = self.arena_tree.node(CDDLType::Identifier(&gr.name)); self.insert(parent, child)?; self.visit_identifier(&gr.name)?; if let Some(params) = &gr.generic_params { let child = self.arena_tree.node(CDDLType::GenericParams(params)); self.insert(parent, child)?; self.visit_generic_params(params)?; } let child = self.arena_tree.node(CDDLType::GroupEntry(&gr.entry)); self.insert(parent, child)?; self.visit_group_entry(&gr.entry) } fn visit_type(&mut self, t: &'b Type<'a>) -> visitor::Result { let parent = self.arena_tree.node(CDDLType::Type(t)); for tc in t.type_choices.iter() { let child = self.arena_tree.node(CDDLType::TypeChoice(tc)); self.insert(parent, child)?; self.visit_type_choice(tc)?; } Ok(()) } fn visit_type_choice(&mut self, tc: &'a TypeChoice<'a>) -> visitor::Result { let parent = self.arena_tree.node(CDDLType::TypeChoice(tc)); let child = self.arena_tree.node(CDDLType::Type1(&tc.type1)); self.insert(parent, child)?; self.visit_type1(&tc.type1) } fn visit_type1(&mut self, t1: &'b Type1<'a>) -> visitor::Result { let parent = self.arena_tree.node(CDDLType::Type1(t1)); if let Some(operator) = &t1.operator { let child = self.arena_tree.node(CDDLType::Operator(operator)); self.insert(parent, child)?; self.visit_operator(t1, operator)?; } let child = self.arena_tree.node(CDDLType::Type2(&t1.type2)); self.insert(parent, child)?; self.visit_type2(&t1.type2) } fn visit_operator( &mut self, target: &'b Type1<'a>, o: &'b Operator<'a>, ) -> visitor::Result { let parent = self.arena_tree.node(CDDLType::Operator(o)); let child = self.arena_tree.node(CDDLType::Type2(&o.type2)); self.insert(parent, child)?; let child = self.arena_tree.node(CDDLType::RangeCtlOp(&o.operator)); self.insert(parent, child)?; self.visit_rangectlop(&o.operator, target, &o.type2) } fn visit_rangectlop( &mut self, op: &'b RangeCtlOp, target: &'b Type1<'a>, controller: &'b Type2<'a>, ) -> visitor::Result { match op { RangeCtlOp::RangeOp { is_inclusive, .. } => { self.visit_range(&target.type2, controller, *is_inclusive) } RangeCtlOp::CtlOp { ctrl, .. } => { let parent = self.arena_tree.node(CDDLType::RangeCtlOp(op)); let child = self.arena_tree.node(CDDLType::ControlOperator(ctrl)); self.insert(parent, child)?; self.visit_control_operator(&target.type2, *ctrl, controller) } } } fn visit_type2(&mut self, t2: &'b Type2<'a>) -> visitor::Result { let parent = self.arena_tree.node(CDDLType::Type2(t2)); match t2 { Type2::IntValue { value, .. } => { let child = self.arena_tree.node(CDDLType::Value(Value::INT(*value))); self.insert(parent, child)?; } Type2::UintValue { value, .. } => { let child = self.arena_tree.node(CDDLType::Value(Value::UINT(*value))); self.insert(parent, child)?; } Type2::FloatValue { value, .. } => { let child = self.arena_tree.node(CDDLType::Value(Value::FLOAT(*value))); self.insert(parent, child)?; } Type2::TextValue { value, .. } => { let child = self .arena_tree .node(CDDLType::Value(Value::TEXT(Cow::Borrowed(value)))); self.insert(parent, child)?; } Type2::UTF8ByteString { value, .. } => { let child = self .arena_tree .node(CDDLType::Value(Value::BYTE(ByteValue::UTF8( Cow::Borrowed(value), )))); self.insert(parent, child)?; } Type2::B16ByteString { value, .. } => { let child = self .arena_tree .node(CDDLType::Value(Value::BYTE(ByteValue::B16(Cow::Borrowed( value, ))))); self.insert(parent, child)?; } Type2::B64ByteString { value, .. } => { let child = self .arena_tree .node(CDDLType::Value(Value::BYTE(ByteValue::B64(Cow::Borrowed( value, ))))); self.insert(parent, child)?; } Type2::Typename { ident, generic_args, .. } => { let child = self.arena_tree.node(CDDLType::Identifier(ident)); self.insert(parent, child)?; if let Some(generic_args) = generic_args { let child = self.arena_tree.node(CDDLType::GenericArgs(generic_args)); self.insert(parent, child)?; self.visit_generic_args(generic_args)?; } } Type2::ParenthesizedType { pt, .. } => { let child = self.arena_tree.node(CDDLType::Type(pt)); self.insert(parent, child)?; self.visit_type(pt)?; } Type2::Map { group, .. } => { let child = self.arena_tree.node(CDDLType::Group(group)); self.insert(parent, child)?; self.visit_group(group)?; } Type2::Array { group, .. } => { let child = self.arena_tree.node(CDDLType::Group(group)); self.insert(parent, child)?; self.visit_group(group)?; } Type2::Unwrap { ident, .. } => { let child = self.arena_tree.node(CDDLType::Identifier(ident)); self.insert(parent, child)?; self.visit_identifier(ident)?; } Type2::ChoiceFromInlineGroup { group, .. } => { let child = self.arena_tree.node(CDDLType::Group(group)); self.insert(parent, child)?; self.visit_group(group)?; } Type2::ChoiceFromGroup { ident, generic_args, .. } => { let child = self.arena_tree.node(CDDLType::Identifier(ident)); self.insert(parent, child)?; if let Some(generic_args) = generic_args { let child = self.arena_tree.node(CDDLType::GenericArgs(generic_args)); self.insert(parent, child)?; self.visit_generic_args(generic_args)?; } } Type2::TaggedData { t, .. } => { let child = self.arena_tree.node(CDDLType::Type(t)); self.insert(parent, child)?; self.visit_type(t)?; } _ => (), } Ok(()) } fn visit_group(&mut self, g: &'b Group<'a>) -> visitor::Result { let parent = self.arena_tree.node(CDDLType::Group(g)); for gc in g.group_choices.iter() { let child = self.arena_tree.node(CDDLType::GroupChoice(gc)); self.insert(parent, child)?; self.visit_group_choice(gc)?; } Ok(()) } fn visit_group_choice(&mut self, gc: &'b GroupChoice<'a>) -> visitor::Result { let parent = self.arena_tree.node(CDDLType::GroupChoice(gc)); for (ge, _) in gc.group_entries.iter() { let child = self.arena_tree.node(CDDLType::GroupEntry(ge)); self.insert(parent, child)?; self.visit_group_entry(ge)?; } Ok(()) } fn visit_group_entry(&mut self, entry: &'b GroupEntry<'a>) -> visitor::Result { let parent = self.arena_tree.node(CDDLType::GroupEntry(entry)); match entry { GroupEntry::ValueMemberKey { ge, .. } => { let child = self.arena_tree.node(CDDLType::ValueMemberKeyEntry(ge)); self.insert(parent, child)?; self.visit_value_member_key_entry(ge)?; } GroupEntry::TypeGroupname { ge, .. } => { let child = self.arena_tree.node(CDDLType::TypeGroupnameEntry(ge)); self.insert(parent, child)?; self.visit_type_groupname_entry(ge)?; } GroupEntry::InlineGroup { occur, group, .. } => { if let Some(occur) = occur { let child = self.arena_tree.node(CDDLType::Occurrence(occur)); self.insert(parent, child)?; self.visit_occurrence(occur)?; } let child = self.arena_tree.node(CDDLType::Group(group)); self.insert(parent, child)?; self.visit_group(group)?; } } Ok(()) } fn visit_value_member_key_entry( &mut self, entry: &'b ValueMemberKeyEntry<'a>, ) -> visitor::Result { let parent = self.arena_tree.node(CDDLType::ValueMemberKeyEntry(entry)); if let Some(occur) = &entry.occur { let child = self.arena_tree.node(CDDLType::Occurrence(occur)); self.insert(parent, child)?; self.visit_occurrence(occur)?; } if let Some(mk) = &entry.member_key { let child = self.arena_tree.node(CDDLType::MemberKey(mk)); self.insert(parent, child)?; self.visit_memberkey(mk)?; } let child = self.arena_tree.node(CDDLType::Type(&entry.entry_type)); self.insert(parent, child)?; self.visit_type(&entry.entry_type) } fn visit_type_groupname_entry( &mut self, entry: &'b TypeGroupnameEntry<'a>, ) -> visitor::Result { let parent = self.arena_tree.node(CDDLType::TypeGroupnameEntry(entry)); if let Some(o) = &entry.occur { let child = self.arena_tree.node(CDDLType::Occurrence(o)); self.insert(parent, child)?; self.visit_occurrence(o)?; } if let Some(ga) = &entry.generic_args { let child = self.arena_tree.node(CDDLType::GenericArgs(ga)); self.insert(parent, child)?; self.visit_generic_args(ga)?; } let child = self.arena_tree.node(CDDLType::Identifier(&entry.name)); self.insert(parent, child)?; self.visit_identifier(&entry.name) } fn visit_inline_group_entry( &mut self, occur: Option<&'b Occurrence<'a>>, g: &'b Group<'a>, ) -> visitor::Result { let parent = self.arena_tree.node(CDDLType::Group(g)); if let Some(o) = occur { self.visit_occurrence(o)?; } for gc in g.group_choices.iter() { let child = self.arena_tree.node(CDDLType::GroupChoice(gc)); self.insert(parent, child)?; } self.visit_group(g) } fn visit_occurrence(&mut self, o: &'b Occurrence<'a>) -> visitor::Result { let parent = self.arena_tree.node(CDDLType::Occurrence(o)); let child = self.arena_tree.node(CDDLType::Occur(o.occur)); self.insert(parent, child)?; Ok(()) } fn visit_memberkey(&mut self, mk: &'b MemberKey<'a>) -> visitor::Result { let parent = self.arena_tree.node(CDDLType::MemberKey(mk)); match mk { MemberKey::Type1 { t1, .. } => { let child = self.arena_tree.node(CDDLType::Type1(t1)); self.insert(parent, child)?; self.visit_type1(t1) } MemberKey::Bareword { ident, .. } => { let child = self.arena_tree.node(CDDLType::Identifier(ident)); self.insert(parent, child)?; self.visit_identifier(ident) } MemberKey::Value { value, .. } => { let child = self.arena_tree.node(CDDLType::Value(value.to_owned())); self.insert(parent, child)?; self.visit_value(value) } MemberKey::NonMemberKey { non_member_key, .. } => { let child = self.arena_tree.node(CDDLType::NonMemberKey(non_member_key)); self.insert(parent, child)?; self.visit_nonmemberkey(non_member_key) } } } fn visit_generic_args(&mut self, args: &'b GenericArgs<'a>) -> visitor::Result { let parent = self.arena_tree.node(CDDLType::GenericArgs(args)); for arg in args.args.iter() { let child = self.arena_tree.node(CDDLType::GenericArg(arg)); self.insert(parent, child)?; self.visit_generic_arg(arg)?; } Ok(()) } fn visit_generic_arg(&mut self, arg: &'b GenericArg<'a>) -> visitor::Result { let parent = self.arena_tree.node(CDDLType::GenericArg(arg)); let child = self.arena_tree.node(CDDLType::Type1(&arg.arg)); self.insert(parent, child)?; self.visit_type1(&arg.arg) } fn visit_generic_params(&mut self, params: &'b GenericParams<'a>) -> visitor::Result { let parent = self.arena_tree.node(CDDLType::GenericParams(params)); for param in params.params.iter() { let child = self.arena_tree.node(CDDLType::GenericParam(param)); self.insert(parent, child)?; self.visit_generic_param(param)?; } Ok(()) } fn visit_generic_param(&mut self, param: &'b GenericParam<'a>) -> visitor::Result { let parent = self.arena_tree.node(CDDLType::GenericParam(param)); let child = self.arena_tree.node(CDDLType::Identifier(¶m.param)); self.insert(parent, child)?; self.visit_identifier(¶m.param) } fn visit_nonmemberkey(&mut self, nmk: &'b NonMemberKey<'a>) -> visitor::Result { let parent = self.arena_tree.node(CDDLType::NonMemberKey(nmk)); match nmk { NonMemberKey::Group(group) => { let child = self.arena_tree.node(CDDLType::Group(group)); self.insert(parent, child)?; self.visit_group(group) } NonMemberKey::Type(t) => { let child = self.arena_tree.node(CDDLType::Type(t)); self.insert(parent, child)?; self.visit_type(t) } } } } #[cfg(test)] #[cfg(not(target_arch = "wasm32"))] mod tests { #![allow(unused_imports)] use crate::cddl_from_str; use std::borrow::Borrow; use super::*; #[test] fn rule_parent_is_cddl() -> Result<()> { let cddl = cddl_from_str(r#"a = "myrule""#, true).unwrap(); let pv = ParentVisitor::new(&cddl).unwrap(); let rule = cddl.rules.first().unwrap(); assert_eq!(rule.parent(&pv).unwrap(), &cddl); Ok(()) } #[test] fn type_and_group_rule_parent_is_rule() -> Result<()> { let cddl = cddl_from_str( r#" a = "myrule" b = ( * tstr ) "#, true, ) .unwrap(); let pv = ParentVisitor::new(&cddl).unwrap(); if let r @ Rule::Type { rule, .. } = cddl.rules.first().unwrap() { assert_eq!(rule.parent(&pv).unwrap(), r); } if let r @ Rule::Group { rule, .. } = cddl.rules.get(1).unwrap() { assert_eq!(rule.parent(&pv).unwrap(), r); } Ok(()) } #[test] fn type_parent_is_type_rule() -> Result<()> { let cddl = cddl_from_str(r#"a = "myrule""#, true).unwrap(); let pv = ParentVisitor::new(&cddl).unwrap(); if let Rule::Type { rule, .. } = cddl.rules.first().unwrap() { let parent: &TypeRule = rule.value.parent(&pv).unwrap(); assert_eq!(parent, rule); } Ok(()) } #[test] fn type_choice_parent_is_type() -> Result<()> { let cddl = cddl_from_str(r#"a = "myrule""#, true).unwrap(); let pv = ParentVisitor::new(&cddl).unwrap(); if let Rule::Type { rule, .. } = cddl.rules.first().unwrap() { assert_eq!( rule .value .type_choices .first() .unwrap() .parent(&pv) .unwrap(), &rule.value ); } Ok(()) } #[test] fn type1_parent_is_type_choice() -> Result<()> { let cddl = cddl_from_str(r#"a = "myrule""#, true).unwrap(); let pv = ParentVisitor::new(&cddl).unwrap(); if let Rule::Type { rule, .. } = cddl.rules.first().unwrap() { let parent: &TypeChoice = rule .value .type_choices .first() .unwrap() .type1 .parent(&pv) .unwrap(); assert_eq!(parent, rule.value.type_choices.first().unwrap()); } Ok(()) } #[test] fn type2_parent_is_type1() -> Result<()> { let cddl = cddl_from_str(r#"a = "myrule""#, true).unwrap(); let pv = ParentVisitor::new(&cddl).unwrap(); if let Rule::Type { rule, .. } = cddl.rules.first().unwrap() { let parent: &Type1 = rule .value .type_choices .first() .unwrap() .type1 .type2 .parent(&pv) .unwrap(); assert_eq!(parent, &rule.value.type_choices.first().unwrap().type1); } Ok(()) } #[test] fn text_value_parent_is_type2() -> Result<()> { let cddl = cddl_from_str(r#"a = "myrule""#, true).unwrap(); let pv = ParentVisitor::new(&cddl).unwrap(); if let Rule::Type { rule, .. } = cddl.rules.first().unwrap() { if let t2 @ Type2::TextValue { value, .. } = &rule.value.type_choices.first().unwrap().type1.type2 { let value = Value::from(value.borrow()); let parent: &Type2 = value.parent(&pv).unwrap(); assert_eq!(parent, t2); } } Ok(()) } #[test] fn group_entry_parent_is_group_rule() -> Result<()> { let cddl = cddl_from_str(r#"a = ( * tstr )"#, true).unwrap(); let pv = ParentVisitor::new(&cddl).unwrap(); if let Rule::Group { rule, .. } = cddl.rules.first().unwrap() { let parent: &GroupRule = rule.entry.parent(&pv).unwrap(); assert_eq!(parent, rule.as_ref()); } Ok(()) } #[test] fn type_rule_name_ident_parent_is_type_rule() -> Result<()> { let cddl = cddl_from_str(r#"a = "myrule""#, true).unwrap(); let pv = ParentVisitor::new(&cddl).unwrap(); if let Rule::Type { rule, .. } = cddl.rules.first().unwrap() { let parent: &TypeRule = rule.name.parent(&pv).unwrap(); assert_eq!(parent, rule); } Ok(()) } #[test] fn generic_params_parent_is_type_rule() -> Result<()> { let cddl = cddl_from_str(r#"a = { type: t }"#, true).unwrap(); let pv = ParentVisitor::new(&cddl).unwrap(); if let Rule::Type { rule, .. } = cddl.rules.first().unwrap() { let parent: &TypeRule = rule.generic_params.as_ref().unwrap().parent(&pv).unwrap(); assert_eq!(parent, rule); } Ok(()) } #[test] fn generic_param_parent_is_generic_params() -> Result<()> { let cddl = cddl_from_str(r#"a = { type: t }"#, true).unwrap(); let pv = ParentVisitor::new(&cddl).unwrap(); if let Rule::Type { rule, .. } = cddl.rules.first().unwrap() { assert_eq!( rule .generic_params .as_ref() .unwrap() .params .first() .unwrap() .parent(&pv) .unwrap(), rule.generic_params.as_ref().unwrap() ); } Ok(()) } #[test] fn generic_args_parent_is_type2() -> Result<()> { let cddl = cddl_from_str( r#" message = {} messages = message<"reboot", "now"> / message<"sleep", 1..100> "#, true, ) .unwrap(); let pv = ParentVisitor::new(&cddl).unwrap(); if let Rule::Type { rule, .. } = cddl.rules.first().unwrap() { if let t2 @ Type2::Typename { generic_args: Some(ga), .. } = &rule.value.type_choices.first().unwrap().type1.type2 { let parent: &Type2 = ga.parent(&pv).unwrap(); assert_eq!(parent, t2); } } Ok(()) } #[test] fn generic_arg_parent_is_generic_args() -> Result<()> { let cddl = cddl_from_str( r#" message = {} messages = message<"reboot", "now"> / message<"sleep", 1..100> "#, true, ) .unwrap(); let pv = ParentVisitor::new(&cddl).unwrap(); if let Rule::Type { rule, .. } = cddl.rules.first().unwrap() { if let Type2::Typename { generic_args: Some(ga), .. } = &rule.value.type_choices.first().unwrap().type1.type2 { assert_eq!(ga.args.first().unwrap().parent(&pv).unwrap(), ga); } } Ok(()) } #[test] fn group_parent_is_type2() -> Result<()> { let cddl = cddl_from_str( r#" a = { b } b = ( * tstr => int ) "#, true, ) .unwrap(); let pv = ParentVisitor::new(&cddl).unwrap(); if let Rule::Type { rule, .. } = cddl.rules.first().unwrap() { if let t2 @ Type2::Map { group, .. } = &rule.value.type_choices.first().unwrap().type1.type2 { let parent: &Type2 = group.parent(&pv).unwrap(); assert_eq!(parent, t2); } } Ok(()) } #[test] fn identifier_parent_is_type2() -> Result<()> { let cddl = cddl_from_str( r#" terminal-color = &basecolors basecolors = ( black: 0, red: 1, green: 2, yellow: 3, blue: 4, magenta: 5, cyan: 6, white: 7, ) "#, true, ) .unwrap(); let pv = ParentVisitor::new(&cddl).unwrap(); if let Rule::Type { rule, .. } = cddl.rules.first().unwrap() { if let t2 @ Type2::ChoiceFromGroup { ident, .. } = &rule.value.type_choices.first().unwrap().type1.type2 { let parent: &Type2 = ident.parent(&pv).unwrap(); assert_eq!(parent, t2); } } Ok(()) } #[test] fn ctrl_parent_is_rangectlop() -> Result<()> { let cddl = cddl_from_str(r#"ip4 = bstr .size 4"#, true).unwrap(); let pv = ParentVisitor::new(&cddl).unwrap(); if let Rule::Type { rule, .. } = cddl.rules.first().unwrap() { if let op @ RangeCtlOp::CtlOp { ctrl, .. } = &rule .value .type_choices .first() .unwrap() .type1 .operator .as_ref() .unwrap() .operator { assert_eq!(ctrl.parent(&pv).unwrap(), op) } } Ok(()) } } cddl-0.9.4/src/bin/cli.rs000064400000000000000000000170361046102023000132270ustar 00000000000000#![cfg(feature = "cbor")] #![cfg(feature = "json")] #![cfg(not(feature = "lsp"))] #[macro_use] extern crate log; use cddl::{ cddl_from_str, parser::root_type_name_from_cddl_str, validate_cbor_from_slice, validate_json_from_str, }; use clap::{ArgGroup, Args, Parser, Subcommand}; use simplelog::*; use std::{ error::Error, fmt::Write, fs::{self, File}, io::{self, BufReader, Read}, path::Path, }; #[derive(Parser)] #[clap(author, version, about = "Tool for verifying conformance of CDDL definitions against RFC 8610 and for validating JSON documents and CBOR binary files", long_about = None)] struct Cli { /// Enable CI mode, failing if files cannot be found or other edge cases. #[clap(long)] ci: bool, #[clap(subcommand)] command: Commands, } #[derive(Subcommand)] enum Commands { #[clap(name = "compile-cddl", about = "Compile CDDL against RFC 8610")] CompileCddl { #[clap(short = 'c', long = "cddl", help = "Path to CDDL document")] file: String, }, #[clap(name = "compile-json", about = "Compile JSON against RFC 8259")] CompileJson { #[clap(short = 'j', long = "json", help = "Path to JSON document")] file: String, }, Validate(Validate), } #[derive(Args)] #[clap(about = "Validate JSON and/or CBOR against a CDDL definition")] #[clap(group(ArgGroup::new("targets").required(true).multiple(true).args(&["stdin", "json", "cbor"])))] struct Validate { #[clap(short = 'd', long = "cddl", help = "CDDL document")] cddl: String, #[clap( short = 'f', long = "features", help = "Optional features to enable during validation", use_value_delimiter = true )] features: Option>, #[clap( short = 'j', long = "json", help = "JSON document(s) to validate", use_value_delimiter = true, multiple_values = true )] json: Option>, #[clap( short = 'c', long = "cbor", help = "CBOR binary file(s) to validate", multiple_values = true, use_value_delimiter = true )] cbor: Option>, #[clap( long = "stdin", help = "JSON or CBOR input from stdin. Assumes UTF-8 encoding is JSON, otherwise parses as CBOR" )] stdin: bool, } macro_rules! error { ($ci: expr, $($args: tt)+ ) => { log::error!($($args)+); if $ci { return Err(format!($($args)+).into()); } }; } fn main() -> Result<(), Box> { TermLogger::init( LevelFilter::Info, ConfigBuilder::new() .set_time_level(LevelFilter::Off) .build(), TerminalMode::Mixed, ColorChoice::Auto, )?; let cli = Cli::parse(); match &cli.command { Commands::CompileCddl { file } => { let p = Path::new(file); if !p.exists() { error!(cli.ci, "CDDL document {:?} does not exist", p); return Ok(()); } let file_content = fs::read_to_string(file)?; cddl_from_str(&file_content, true).map(|_| ())?; info!("{} is conformant", file); } Commands::CompileJson { file } => { let p = Path::new(file); if !p.exists() { error!(cli.ci, "JSON document {:?} does not exist", p); return Ok(()); } let file = File::open(file)?; let reader = BufReader::new(file); let _: serde_json::Value = serde_json::from_reader(reader)?; return Ok(()); } Commands::Validate(validate) => { #[cfg(feature = "additional-controls")] let enabled_features: Option> = validate .features .as_ref() .map(|f| f.iter().map(|s| s.as_str()).collect()); #[cfg(feature = "additional-controls")] if let Some(enabled_features) = &enabled_features { let mut feature_str = String::from("enabled features: ["); for (idx, feature) in enabled_features.iter().enumerate() { if idx == 0 { let _ = write!(feature_str, "\"{}\"", feature); } else { let _ = write!(feature_str, ", \"{}\"", feature); } } feature_str.push(']'); info!("{}", feature_str); } let p = Path::new(&validate.cddl); if !p.exists() { error!(cli.ci, "CDDL document {:?} does not exist", p); return Ok(()); } let cddl_str = fs::read_to_string(&validate.cddl)?; info!( "Root type for validation: {}", root_type_name_from_cddl_str(&cddl_str)? ); if let Some(files) = &validate.json { for file in files { let p = Path::new(file); if !p.exists() { error!(cli.ci, "File {:?} does not exist", p); continue; } #[cfg(feature = "additional-controls")] let r = validate_json_from_str( &cddl_str, &fs::read_to_string(file)?, enabled_features.as_deref(), ); #[cfg(not(feature = "additional-controls"))] let r = validate_json_from_str(&cddl_str, &fs::read_to_string(file)?); match r { Ok(_) => { info!("Validation of {:?} is successful", p); } Err(e) => { error!( cli.ci, "Validation of {:?} failed: {}", p, e.to_string().trim_end() ); } } } } if let Some(files) = &validate.cbor { for file in files { let p = Path::new(file); if !p.exists() { error!(cli.ci, "CBOR binary file {:?} does not exist", p); continue; } let mut f = File::open(p)?; let mut data = Vec::new(); f.read_to_end(&mut data)?; #[cfg(feature = "additional-controls")] let c = validate_cbor_from_slice(&cddl_str, &data, None); #[cfg(not(feature = "additional-controls"))] let c = validate_cbor_from_slice(&cddl_str, &data); match c { Ok(_) => { info!("Validation of {:?} is successful", p); } Err(e) => { error!( cli.ci, "Validation of {:?} failed: {}", p, e.to_string().trim_end() ); } } } } if validate.stdin { let stdin = io::stdin(); let mut reader = stdin.lock(); let mut data = Vec::new(); reader.read_to_end(&mut data)?; if let Ok(json) = std::str::from_utf8(&data) { #[cfg(feature = "additional-controls")] let r = validate_json_from_str(&cddl_str, json, None); #[cfg(not(feature = "additional-controls"))] let r = validate_json_from_str(&cddl_str, json); match r { Ok(_) => { info!("Validation from stdin is successful"); } Err(e) => { error!( cli.ci, "Validation from stdin failed: {}", e.to_string().trim_end() ); } } } else { #[cfg(feature = "additional-controls")] let c = validate_cbor_from_slice(&cddl_str, &data, enabled_features.as_deref()); #[cfg(not(feature = "additional-controls"))] let c = validate_cbor_from_slice(&cddl_str, &data); match c { Ok(_) => { info!("Validation from stdin is successful"); } Err(e) => { error!( cli.ci, "Validation from stdin failed: {}", e.to_string().trim_end() ); } } } } } } Ok(()) } cddl-0.9.4/src/error.rs000064400000000000000000000140601046102023000130330ustar 00000000000000use std::fmt; #[cfg(target_arch = "wasm32")] use serde::Serialize; #[cfg(not(feature = "std"))] use alloc::string::String; #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, Clone)] pub struct ErrorMsg { pub short: String, pub extended: Option, } impl fmt::Display for ErrorMsg { fn fmt(&self, f: &mut std::fmt::Formatter) -> fmt::Result { write!(f, "{}", self.short) } } #[derive(Debug, Copy, Clone)] pub enum MsgType { // Parser DuplicateRuleIdentifier, InvalidRuleIdentifier, MissingAssignmentToken, InvalidGenericSyntax, MissingGenericClosingDelimiter, InvalidGenericIdentifier, InvalidUnwrapSyntax, InvalidGroupToChoiceEnumSyntax, InvalidTagSyntax, MissingGroupEntryMemberKey, MissingGroupEntry, InvalidGroupEntrySyntax, MissingClosingDelimiter, MissingClosingParend, InvalidMemberKeyArrowMapSyntax, InvalidMemberKeySyntax, InvalidOccurrenceSyntax, NoRulesDefined, IncompleteRuleEntry, TypeSocketNamesMustBeTypeAugmentations, GroupSocketNamesMustBeGroupAugmentations, // Lexer UnableToAdvanceToken, InvalidControlOperator, InvalidCharacter, InvalidEscapeCharacter, InvalidTextStringLiteralCharacter, EmptyTextStringLiteral, InvalidByteStringLiteralCharacter, EmptyByteStringLiteral, InvalidHexFloat, InvalidExponent, } impl From for ErrorMsg { fn from(mt: MsgType) -> ErrorMsg { match mt { MsgType::DuplicateRuleIdentifier => ErrorMsg { short: "rule with the same identifier is already defined".into(), extended: None, }, MsgType::InvalidRuleIdentifier => ErrorMsg { short: "expected rule identifier followed by an assignment token '=', '/=' or '//='".into(), extended: None, }, MsgType::MissingAssignmentToken => ErrorMsg { short: "expected assignment token '=', '/=' or '//=' after rule identifier".into(), extended: None, }, MsgType::InvalidGenericSyntax => ErrorMsg { short: "generic parameters should be between angle brackets '<' and '>' and separated by a comma ','".into(), extended: None, }, MsgType::MissingGenericClosingDelimiter => ErrorMsg { short: "missing closing '>'".into(), extended: None, }, MsgType::InvalidGenericIdentifier => ErrorMsg { short: "generic parameters must be named identifiers".into(), extended: None, }, MsgType::InvalidUnwrapSyntax => ErrorMsg { short: "invalid unwrap syntax".into(), extended: None, }, MsgType::InvalidGroupToChoiceEnumSyntax => ErrorMsg { short: "invalid group to choice enumeration syntax".into(), extended: None, }, MsgType::InvalidTagSyntax => ErrorMsg { short: "invalid tag syntax".into(), extended: None, }, MsgType::MissingGroupEntryMemberKey => ErrorMsg { short: "missing group entry member key".into(), extended: None, }, MsgType::MissingGroupEntry => ErrorMsg { short: "missing group entry".into(), extended: None, }, MsgType::InvalidGroupEntrySyntax => ErrorMsg { short: "invalid group entry syntax".into(), extended: None, }, MsgType::MissingClosingDelimiter => ErrorMsg { short: "missing closing delimiter".into(), extended: None, }, MsgType::MissingClosingParend => ErrorMsg { short: "missing closing parend ')'".into(), extended: None, }, MsgType::InvalidMemberKeyArrowMapSyntax => ErrorMsg { short: "invalid memberkey. missing '=>'".into(), extended: None, }, MsgType::InvalidMemberKeySyntax => ErrorMsg { short: "invalid memberkey. missing '=>' or ':'".into(), extended: None, }, MsgType::InvalidOccurrenceSyntax => ErrorMsg { short: "invalid occurrence indicator syntax".into(), extended: None, }, MsgType::UnableToAdvanceToken => ErrorMsg { short: "unable to advance to the next token".into(), extended: None, }, MsgType::InvalidControlOperator => ErrorMsg { short: "invalid control operator".into(), extended: None, }, MsgType::InvalidCharacter => ErrorMsg { short: "invalid character".into(), extended: None, }, MsgType::InvalidEscapeCharacter => ErrorMsg { short: "invalid escape character".into(), extended: None, }, MsgType::InvalidTextStringLiteralCharacter => ErrorMsg { short: "invalid character in text string literal. expected closing \"".into(), extended: None, }, MsgType::EmptyTextStringLiteral => ErrorMsg { short: "empty text string literal".into(), extended: None, }, MsgType::InvalidByteStringLiteralCharacter => ErrorMsg { short: "invalid character in byte string literal. expected closing '".into(), extended: None, }, MsgType::EmptyByteStringLiteral => ErrorMsg { short: "empty byte string literal".into(), extended: None, }, MsgType::NoRulesDefined => ErrorMsg { short: "you must have at least one rule defined".into(), extended: None, }, MsgType::IncompleteRuleEntry => ErrorMsg { short: "missing rule entry after assignment".into(), extended: None, }, MsgType::TypeSocketNamesMustBeTypeAugmentations => ErrorMsg { short: "all plugs for type socket names must be augmentations using '/=' (alternatively change the definition to be a group socket)".into(), extended: None, }, MsgType::GroupSocketNamesMustBeGroupAugmentations => ErrorMsg { short: "all plugs for group socket names must be augmentations using '//=' (alternatively change the definition to be a type socket)".into(), extended: None, }, MsgType::InvalidHexFloat => ErrorMsg { short: "invalid hexfloat".into(), extended: None, }, MsgType::InvalidExponent => ErrorMsg { short: "invalid exponent".into(), extended: None, } } } } cddl-0.9.4/src/lexer.rs000064400000000000000000001165111046102023000130250ustar 00000000000000use super::{ error::{ ErrorMsg, MsgType::{self, *}, }, token::{self, ByteValue, Token, Value}, }; use codespan_reporting::{ diagnostic::{Diagnostic, Label}, files::SimpleFiles, term, }; use std::{ fmt, iter::Peekable, num, result, str::{self, CharIndices}, }; #[cfg(feature = "std")] use std::{borrow::Cow, string}; #[cfg(not(feature = "std"))] use alloc::{ borrow::Cow, string::{self, String, ToString}, vec::Vec, }; use lexical_core as lexical; #[cfg(target_arch = "wasm32")] use serde::Serialize; /// Alias for `Result` with an error of type `cddl::LexerError` pub type Result = result::Result; /// Lexer position #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(Debug, Copy, Clone)] pub struct Position { /// Line number pub line: usize, /// Column number pub column: usize, /// Token begin and end index range pub range: (usize, usize), /// Lexer index pub index: usize, } impl Default for Position { fn default() -> Self { Position { line: 1, column: 1, range: (0, 0), index: 0, } } } /// Lexer error #[derive(Debug)] pub struct Error { /// Error type pub error_type: LexerErrorType, input: String, position: Position, } /// Various error types emitted by the lexer #[derive(Debug)] pub enum LexerErrorType { /// CDDL lexing syntax error LEXER(MsgType), /// UTF-8 parsing error UTF8(string::FromUtf8Error), /// Byte string not properly encoded as base 16 BASE16(String), /// Byte string not properly encoded as base 64 BASE64(String), /// Error parsing integer PARSEINT(num::ParseIntError), /// Error parsing float PARSEFLOAT(lexical::Error), /// Error parsing hexfloat PARSEHEXF(hexf_parse::ParseHexfError), } #[cfg(feature = "std")] impl std::error::Error for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut files = SimpleFiles::new(); let file_id = files.add("input", self.input.as_str()); let config = term::Config::default(); let mut buffer = Vec::new(); let mut writer = term::termcolor::NoColor::new(&mut buffer); match &self.error_type { LexerErrorType::LEXER(le) => { let diagnostic = Diagnostic::error() .with_message("lexer error") .with_labels(vec![Label::primary( file_id, self.position.range.0..self.position.range.1, ) .with_message(ErrorMsg::from(*le).to_string())]); term::emit(&mut writer, &config, &files, &diagnostic).map_err(|_| fmt::Error)?; write!(f, "{}", String::from_utf8(buffer).map_err(|_| fmt::Error)?) } LexerErrorType::UTF8(utf8e) => { let diagnostic = Diagnostic::error() .with_message("lexer error") .with_labels(vec![Label::primary( file_id, self.position.range.0..self.position.range.1, ) .with_message(utf8e.to_string())]); term::emit(&mut writer, &config, &files, &diagnostic).map_err(|_| fmt::Error)?; write!(f, "{}", String::from_utf8(buffer).map_err(|_| fmt::Error)?) } LexerErrorType::BASE16(b16e) => { let diagnostic = Diagnostic::error() .with_message("lexer error") .with_labels(vec![Label::primary( file_id, self.position.range.0..self.position.range.1, ) .with_message(b16e.to_string())]); term::emit(&mut writer, &config, &files, &diagnostic).map_err(|_| fmt::Error)?; write!(f, "{}", String::from_utf8(buffer).map_err(|_| fmt::Error)?) } LexerErrorType::BASE64(b64e) => { let diagnostic = Diagnostic::error() .with_message("lexer error") .with_labels(vec![Label::primary( file_id, self.position.range.0..self.position.range.1, ) .with_message(b64e.to_string())]); term::emit(&mut writer, &config, &files, &diagnostic).map_err(|_| fmt::Error)?; write!(f, "{}", String::from_utf8(buffer).map_err(|_| fmt::Error)?) } LexerErrorType::PARSEINT(pie) => { let diagnostic = Diagnostic::error() .with_message("lexer error") .with_labels(vec![Label::primary( file_id, self.position.range.0..self.position.range.1, ) .with_message(pie.to_string())]); term::emit(&mut writer, &config, &files, &diagnostic).map_err(|_| fmt::Error)?; write!(f, "{}", String::from_utf8(buffer).map_err(|_| fmt::Error)?) } LexerErrorType::PARSEFLOAT(pfe) => { let diagnostic = Diagnostic::error() .with_message("lexer error") .with_labels(vec![Label::primary( file_id, self.position.range.0..self.position.range.1, ) .with_message(format!("{:#?}", pfe))]); term::emit(&mut writer, &config, &files, &diagnostic).map_err(|_| fmt::Error)?; write!(f, "{}", String::from_utf8(buffer).map_err(|_| fmt::Error)?) } LexerErrorType::PARSEHEXF(phf) => { let diagnostic = Diagnostic::error() .with_message("lexer error") .with_labels(vec![Label::primary( file_id, self.position.range.0..self.position.range.1, ) .with_message(format!("{:#?}", phf))]); term::emit(&mut writer, &config, &files, &diagnostic).map_err(|_| fmt::Error)?; write!(f, "{}", String::from_utf8(buffer).map_err(|_| fmt::Error)?) } } } } impl From<(&str, Position, MsgType)> for Error { fn from(e: (&str, Position, MsgType)) -> Self { Error { error_type: LexerErrorType::LEXER(e.2), input: e.0.to_string(), position: e.1, } } } impl From<(&str, Position, string::FromUtf8Error)> for Error { fn from(e: (&str, Position, string::FromUtf8Error)) -> Self { Error { error_type: LexerErrorType::UTF8(e.2), input: e.0.to_string(), position: e.1, } } } impl From<(&str, Position, base16::DecodeError)> for Error { fn from(e: (&str, Position, base16::DecodeError)) -> Self { Error { error_type: LexerErrorType::BASE16(e.2.to_string()), input: e.0.to_string(), position: e.1, } } } impl From<(&str, Position, data_encoding::DecodeError)> for Error { fn from(e: (&str, Position, data_encoding::DecodeError)) -> Self { Error { error_type: LexerErrorType::BASE64(e.2.to_string()), input: e.0.to_string(), position: e.1, } } } impl From<(&str, Position, num::ParseIntError)> for Error { fn from(e: (&str, Position, num::ParseIntError)) -> Self { Error { error_type: LexerErrorType::PARSEINT(e.2), input: e.0.to_string(), position: e.1, } } } impl From<(&str, Position, lexical::Error)> for Error { fn from(e: (&str, Position, lexical::Error)) -> Self { Error { error_type: LexerErrorType::PARSEFLOAT(e.2), input: e.0.to_string(), position: e.1, } } } impl From<(&str, Position, hexf_parse::ParseHexfError)> for Error { fn from(e: (&str, Position, hexf_parse::ParseHexfError)) -> Self { Error { error_type: LexerErrorType::PARSEHEXF(e.2), input: e.0.to_string(), position: e.1, } } } /// Lexer which holds a byte slice and iterator over the byte slice #[derive(Debug)] pub struct Lexer<'a> { /// CDDL input string pub str_input: &'a str, // TODO: Remove duplicate iterator in favor of multipeek input: Peekable>, multipeek: itertools::MultiPeek>, /// Lexer position in input pub position: Position, } /// Iterator over a lexer pub struct LexerIter<'a> { l: Lexer<'a>, } /// Iterated lexer token item pub type Item<'a> = std::result::Result<(Position, Token<'a>), Error>; impl<'a> Iterator for LexerIter<'a> { type Item = Item<'a>; fn next(&mut self) -> Option { let next_token = self.l.next_token(); Some(next_token) } } /// Creates a `Lexer` from a string slice /// /// # Arguments /// /// `str_input` - String slice with input pub fn lexer_from_str(str_input: &str) -> Lexer { Lexer::new(str_input) } impl<'a> Lexer<'a> { /// Creates a new `Lexer` from a given `&str` input pub fn new(str_input: &'a str) -> Lexer<'a> { Lexer { str_input, input: str_input.char_indices().peekable(), multipeek: itertools::multipeek(str_input.char_indices()), position: Position { line: 1, column: 1, range: (0, 0), index: 0, }, } } /// Creates a Lexer from a byte slice pub fn from_slice(input: &[u8]) -> Lexer { let str_input = std::str::from_utf8(input).unwrap(); Lexer::new(str_input) } /// Returns an iterator over a lexer pub fn iter(self) -> LexerIter<'a> { LexerIter { l: self } } fn read_char(&mut self) -> Result<(usize, char)> { self.multipeek.next(); self .input .next() .map(|c| { if c.1 == '\n' { self.position.line += 1; self.position.column = 1; } else { self.position.column += 1; } if !c.1.is_ascii_whitespace() { self.position.index = c.0; } c }) .ok_or_else(|| (self.str_input, self.position, UnableToAdvanceToken).into()) } /// Advances the index of the str iterator over the input and returns a /// `Token` pub fn next_token(&mut self) -> Result<(Position, Token<'a>)> { self.skip_whitespace()?; let token_offset = self.position.index; if let Ok(c) = self.read_char() { match c { (_, '\n') => { self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::NEWLINE)) } (_, '=') => match self.peek_char() { Some(&c) if c.1 == '>' => { let _ = self.read_char()?; self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::ARROWMAP)) } _ => { self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::ASSIGN)) } }, (_, '+') => { self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::ONEORMORE)) } (_, '?') => { self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::OPTIONAL)) } (_, '*') => { self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::ASTERISK)) } (_, '(') => { self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::LPAREN)) } (_, ')') => { self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::RPAREN)) } (_, '[') => { self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::LBRACKET)) } (_, ']') => { self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::RBRACKET)) } (_, '<') => { self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::LANGLEBRACKET)) } (idx, '"') => { let tv = self.read_text_value(idx)?; self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::VALUE(Value::TEXT(tv.into())))) } (_, '{') => { self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::LBRACE)) } (_, '}') => { self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::RBRACE)) } (_, ',') => { self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::COMMA)) } (idx, ';') => { let comment = self.read_comment(idx)?; self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::COMMENT(comment))) } (_, ':') => { self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::COLON)) } (_, '^') => { self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::CUT)) } (_, '&') => { self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::GTOCHOICE)) } (_, '>') => { self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::RANGLEBRACKET)) } (_, '~') => { self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::UNWRAP)) } (_, '/') => match self.peek_char() { Some(&c) if c.1 == '/' => { let _ = self.read_char()?; match self.peek_char() { Some(&c) if c.1 == '=' => { let _ = self.read_char()?; self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::GCHOICEALT)) } _ => { self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::GCHOICE)) } } } Some(&c) if c.1 == '=' => { let _ = self.read_char()?; self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::TCHOICEALT)) } _ => { self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::TCHOICE)) } }, (_, '#') => match self.peek_char() { Some(&c) if is_digit(c.1) => { let (idx, _) = self.read_char()?; let t = self.read_number(idx)?.1; match self.peek_char() { Some(&c) if c.1 == '.' => { let _ = self.read_char()?; let (idx, _) = self.read_char()?; self.position.range = (token_offset, self.position.index + 1); #[cfg(not(target_arch = "wasm32"))] { Ok(( self.position, Token::TAG(Some(t as u8), Some(self.read_number(idx)?.1)), )) } #[cfg(target_arch = "wasm32")] { Ok(( self.position, Token::TAG(Some(t as u8), Some(self.read_number(idx)?.1 as usize)), )) } } _ => { self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::TAG(Some(t as u8), None))) } } } _ => { self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::TAG(None, None))) } }, (_, '\'') => { let (idx, _) = self.read_char()?; let bsv = self.read_byte_string(idx)?; self.position.range = (token_offset, self.position.index + 1); Ok(( self.position, Token::VALUE(Value::BYTE(ByteValue::UTF8(bsv.as_bytes().into()))), )) } (idx, '.') => { if let Some(&c) = self.peek_char() { if c.1 == '.' { // Rangeop let _ = self.read_char()?; if let Some(&c) = self.peek_char() { if c.1 == '.' { let _ = self.read_char()?; self.position.range = (token_offset, self.position.index + 1); return Ok((self.position, Token::RANGEOP(false))); } } self.position.range = (token_offset, self.position.index + 1); return Ok((self.position, Token::RANGEOP(true))); } else if is_ealpha(c.1) { // Controlop let ctrlop = token::lookup_control_from_str(self.read_identifier(idx)?).ok_or_else(|| { self.position.range = (token_offset, self.position.index + 1); Error::from((self.str_input, self.position, InvalidControlOperator)) })?; self.position.range = (token_offset, self.position.index + 1); return Ok((self.position, Token::ControlOperator(ctrlop))); } } self.position.range = (token_offset, self.position.index + 1); Err((self.str_input, self.position, InvalidCharacter).into()) } (idx, ch) => { if is_ealpha(ch) { // base 16 (hex) encoded byte string if ch == 'h' { if let Some(&c) = self.peek_char() { if c.1 == '\'' { let _ = self.read_char()?; let (idx, _) = self.read_char()?; // Ensure that the byte string has been properly encoded. let b = self.read_prefixed_byte_string(idx)?; let mut buf = [0u8; 1024]; return base16::decode_slice(&b[..], &mut buf) .map_err(|e| (self.str_input, self.position, e).into()) .map(|_| { self.position.range = (token_offset, self.position.index + 1); (self.position, Token::VALUE(Value::BYTE(ByteValue::B16(b)))) }); } } } // base 64 encoded byte string if ch == 'b' { if let Some(&c) = self.peek_char() { if c.1 == '6' { let _ = self.read_char()?; if let Some(&c) = self.peek_char() { if c.1 == '4' { let _ = self.read_char()?; if let Some(&c) = self.peek_char() { if c.1 == '\'' { let _ = self.read_char()?; let (idx, _) = self.read_char()?; // Ensure that the byte string has been properly // encoded let bs = self.read_prefixed_byte_string(idx)?; let mut buf = vec![0; data_encoding::BASE64.decode_len(bs.len()).unwrap()]; return data_encoding::BASE64URL .decode_mut(&bs, &mut buf) .map_err(|e| (self.str_input, self.position, e.error).into()) .map(|_| { self.position.range = (token_offset, self.position.index + 1); (self.position, Token::VALUE(Value::BYTE(ByteValue::B64(bs)))) }); } } } } } } } let ident = token::lookup_ident(self.read_identifier(idx)?); self.position.range = (token_offset, self.position.index + 1); return Ok((self.position, ident)); } else if is_digit(ch) || ch == '-' { let number = self.read_int_or_float(idx)?; self.position.range = (token_offset, self.position.index + 1); return Ok((self.position, number)); } self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::ILLEGAL(&self.str_input[idx..=idx]))) } } } else { self.position.range = (token_offset, self.position.index + 1); Ok((self.position, Token::EOF)) } } fn read_identifier(&mut self, idx: usize) -> Result<&'a str> { let mut end_idx = idx; while let Some(&c) = self.peek_char() { if is_ealpha(c.1) || is_digit(c.1) || c.1 == '.' || c.1 == '-' { match c.1 { // Check for range '.' => { end_idx = self.read_char()?.0; if let Some(&c) = self.peek_char() { if c.1 == '\u{0020}' { return Ok(&self.str_input[idx..end_idx]); } } } _ => end_idx = self.read_char()?.0, } } else { break; } } Ok(&self.str_input[idx..=end_idx]) } fn read_text_value(&mut self, idx: usize) -> Result<&'a str> { while let Some(&(_, ch)) = self.peek_char() { match ch { // SCHAR '\x20'..='\x21' | '\x23'..='\x5b' | '\x5d'..='\x7e' | '\u{0080}'..='\u{10FFFD}' => { let _ = self.read_char()?; } // SESC '\\' => { let _ = self.read_char(); if let Some(&(_, ch)) = self.peek_char() { match ch { '\x20'..='\x7e' | '\u{0080}'..='\u{10FFFD}' => { let _ = self.read_char()?; } _ => return Err((self.str_input, self.position, InvalidEscapeCharacter).into()), } } } // Closing " '\x22' => { return Ok(&self.str_input[idx + 1..self.read_char()?.0]); } _ => { return Err( ( self.str_input, self.position, InvalidTextStringLiteralCharacter, ) .into(), ) } } } Err((self.str_input, self.position, EmptyTextStringLiteral).into()) } fn read_byte_string(&mut self, idx: usize) -> Result<&'a str> { while let Some(&(_, ch)) = self.peek_char() { match ch { // BCHAR '\x20'..='\x26' | '\x28'..='\x5b' | '\x5d'..='\x7e' | '\u{0080}'..='\u{10FFFD}' => { let _ = self.read_char(); } // SESC '\\' => { let _ = self.read_char(); if let Some(&(_, ch)) = self.peek_char() { match ch { '\x20'..='\x7e' | '\u{0080}'..='\u{10FFFD}' => { let _ = self.read_char()?; } _ => return Err((self.str_input, self.position, InvalidEscapeCharacter).into()), } } } // Closing ' '\x27' => return Ok(&self.str_input[idx..self.read_char()?.0]), _ => { if ch.is_ascii_whitespace() { let _ = self.read_char()?; } else { return Err( ( self.str_input, self.position, InvalidByteStringLiteralCharacter, ) .into(), ); } } } } Err((self.str_input, self.position, EmptyByteStringLiteral).into()) } fn read_prefixed_byte_string(&mut self, idx: usize) -> Result> { let mut has_whitespace = false; while let Some(&(_, ch)) = self.peek_char() { match ch { // BCHAR '\x20'..='\x26' | '\x28'..='\x5b' | '\x5d'..='\x7e' | '\u{0080}'..='\u{10FFFD}' => { let _ = self.read_char(); } // SESC '\\' => { let _ = self.read_char(); if let Some(&(_, ch)) = self.peek_char() { match ch { '\x20'..='\x7e' | '\u{0080}'..='\u{10FFFD}' => { let _ = self.read_char()?; } _ => return Err((self.str_input, self.position, InvalidEscapeCharacter).into()), } } } // Closing ' '\x27' => { // Whitespace is ignored for prefixed byte strings and requires allocation if has_whitespace { return Ok( self.str_input[idx..self.read_char()?.0] .to_string() .replace(' ', "") .into_bytes() .into(), ); } return Ok(self.str_input[idx..self.read_char()?.0].as_bytes().into()); } // CRLF _ => { // TODO: if user forgets closing "'", but another "'" is found later // in the string, the error emitted here can be confusing if ch.is_ascii_whitespace() { has_whitespace = true; let _ = self.read_char()?; } else { return Err( ( self.str_input, self.position, InvalidByteStringLiteralCharacter, ) .into(), ); } } } } Err((self.str_input, self.position, EmptyByteStringLiteral).into()) } fn read_comment(&mut self, idx: usize) -> Result<&'a str> { let mut comment_char = (idx, char::default()); while let Some(&(_, ch)) = self.peek_char() { if ch != '\x0a' && ch != '\x0d' { comment_char = self.read_char()?; } else { return Ok(&self.str_input[idx + 1..self.read_char()?.0]); } } Ok(&self.str_input[idx + 1..=comment_char.0]) } fn skip_whitespace(&mut self) -> Result<()> { while let Some(&(idx, ch)) = self.peek_char() { if ch == '\n' { self.position.index = idx; return Ok(()); } if ch.is_whitespace() { let _ = self.read_char()?; } else { self.position.index = idx; break; } } Ok(()) } fn read_int_or_float(&mut self, mut idx: usize) -> Result> { let mut is_signed = false; let mut signed_idx = 0; if self.str_input.as_bytes()[idx] == b'-' { is_signed = true; signed_idx = idx; idx = self.read_char()?.0; } let (mut end_idx, i) = self.read_number(idx)?; if let Some(&c) = self.multipeek.peek() { let mut hexfloat = false; if i == 0 && c.0 - idx == 1 && c.1 == 'x' { let _ = self.read_char()?; if self.multipeek.peek().is_none() { return Err((self.str_input, self.position, InvalidHexFloat).into()); } let (idx, _) = self.read_char()?; let _ = self.read_hexdigit(idx)?; hexfloat = true; } if c.1 == '.' || c.1 == 'x' { if c.1 == 'x' { let _ = self.read_char()?; } if let Some(&c) = self.multipeek.peek() { if hexfloat && is_hexdigit(c.1) { let _ = self.read_char()?; let _ = self.read_hexdigit(c.0)?; if self.read_char()?.1 != 'p' { return Err((self.str_input, self.position, InvalidHexFloat).into()); } let (exponent_idx, _) = self.read_char()?; end_idx = self.read_exponent(exponent_idx)?.0; if is_signed { return Ok(Token::VALUE(Value::FLOAT( hexf_parse::parse_hexf64(&self.str_input[signed_idx..=end_idx], false) .map_err(|e| Error::from((self.str_input, self.position, e)))?, ))); } return Ok(Token::VALUE(Value::FLOAT( hexf_parse::parse_hexf64(&self.str_input[idx..=end_idx], false) .map_err(|e| Error::from((self.str_input, self.position, e)))?, ))); } if is_digit(c.1) { let _ = self.read_char()?; end_idx = self.read_number(c.0)?.0; if let Some(&(_, 'e')) = self.peek_char() { let _ = self.read_char()?; let (exponent_idx, _) = self.read_char()?; end_idx = self.read_exponent(exponent_idx)?.0; } if is_signed { return Ok(Token::VALUE(Value::FLOAT( lexical::parse::(self.str_input[signed_idx..=end_idx].as_bytes()) .map_err(|e| Error::from((self.str_input, self.position, e)))?, ))); } return Ok(Token::VALUE(Value::FLOAT( lexical::parse::(self.str_input[idx..=end_idx].as_bytes()) .map_err(|e| Error::from((self.str_input, self.position, e)))?, ))); } } } } let mut is_exponent = false; if let Some(&(_, 'e')) = self.peek_char() { let _ = self.read_char()?; let (exponent_idx, _) = self.read_char()?; end_idx = self.read_exponent(exponent_idx)?.0; is_exponent = true; } if is_signed { if is_exponent { return Ok(Token::VALUE(Value::INT( lexical::parse::(self.str_input[signed_idx..=end_idx].as_bytes()) .map_err(|e| Error::from((self.str_input, self.position, e)))? as isize, ))); } else { return Ok(Token::VALUE(Value::INT( self.str_input[signed_idx..=end_idx] .parse() .map_err(|e| Error::from((self.str_input, self.position, e)))?, ))); } } if is_exponent { return Ok(Token::VALUE(Value::UINT( lexical::parse::(self.str_input[idx..=end_idx].as_bytes()) .map_err(|e| Error::from((self.str_input, self.position, e)))? as usize, ))); } #[cfg(not(target_arch = "wasm32"))] { Ok(Token::VALUE(Value::UINT(i))) } #[cfg(target_arch = "wasm32")] { Ok(Token::VALUE(Value::UINT(i as usize))) } } #[cfg(not(target_arch = "wasm32"))] fn read_number(&mut self, idx: usize) -> Result<(usize, usize)> { let mut end_index = idx; while let Some(&c) = self.peek_char() { if is_digit(c.1) { let (ei, _) = self.read_char()?; end_index = ei; } else { break; } } Ok(( end_index, self.str_input[idx..=end_index] .parse() .map_err(|e| Error::from((self.str_input, self.position, e)))?, )) } #[cfg(target_arch = "wasm32")] fn read_number(&mut self, idx: usize) -> Result<(usize, u64)> { let mut end_index = idx; while let Some(&c) = self.peek_char() { if is_digit(c.1) { let (ei, _) = self.read_char()?; end_index = ei; } else { break; } } Ok(( end_index, self.str_input[idx..=end_index] .parse() .map_err(|e| Error::from((self.str_input, self.position, e)))?, )) } fn read_exponent(&mut self, idx: usize) -> Result<(usize, &str)> { let mut end_index = idx; if let Some(&c) = self.peek_char() { if c.1 != '-' && c.1 != '+' && !is_digit(c.1) { return Err((self.str_input, self.position, InvalidExponent).into()); } } while let Some(&c) = self.peek_char() { if is_digit(c.1) { let (ei, _) = self.read_char()?; end_index = ei; } else { break; } } Ok((end_index, &self.str_input[idx..=end_index])) } fn read_hexdigit(&mut self, idx: usize) -> Result<(usize, &str)> { let mut end_index = idx; while let Some(&c) = self.peek_char() { if is_hexdigit(c.1) { let (ei, _) = self.read_char()?; end_index = ei; } else { break; } } Ok((end_index, &self.str_input[idx..=end_index])) } fn peek_char(&mut self) -> Option<&(usize, char)> { self.input.peek() } } fn is_ealpha(ch: char) -> bool { ch.is_alphabetic() || ch == '@' || ch == '_' || ch == '$' } fn is_digit(ch: char) -> bool { ch.is_ascii_digit() } fn is_hexdigit(ch: char) -> bool { ch.is_ascii_hexdigit() } #[cfg(test)] mod tests { use super::{ super::token::{ControlOperator, SocketPlug, Token::*}, *, }; use pretty_assertions::assert_eq; #[cfg(not(feature = "std"))] use super::super::alloc::string::ToString; use indoc::indoc; #[test] fn verify_next_token() -> Result<()> { let input = indoc!( r#" ; this is a comment ; this is another comment mynumber = 10.5 mytag = #6.1234(tstr) myfirstrule = "myotherrule" mybytestring = 'hello there' mybase16rule = h'68656c6c6f20776f726c64' mybase64rule = b64'aGVsbG8gd29ybGQ=' mysecondrule = mynumber .. 100.5 myintrule = -10 mysignedfloat = -10.5 myintrange = -10..10 mycontrol = mynumber .gt 0 @terminal-color = basecolors / othercolors ; an inline comment messages = message<"reboot", "now"> address = { delivery } delivery = ( street: tstr, ? number ^ => uint, city // po-box: uint, city // per-pickup: true ) city = ( name: tstr zip-code: uint 1*3 $$tcp-option, ) ; test"# ); let expected_tok = [ (COMMENT(" this is a comment"), "; this is a comment"), ( COMMENT(" this is another comment"), "; this is another comment", ), (NEWLINE, ""), (IDENT("mynumber", None), "mynumber"), (ASSIGN, "="), (VALUE(Value::FLOAT(10.5)), "10.5"), (NEWLINE, ""), (NEWLINE, ""), (IDENT("mytag", None), "mytag"), (ASSIGN, "="), (TAG(Some(6), Some(1234)), "#6.1234"), (LPAREN, "("), (TSTR, "tstr"), (RPAREN, ")"), (NEWLINE, ""), (NEWLINE, ""), (IDENT("myfirstrule", None), "myfirstrule"), (ASSIGN, "="), (VALUE(Value::TEXT("myotherrule".into())), "\"myotherrule\""), (NEWLINE, ""), (NEWLINE, ""), (IDENT("mybytestring", None), "mybytestring"), (ASSIGN, "="), ( VALUE(Value::BYTE(ByteValue::UTF8(b"hello there".as_ref().into()))), "'hello there'", ), (NEWLINE, ""), (NEWLINE, ""), (IDENT("mybase16rule", None), "mybase16rule"), (ASSIGN, "="), ( VALUE(Value::BYTE(ByteValue::B16( b"68656c6c6f20776f726c64".as_ref().into(), ))), "h'68656c6c6f20776f726c64'", ), (NEWLINE, ""), (NEWLINE, ""), (IDENT("mybase64rule", None), "mybase64rule"), (ASSIGN, "="), ( VALUE(Value::BYTE(ByteValue::B64( b"aGVsbG8gd29ybGQ=".as_ref().into(), ))), "b64'aGVsbG8gd29ybGQ='", ), (NEWLINE, ""), (NEWLINE, ""), (IDENT("mysecondrule", None), "mysecondrule"), (ASSIGN, "="), (IDENT("mynumber", None), "mynumber"), (RANGEOP(true), ".."), (VALUE(Value::FLOAT(100.5)), "100.5"), (NEWLINE, ""), (NEWLINE, ""), (IDENT("myintrule", None), "myintrule"), (ASSIGN, "="), (VALUE(Value::INT(-10)), "-10"), (NEWLINE, ""), (NEWLINE, ""), (IDENT("mysignedfloat", None), "mysignedfloat"), (ASSIGN, "="), (VALUE(Value::FLOAT(-10.5)), "-10.5"), (NEWLINE, ""), (NEWLINE, ""), (IDENT("myintrange", None), "myintrange"), (ASSIGN, "="), (VALUE(Value::INT(-10)), "-10"), (RANGEOP(true), ".."), (VALUE(Value::UINT(10)), "10"), (NEWLINE, ""), (NEWLINE, ""), (IDENT("mycontrol", None), "mycontrol"), (ASSIGN, "="), (IDENT("mynumber", None), "mynumber"), (ControlOperator(ControlOperator::GT), ".gt"), (VALUE(Value::UINT(0)), "0"), (NEWLINE, ""), (NEWLINE, ""), (IDENT("@terminal-color", None), "@terminal-color"), (ASSIGN, "="), (IDENT("basecolors", None), "basecolors"), (TCHOICE, "/"), (IDENT("othercolors", None), "othercolors"), (COMMENT(" an inline comment"), "; an inline comment"), (NEWLINE, ""), (IDENT("messages", None), "messages"), (ASSIGN, "="), (IDENT("message", None), "message"), (LANGLEBRACKET, "<"), (VALUE(Value::TEXT("reboot".into())), "\"reboot\""), (COMMA, ","), (VALUE(Value::TEXT("now".into())), "\"now\""), (RANGLEBRACKET, ">"), (NEWLINE, ""), (NEWLINE, ""), (IDENT("address", None), "address"), (ASSIGN, "="), (LBRACE, "{"), (IDENT("delivery", None), "delivery"), (RBRACE, "}"), (NEWLINE, ""), (NEWLINE, ""), (IDENT("delivery", None), "delivery"), (ASSIGN, "="), (LPAREN, "("), (NEWLINE, ""), (IDENT("street", None), "street"), (COLON, ":"), (TSTR, "tstr"), (COMMA, ","), (OPTIONAL, "?"), (NUMBER, "number"), (CUT, "^"), (ARROWMAP, "=>"), (UINT, "uint"), (COMMA, ","), (IDENT("city", None), "city"), (GCHOICE, "//"), (NEWLINE, ""), (IDENT("po-box", None), "po-box"), (COLON, ":"), (UINT, "uint"), (COMMA, ","), (IDENT("city", None), "city"), (GCHOICE, "//"), (NEWLINE, ""), (IDENT("per-pickup", None), "per-pickup"), (COLON, ":"), (TRUE, "true"), (NEWLINE, ""), (RPAREN, ")"), (NEWLINE, ""), (NEWLINE, ""), (IDENT("city", None), "city"), (ASSIGN, "="), (LPAREN, "("), (NEWLINE, ""), (IDENT("name", None), "name"), (COLON, ":"), (TSTR, "tstr"), (NEWLINE, ""), (IDENT("zip-code", None), "zip-code"), (COLON, ":"), (UINT, "uint"), (NEWLINE, ""), (VALUE(Value::UINT(1)), "1"), (ASTERISK, "*"), (VALUE(Value::UINT(3)), "3"), (IDENT("tcp-option", Some(SocketPlug::GROUP)), "$$tcp-option"), (COMMA, ","), (NEWLINE, ""), (RPAREN, ")"), (COMMENT(" test"), "; test"), ]; let mut l = Lexer::new(input); for (expected_tok, literal) in expected_tok.iter() { let tok = l.next_token()?; assert_eq!((&tok.1, &*tok.1.to_string()), (expected_tok, *literal)) } Ok(()) } #[test] fn verify_controlop() -> Result<()> { let input = r#".size"#; let expected_tok = Token::ControlOperator(ControlOperator::SIZE); let mut l = Lexer::new(input); assert_eq!(expected_tok.to_string(), l.next_token()?.1.to_string()); Ok(()) } #[test] fn verify_range() -> Result<()> { let input = r#"-10.5..10.5"#; let mut l = Lexer::new(input); let expected_tokens = [ (VALUE(Value::FLOAT(-10.5)), "-10.5"), (RANGEOP(true), ".."), (VALUE(Value::FLOAT(10.5)), "10.5"), ]; for (expected_tok, literal) in expected_tokens.iter() { let tok = l.next_token()?; assert_eq!((expected_tok, *literal), (&tok.1, &*tok.1.to_string())) } Ok(()) } #[test] fn verify_multiline_byte_string() -> Result<()> { let input = r#"'test test'"#; let mut l = Lexer::new(input); let tok = l.next_token()?; assert_eq!( ( &VALUE(Value::BYTE(ByteValue::UTF8(Cow::Borrowed( b"test\n test" )))), "'test\n test'" ), (&tok.1, &*tok.1.to_string()) ); Ok(()) } #[test] fn verify_hexfloat() -> Result<()> { let input = r#"0x1.999999999999ap-4"#; let mut l = Lexer::new(input); let tok = l.next_token()?; assert_eq!( (&VALUE(Value::FLOAT(0.1)), "0.1"), (&tok.1, &*tok.1.to_string()) ); Ok(()) } #[test] fn verify_exponent() -> Result<()> { let input = r#"-100.7e-1"#; let mut l = Lexer::new(input); let tok = l.next_token()?; assert_eq!( (&VALUE(Value::FLOAT(-10.07)), "-10.07"), (&tok.1, &*tok.1.to_string()) ); Ok(()) } #[test] fn verify_lexer_diagnostic() -> Result<()> { let input = r#"myrule = number .asdf 10"#; let mut l = Lexer::new(input); l.next_token()?; l.next_token()?; l.next_token()?; match l.next_token() { Ok(_) => Ok(()), Err(e) => { #[cfg(feature = "std")] println!("{}", e); assert_eq!( e.to_string(), indoc!( r#" error: lexer error ┌─ input:1:17 │ 1 │ myrule = number .asdf 10 │ ^^^^^ invalid control operator "# ) ); Ok(()) } } } } cddl-0.9.4/src/lib.rs000064400000000000000000000704071046102023000124570ustar 00000000000000//! # cddl-rs //! //! [![crates.io](https://img.shields.io/crates/v/cddl.svg)](https://crates.io/crates/cddl) //! [![docs.rs](https://docs.rs/cddl/badge.svg)](https://docs.rs/cddl) //! [![Publish //! packages](https://github.com/anweiss/cddl/workflows/Publish%20packages/badge.svg?branch=0.9.3&event=release)](https://github.com/anweiss/cddl/actions?query=workflow%3A%22Publish+packages%22) //! [![Build and //! Test](https://github.com/anweiss/cddl/workflows/Build%20and%20Test/badge.svg)](https://github.com/anweiss/cddl/actions?query=workflow%3A%22Build+and+Test%22) //! [![Active //! Development](https://img.shields.io/badge/Maintenance%20Level-Actively%20Developed-brightgreen.svg)](https://gist.github.com/cheerfulstoic/d107229326a01ff0f333a1d3476e068d) //! //! > This crate was originally developed as a personal learning exercise for //! > getting acquainted with Rust and parsing in general. There are likely more //! > performant and stable libraries out there for parsing CDDL. While there //! > are some examples of this crate being used in production, careful //! > consideration should be made prior to using this crate as such. //! //! A Rust implementation of the Concise data definition language (CDDL). CDDL //! is an IETF standard that "proposes a notational convention to express CBOR //! and JSON data structures." As of 2019-06-12, it is published as RFC 8610 //! (Proposed Standard) at //! [https://tools.ietf.org/html/rfc8610](https://tools.ietf.org/html/rfc8610). //! //! This crate includes a handwritten parser and lexer for CDDL, and its //! development has been heavily inspired by the techniques outlined in Thorsten //! Ball's book ["Writing An Interpretor In Go"](https://interpreterbook.com/). //! The AST has been built to closely match the rules defined by the ABNF //! grammar in [Appendix B.](https://tools.ietf.org/html/rfc8610#appendix-B) of //! the spec. All CDDL must use UTF-8 for its encoding per the spec. //! //! This crate supports validation of both CBOR and JSON data structures. An //! extremely basic REPL is included as well. This crate's minimum supported //! Rust version (MSRV) is 1.67.0. //! //! Also bundled into this repository is a basic language server implementation //! and extension for Visual Studio Code for editing CDDL. The implementation is //! backed by the compiled WebAssembly target included in this crate. //! //! ## Goals //! //! - [x] Parse CDDL documents into an AST //! - [x] Verify conformance of CDDL documents against RFC 8610 //! - [x] Validate CBOR data structures //! - [x] Validate JSON documents //! - [x] Basic REPL //! - [ ] Generate dummy JSON from conformant CDDL //! - [x] As close to zero-copy as possible //! - [x] Compile WebAssembly target for browser and Node.js //! - [x] `no_std` support (lexing and parsing only) //! - [x] Language server implementation and Visual Studio Code Extension //! //! ## Non-goals //! //! - Performance (if this crate gains enough traction, it may be prudent to //! conduct more formal profiling and/or explore using a parser-combinator //! framework like [nom](https://github.com/Geal/nom)) //! - Support CBOR diagnostic notation //! - I-JSON compatibility //! //! ## Why Rust? //! //! Rust is a systems programming language designed around safety and is //! ideally-suited for resource-constrained systems. CDDL and CBOR are designed //! around small code and message sizes and constrained nodes, scenarios for //! which Rust has also been designed. //! //! ## CLI //! //! A CLI is available for various platforms. The tool supports parsing of CDDL //! files for verifying conformance against RFC 8610. It can also be used to //! validate JSON documents and CBOR binary files against CDDL documents. //! Detailed information about the JSON and CBOR validation implementation can //! be found in the sections below. //! //! ### Installation //! //! #### GitHub Releases //! //! Binaries for Linux, macOS and Windows can be downloaded from GitHub //! [Releases](https://github.com/anweiss/cddl/releases). //! //! #### Cargo //! //! ```sh //! cargo install cddl //! ``` //! //! #### Docker //! //! ```sh //! docker pull ghcr.io/anweiss/cddl-cli:latest //! ``` //! //! ### CLI usage //! //! Instructions for using the tool can be viewed by executing the `help` //! subcommand: //! //! ```sh //! cddl help //! ``` //! //! If using Docker: //! //! > Replace `` with an appropriate //! > [release](https://github.com/anweiss/cddl/releases) tag. Requires use of //! > the `--volume` argument for mounting `.cddl` documents into the container //! > when executing the command. JSON or CBOR files can either be included in //! > the volume mount or passed into the command via STDIN. //! //! ```sh //! docker run -it --rm -v $PWD:/cddl -w /cddl ghcr.io/anweiss/cddl-cli: help //! ``` //! //! You can validate JSON documents and/or CBOR binary files: //! //! ```sh //! cddl validate [OPTIONS] --cddl <--stdin|--json ...|--cbor ...> //! ``` //! //! It also supports validating files from STDIN (if it detects the input as //! valid UTF-8, it will attempt to validate the input as JSON, otherwise it //! will treat it as CBOR): //! //! ```sh //! cat reputon.json | cddl validate --cddl reputon.cddl --stdin //! cat reputon.cbor | cddl validate --cddl reputon.cddl --stdin //! ``` //! //! or using Docker: //! //! ```sh //! docker run -i --rm -v $PWD:/data -w /data ghcr.io/anweiss/cddl-cli:0.9.3 validate --cddl reputon.cddl --stdin < reputon.json //! ``` //! //! ## Website //! //! You can also find a simple RFC 8610 conformance tool at //! [https://cddl.anweiss.tech](https://cddl.anweiss.tech). This same codebase //! has been compiled for use in the browser via WebAssembly. //! //! ## Visual Studio Code extension //! //! An extension for editing CDDL documents with Visual Studio Code has been //! published to the Marketplace //! [here](https://marketplace.visualstudio.com/items?itemName=anweiss.cddl-languageserver). //! You can find more information in the [README](cddl-lsp/README.md). //! //! ## Supported features //! //! - [x] maps //! - [x] structs //! - [x] tables //! - [x] cuts //! - [x] groups //! - [x] arrays //! - [x] values //! - [x] choices //! - [x] ranges //! - [x] enumeration (building a choice from a group) //! - [x] root type //! - [x] occurrence //! - [x] predefined types //! - [x] tags //! - [x] unwrapping //! - [x] controls //! - [x] socket/plug //! - [x] generics //! - [x] operator precedence //! - [x] comments //! - [x] numerical int/uint values //! - [x] numerical hexfloat values //! - [x] numerical values with exponents //! - [x] unprefixed byte strings //! - [x] prefixed byte strings //! //! ## Usage //! //! Simply add the dependency to `Cargo.toml`: //! //! ```toml //! [dependencies] //! cddl = "0.9.3" //! ``` //! //! Both JSON and CBOR validation require `std`. //! //! ### Feature flags //! //! A few convenience features have been included to make the AST more concise //! and for enabling additional functionality. You can build with //! `default-features = false` for a `no_std` build and selectively enable any //! of the features below. //! //! **`--feature ast-span`** //! //! Add the `Span` type to the AST for keeping track of the position of the //! lexer and parser. Enabled by default. //! //! **`--feature ast-comments`** //! //! Include comment strings in the AST. Enabled by default. //! //! **`--feature ast-parent`** //! //! Add the `ParentVisitor` implementation so that the AST can be traversed //! using parent pointers. Enabled by default. //! //! **`--feature json`** //! //! Enable JSON validation. Enabled by default. //! //! **`--feature cbor`** //! //! Enable CBOR validation. Enabled by default. //! //! **`--feature additional-controls`** //! //! Enable validation support for the additional control operators defined in //! [RFC 9165](https://datatracker.ietf.org/doc/html/rfc9165). Enabled by //! default. //! //! ### Parsing CDDL //! //! ```rust //! use cddl::cddl_from_str; //! //! let input = r#"myrule = int"#; //! assert!(cddl_from_str(input, true).is_ok()) //! ``` //! //! ### Validating JSON //! //! ```rust //! use cddl::validate_json_from_str; //! //! let cddl = r#"person = { //! name: tstr, //! age: uint, //! address: tstr, //! }"#; //! //! let json = r#"{ //! "name": "John", //! "age": 50, //! "address": "1234 Lakeshore Dr" //! }"#; //! //! #[cfg(not(feature = "additional-controls"))] //! assert!(validate_json_from_str(cddl, json).is_ok()) //! ``` //! //! This crate uses the [Serde](https://serde.rs/) framework, and more //! specifically, the [serde_json](https://crates.io/crates/serde_json) crate, //! for parsing and validating JSON. Serde was chosen due to its maturity in the //! ecosystem and its support for serializing and deserializing CBOR via the //! [ciborium](https://crates.io/crates/ciborium) crate. //! //! As outlined in [Appendix E.](https://tools.ietf.org/html/rfc8610#appendix-E) //! of the standard, only the JSON data model subset of CBOR can be used for //! validation. The limited prelude from the spec has been included below for //! brevity: //! //! ```cddl //! any = # //! //! uint = #0 //! nint = #1 //! int = uint / nint //! //! tstr = #3 //! text = tstr //! //! number = int / float //! //! float16 = #7.25 //! float32 = #7.26 //! float64 = #7.27 //! float16-32 = float16 / float32 //! float32-64 = float32 / float64 //! float = float16-32 / float64 //! //! false = #7.20 //! true = #7.21 //! bool = false / true //! nil = #7.22 //! null = nil //! ``` //! //! Furthermore, the following data types from the standard prelude can be used //! for validating JSON strings and numbers: //! //! ```cddl //! tdate = #6.0(tstr) //! uri = #6.32(tstr) //! b64url = #6.33(tstr) //! time = #6.1(number) //! ``` //! //! The first non-group rule defined by a CDDL data structure definition //! determines the root type, which is subsequently used for validating the //! top-level JSON data type. //! //! #### Supported JSON validation features //! //! The following types and features of CDDL are supported by this crate for //! validating JSON: //! //! | CDDL | JSON | //! | ---------------------- | ----------------------------------------------------------- | //! | structs | objects | //! | arrays | arrays[1](#arrays) | //! | `text / tstr` | string | //! | `uri` | string (valid RFC3986 URI) | //! | `tdate` | string (valid RFC3339 date/time) | //! | `b64url` | string (base64url-encoded) | //! | `time` | number (valid UNIX timestamp integer in seconds) | //! | `number / int / float` | number[2](#number) | //! | `bool / true / false` | boolean | //! | `null / nil` | null | //! | `any` | any valid JSON | //! | byte strings | not yet implemented | //! | unwrap (`~`) | any JSON that matches unwrapped type from map, array or tag | //! //! CDDL groups, generics, sockets/plugs and group-to-choice enumerations can //! all be used when validating JSON. //! //! Since JSON objects only support keys whose types are JSON strings, when //! validating JSON, member keys defined in CDDL structs must use either the //! colon syntax (`mykey: tstr` or `"mykey": tstr`) or the double arrow syntax //! provided that the member key is either a text string value (`"mykey" => //! tstr`) or a bareword that resolves to either a string data type (`text` or //! `tstr`) or another text string value (`* tstr => any`). //! //! Occurrence indicators can be used to validate key/value pairs in a JSON //! object and the number of elements in a JSON array; depending on how the //! indicators are defined in a CDDL data definition. //! //! Below is the table of supported control operators: //! //! | Control operator | Supported | //! | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | //! | `.pcre` | ✔️[3](#regex) | //! | `.regex` | ✔️[3](#regex) (alias for `.pcre`) | //! | `.size` | ✔️ | //! | `.bits` | Ignored when validating JSON | //! | `.cbor` | Ignored when validating JSON | //! | `.cborseq` | Ignored when validating JSON | //! | `.within` | ✔️ | //! | `.and` | ✔️ | //! | `.lt` | ✔️ | //! | `.le` | ✔️ | //! | `.gt` | ✔️ | //! | `.ge` | ✔️ | //! | `.eq` | ✔️ | //! | `.ne` | ✔️ | //! | `.default` | ✔️ | //! //! 1: When groups with multiple group entries are used to //! validate arrays, occurrence indicators are "greedy" in that only the first //! occurrence indicator that is come across is used in the validation. //! Subsequent entries with occurrence indicators are ignored due to //! complexities involved with processing these ambiguities. For proper JSON //! validation, avoid writing CDDL that looks like the following: `[ * a: int, //! b: tstr, ? c: int ]`. //! //! 2: While JSON itself does not distinguish between //! integers and floating-point numbers, this crate does provide the ability to //! validate numbers against a more specific numerical CBOR type, provided that //! its equivalent representation is allowed by JSON. Refer to [Appendix //! E.](https://tools.ietf.org/html/rfc8610#appendix-E) of the standard for more //! details on the implications of using CDDL with JSON numbers. //! //! 3: Due to Perl-Compatible Regular Expressions (PCREs) //! being more widely used than XSD regular expressions, this crate also //! provides support for the proposed `.pcre` control extension in place of the //! `.regexp` operator (see //! [Discussion](https://tools.ietf.org/html/rfc8610#section-3.8.3.2) and //! [CDDL-Freezer //! proposal](https://tools.ietf.org/html/draft-bormann-cbor-cddl-freezer-03#section-5.1)). //! Ensure that your regex string is properly JSON escaped when using this //! control. //! //! If you've enabled the `additional-controls` feature, the table of controls //! below is also available for use: //! //! | Control operator | Supported | //! | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | //! | `.plus` | ✔️ | //! | `.cat` | ✔️ | //! | `.det` | ✔️ | //! | `.abnf` | ✔️ | //! | `.abnfb` | Ignored when validating JSON | //! | `.feature` | ✔️ | //! //! You can activate features during validation as follows: //! //! ```rust //! use cddl::validate_json_from_str; //! //! let cddl = r#" //! v = JC<"v", 2> //! JC = C .feature "cbor" / J .feature "json" //! "#; //! //! let json = r#""v""#; //! //! #[cfg(not(feature = "additional-controls"))] //! assert!(validate_json_from_str(cddl, json, Some(&["json"])).is_ok()) //! ``` //! //! #### Comparing with JSON schema and JSON schema language //! //! [CDDL](https://tools.ietf.org/html/rfc8610), [JSON //! schema](https://json-schema.org/) and [JSON schema //! language](https://tools.ietf.org/html/draft-json-schema-language-02) can all //! be used to define JSON data structures. However, the approaches taken to //! develop each of these are vastly different. A good place to find past //! discussions on the differences between these formats is the [IETF mail //! archive](https://mailarchive.ietf.org/arch/), specifically in the JSON and //! CBOR lists. The purpose of this crate is not to argue for the use of CDDL //! over any one of these formats, but simply to provide an example //! implementation in Rust. //! //! ### Validating CBOR //! //! ```rust //! use cddl::validate_cbor_from_slice; //! //! let cddl = r#"rule = false"#; //! //! let cbor = b"\xF4"; //! //! #[cfg(not(feature = "additional-controls"))] //! assert!(validate_cbor_from_slice(cddl, cbor).is_ok()) //! ``` //! //! This crate also uses [Serde](https://serde.rs/) and //! [ciborium](https://crates.io/crates/ciborium) for validating CBOR data //! structures. CBOR validation is done via the loosely typed //! [`ciborium::value::Value`](https://github.com/enarx/ciborium/blob/main/ciborium/src/value/mod.rs#L22) //! enum. In addition to all of the same features implemented by the JSON //! validator, this crate also supports validating CBOR tags (e.g. //! `#6.32(tstr)`), CBOR major types (e.g. `#1.2`), table types (e.g. `{ [ + //! tstr ] => int }`) and byte strings. The `.bits`, `.cbor` and `.cborseq` //! control operators are all supported as well. //! //! The following tags are supported when validating CBOR: //! //! | Tag | Supported | //! | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | //! | `tdate = #6.0(tstr)` | ✔️ | //! | `time = #6.1(number)` | ✔️ | //! | `biguint = #6.2(bstr)` | ✔️ | //! | `bignint = #6.3(bstr)` | ✔️ | //! | `decfrac = #6.4([e10: int, m: integer])` | not yet implemented | //! | `bigfloat = #6.5([e2: int, m: integer])` | not yet implemented | //! | `eb64url = #6.21(any)` | ✔️ | //! | `eb64legacy = #6.22(any)` | ✔️ | //! | `eb16 = #6.23(any)` | ✔️ | //! | `encoded-cbor = #6.24(bstr)` | ✔️ | //! | `uri = #6.32(tstr)` | ✔️ | //! | `b64url = #6.33(tstr)` | ✔️ | //! | `b64legacy = #6.34(tstr)` | ✔️ | //! | `regexp = #6.35(tstr)` | ✔️ | //! | `mime-message = #6.36(tstr)` | ✔️ | //! | `cbor-any = #6.55799(any)` | ✔️ | //! //! If you've enabled the `additional-controls` feature, the table of controls //! below is also available for use: //! //! | Control operator | Supported | //! | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | //! | `.plus` | ✔️ | //! | `.cat` | ✔️ | //! | `.det` | ✔️ | //! | `.abnf` | ✔️ | //! | `.abnfb` | ✔️ | //! | `.feature` | ✔️ | //! //! You can activate features during validation by passing a slice of feature //! strings as follows: //! //! ```rust //! use cddl::validate_cbor_from_slice; //! //! let cddl = r#" //! v = JC<"v", 2> //! JC = C .feature "cbor" / J .feature "json" //! "#; //! //! let cbor = b"\x02"; //! //! assert!(validate_cbor_from_slice(cddl, cbor, Some(&["cbor"])).is_ok()) //! ``` //! //! ## `no_std` support //! //! Only the lexer and parser can be used in a `no_std` context provided that a //! heap allocator is available. This can be enabled by opting out of the //! default features in your `Cargo.toml` file as follows: //! //! ```toml //! [dependencies] //! cddl = { version = "0.9.3", default-features = false } //! ``` //! //! Zero-copy parsing is implemented to the extent that is possible. Allocation //! is required for error handling and diagnostics. //! //! Both JSON and CBOR validation are dependent on their respective heap //! allocated `Value` types, but since these types aren't supported in a //! `no_std` context, they subsequently aren't supported by this crate in //! `no_std`. //! //! ## Projects using this crate //! //! Below are some known projects that leverage this crate: //! //! - [https://github.com/Emurgo/cddl-codegen](https://github.com/Emurgo/cddl-codegen) //! - [https://github.com/p2panda/p2panda](https://github.com/p2panda/p2panda) //! #![allow(dead_code)] #![cfg_attr(not(feature = "std"), no_std)] #![warn(missing_docs)] #[macro_use] #[cfg(not(feature = "std"))] extern crate alloc; #[cfg(not(feature = "std"))] extern crate core as std; #[cfg(feature = "std")] extern crate serde_json; #[cfg(feature = "std")] extern crate uriparse; #[cfg(feature = "std")] extern crate base64_url; /// Abstract syntax tree representing a CDDL definition pub mod ast; /// Static error messages #[allow(missing_docs)] pub mod error; /// Lexer for CDDL pub mod lexer; /// Parser for CDDL pub mod parser; /// CDDL tokens for lexing pub mod token; /// Validators for JSON and CBOR data structures #[cfg(feature = "std")] pub mod validator; /// CDDL AST visitor pub mod visitor; mod parser_tests; #[doc(inline)] pub use self::{ lexer::lexer_from_str, parser::{cddl_from_str, Error}, token::Token, }; #[doc(inline)] #[cfg(feature = "std")] #[cfg(feature = "cbor")] #[cfg(not(feature = "lsp"))] #[cfg(not(target_arch = "wasm32"))] pub use self::validator::validate_cbor_from_slice; #[doc(inline)] #[cfg(feature = "std")] #[cfg(feature = "json")] #[cfg(not(feature = "lsp"))] #[cfg(not(target_arch = "wasm32"))] pub use self::validator::validate_json_from_str; cddl-0.9.4/src/parser.rs000064400000000000000000003325751046102023000132140ustar 00000000000000use super::{ ast::*, error::{ ErrorMsg, MsgType::{self, *}, }, lexer::{self, Position}, token::{self, SocketPlug, Token}, }; use std::{cmp::Ordering, marker::PhantomData, mem, result}; use codespan_reporting::{ diagnostic::{Diagnostic, Label}, files::SimpleFiles, term, }; use displaydoc::Display; #[cfg(feature = "std")] use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; #[cfg(feature = "std")] use std::{borrow::Cow, collections::BTreeSet, rc::Rc}; #[cfg(not(feature = "std"))] use alloc::{ borrow::{Cow, ToOwned}, boxed::Box, collections::BTreeSet, rc::Rc, string::{String, ToString}, vec::Vec, }; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] use serde::Serialize; /// Alias for `Result` with an error of type `cddl::ParserError` pub type Result = result::Result; /// Parser type pub struct Parser<'a> { tokens: Box> + 'a>, str_input: &'a str, cur_token: Token<'a>, peek_token: Token<'a>, lexer_position: Position, peek_lexer_position: Position, #[cfg(feature = "ast-span")] parser_position: Position, /// Vec of collected parsing errors pub errors: Vec, current_rule_generic_param_idents: Option>, typenames: Rc>, groupnames: Rc>, #[cfg(feature = "ast-span")] unknown_rule_idents: Vec<(&'a str, Span)>, #[cfg(not(feature = "ast-span"))] unknown_rule_idents: Vec<&'a str>, is_guaranteed: bool, } /// Parsing error types #[derive(Debug, Display)] pub enum Error { /// Parsing errors #[displaydoc("{0}")] CDDL(String), #[cfg_attr( feature = "ast-span", displaydoc("parsing error: position {position:?}, msg: {msg}") )] #[cfg_attr(not(feature = "ast-span"), displaydoc("parsing error: msg: {msg}"))] /// Parsing error occurred PARSER { /// Error position #[cfg(feature = "ast-span")] position: Position, /// Error message msg: ErrorMsg, }, #[displaydoc("{0}")] /// Lexing error LEXER(lexer::Error), /// Regex error #[displaydoc("regex parsing error: {0}")] REGEX(regex::Error), #[displaydoc("incremental parsing error")] /// Incremental parsing error INCREMENTAL, #[displaydoc("defer parsing error")] /// Incremental parsing error GROUP, } #[cfg(feature = "std")] impl std::error::Error for Error {} impl<'a> Parser<'a> { /// Create a new `Parser` from a given str input and iterator over /// `lexer::Item`. /// /// # Example /// /// ``` /// use cddl::parser::Parser; /// use cddl::lexer::Lexer; /// /// let input = r#"mycddl = ( int / float )"#; /// let p = Parser::new(input, Box::new(Lexer::new(input).iter())); /// ``` pub fn new( str_input: &'a str, tokens: Box> + 'a>, ) -> Result> { let mut p = Parser { tokens, str_input, cur_token: Token::EOF, peek_token: Token::EOF, errors: Vec::default(), lexer_position: Position::default(), peek_lexer_position: Position::default(), #[cfg(feature = "ast-span")] parser_position: Position::default(), current_rule_generic_param_idents: None, typenames: Rc::new(BTreeSet::from([ "any", "uint", "nint", "int", "bstr", "bytes", "tstr", "text", "tdate", "time", "number", "biguint", "bignint", "bigint", "integer", "unsigned", "decfrac", "bigfloat", "eb64url", "eb64legacy", "eb16", "encoded-cbor", "uri", "b64url", "b64legacy", "regexp", "mime-message", "cbor-any", "float16", "float32", "float64", "float16-32", "float32-64", "float", "false", "true", "bool", "nil", "null", "undefined", ])), groupnames: Rc::new(BTreeSet::default()), unknown_rule_idents: Vec::default(), is_guaranteed: false, }; p.next_token()?; p.next_token()?; Ok(p) } /// Print parser errors if there are any. Used with the `Error::PARSER` /// variant /// /// # Arguments /// /// * `to_stderr` - When true, outputs formatted errors to stderr /// /// # Example /// /// ``` /// use cddl::parser::{Error, Parser}; /// use cddl::lexer::Lexer; /// /// let input = r#"mycddl = ( int / float )"#; /// if let Ok(mut p) = Parser::new(input, Box::new(Lexer::new(input).iter())) { /// if let Err(Error::INCREMENTAL) = p.parse_cddl() { /// let _ = p.report_errors(true); /// } /// } /// ``` #[cfg(feature = "std")] pub fn report_errors( &self, to_stderr: bool, ) -> std::result::Result, Box> { if self.errors.is_empty() { return Ok(None); } let mut files = SimpleFiles::new(); let file_id = files.add("input", self.str_input); let mut labels = Vec::new(); for error in self.errors.iter() { if let Error::PARSER { #[cfg(feature = "ast-span")] position, msg, } = error { labels.push( #[cfg(feature = "ast-span")] Label::primary(file_id, position.range.0..position.range.1).with_message(msg.to_string()), #[cfg(not(feature = "ast-span"))] Label::primary(file_id, 0..0).with_message(msg.to_string()), ); } } let diagnostic = Diagnostic::error() .with_message("parser errors") .with_labels(labels); let config = term::Config::default(); if to_stderr { let writer = StandardStream::stderr(ColorChoice::Auto); // TODO: Use `map_or_else()` once it is determined this crate should set // its minimum version to 1.41 match term::emit(&mut writer.lock(), &config, &files, &diagnostic) { Ok(_) => return Ok(None), Err(e) => return Err(Box::from(e)), }; } let mut buffer = Vec::new(); let mut writer = term::termcolor::NoColor::new(&mut buffer); term::emit(&mut writer, &config, &files, &diagnostic)?; Ok(Some(String::from_utf8(buffer)?)) } /// Print parser errors if there are any. Used with the `Error::PARSER` /// variant /// /// # Example /// /// ``` /// use cddl::parser::{Error, Parser}; /// use cddl::lexer::Lexer; /// /// let input = r#"mycddl = ( int / float )"#; /// if let Ok(mut p) = Parser::new(Lexer::new(input).iter(), input) { /// if let Err(Error::PARSER) = p.parse_cddl() { /// let _ = p.report_errors(); /// } /// } /// ``` #[cfg(not(feature = "std"))] pub fn report_errors(&self) -> Option { if self.errors.is_empty() { return None; } let mut files = SimpleFiles::new(); let file_id = files.add("input", self.str_input); let mut labels = Vec::new(); for error in self.errors.iter() { if let Error::PARSER { #[cfg(feature = "ast-span")] position, msg, } = error { labels.push( #[cfg(feature = "ast-span")] Label::primary(file_id, position.range.0..position.range.1).with_message(msg.to_string()), #[cfg(not(feature = "ast-span"))] Label::primary(file_id, 0..0).with_message(msg.to_string()), ); } } let diagnostic = Diagnostic::error() .with_message("parser errors") .with_labels(labels); let config = term::Config::default(); let mut buffer = Vec::new(); let mut writer = term::termcolor::NoColor::new(&mut buffer); term::emit(&mut writer, &config, &files, &diagnostic).ok()?; String::from_utf8(buffer).ok() } fn next_token(&mut self) -> Result<()> { mem::swap(&mut self.cur_token, &mut self.peek_token); mem::swap(&mut self.lexer_position, &mut self.peek_lexer_position); if let Some(next_token) = self.tokens.next() { let nt = next_token.map_err(Error::LEXER)?; self.peek_token = nt.1; self.peek_lexer_position = nt.0; } Ok(()) } fn advance_to_next_rule(&mut self) -> Result<()> { let mut is_possible_rule = false; while !is_possible_rule { self.next_token()?; if let Token::IDENT(..) = self.cur_token { match self.peek_token { Token::ASSIGN | Token::TCHOICEALT | Token::GCHOICEALT => is_possible_rule = true, _ => continue, } } else if let Token::EOF = self.cur_token { is_possible_rule = true; } } Ok(()) } #[cfg(feature = "ast-comments")] fn collect_comments(&mut self) -> Result>> { #[cfg_attr(not(feature = "lsp"), allow(unused_mut))] let mut comments: Option = None; while let Token::COMMENT(_comment) = self.cur_token { #[cfg(not(feature = "lsp"))] comments.get_or_insert(Comments::default()).0.push(_comment); self.next_token()?; } while let Token::NEWLINE = self.cur_token { #[cfg(feature = "lsp")] comments.get_or_insert(Comments::default()).0.push("\n"); self.next_token()?; } if let Token::COMMENT(_) = self.cur_token { if let Some(c) = self.collect_comments()? { #[cfg_attr(not(feature = "lsp"), allow(unused_mut))] for comment in c.0.iter() { comments.get_or_insert(Comments::default()).0.push(comment); } } } Ok(comments) } #[cfg(not(feature = "ast-comments"))] fn advance_newline(&mut self) -> Result<()> { while let Token::NEWLINE = self.cur_token { #[cfg(feature = "lsp")] comments.get_or_insert(Comments::default()).0.push("\n"); self.next_token()?; } Ok(()) } fn register_rule(&mut self, rule: &Rule<'a>) { match &rule { Rule::Type { rule, .. } => Rc::make_mut(&mut self.typenames).insert(rule.name.ident), Rule::Group { rule, .. } => Rc::make_mut(&mut self.groupnames).insert(rule.name.ident), }; } /// Parses into a `CDDL` AST pub fn parse_cddl(&mut self) -> Result> { #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; let mut c = CDDL { #[cfg(feature = "ast-comments")] comments: self.collect_comments()?, ..Default::default() }; struct UnknownRule<'a> { rule: Rule<'a>, index: usize, range: (usize, usize), } let mut unknown_rules = Vec::default(); while self.cur_token != Token::EOF { let begin_rule_range = self.lexer_position.range.0; match self.parse_rule(false) { Ok(r) => { let rule_exists = |existing_rule: &Rule| r.name() == existing_rule.name() && !r.is_choice_alternate(); if c.rules.iter().any(rule_exists) { #[cfg(feature = "ast-span")] { self.parser_position.range = (r.span().0, r.span().1); self.parser_position.line = r.span().2; } self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.parser_position, msg: DuplicateRuleIdentifier.into(), }); continue; } if !self.unknown_rule_idents.is_empty() { if self.is_guaranteed { self.register_rule(&r); } unknown_rules.push(UnknownRule { rule: r, index: c.rules.len(), range: (begin_rule_range, self.lexer_position.range.1), }); self.unknown_rule_idents = Vec::default(); } else { self.register_rule(&r); c.rules.push(r); } self.is_guaranteed = false; } Err(Error::INCREMENTAL) => { if !self.cur_token_is(Token::EOF) { self.advance_to_next_rule()?; } } Err(e) => return Err(e), } } // In practice unknown rules usually are declared backwards, so we reverse // it here. unknown_rules.reverse(); // Try to specialize unknown rules until the set of them stabilizes. { let mut errors; let mut known_rules = Vec::default(); loop { let mut resolved_rules = Vec::default(); let mut unresolved_rules = Vec::default(); errors = Vec::default(); for unknown_rule in unknown_rules { match self.resolve_rule(unknown_rule.range, false) { Ok(rule) => resolved_rules.push((unknown_rule.index, rule)), Err(_) => match self.resolve_rule(unknown_rule.range, true) { Ok(rule) => resolved_rules.push((unknown_rule.index, rule)), Err(mut error) => { errors.append(&mut error); unresolved_rules.push(unknown_rule); } }, } } if resolved_rules.is_empty() { break; } for (_, rule) in &resolved_rules { self.register_rule(rule); } known_rules.append(&mut resolved_rules); unknown_rules = unresolved_rules; } self.errors.append(&mut errors); known_rules.sort_by(|(a, _), (b, _)| b.partial_cmp(a).unwrap()); for (index, rule) in known_rules { c.rules.insert(index, rule); } } if !self.errors.is_empty() { return Err(Error::INCREMENTAL); } if c.rules.is_empty() { self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.parser_position, msg: NoRulesDefined.into(), }); return Err(Error::INCREMENTAL); } Ok(c) } fn resolve_rule( &mut self, range: (usize, usize), parse_group_rule: bool, ) -> result::Result, Vec> { let tokens = Box::new(lexer::Lexer::new(&self.str_input[range.0..range.1]).iter()); let mut parser = Parser::new(self.str_input, tokens).map_err(|err| vec![err])?; parser.groupnames = self.groupnames.clone(); parser.typenames = self.typenames.clone(); let rule = parser .parse_rule(parse_group_rule) .map_err(|err| vec![err])?; if !parser.unknown_rule_idents.is_empty() { Err( #[cfg(feature = "ast-span")] parser .unknown_rule_idents .into_iter() .map(|(ident, span)| Error::PARSER { position: Position { column: 0, index: span.0, line: span.2, range: (span.0 + range.0, span.1 + range.0), }, msg: ErrorMsg { short: format!("missing definition for rule {}", ident), extended: None, }, }) .collect(), #[cfg(not(feature = "ast-span"))] parser .unknown_rule_idents .into_iter() .map(|ident| Error::PARSER { msg: ErrorMsg { short: format!("missing definition for rule {}", ident), extended: None, }, }) .collect(), ) } else { Ok(rule) } } #[allow(missing_docs)] pub fn parse_rule(&mut self, parse_group_rule: bool) -> Result> { #[cfg(feature = "ast-span")] let begin_rule_range = self.lexer_position.range.0; #[cfg(feature = "ast-span")] let begin_rule_line = self.lexer_position.line; #[cfg(feature = "ast-span")] let begin_rule_col = self.lexer_position.column; let ident = match &self.cur_token { Token::IDENT(i, s) => self.identifier_from_ident_token(i, *s), _ => { #[cfg(feature = "ast-span")] { self.parser_position.range = self.lexer_position.range; self.parser_position.line = self.lexer_position.line; } self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.parser_position, msg: InvalidRuleIdentifier.into(), }); return Err(Error::INCREMENTAL); } }; let gp = if self.peek_token_is(&Token::LANGLEBRACKET) { self.next_token()?; let params = self.parse_genericparm()?; let mut param_list = Vec::default(); for param in params.params.iter() { param_list.push(param.param.ident); } self.current_rule_generic_param_idents = Some(param_list); Some(params) } else { None }; #[cfg(feature = "ast-comments")] let comments_before_assign = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; if !self.expect_peek(&Token::ASSIGN)? && !self.expect_peek(&Token::TCHOICEALT)? && !self.expect_peek(&Token::GCHOICEALT)? { #[cfg(feature = "ast-span")] { self.parser_position.range = (begin_rule_range, self.lexer_position.range.1); self.parser_position.line = self.lexer_position.line; } self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.parser_position, msg: MsgType::MissingAssignmentToken.into(), }); return Err(Error::INCREMENTAL); } let mut is_type_choice_alternate = false; let mut is_group_choice_alternate = false; if let Token::TCHOICEALT = &self.cur_token { is_type_choice_alternate = true; } else if let Token::GCHOICEALT = &self.cur_token { is_group_choice_alternate = true; } if let Some(socket) = &ident.socket { match socket { SocketPlug::TYPE if !is_type_choice_alternate => { #[cfg(feature = "ast-span")] { self.parser_position.range = (begin_rule_range, self.lexer_position.range.1); self.parser_position.line = self.lexer_position.line; } self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.parser_position, msg: MsgType::TypeSocketNamesMustBeTypeAugmentations.into(), }); return Err(Error::INCREMENTAL); } SocketPlug::GROUP if !is_group_choice_alternate => { #[cfg(feature = "ast-span")] { self.parser_position.range = (begin_rule_range, self.lexer_position.range.1); self.parser_position.line = self.lexer_position.line; } self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.parser_position, msg: MsgType::GroupSocketNamesMustBeGroupAugmentations.into(), }); return Err(Error::INCREMENTAL); } _ => (), } } self.next_token()?; #[cfg(feature = "ast-comments")] let comments_after_assign = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; // If token is group socket or rule is a group plug alternative, parse // as group rule if matches!(self.cur_token, Token::IDENT(_, Some(SocketPlug::GROUP))) || is_group_choice_alternate || parse_group_rule { let ge = self.parse_grpent(true)?; #[cfg(feature = "ast-comments")] let comments_after_rule = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; #[cfg(feature = "ast-span")] let span = ( begin_rule_range, self.parser_position.range.1, begin_rule_line, ); self.current_rule_generic_param_idents = None; self.is_guaranteed = true; return Ok(Rule::Group { rule: Box::from(GroupRule { name: ident, generic_params: gp, is_group_choice_alternate, entry: ge, #[cfg(feature = "ast-comments")] comments_before_assigng: comments_before_assign, #[cfg(feature = "ast-comments")] comments_after_assigng: comments_after_assign, }), #[cfg(feature = "ast-comments")] comments_after_rule, #[cfg(feature = "ast-span")] span, }); } match self.cur_token { Token::LPAREN | Token::ASTERISK | Token::ONEORMORE | Token::OPTIONAL => { #[cfg(feature = "ast-span")] let begin_pt_range = self.lexer_position.range.0; let ge = self.parse_grpent(true)?; #[cfg(feature = "ast-span")] let mut end_rule_range = self.parser_position.range.1; #[cfg(feature = "ast-comments")] let comments_after_rule = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; // If a group entry is an inline group with no leading occurrence // indicator, and its group has only a single element that is not // preceded by an occurrence indicator nor member key, then there are // two valid interpretations: either it's a parenthesized inline group // with a type or a parenthesized type. Both cases are interpreted in // the same way, but according to the BNF, the parenthesized type takes // priority. // // A priori, we coerce this group into a parenthesized type. This is one // of the few situations where `clone` is required if let GroupEntry::InlineGroup { occur: None, group, #[cfg(feature = "ast-comments")] comments_before_group, #[cfg(feature = "ast-comments")] comments_after_group, .. } = &ge { if group.group_choices.len() == 1 { if let Some(gc) = group.group_choices.get(0) { if gc.group_entries.len() == 1 { if let Some(group_entry) = gc.group_entries.get(0) { // Check that there is no trailing comma if !group_entry.1.optional_comma { // EXAMPLE: non-empty = (M) .and ({ + any => any }) if let GroupEntry::TypeGroupname { ge, #[cfg(feature = "ast-comments")] leading_comments, #[cfg(feature = "ast-comments")] trailing_comments, .. } = &group_entry.0 { if ge.occur.is_none() && matches!(self.cur_token, Token::ControlOperator(_)) { let value = self.parse_type(Some(Type2::ParenthesizedType { #[cfg(feature = "ast-comments")] comments_before_type: comments_before_group.clone(), pt: Type { type_choices: vec![TypeChoice { #[cfg(feature = "ast-comments")] comments_before_type: leading_comments.clone(), #[cfg(feature = "ast-comments")] comments_after_type: trailing_comments.clone(), type1: Type1 { type2: Type2::Typename { ident: ge.name.clone(), generic_args: ge.generic_args.clone(), #[cfg(feature = "ast-span")] span: ge.name.span, }, operator: None, #[cfg(feature = "ast-span")] span: ge.name.span, #[cfg(feature = "ast-comments")] comments_after_type: None, }, }], #[cfg(feature = "ast-span")] span: ge.name.span, }, #[cfg(feature = "ast-comments")] comments_after_type: comments_after_group.clone(), #[cfg(feature = "ast-span")] span: ( begin_pt_range, self.parser_position.range.1, begin_rule_line, ), }))?; #[cfg(feature = "ast-span")] { end_rule_range = self.parser_position.range.1; } self.current_rule_generic_param_idents = None; return Ok(Rule::Type { rule: TypeRule { name: ident, generic_params: gp, is_type_choice_alternate, value, #[cfg(feature = "ast-comments")] comments_before_assignt: comments_before_assign, #[cfg(feature = "ast-comments")] comments_after_assignt: comments_after_assign, }, #[cfg(feature = "ast-comments")] comments_after_rule, #[cfg(feature = "ast-span")] span: (begin_rule_range, end_rule_range, begin_rule_line), }); } } // TODO: Replace with box pattern destructuring once supported in stable if let GroupEntry::ValueMemberKey { ge, .. } = &group_entry.0 { if ge.occur.is_none() && ge.member_key.is_none() { let value = self.parse_type(Some(Type2::ParenthesizedType { #[cfg(feature = "ast-comments")] comments_before_type: comments_before_group.clone(), pt: ge.entry_type.clone(), #[cfg(feature = "ast-comments")] comments_after_type: comments_after_group.clone(), #[cfg(feature = "ast-span")] span: ( begin_pt_range, self.parser_position.range.1, begin_rule_line, ), }))?; #[cfg(feature = "ast-span")] { end_rule_range = self.parser_position.range.1; } self.current_rule_generic_param_idents = None; return Ok(Rule::Type { rule: TypeRule { name: ident, generic_params: gp, is_type_choice_alternate, value, #[cfg(feature = "ast-comments")] comments_before_assignt: comments_before_assign, #[cfg(feature = "ast-comments")] comments_after_assignt: comments_after_assign, }, #[cfg(feature = "ast-comments")] comments_after_rule, #[cfg(feature = "ast-span")] span: (begin_rule_range, end_rule_range, begin_rule_line), }); } } } } } } } } self.current_rule_generic_param_idents = None; Ok(Rule::Group { rule: Box::from(GroupRule { name: ident, generic_params: gp, is_group_choice_alternate, entry: ge, #[cfg(feature = "ast-comments")] comments_before_assigng: comments_before_assign, #[cfg(feature = "ast-comments")] comments_after_assigng: comments_after_assign, }), #[cfg(feature = "ast-comments")] comments_after_rule, #[cfg(feature = "ast-span")] span: (begin_rule_range, end_rule_range, begin_rule_line), }) } _ => { // If type rule is an unwrap type, advance token after parsing type let advance_token = matches!(self.cur_token, Token::UNWRAP); #[cfg(feature = "ast-comments")] let mut t = self.parse_type(None)?; #[cfg(not(feature = "ast-comments"))] let t = self.parse_type(None)?; if advance_token { self.next_token()?; } #[cfg(feature = "ast-comments")] let comments_after_rule = if let Some(comments) = t.split_comments_after_type() { Some(comments) } else { self.collect_comments()? }; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; if let Token::ASSIGN | Token::TCHOICEALT | Token::GCHOICEALT = &self.cur_token { self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: Position { line: begin_rule_line, column: begin_rule_col, range: (ident.span.0, ident.span.1), index: self.parser_position.range.0, }, msg: IncompleteRuleEntry.into(), }); return Err(Error::INCREMENTAL); } #[cfg(feature = "ast-span")] let span = ( begin_rule_range, self.parser_position.range.1, begin_rule_line, ); self.current_rule_generic_param_idents = None; if t.type_choices.len() > 1 || !matches!( t.type_choices[0].type1.type2, Type2::ParenthesizedType { .. } | Type2::Typename { .. } ) { self.is_guaranteed = true; } Ok(Rule::Type { rule: TypeRule { name: ident, generic_params: gp, is_type_choice_alternate, value: t, #[cfg(feature = "ast-comments")] comments_before_assignt: comments_before_assign, #[cfg(feature = "ast-comments")] comments_after_assignt: comments_after_assign, }, #[cfg(feature = "ast-comments")] comments_after_rule, #[cfg(feature = "ast-span")] span, }) } } } #[allow(missing_docs)] pub fn parse_genericparm(&mut self) -> Result> { #[cfg(feature = "ast-span")] let begin_range = self.lexer_position.range.0; if let Token::LANGLEBRACKET = &self.cur_token { self.next_token()?; } let mut generic_params = GenericParams::default(); while !self.cur_token_is(Token::RANGLEBRACKET) { #[cfg(feature = "ast-comments")] let comments_before_ident = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; match &self.cur_token { Token::IDENT(ident, socket) => { let param = self.identifier_from_ident_token(ident, *socket); self.next_token()?; #[cfg(feature = "ast-comments")] let comments_after_ident = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; generic_params.params.push(GenericParam { param, #[cfg(feature = "ast-comments")] comments_before_ident, #[cfg(feature = "ast-comments")] comments_after_ident, }); if !self.cur_token_is(Token::COMMA) && !self.cur_token_is(Token::RANGLEBRACKET) { #[cfg(feature = "ast-span")] { self.parser_position.range = (begin_range + 1, self.peek_lexer_position.range.0); self.parser_position.line = self.lexer_position.line; } self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.parser_position, msg: InvalidGenericSyntax.into(), }); return Err(Error::INCREMENTAL); } } Token::COMMA => self.next_token()?, Token::VALUE(_) => { #[cfg(feature = "ast-span")] { self.parser_position.range = (self.lexer_position.range.0, self.lexer_position.range.1); self.parser_position.line = self.lexer_position.line; } self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.parser_position, msg: InvalidGenericIdentifier.into(), }); return Err(Error::INCREMENTAL); } _ => { #[cfg(feature = "ast-span")] { self.parser_position.range = (begin_range, self.lexer_position.range.0); self.parser_position.line = self.lexer_position.line; } self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.parser_position, msg: InvalidGenericSyntax.into(), }); return Err(Error::INCREMENTAL); } } } // Since generic params are only found after the identifier of a rule, don't // advance beyond the closing '>' to retain the expect_peek semantics for // '=', '/=' and '//=' #[cfg(feature = "ast-span")] { let end_range = self.lexer_position.range.1; generic_params.span = (begin_range, end_range, self.lexer_position.line); } Ok(generic_params) } #[allow(missing_docs)] pub fn parse_genericargs(&mut self) -> Result> { if self.peek_token_is(&Token::LANGLEBRACKET) { self.next_token()?; } #[cfg(feature = "ast-span")] let begin_generic_arg_range = self.lexer_position.range.0; #[cfg(feature = "ast-span")] let begin_generic_arg_line = self.lexer_position.line; // Required for type2 mutual recursion if let Token::LANGLEBRACKET = &self.cur_token { self.next_token()?; } let mut generic_args = GenericArgs::default(); while !self.cur_token_is(Token::RANGLEBRACKET) { #[cfg(feature = "ast-comments")] let leading_comments = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; let t1 = self.parse_type1(None)?; #[cfg(feature = "ast-comments")] let trailing_comments = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; generic_args.args.push(GenericArg { #[cfg(feature = "ast-comments")] comments_before_type: leading_comments, arg: Box::from(t1), #[cfg(feature = "ast-comments")] comments_after_type: trailing_comments, }); if let Token::COMMA = self.cur_token { self.next_token()?; } if let Token::EOF = &self.cur_token { self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.parser_position, msg: MissingGenericClosingDelimiter.into(), }); return Err(Error::INCREMENTAL); } } if let Token::RANGLEBRACKET = &self.cur_token { #[cfg(feature = "ast-span")] { self.parser_position.range.1 = self.lexer_position.range.1; } self.next_token()?; } #[cfg(feature = "ast-span")] { generic_args.span = ( begin_generic_arg_range, self.parser_position.range.1, begin_generic_arg_line, ); } Ok(generic_args) } // parenthesized_type can be provided as an argument to retrieve its span and // comments if it has been previously parsed #[allow(missing_docs)] pub fn parse_type(&mut self, parenthesized_type: Option>) -> Result> { #[cfg(feature = "ast-span")] { self.parser_position.range = self.lexer_position.range; self.parser_position.line = self.lexer_position.line; } #[cfg(feature = "ast-span")] let begin_type_range = if let Some(Type2::ParenthesizedType { span, .. }) = parenthesized_type { self.parser_position.line = span.2; span.0 } else { self.parser_position.range.0 }; let mut t = Type { type_choices: Vec::new(), #[cfg(feature = "ast-span")] span: (begin_type_range, 0, self.parser_position.line), }; #[cfg(feature = "ast-comments")] let mut tc = TypeChoice { type1: self.parse_type1(parenthesized_type)?, comments_before_type: None, comments_after_type: None, }; #[cfg(not(feature = "ast-comments"))] let tc = TypeChoice { type1: self.parse_type1(parenthesized_type)?, }; #[cfg(feature = "ast-comments")] { tc.comments_after_type = self.collect_comments()?; } #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; t.type_choices.push(tc); while let Token::TCHOICE = &self.cur_token { self.next_token()?; #[cfg(feature = "ast-comments")] let comments_before_type = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; #[cfg(feature = "ast-comments")] let mut tc = TypeChoice { comments_before_type, comments_after_type: None, type1: self.parse_type1(None)?, }; #[cfg(not(feature = "ast-comments"))] let tc = TypeChoice { type1: self.parse_type1(None)?, }; #[cfg(feature = "ast-comments")] { tc.comments_after_type = self.collect_comments()?; } #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; t.type_choices.push(tc); } #[cfg(feature = "ast-span")] { t.span.1 = self.parser_position.range.1; } Ok(t) } // parenthesized_type can be provided as an argument to retrieve its span and // comments if it has been previously parsed #[allow(missing_docs)] pub fn parse_type1(&mut self, parenthesized_type: Option>) -> Result> { #[cfg(feature = "ast-span")] let mut begin_type1_line = self.lexer_position.line; #[cfg(feature = "ast-span")] let mut begin_type1_range = self.lexer_position.range.0; let t2_1 = if let Some(Type2::ParenthesizedType { #[cfg(feature = "ast-comments")] comments_before_type, pt, #[cfg(feature = "ast-comments")] comments_after_type, #[cfg(feature = "ast-span")] span, }) = parenthesized_type { #[cfg(feature = "ast-span")] { begin_type1_line = span.2; begin_type1_range = span.0; } Type2::ParenthesizedType { #[cfg(feature = "ast-comments")] comments_before_type, pt, #[cfg(feature = "ast-comments")] comments_after_type, #[cfg(feature = "ast-span")] span, } } else { self.parse_type2()? }; #[cfg(feature = "ast-span")] let mut span = ( begin_type1_range, self.lexer_position.range.1, begin_type1_line, ); #[cfg(feature = "ast-comments")] let comments_after_type = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; let op = match &self.cur_token { Token::RANGEOP(i) => { #[cfg(feature = "ast-span")] { span.0 = self.lexer_position.range.0; } Some(RangeCtlOp::RangeOp { is_inclusive: *i, #[cfg(feature = "ast-span")] span, }) } Token::ControlOperator(ctrl) => { #[cfg(feature = "ast-span")] { span.0 = self.lexer_position.range.0; } Some(RangeCtlOp::CtlOp { ctrl: *ctrl, #[cfg(feature = "ast-span")] span, }) } _ => None, }; #[cfg(feature = "ast-span")] { span = ( begin_type1_range, self.parser_position.range.1, begin_type1_line, ); } match op { Some(operator) => { self.next_token()?; #[cfg(feature = "ast-comments")] let comments_after_operator = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; let t2 = self.parse_type2()?; #[cfg(feature = "ast-span")] { span.1 = self.parser_position.range.1; } Ok(Type1 { type2: t2_1, operator: Some(Operator { #[cfg(feature = "ast-comments")] comments_before_operator: comments_after_type, operator, #[cfg(feature = "ast-comments")] comments_after_operator, type2: t2, }), #[cfg(feature = "ast-comments")] comments_after_type: None, #[cfg(feature = "ast-span")] span, }) } None => Ok(Type1 { type2: t2_1, operator: None, #[cfg(feature = "ast-comments")] comments_after_type, #[cfg(feature = "ast-span")] span, }), } } #[allow(missing_docs)] pub fn parse_type2(&mut self) -> Result> { let t2 = match &self.cur_token { // value Token::VALUE(value) => { #[cfg(feature = "ast-span")] { self.parser_position.range = self.lexer_position.range; self.parser_position.line = self.lexer_position.line; } #[cfg(feature = "ast-span")] let span = ( self.parser_position.range.0, self.parser_position.range.1, self.parser_position.line, ); match value { token::Value::TEXT(t) => Ok(Type2::TextValue { value: t.clone(), #[cfg(feature = "ast-span")] span, }), token::Value::INT(i) => Ok(Type2::IntValue { value: *i, #[cfg(feature = "ast-span")] span, }), token::Value::UINT(ui) => Ok(Type2::UintValue { value: *ui, #[cfg(feature = "ast-span")] span, }), token::Value::FLOAT(f) => Ok(Type2::FloatValue { value: *f, #[cfg(feature = "ast-span")] span, }), token::Value::BYTE(token::ByteValue::UTF8(Cow::Borrowed(utf8))) => { Ok(Type2::UTF8ByteString { value: Cow::Borrowed(utf8), #[cfg(feature = "ast-span")] span, }) } token::Value::BYTE(token::ByteValue::UTF8(Cow::Owned(utf8))) => { Ok(Type2::UTF8ByteString { value: Cow::Owned(utf8.to_owned()), #[cfg(feature = "ast-span")] span, }) } token::Value::BYTE(token::ByteValue::B16(Cow::Borrowed(b16))) => { Ok(Type2::B16ByteString { value: Cow::Borrowed(b16), #[cfg(feature = "ast-span")] span, }) } token::Value::BYTE(token::ByteValue::B16(Cow::Owned(b16))) => Ok(Type2::B16ByteString { value: Cow::Owned(b16.to_owned()), #[cfg(feature = "ast-span")] span, }), token::Value::BYTE(token::ByteValue::B64(Cow::Borrowed(b64))) => { Ok(Type2::B64ByteString { value: Cow::Borrowed(b64), #[cfg(feature = "ast-span")] span, }) } token::Value::BYTE(token::ByteValue::B64(Cow::Owned(b64))) => Ok(Type2::B64ByteString { value: Cow::Owned(b64.to_owned()), #[cfg(feature = "ast-span")] span, }), } } // typename [genericarg] Token::IDENT(ident, socket) => { #[cfg(feature = "ast-span")] let begin_type2_range = self.lexer_position.range.0; #[cfg(feature = "ast-span")] let begin_type2_line = self.lexer_position.line; // optional genericarg detected if self.peek_token_is(&Token::LANGLEBRACKET) { let ident = self.identifier_from_ident_token(ident, *socket); let ga = self.parse_genericargs()?; #[cfg(feature = "ast-span")] let end_type2_range = self.parser_position.range.1; if ident.socket.is_none() { let mut is_generic_param = false; if let Some(idents) = &self.current_rule_generic_param_idents { is_generic_param = idents.iter().any(|&id| id == ident.ident); } #[cfg(feature = "ast-span")] if !is_generic_param && !self.typenames.contains(ident.ident) { self.unknown_rule_idents.push((ident.ident, ident.span)); } #[cfg(not(feature = "ast-span"))] if !is_generic_param && !self.typenames.contains(ident.ident) { self.unknown_rule_idents.push(ident.ident); } } return Ok(Type2::Typename { ident, generic_args: Some(ga), #[cfg(feature = "ast-span")] span: (begin_type2_range, end_type2_range, begin_type2_line), }); } #[cfg(feature = "ast-span")] { self.parser_position.range = self.lexer_position.range; self.parser_position.line = self.lexer_position.line; } let ident = self.identifier_from_ident_token(ident, *socket); if ident.socket.is_none() { let mut is_generic_param = false; if let Some(idents) = &self.current_rule_generic_param_idents { is_generic_param = idents.iter().any(|&id| id == ident.ident); } #[cfg(feature = "ast-span")] if !is_generic_param && !self.typenames.contains(ident.ident) { self.unknown_rule_idents.push((ident.ident, ident.span)); } #[cfg(not(feature = "ast-span"))] if !is_generic_param && !self.typenames.contains(ident.ident) { self.unknown_rule_idents.push(ident.ident); } } Ok(Type2::Typename { ident, generic_args: None, #[cfg(feature = "ast-span")] span: ( self.parser_position.range.0, self.parser_position.range.1, self.parser_position.line, ), }) } // ( type ) Token::LPAREN => { #[cfg(feature = "ast-span")] let begin_type2_range = self.lexer_position.range.0; #[cfg(feature = "ast-span")] let begin_type2_line = self.lexer_position.line; self.next_token()?; #[cfg(feature = "ast-comments")] let comments_before_type = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; let pt = self.parse_type(None)?; #[cfg(feature = "ast-span")] { self.parser_position.range.0 = begin_type2_range; self.parser_position.range.1 = self.lexer_position.range.1; self.parser_position.line = begin_type2_line; } #[cfg(feature = "ast-comments")] let comments_after_type = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; Ok(Type2::ParenthesizedType { #[cfg(feature = "ast-comments")] comments_before_type, #[cfg(feature = "ast-comments")] comments_after_type, pt, #[cfg(feature = "ast-span")] span: ( self.parser_position.range.0, self.parser_position.range.1, self.parser_position.line, ), }) } // { group } Token::LBRACE => { #[cfg(feature = "ast-span")] let begin_type2_range = self.lexer_position.range.0; #[cfg(feature = "ast-span")] let begin_type2_line = self.lexer_position.line; #[cfg(feature = "ast-comments")] let mut group = self.parse_group()?; #[cfg(not(feature = "ast-comments"))] let group = self.parse_group()?; // if the group starts with a multi-line comment, // we take the first comment inside the 1st group to be comments_before_group #[cfg(feature = "ast-comments")] let comments_before_group = if let Some(GroupChoice { comments_before_grpchoice, .. }) = group.group_choices.first_mut() { comments_before_grpchoice .as_mut() .and_then(|comments| { if comments.0.len() > 1 { Some(comments.0.remove(0)) } else { None } }) .map(|comment| Comments(vec![comment])) } else { None }; #[cfg(feature = "ast-span")] let span = ( begin_type2_range, self.lexer_position.range.1, begin_type2_line, ); #[cfg(feature = "ast-comments")] let comments_after_group = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; Ok(Type2::Map { #[cfg(feature = "ast-comments")] comments_before_group, group, #[cfg(feature = "ast-span")] span, #[cfg(feature = "ast-comments")] comments_after_group, }) } // [ group ] Token::LBRACKET => { #[cfg(feature = "ast-span")] let begin_type2_range = self.lexer_position.range.0; #[cfg(feature = "ast-span")] let begin_type2_line = self.lexer_position.line; #[cfg(feature = "ast-comments")] let mut group = self.parse_group()?; #[cfg(not(feature = "ast-comments"))] let group = self.parse_group()?; // if the group starts with a multi-line comment, // we take the first comment inside the 1st group to be comments_before_group #[cfg(feature = "ast-comments")] let comments_before_group = if let Some(GroupChoice { comments_before_grpchoice, .. }) = group.group_choices.first_mut() { comments_before_grpchoice .as_mut() .and_then(|comments| { if comments.0.len() > 1 { Some(comments.0.remove(0)) } else { None } }) .map(|comment| Comments(vec![comment])) } else { None }; #[cfg(feature = "ast-span")] let span = ( begin_type2_range, self.lexer_position.range.1, begin_type2_line, ); #[cfg(feature = "ast-comments")] let comments_after_group = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; Ok(Type2::Array { #[cfg(feature = "ast-comments")] comments_before_group, group, #[cfg(feature = "ast-comments")] comments_after_group, #[cfg(feature = "ast-span")] span, }) } // ~ typename [genericarg] Token::UNWRAP => { self.next_token()?; #[cfg(feature = "ast-comments")] let comments = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; let ident = if let Some(ident) = self.cur_token.in_standard_prelude() { Some(self.identifier_from_ident_token(ident, None)) } else if let Token::IDENT(ident, socket) = &self.cur_token { Some(self.identifier_from_ident_token(ident, *socket)) } else { None }; if let Some(ident) = ident { if self.peek_token_is(&Token::LANGLEBRACKET) { self.next_token()?; return Ok(Type2::Unwrap { #[cfg(feature = "ast-comments")] comments, ident, generic_args: Some(self.parse_genericargs()?), #[cfg(feature = "ast-span")] span: (0, 0, 0), }); } return Ok(Type2::Unwrap { #[cfg(feature = "ast-comments")] comments, ident, generic_args: None, #[cfg(feature = "ast-span")] span: (0, 0, 0), }); } self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.parser_position, msg: InvalidUnwrapSyntax.into(), }); Err(Error::INCREMENTAL) } // & ( group ) // & groupname [genericarg] Token::GTOCHOICE => { #[cfg(feature = "ast-span")] let begin_type2_range = self.lexer_position.range.0; #[cfg(feature = "ast-span")] let begin_type2_line = self.lexer_position.line; self.next_token()?; #[cfg(feature = "ast-comments")] let comments = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; match &self.cur_token { Token::LPAREN => { self.next_token()?; #[cfg(feature = "ast-comments")] let comments_before_group = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; let group = self.parse_group()?; #[cfg(feature = "ast-comments")] let comments_after_group = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; Ok(Type2::ChoiceFromInlineGroup { #[cfg(feature = "ast-comments")] comments, #[cfg(feature = "ast-comments")] comments_before_group, group, #[cfg(feature = "ast-comments")] comments_after_group, #[cfg(feature = "ast-span")] span: ( begin_type2_range, self.parser_position.range.1, begin_type2_line, ), }) } Token::IDENT(ident, socket) => { let ident = self.identifier_from_ident_token(ident, *socket); if self.peek_token_is(&Token::LANGLEBRACKET) { self.next_token()?; let generic_args = Some(self.parse_genericargs()?); return Ok(Type2::ChoiceFromGroup { #[cfg(feature = "ast-comments")] comments, ident, generic_args, #[cfg(feature = "ast-span")] span: ( begin_type2_range, self.parser_position.range.1, begin_type2_line, ), }); } #[cfg(feature = "ast-span")] { self.parser_position.range.1 = self.lexer_position.range.1; } Ok(Type2::ChoiceFromGroup { #[cfg(feature = "ast-comments")] comments, ident, generic_args: None, #[cfg(feature = "ast-span")] span: ( begin_type2_range, self.parser_position.range.1, begin_type2_line, ), }) } _ => { self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.parser_position, msg: InvalidGroupToChoiceEnumSyntax.into(), }); Err(Error::INCREMENTAL) } } } // # 6 ["." uint] ( type ) // # DIGIT ["." uint] ; major/ai // # ; any // Token::TAG(tag) => match tag { // Tag::DATA(data) => Ok(Type2::TaggedData(data.clone())), // Tag::MAJORTYPE(mt) => Ok(Type2::DataMajorType(*mt)), // Tag::ANY => Ok(Type2::Any), // }, Token::TAG(mt, constraint) => { #[cfg(feature = "ast-span")] let begin_type2_range = self.lexer_position.range.0; #[cfg(feature = "ast-span")] let begin_type2_line = self.lexer_position.line; match (*mt, *constraint) { // Tagged data item containing the given type as the tagged value (Some(6), tag) => { self.next_token()?; if !self.cur_token_is(Token::LPAREN) { self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.parser_position, msg: InvalidTagSyntax.into(), }); return Err(Error::INCREMENTAL); } self.next_token()?; #[cfg(feature = "ast-comments")] let comments_before_type = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; let t = self.parse_type(None)?; #[cfg(feature = "ast-comments")] let comments_after_type = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; if !self.cur_token_is(Token::RPAREN) { self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.parser_position, msg: InvalidTagSyntax.into(), }); return Err(Error::INCREMENTAL); } Ok(Type2::TaggedData { tag, #[cfg(feature = "ast-comments")] comments_before_type, t, #[cfg(feature = "ast-comments")] comments_after_type, #[cfg(feature = "ast-span")] span: ( begin_type2_range, self.parser_position.range.1, begin_type2_line, ), }) } // Tagged data of a major type (Some(mt), constraint) => Ok(Type2::DataMajorType { mt, constraint, #[cfg(feature = "ast-span")] span: ( begin_type2_range, self.lexer_position.range.1, begin_type2_line, ), }), #[cfg(feature = "ast-span")] _ => Ok(Type2::Any { span: ( begin_type2_range, self.lexer_position.range.1, begin_type2_line, ), }), #[cfg(not(feature = "ast-span"))] _ => Ok(Type2::Any {}), } } _ => { #[cfg(feature = "ast-comments")] self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; match self.cur_token.in_standard_prelude() { Some(s) => { let ident = self.identifier_from_ident_token(s, None); #[cfg(feature = "ast-span")] { self.parser_position.range = self.lexer_position.range; self.parser_position.line = self.lexer_position.line; } Ok(Type2::Typename { ident, generic_args: None, #[cfg(feature = "ast-span")] span: ( self.parser_position.range.0, self.parser_position.range.1, self.parser_position.line, ), }) } None => { #[cfg(feature = "ast-span")] { self.parser_position.line = self.lexer_position.line; self.parser_position.range = self.lexer_position.range; } if let Token::COLON | Token::ARROWMAP = &self.cur_token { self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.parser_position, msg: MissingGroupEntryMemberKey.into(), }); return Err(Error::INCREMENTAL); } if let Token::RBRACE | Token::RBRACKET | Token::RPAREN = &self.cur_token { self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.parser_position, msg: MissingGroupEntry.into(), }); return Err(Error::INCREMENTAL); } self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.parser_position, msg: InvalidGroupEntrySyntax.into(), }); Err(Error::INCREMENTAL) } } } }; #[cfg(feature = "ast-span")] { self.parser_position.range.1 = self.lexer_position.range.1; } self.next_token()?; t2 } #[allow(missing_docs)] pub fn parse_group(&mut self) -> Result> { #[cfg(feature = "ast-span")] let begin_group_range = if let Token::LBRACE | Token::LPAREN | Token::LBRACKET | Token::GCHOICE = &self.cur_token { self.peek_lexer_position.range.0 } else { self.lexer_position.range.0 }; let closing_delimiter = token::closing_delimiter(&self.cur_token); let mut group = Group { group_choices: Vec::new(), #[cfg(feature = "ast-span")] span: (begin_group_range, 0, self.lexer_position.line), }; group.group_choices.push(self.parse_grpchoice()?); while let Token::GCHOICE = &self.cur_token { group.group_choices.push(self.parse_grpchoice()?); } #[cfg(feature = "ast-span")] { group.span.1 = self.parser_position.range.1; } if let Some(cd) = closing_delimiter.as_ref() { if cd != &self.cur_token { self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.lexer_position, msg: MissingClosingDelimiter.into(), }); return Err(Error::INCREMENTAL); } } Ok(group) } #[allow(missing_docs)] pub fn parse_grpchoice(&mut self) -> Result> { let mut grpchoice = GroupChoice { group_entries: Vec::new(), #[cfg(feature = "ast-comments")] comments_before_grpchoice: None, #[cfg(feature = "ast-span")] span: (self.lexer_position.range.0, 0, self.lexer_position.line), }; if let Token::GCHOICE = &self.cur_token { self.next_token()?; #[cfg(feature = "ast-comments")] { grpchoice.comments_before_grpchoice = self.collect_comments()?; } #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; #[cfg(feature = "ast-span")] { grpchoice.span.0 = self.lexer_position.range.0; } } else if let Token::LBRACE | Token::LBRACKET = &self.cur_token { self.next_token()?; #[cfg(feature = "ast-span")] { grpchoice.span.0 = self.lexer_position.range.0; } #[cfg(feature = "ast-comments")] { grpchoice.comments_before_grpchoice = self.collect_comments()?; } #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; }; // TODO: The logic in this while loop is quite messy. Need to figure out a // better way to advance the token when parsing the entries in a group // choice while !self.cur_token_is(Token::RBRACE) && !self.cur_token_is(Token::RPAREN) && !self.cur_token_is(Token::RBRACKET) && !self.cur_token_is(Token::EOF) { let ge = self.parse_grpent(false)?; if let Token::GCHOICE = &self.cur_token { grpchoice.group_entries.push(( ge, OptionalComma { optional_comma: false, #[cfg(feature = "ast-comments")] trailing_comments: None, _a: PhantomData, }, )); #[cfg(feature = "ast-span")] { grpchoice.span.1 = self.parser_position.range.1; } return Ok(grpchoice); } // Don't advance the token if it is part of a member key, comma or an // opening or closing map/group delimiter. Otherwise, advance if !self.cur_token_is(Token::RPAREN) && !self.cur_token_is(Token::RBRACE) && !self.cur_token_is(Token::RBRACKET) && !self.cur_token_is(Token::LPAREN) && !self.cur_token_is(Token::LBRACE) && !self.cur_token_is(Token::LBRACKET) && !self.cur_token_is(Token::COMMA) && !self.cur_token_is(Token::OPTIONAL) && !self.cur_token_is(Token::ONEORMORE) && !self.cur_token_is(Token::ASTERISK) && !self.peek_token_is(&Token::COLON) && !self.peek_token_is(&Token::ARROWMAP) && !self.cur_token_is(Token::EOF) && !matches!(self.cur_token, Token::IDENT(..)) { #[cfg(feature = "ast-span")] { self.parser_position.range.1 = self.lexer_position.range.1; } self.next_token()?; } let mut optional_comma = false; if let Token::COMMA = &self.cur_token { optional_comma = true; #[cfg(feature = "ast-span")] { self.parser_position.range.1 = self.lexer_position.range.1; } self.next_token()?; } #[cfg(feature = "ast-comments")] let trailing_comments = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; grpchoice.group_entries.push(( ge, OptionalComma { optional_comma, #[cfg(feature = "ast-comments")] trailing_comments, _a: PhantomData, }, )); } #[cfg(feature = "ast-span")] { grpchoice.span.1 = self.parser_position.range.1; } Ok(grpchoice) } #[allow(missing_docs)] pub fn parse_grpent(&mut self, from_rule: bool) -> Result> { #[cfg(feature = "ast-span")] let begin_grpent_range = self.lexer_position.range.0; #[cfg(feature = "ast-span")] let begin_grpent_line = self.lexer_position.line; let occur = self.parse_occur(true)?; // If parsing group entry from a rule, set member key to none let member_key = if from_rule { None } else { self.parse_memberkey(true)? }; if self.cur_token_is(Token::LPAREN) && member_key.is_none() { self.next_token()?; #[cfg(feature = "ast-comments")] let comments_before_group = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; let group = self.parse_group()?; #[cfg(feature = "ast-span")] let mut span = ( begin_grpent_range, self.parser_position.range.1, begin_grpent_line, ); #[cfg(feature = "ast-span")] { self.parser_position.range.1 = self.lexer_position.range.1; } #[cfg(feature = "ast-comments")] let comments_after_group = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; if !self.cur_token_is(Token::RPAREN) { self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.lexer_position, msg: MissingClosingParend.into(), }); return Err(Error::INCREMENTAL); } #[cfg(feature = "ast-span")] { span.1 = self.parser_position.range.1; } self.next_token()?; return Ok(GroupEntry::InlineGroup { occur, group, #[cfg(feature = "ast-comments")] comments_before_group, #[cfg(feature = "ast-comments")] comments_after_group, #[cfg(feature = "ast-span")] span, }); } #[cfg(feature = "ast-span")] let mut span = ( begin_grpent_range, self.parser_position.range.1, begin_grpent_line, ); match member_key { Some(MemberKey::NonMemberKey { #[cfg(feature = "ast-comments")] non_member_key: NonMemberKey::Type(mut entry_type), #[cfg(not(feature = "ast-comments"))] non_member_key: NonMemberKey::Type(entry_type), #[cfg(feature = "ast-comments")] comments_before_type_or_group, #[cfg(feature = "ast-comments")] comments_after_type_or_group, }) => { #[cfg(feature = "ast-span")] if let Token::COMMA = &self.cur_token { span.1 = self.lexer_position.range.1; } #[cfg(feature = "ast-comments")] let trailing_comments = entry_type.take_comments_after_type(); #[cfg(feature = "ast-span")] if let Some((name, generic_args, _)) = entry_type.groupname_entry() { if self.groupnames.contains(name.ident) || matches!(name.socket, Some(SocketPlug::GROUP)) { if name.socket.is_none() { self.unknown_rule_idents = self .unknown_rule_idents .clone() .into_iter() .filter(|(ident, _)| ident != &name.ident) .collect(); } return Ok(GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur, name, generic_args, }, #[cfg(feature = "ast-comments")] leading_comments: comments_before_type_or_group, #[cfg(feature = "ast-comments")] trailing_comments, span, }); } } #[cfg(not(feature = "ast-span"))] if let Some((name, generic_args)) = entry_type.groupname_entry() { if self.groupnames.contains(name.ident) || matches!(name.socket, Some(SocketPlug::GROUP)) { if name.socket.is_none() { self.unknown_rule_idents = self .unknown_rule_idents .clone() .into_iter() .filter(|ident| ident != &name.ident) .collect(); } return Ok(GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur, name, generic_args, }, #[cfg(feature = "ast-comments")] leading_comments: comments_before_type_or_group, #[cfg(feature = "ast-comments")] trailing_comments, }); } } // A parse tree that returns a type instead of a member key needs to // advance the token in the case of "(", "{" or "[". Otherwise, infinite // recursive loop occurs if let Token::LPAREN | Token::LBRACE | Token::LBRACKET = self.cur_token { self.next_token()?; } #[cfg(feature = "ast-comments")] let trailing_comments = if let Some(comments) = entry_type.split_comments_after_type() { Some(comments) } else { comments_after_type_or_group }; Ok(GroupEntry::ValueMemberKey { ge: Box::from(ValueMemberKeyEntry { occur, member_key: None, entry_type, }), #[cfg(feature = "ast-comments")] leading_comments: comments_before_type_or_group, #[cfg(feature = "ast-comments")] trailing_comments, #[cfg(feature = "ast-span")] span, }) } Some(MemberKey::NonMemberKey { non_member_key: NonMemberKey::Group(group), #[cfg(feature = "ast-comments")] comments_before_type_or_group, #[cfg(feature = "ast-comments")] comments_after_type_or_group, }) => { #[cfg(feature = "ast-span")] if let Token::COMMA = &self.cur_token { span.1 = self.lexer_position.range.1; } Ok(GroupEntry::InlineGroup { occur, group, #[cfg(feature = "ast-span")] span, #[cfg(feature = "ast-comments")] comments_before_group: comments_before_type_or_group, #[cfg(feature = "ast-comments")] comments_after_group: comments_after_type_or_group, }) } member_key @ Some(_) => { #[cfg(feature = "ast-comments")] let mut entry_type = self.parse_type(None)?; #[cfg(not(feature = "ast-comments"))] let entry_type = self.parse_type(None)?; #[cfg(feature = "ast-comments")] let trailing_comments = entry_type.split_comments_after_type(); #[cfg(feature = "ast-span")] { span.1 = self.parser_position.range.1; } #[cfg(feature = "ast-span")] if let Token::COMMA = &self.cur_token { span.1 = self.lexer_position.range.1; } Ok(GroupEntry::ValueMemberKey { ge: Box::from(ValueMemberKeyEntry { occur, member_key, entry_type, }), #[cfg(feature = "ast-comments")] leading_comments: None, #[cfg(feature = "ast-comments")] trailing_comments, #[cfg(feature = "ast-span")] span, }) } None => { #[cfg(feature = "ast-comments")] let mut entry_type = self.parse_type(None)?; #[cfg(not(feature = "ast-comments"))] let entry_type = self.parse_type(None)?; #[cfg(feature = "ast-span")] { span.1 = self.parser_position.range.1; } #[cfg(feature = "ast-comments")] let trailing_comments = if let Some(comments) = entry_type.take_comments_after_type() { Some(comments) } else { self.collect_comments()? }; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; #[cfg(feature = "ast-span")] if let Token::COMMA = &self.cur_token { span.1 = self.lexer_position.range.1; } #[cfg(feature = "ast-span")] if let Some((name, generic_args, _)) = entry_type.groupname_entry() { if self.groupnames.contains(name.ident) || matches!(name.socket, Some(SocketPlug::GROUP)) { if generic_args.is_some() && self.peek_token_is(&Token::LANGLEBRACKET) { while !self.peek_token_is(&Token::RANGLEBRACKET) { self.next_token()?; } self.next_token()?; } if name.socket.is_none() { self.unknown_rule_idents = self .unknown_rule_idents .clone() .into_iter() .filter(|(ident, _)| ident != &name.ident) .collect(); } return Ok(GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur, name, generic_args, }, #[cfg(feature = "ast-comments")] leading_comments: None, #[cfg(feature = "ast-comments")] trailing_comments, span, }); } } #[cfg(not(feature = "ast-span"))] if let Some((name, generic_args)) = entry_type.groupname_entry() { if self.groupnames.contains(name.ident) || matches!(name.socket, Some(SocketPlug::GROUP)) { if generic_args.is_some() && self.peek_token_is(&Token::LANGLEBRACKET) { while !self.peek_token_is(&Token::RANGLEBRACKET) { self.next_token()?; } self.next_token()?; } if name.socket.is_none() { self.unknown_rule_idents = self .unknown_rule_idents .clone() .into_iter() .filter(|ident| ident != &name.ident) .collect(); } return Ok(GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur, name, generic_args, }, #[cfg(feature = "ast-comments")] leading_comments: None, #[cfg(feature = "ast-comments")] trailing_comments, }); } } Ok(GroupEntry::ValueMemberKey { ge: Box::from(ValueMemberKeyEntry { occur, member_key: None, entry_type, }), #[cfg(feature = "ast-comments")] leading_comments: None, #[cfg(feature = "ast-comments")] trailing_comments, #[cfg(feature = "ast-span")] span, }) } } } // An ident memberkey could one of the following: // type1 S ["^" S] "=>" // / bareword S ": fn parse_memberkey_from_ident( &mut self, is_optional: bool, ident: &'a str, socket: Option, #[cfg(feature = "ast-span")] begin_memberkey_range: usize, #[cfg(feature = "ast-span")] begin_memberkey_line: usize, ) -> Result>> { if !self.peek_token_is(&Token::COLON) && !self.peek_token_is(&Token::ARROWMAP) && !self.peek_token_is(&Token::CUT) && is_optional { return Ok(None); } #[cfg(feature = "ast-span")] { self.parser_position.range.1 = self.peek_lexer_position.range.1; } #[cfg(feature = "ast-span")] let end_t1_range = self.lexer_position.range.1; #[cfg(feature = "ast-span")] let mut ident = self.identifier_from_ident_token(ident, socket); #[cfg(not(feature = "ast-span"))] let ident = self.identifier_from_ident_token(ident, socket); #[cfg(feature = "ast-span")] { ident.span = (begin_memberkey_range, end_t1_range, begin_memberkey_line); } self.next_token()?; #[cfg(feature = "ast-comments")] let comments_before_cut = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; let mk = if let Token::CUT = &self.cur_token { self.next_token()?; #[cfg(feature = "ast-comments")] let comments_after_cut = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; if !self.cur_token_is(Token::ARROWMAP) { self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.lexer_position, msg: InvalidMemberKeyArrowMapSyntax.into(), }); return Err(Error::INCREMENTAL); } #[cfg(feature = "ast-span")] let end_memberkey_range = self.lexer_position.range.1; #[cfg(feature = "ast-comments")] let comments_after_arrowmap = if let Token::COMMENT(_) = self.peek_token { self.next_token()?; self.collect_comments()? } else { None }; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; let t1 = MemberKey::Type1 { t1: Box::from(Type1 { type2: Type2::Typename { ident, generic_args: None, #[cfg(feature = "ast-span")] span: (begin_memberkey_range, end_t1_range, begin_memberkey_line), }, operator: None, #[cfg(feature = "ast-comments")] comments_after_type: None, #[cfg(feature = "ast-span")] span: (begin_memberkey_range, end_t1_range, begin_memberkey_line), }), #[cfg(feature = "ast-comments")] comments_before_cut, is_cut: true, #[cfg(feature = "ast-comments")] comments_after_cut, #[cfg(feature = "ast-comments")] comments_after_arrowmap, #[cfg(feature = "ast-span")] span: ( begin_memberkey_range, end_memberkey_range, begin_memberkey_line, ), }; self.next_token()?; Some(t1) } else if let Token::ARROWMAP = &self.cur_token { #[cfg(feature = "ast-span")] let end_memberkey_range = self.lexer_position.range.1; #[cfg(feature = "ast-comments")] let comments_after_arrowmap = if let Token::COMMENT(_) = &self.peek_token { self.next_token()?; self.collect_comments()? } else { None }; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; let t1 = MemberKey::Type1 { t1: Box::from(Type1 { type2: Type2::Typename { ident, generic_args: None, #[cfg(feature = "ast-span")] span: (begin_memberkey_range, end_t1_range, begin_memberkey_line), }, operator: None, #[cfg(feature = "ast-comments")] comments_after_type: None, #[cfg(feature = "ast-span")] span: (begin_memberkey_range, end_t1_range, begin_memberkey_line), }), #[cfg(feature = "ast-comments")] comments_before_cut, is_cut: false, #[cfg(feature = "ast-comments")] comments_after_cut: None, #[cfg(feature = "ast-comments")] comments_after_arrowmap, #[cfg(feature = "ast-span")] span: ( begin_memberkey_range, end_memberkey_range, begin_memberkey_line, ), }; self.next_token()?; #[cfg(feature = "ast-comments")] let _ = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; Some(t1) } else { if let Token::COLON = &self.cur_token { self.next_token()?; } #[cfg(feature = "ast-comments")] let comments_after_colon = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; Some(MemberKey::Bareword { ident, #[cfg(feature = "ast-comments")] comments: comments_before_cut, #[cfg(feature = "ast-comments")] comments_after_colon, #[cfg(feature = "ast-span")] span: ( begin_memberkey_range, self.parser_position.range.1, begin_memberkey_line, ), }) }; Ok(mk) } #[allow(missing_docs)] pub fn parse_memberkey(&mut self, is_optional: bool) -> Result>> { #[cfg(feature = "ast-span")] let begin_memberkey_range = self.lexer_position.range.0; #[cfg(feature = "ast-span")] let begin_memberkey_line = self.lexer_position.line; if let Some(t) = self.cur_token.in_standard_prelude() { return self.parse_memberkey_from_ident( is_optional, t, None, #[cfg(feature = "ast-span")] begin_memberkey_range, #[cfg(feature = "ast-span")] begin_memberkey_line, ); } match &self.cur_token { Token::IDENT(ident, socket) => { let ident = *ident; let socket = *socket; self.parse_memberkey_from_ident( is_optional, ident, socket, #[cfg(feature = "ast-span")] begin_memberkey_range, #[cfg(feature = "ast-span")] begin_memberkey_line, ) } Token::VALUE(value) => { if !self.peek_token_is(&Token::COLON) && !self.peek_token_is(&Token::ARROWMAP) && !self.peek_token_is(&Token::CUT) && is_optional { return Ok(None); } #[cfg(feature = "ast-span")] { self.parser_position.range.1 = self.peek_lexer_position.range.1; } let value = value.clone(); let t1 = self.parse_type1(None)?; #[cfg(feature = "ast-comments")] let comments_before_cut = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; let mk = if let Token::CUT = &self.cur_token { self.next_token()?; #[cfg(feature = "ast-comments")] let comments_after_cut = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; if !self.cur_token_is(Token::ARROWMAP) { self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.lexer_position, msg: InvalidMemberKeyArrowMapSyntax.into(), }); return Err(Error::INCREMENTAL); } #[cfg(feature = "ast-span")] let end_memberkey_range = self.lexer_position.range.1; self.next_token()?; #[cfg(feature = "ast-comments")] let memberkey_comments = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; Some(MemberKey::Type1 { t1: Box::from(t1), #[cfg(feature = "ast-comments")] comments_before_cut, is_cut: true, #[cfg(feature = "ast-comments")] comments_after_cut, #[cfg(feature = "ast-comments")] comments_after_arrowmap: memberkey_comments, #[cfg(feature = "ast-span")] span: ( begin_memberkey_range, end_memberkey_range, begin_memberkey_line, ), }) } else { #[cfg(feature = "ast-comments")] let comments = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; if !self.cur_token_is(Token::ARROWMAP) && !self.cur_token_is(Token::COLON) { self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.lexer_position, msg: InvalidMemberKeySyntax.into(), }); return Err(Error::INCREMENTAL); } #[cfg(feature = "ast-span")] { self.parser_position.range.1 = self.lexer_position.range.1; } self.next_token()?; #[cfg(feature = "ast-comments")] let memberkey_comments = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; Some(MemberKey::Value { value, #[cfg(feature = "ast-comments")] comments, #[cfg(feature = "ast-comments")] comments_after_colon: memberkey_comments, #[cfg(feature = "ast-span")] span: ( begin_memberkey_range, self.parser_position.range.1, begin_memberkey_line, ), }) }; if let Token::COLON = &self.cur_token { self.next_token()?; } Ok(mk) } // Indicates either an inline parenthesized type or an inline group. If // the latter, don't parse as memberkey Token::LPAREN => { #[cfg(feature = "ast-span")] let begin_memberkey_range = self.lexer_position.range.0; #[cfg(feature = "ast-span")] let begin_memberkey_line = self.lexer_position.line; let mut nested_parend_count = 0; self.next_token()?; #[cfg(feature = "ast-comments")] let comments_before_type_or_group = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; let mut tokens: Vec = Vec::new(); #[cfg(feature = "ast-comments")] let mut comments_after_type_or_group = None; let mut has_group_entries = false; let mut closing_parend = false; #[cfg(feature = "ast-span")] let mut closing_parend_index = 0; while !closing_parend { if let Token::ARROWMAP | Token::COLON | Token::OPTIONAL | Token::ASTERISK | Token::GCHOICE = &self.cur_token { has_group_entries = true; } // TODO: parse nested comments if let Token::LPAREN = &self.cur_token { nested_parend_count += 1; } if let Token::RPAREN = &self.cur_token { match nested_parend_count.cmp(&0) { Ordering::Greater => nested_parend_count -= 1, Ordering::Equal | Ordering::Less => { closing_parend = true; #[cfg(feature = "ast-span")] { closing_parend_index = self.lexer_position.range.1; } } } } tokens.push(Ok((self.lexer_position, self.cur_token.clone()))); #[cfg(feature = "ast-span")] { self.parser_position.range.1 = self.lexer_position.range.1; } self.next_token()?; #[cfg(feature = "ast-comments")] { comments_after_type_or_group = self.collect_comments()?; } #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; if let Token::EOF = &self.cur_token { self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.lexer_position, msg: MissingClosingParend.into(), }); return Err(Error::INCREMENTAL); } } // Create a new parser for the previously-lexed tokens. let mut parser = Parser::new(self.str_input, Box::new(tokens.into_iter()))?; parser.groupnames = self.groupnames.clone(); parser.typenames = self.typenames.clone(); // Parse tokens vec as group if has_group_entries { let group = match parser.parse_group() { Ok(g) => g, Err(Error::INCREMENTAL) => { for e in parser.errors.into_iter() { self.errors.push(e); } return Err(Error::INCREMENTAL); } Err(e) => return Err(e), }; self .unknown_rule_idents .append(&mut parser.unknown_rule_idents); return Ok(Some(MemberKey::NonMemberKey { non_member_key: NonMemberKey::Group(group), #[cfg(feature = "ast-comments")] comments_before_type_or_group, #[cfg(feature = "ast-comments")] comments_after_type_or_group, })); } // Parse tokens vec as type let t = match parser.parse_type(None) { Ok(t) => t, Err(Error::INCREMENTAL) => { for e in parser.errors.into_iter() { self.errors.push(e); } return Err(Error::INCREMENTAL); } Err(e) => return Err(e), }; self .unknown_rule_idents .append(&mut parser.unknown_rule_idents); #[cfg(feature = "ast-comments")] let comments_before_cut = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; if let Token::CUT = &self.cur_token { self.next_token()?; #[cfg(feature = "ast-comments")] let comments_after_cut = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; if !self.cur_token_is(Token::ARROWMAP) { self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.lexer_position, msg: InvalidMemberKeyArrowMapSyntax.into(), }); return Err(Error::INCREMENTAL); } #[cfg(feature = "ast-span")] let end_memberkey_range = self.lexer_position.range.1; let t1 = Some(MemberKey::Type1 { t1: Box::from(Type1 { type2: Type2::ParenthesizedType { pt: t, #[cfg(feature = "ast-comments")] comments_before_type: comments_before_type_or_group, #[cfg(feature = "ast-comments")] comments_after_type: comments_after_type_or_group, #[cfg(feature = "ast-span")] span: ( begin_memberkey_range, closing_parend_index, begin_memberkey_line, ), }, #[cfg(feature = "ast-comments")] comments_after_type: comments_before_cut.clone(), operator: None, #[cfg(feature = "ast-span")] span: ( begin_memberkey_range, closing_parend_index, begin_memberkey_line, ), }), #[cfg(feature = "ast-comments")] comments_before_cut, is_cut: true, #[cfg(feature = "ast-comments")] comments_after_cut, #[cfg(feature = "ast-comments")] comments_after_arrowmap: None, #[cfg(feature = "ast-span")] span: ( begin_memberkey_range, end_memberkey_range, begin_memberkey_line, ), }); return Ok(t1); } let t1 = if let Token::ARROWMAP = &self.cur_token { self.next_token()?; #[cfg(feature = "ast-span")] { self.parser_position.range.1 = self.lexer_position.range.1; } #[cfg(feature = "ast-comments")] let memberkey_comments = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; Some(MemberKey::Type1 { t1: Box::from(Type1 { type2: Type2::ParenthesizedType { pt: t, #[cfg(feature = "ast-comments")] comments_before_type: comments_before_type_or_group, #[cfg(feature = "ast-comments")] comments_after_type: comments_after_type_or_group, #[cfg(feature = "ast-span")] span: ( begin_memberkey_range, closing_parend_index, begin_memberkey_line, ), }, #[cfg(feature = "ast-comments")] comments_after_type: comments_before_cut.clone(), operator: None, #[cfg(feature = "ast-span")] span: ( begin_memberkey_range, closing_parend_index, begin_memberkey_line, ), }), #[cfg(feature = "ast-comments")] comments_before_cut, is_cut: false, #[cfg(feature = "ast-comments")] comments_after_cut: None, #[cfg(feature = "ast-comments")] comments_after_arrowmap: memberkey_comments, #[cfg(feature = "ast-span")] span: ( begin_memberkey_range, self.lexer_position.range.0, begin_memberkey_line, ), }) } else { Some(MemberKey::NonMemberKey { non_member_key: NonMemberKey::Type(Type { type_choices: t.type_choices, #[cfg(feature = "ast-span")] span: ( begin_memberkey_range, self.parser_position.range.1, begin_memberkey_line, ), }), #[cfg(feature = "ast-comments")] comments_before_type_or_group, #[cfg(feature = "ast-comments")] comments_after_type_or_group, }) }; Ok(t1) } _ => { let t1 = self.parse_type1(None)?; #[cfg(feature = "ast-comments")] let comments_before_cut = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; if let Token::CUT = &self.cur_token { self.next_token()?; #[cfg(feature = "ast-comments")] let comments_after_cut = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; if !self.cur_token_is(Token::ARROWMAP) { self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.lexer_position, msg: InvalidMemberKeyArrowMapSyntax.into(), }); return Err(Error::INCREMENTAL); } #[cfg(feature = "ast-span")] let end_memberkey_range = self.lexer_position.range.1; self.next_token()?; #[cfg(feature = "ast-comments")] let memberkey_comments = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; return Ok(Some(MemberKey::Type1 { t1: Box::from(t1), #[cfg(feature = "ast-comments")] comments_before_cut, is_cut: true, #[cfg(feature = "ast-comments")] comments_after_cut, #[cfg(feature = "ast-comments")] comments_after_arrowmap: memberkey_comments, #[cfg(feature = "ast-span")] span: ( begin_memberkey_range, end_memberkey_range, begin_memberkey_line, ), })); } let t1 = if let Token::ARROWMAP = &self.cur_token { self.next_token()?; #[cfg(feature = "ast-span")] { self.parser_position.range.1 = self.lexer_position.range.1; } #[cfg(feature = "ast-comments")] let memberkey_comments = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; Some(MemberKey::Type1 { t1: Box::from(t1), #[cfg(feature = "ast-comments")] comments_before_cut, is_cut: false, #[cfg(feature = "ast-comments")] comments_after_cut: None, #[cfg(feature = "ast-comments")] comments_after_arrowmap: memberkey_comments, #[cfg(feature = "ast-span")] span: ( begin_memberkey_range, self.parser_position.range.1, begin_memberkey_line, ), }) } else { Some(MemberKey::NonMemberKey { non_member_key: NonMemberKey::Type(Type { type_choices: vec![TypeChoice { #[cfg(feature = "ast-comments")] comments_before_type: None, #[cfg(feature = "ast-comments")] comments_after_type: None, type1: t1, }], #[cfg(feature = "ast-span")] span: ( begin_memberkey_range, self.parser_position.range.1, begin_memberkey_line, ), }), #[cfg(feature = "ast-comments")] comments_before_type_or_group: None, #[cfg(feature = "ast-comments")] comments_after_type_or_group: comments_before_cut, }) }; Ok(t1) } } } #[allow(missing_docs)] pub fn parse_occur(&mut self, is_optional: bool) -> Result>> { #[cfg(feature = "ast-span")] let begin_occur_range = self.lexer_position.range.0; #[cfg(feature = "ast-span")] let begin_occur_line = self.lexer_position.line; #[cfg(feature = "ast-span")] { self.parser_position.line = self.lexer_position.line; } match &self.cur_token { Token::OPTIONAL => { #[cfg(feature = "ast-span")] { self.parser_position.range = self.lexer_position.range; } self.next_token()?; #[cfg(feature = "ast-comments")] let comments = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; Ok(Some(Occurrence { #[cfg(feature = "ast-span")] occur: Occur::Optional { span: ( self.parser_position.range.0, self.parser_position.range.1, self.parser_position.line, ), }, #[cfg(not(feature = "ast-span"))] occur: Occur::Optional {}, #[cfg(feature = "ast-comments")] comments, _a: PhantomData, })) } Token::ONEORMORE => { #[cfg(feature = "ast-span")] { self.parser_position.range = self.lexer_position.range; } self.next_token()?; #[cfg(feature = "ast-comments")] let comments = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; Ok(Some(Occurrence { #[cfg(feature = "ast-span")] occur: Occur::OneOrMore { span: ( self.parser_position.range.0, self.parser_position.range.1, self.parser_position.line, ), }, #[cfg(not(feature = "ast-span"))] occur: Occur::OneOrMore {}, #[cfg(feature = "ast-comments")] comments, _a: PhantomData, })) } Token::ASTERISK => { let occur = if let Token::VALUE(token::Value::UINT(u)) = &self.peek_token { #[cfg(feature = "ast-span")] { self.parser_position.range.0 = self.lexer_position.range.0; self.parser_position.range.1 = self.peek_lexer_position.range.1; } Occur::Exact { lower: None, upper: Some(*u), #[cfg(feature = "ast-span")] span: ( self.parser_position.range.0, self.parser_position.range.1, self.parser_position.line, ), } } else { #[cfg(feature = "ast-span")] { self.parser_position.range = self.lexer_position.range; Occur::ZeroOrMore { span: ( self.parser_position.range.0, self.parser_position.range.1, self.parser_position.line, ), } } #[cfg(not(feature = "ast-span"))] Occur::ZeroOrMore {} }; self.next_token()?; if let Token::VALUE(token::Value::UINT(_)) = &self.cur_token { self.next_token()?; } #[cfg(feature = "ast-comments")] let comments = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; Ok(Some(Occurrence { occur, #[cfg(feature = "ast-comments")] comments, _a: PhantomData, })) } Token::VALUE(_) => { let lower = if let Token::VALUE(token::Value::UINT(li)) = &self.cur_token { Some(*li) } else { None }; if !self.peek_token_is(&Token::ASTERISK) { if is_optional { return Ok(None); } self.errors.push(Error::PARSER { #[cfg(feature = "ast-span")] position: self.lexer_position, msg: InvalidOccurrenceSyntax.into(), }); return Err(Error::INCREMENTAL); } self.next_token()?; #[cfg(feature = "ast-span")] { self.parser_position.range.1 = self.lexer_position.range.1; } self.next_token()?; let upper = if let Token::VALUE(token::Value::UINT(ui)) = &self.cur_token { let ui = *ui; #[cfg(feature = "ast-span")] { self.parser_position.range.1 = self.lexer_position.range.1; } self.next_token()?; Some(ui) } else { None }; #[cfg(feature = "ast-comments")] let comments = self.collect_comments()?; #[cfg(not(feature = "ast-comments"))] self.advance_newline()?; Ok(Some(Occurrence { occur: Occur::Exact { lower, upper, #[cfg(feature = "ast-span")] span: ( begin_occur_range, self.parser_position.range.1, begin_occur_line, ), }, #[cfg(feature = "ast-comments")] comments, _a: PhantomData, })) } _ => Ok(None), } } fn cur_token_is(&self, t: Token) -> bool { mem::discriminant(&self.cur_token) == mem::discriminant(&t) } fn peek_token_is(&self, t: &Token) -> bool { mem::discriminant(&self.peek_token) == mem::discriminant(t) } fn expect_peek(&mut self, t: &Token) -> Result { if self.peek_token_is(t) { return self.next_token().map(|_| true); } Ok(false) } /// Create `ast::Identifier` from `Token::IDENT(ident)` fn identifier_from_ident_token( &self, ident: &'a str, socket: Option, ) -> Identifier<'a> { Identifier { ident, socket, #[cfg(feature = "ast-span")] span: ( self.lexer_position.range.0, self.lexer_position.range.1, self.lexer_position.line, ), } } } /// Returns a `ast::CDDL` from a `&str` /// /// # Arguments /// /// * `input` - A string slice with the CDDL text input /// * `print_stderr` - When true, print any errors to stderr /// /// # Example /// /// ``` /// use cddl::parser::cddl_from_str; /// /// let input = r#"myrule = int"#; /// let _ = cddl_from_str(input, true); #[cfg(not(target_arch = "wasm32"))] #[cfg(feature = "std")] pub fn cddl_from_str(input: &str, print_stderr: bool) -> std::result::Result { match Parser::new(input, Box::new(lexer::lexer_from_str(input).iter())).map_err(|e| e.to_string()) { Ok(mut p) => match p.parse_cddl() { Ok(c) => Ok(c), Err(Error::INCREMENTAL) => { let e = if print_stderr { p.report_errors(true) } else { p.report_errors(false) }; if let Ok(Some(e)) = e { return Err(e); } Err(Error::INCREMENTAL.to_string()) } Err(e) => Err(e.to_string()), }, Err(e) => Err(e), } } /// Identify root type name from CDDL input string #[cfg(feature = "std")] #[cfg(not(target_arch = "wasm32"))] pub fn root_type_name_from_cddl_str(input: &str) -> std::result::Result { let cddl = cddl_from_str(input, false)?; for r in cddl.rules.iter() { // First type rule is root if let Rule::Type { rule, .. } = r { if rule.generic_params.is_none() { return Ok(rule.name.to_string()); } } } Err("cddl spec contains no root type".to_string()) } impl<'a> CDDL<'a> { /// Parses CDDL from a byte slice #[cfg(not(target_arch = "wasm32"))] #[cfg(feature = "std")] pub fn from_slice(input: &[u8]) -> std::result::Result { let str_input = std::str::from_utf8(input).map_err(|e| e.to_string())?; match Parser::new(str_input, Box::new(lexer::Lexer::from_slice(input).iter())) .map_err(|e| e.to_string()) { Ok(mut p) => match p.parse_cddl() { Ok(c) => Ok(c), Err(Error::INCREMENTAL) => { if let Ok(Some(e)) = p.report_errors(false) { return Err(e); } Err(Error::INCREMENTAL.to_string()) } Err(e) => Err(e.to_string()), }, Err(e) => Err(e), } } /// Parses CDDL from a byte slice #[cfg(not(target_arch = "wasm32"))] #[cfg(not(feature = "std"))] pub fn from_slice(input: &[u8]) -> std::result::Result { let str_input = std::str::from_utf8(input).map_err(|e| e.to_string())?; match Parser::new(str_input, Box::new(lexer::Lexer::from_slice(input).iter())) .map_err(|e| e.to_string()) { Ok(mut p) => match p.parse_cddl() { Ok(c) => Ok(c), Err(Error::INCREMENTAL) => { if let Some(e) = p.report_errors() { return Err(e); } Err(Error::INCREMENTAL.to_string()) } Err(e) => Err(e.to_string()), }, Err(e) => Err(e), } } } /// Returns a `ast::CDDL` from a `&str` /// /// # Arguments /// /// * `lexer` - A mutable reference to a `lexer::Lexer`. Can be created from /// `cddl::lexer_from_str()` /// * `input` - A string slice with the CDDL text input /// /// # Example /// /// ``` /// use cddl::cddl_from_str; /// /// let input = r#"myrule = int"#; /// /// let _ = cddl_from_str(input); /// ``` #[cfg(not(target_arch = "wasm32"))] #[cfg(not(feature = "std"))] pub fn cddl_from_str(input: &str) -> std::result::Result { match Parser::new(input, Box::new(lexer::lexer_from_str(input).iter())).map_err(|e| e.to_string()) { Ok(mut p) => match p.parse_cddl() { Ok(c) => Ok(c), Err(Error::INCREMENTAL) => { if let Some(e) = p.report_errors() { return Err(e); } Err(Error::INCREMENTAL.to_string()) } Err(e) => Err(e.to_string()), }, Err(e) => Err(e), } } /// Returns a `ast::CDDL` wrapped in `JsValue` from a `&str` /// /// # Arguments /// /// * `input` - A string slice with the CDDL text input /// /// # Example /// /// ```typescript /// import * as wasm from 'cddl'; /// /// let cddl: any; /// try { /// cddl = wasm.cddl_from_str(text); /// } catch (e) { /// console.error(e); /// } /// ``` #[cfg(target_arch = "wasm32")] #[wasm_bindgen] pub fn cddl_from_str(input: &str) -> result::Result { #[derive(Serialize)] struct ParserError { position: Position, msg: ErrorMsg, } match Parser::new(input, Box::new(lexer::Lexer::new(input).iter())) { Ok(mut p) => match p.parse_cddl() { Ok(c) => serde_wasm_bindgen::to_value(&c).map_err(|e| JsValue::from(e.to_string())), Err(Error::INCREMENTAL) => { if !p.errors.is_empty() { return Err( serde_wasm_bindgen::to_value( &p.errors .iter() .filter_map(|e| { if let Error::PARSER { position, msg } = e { Some(ParserError { position: *position, msg: msg.clone(), }) } else { None } }) .collect::>(), ) .map_err(|e| JsValue::from(e.to_string()))?, ); } Err(JsValue::from(Error::INCREMENTAL.to_string())) } Err(e) => Err(JsValue::from(e.to_string())), }, Err(e) => Err(JsValue::from(e.to_string())), } } #[cfg(feature = "lsp")] #[cfg(target_arch = "wasm32")] #[wasm_bindgen] /// Formats cddl from input string pub fn format_cddl_from_str(input: &str) -> result::Result { #[derive(Serialize)] struct ParserError { position: Position, msg: ErrorMsg, } match Parser::new(input, Box::new(lexer::Lexer::new(input).iter())) { Ok(mut p) => match p.parse_cddl() { Ok(c) => Ok(c.to_string()), Err(Error::INCREMENTAL) => { if !p.errors.is_empty() { return Err( serde_wasm_bindgen::to_value( &p.errors .iter() .filter_map(|e| { if let Error::PARSER { position, msg } = e { Some(ParserError { position: *position, msg: msg.clone(), }) } else { None } }) .collect::>(), ) .map_err(|e| JsValue::from(e.to_string()))?, ); } Err(JsValue::from(Error::INCREMENTAL.to_string())) } Err(e) => Err(JsValue::from(e.to_string())), }, Err(e) => Err(JsValue::from(e.to_string())), } } cddl-0.9.4/src/parser_tests.rs000064400000000000000000002341541046102023000144300ustar 00000000000000#[cfg(test)] #[allow(unused_imports)] #[cfg(feature = "ast-span")] #[cfg(feature = "ast-comments")] #[cfg(not(feature = "ast-parent"))] mod tests { use crate::{ ast::*, lexer::Lexer, parser::*, token::{self, SocketPlug}, }; use std::marker::PhantomData; use indoc::indoc; use pretty_assertions::assert_eq; #[test] fn verify_rule_diagnostic() -> Result<()> { let input = indoc!( r#" a = 1234 a = b "# ); match Parser::new(input, Box::new(Lexer::new(input).iter())) { Ok(mut p) => match p.parse_cddl() { Ok(_) => Ok(()), #[cfg(feature = "std")] Err(Error::INCREMENTAL) if !p.errors.is_empty() => { let e = p.report_errors(false).unwrap().unwrap(); #[cfg(feature = "std")] println!("{}", e); assert_eq!( e, indoc!( r#" error: parser errors ┌─ input:2:1 │ 2 │ a = b │ ^^^^^ rule with the same identifier is already defined "# ) ); Ok(()) } #[cfg(not(feature = "std"))] Err(Error::INCREMENTAL) if !p.errors.is_empty() => { assert_eq!( p.report_errors().unwrap(), indoc!( r#" error: parser errors ┌── input:2:1 ─── │ 2 │ a = b │ ^^^^^ rule with the same identifier is already defined "# ) ); Ok(()) } Err(e) => Err(e), }, Err(e) => Err(e), } } #[test] fn verify_genericparams() -> Result<()> { let input = r#""#; let l = Lexer::new(input); let gps = Parser::new(input, Box::new(l.iter()))?.parse_genericparm()?; let expected_output = GenericParams { params: vec![ GenericParam { param: Identifier { ident: "t", socket: None, span: (1, 2, 1), }, comments_before_ident: None, comments_after_ident: None, }, GenericParam { param: Identifier { ident: "v", socket: None, span: (4, 5, 1), }, comments_before_ident: None, comments_after_ident: None, }, ], span: (0, 6, 1), }; assert_eq!(gps, expected_output); assert_eq!(gps.to_string(), expected_output.to_string()); Ok(()) } #[test] fn verify_genericparm_diagnostic() -> Result<()> { let input = r#"<1, 2>"#; match Parser::new(input, Box::new(Lexer::new(input).iter())) { Ok(mut p) => match p.parse_genericparm() { Ok(_) => Ok(()), #[cfg(feature = "std")] Err(Error::INCREMENTAL) if !p.errors.is_empty() => { let e = p.report_errors(false).unwrap().unwrap(); #[cfg(feature = "std")] println!("{}", e); assert_eq!( e, indoc!( r#" error: parser errors ┌─ input:1:2 │ 1 │ <1, 2> │ ^ generic parameters must be named identifiers "# ) ); Ok(()) } #[cfg(not(feature = "std"))] Err(Error::INCREMENTAL) if !p.errors.is_empty() => { assert_eq!( p.report_errors().unwrap(), indoc!( r#" error: parser errors ┌── input:1:2 ─── │ 1 │ <1, 2> │ ^ generic parameters must be named identifiers "# ) ); Ok(()) } Err(e) => Err(e), }, Err(e) => Err(e), } } #[test] fn verify_genericparm_rule_diagnostic() -> Result<()> { let input = indoc!( r#" rule = test ruleb = rulec ruleb = ruled rulec = rulee rulec = rulee2 "# ); match Parser::new(input, Box::new(Lexer::new(input).iter())) { Ok(mut p) => match p.parse_cddl() { Ok(_) => Ok(()), #[cfg(feature = "std")] Err(Error::INCREMENTAL) if !p.errors.is_empty() => { let e = p.report_errors(false).unwrap().unwrap(); println!("{}", e); assert_eq!( e, indoc!( r#" error: parser errors ┌─ input:1:6 │ 1 │ rule = test │ ^^^^^^^^^^^^^ generic parameters should be between angle brackets '<' and '>' and separated by a comma ',' 2 │ ruleb = rulec 3 │ ruleb = ruled │ ^^^^^^^^^^^^^ rule with the same identifier is already defined 4 │ rulec = rulee 5 │ rulec = rulee2 │ ^^^^^^^^^^^^^^ rule with the same identifier is already defined "# ) ); Ok(()) } #[cfg(not(feature = "std"))] Err(Error::INCREMENTAL) if !p.errors.is_empty() => { assert_eq!( p.report_errors().unwrap(), indoc!( r#" error: parser errors ┌── input:1:6 ─── │ 1 │ rule = test │ ^^^^^^^^^^^^^ Generic parameters should be between angle brackets '<' and '>' and separated by a comma ',' 2 │ ruleb = rulec 3 │ ruleb = ruled │ ^^^^^^^^^^^^^ rule with the same identifier is already defined 4 │ rulec = rulee 5 │ rulec = rulee2 │ ^^^^^^^^^^^^^^ rule with the same identifier is already defined "# ) ); Ok(()) } Err(e) => Err(e), }, Err(e) => Err(e), } } #[test] fn verify_genericargs() -> Result<()> { let input = r#"<"reboot", "now">"#; let generic_args = Parser::new(input, Box::new(Lexer::new(input).iter()))?.parse_genericargs()?; let expected_output = GenericArgs { args: vec![ GenericArg { arg: Box::from(Type1 { type2: Type2::TextValue { value: "reboot".into(), span: (1, 9, 1), }, operator: None, comments_after_type: None, span: (1, 9, 1), }), comments_before_type: None, comments_after_type: None, }, GenericArg { arg: Box::from(Type1 { type2: Type2::TextValue { value: "now".into(), span: (11, 16, 1), }, operator: None, comments_after_type: None, span: (11, 16, 1), }), comments_before_type: None, comments_after_type: None, }, ], span: (0, 17, 1), }; assert_eq!(generic_args, expected_output); assert_eq!(generic_args.to_string(), expected_output.to_string()); Ok(()) } #[test] fn verify_type() -> Result<()> { let input = r#"( tchoice1 / tchoice2 )"#; let t = Parser::new(input, Box::new(Lexer::new(input).iter()))?.parse_type(None)?; let expected_output = Type { type_choices: vec![TypeChoice { type1: Type1 { type2: Type2::ParenthesizedType { pt: Type { type_choices: vec![ TypeChoice { type1: Type1 { type2: Type2::Typename { ident: Identifier { ident: "tchoice1", socket: None, span: (2, 10, 1), }, generic_args: None, span: (2, 10, 1), }, operator: None, comments_after_type: None, span: (2, 10, 1), }, comments_before_type: None, comments_after_type: None, }, TypeChoice { type1: Type1 { type2: Type2::Typename { ident: Identifier { ident: "tchoice2", socket: None, span: (13, 21, 1), }, generic_args: None, span: (13, 21, 1), }, operator: None, comments_after_type: None, span: (13, 21, 1), }, comments_before_type: None, comments_after_type: None, }, ], span: (2, 21, 1), }, comments_before_type: None, comments_after_type: None, span: (0, 23, 1), }, operator: None, comments_after_type: None, span: (0, 23, 1), }, comments_before_type: None, comments_after_type: None, }], span: (0, 23, 1), }; assert_eq!(t, expected_output); assert_eq!(t.to_string(), expected_output.to_string()); Ok(()) } #[test] fn verify_type1() -> Result<()> { let inputs = [ r#"5..10"#, r#"-10.5...10.1"#, r#"1.5..4.5"#, r#"my..lower ... upper"#, r#"target .lt controller"#, r#"( text / tstr ) .eq "hello""#, ]; let expected_outputs = [ Type1 { type2: Type2::UintValue { value: 5, span: (0, 1, 1), }, operator: Some(Operator { operator: RangeCtlOp::RangeOp { is_inclusive: true, span: (1, 3, 1), }, type2: Type2::UintValue { value: 10, span: (3, 5, 1), }, comments_before_operator: None, comments_after_operator: None, }), comments_after_type: None, span: (0, 5, 1), }, Type1 { type2: Type2::FloatValue { value: -10.5, span: (0, 5, 1), }, operator: Some(Operator { operator: RangeCtlOp::RangeOp { is_inclusive: false, span: (5, 8, 1), }, type2: Type2::FloatValue { value: 10.1, span: (8, 12, 1), }, comments_before_operator: None, comments_after_operator: None, }), comments_after_type: None, span: (0, 12, 1), }, Type1 { type2: Type2::FloatValue { value: 1.5, span: (0, 3, 1), }, operator: Some(Operator { operator: RangeCtlOp::RangeOp { is_inclusive: true, span: (3, 5, 1), }, type2: Type2::FloatValue { value: 4.5, span: (5, 8, 1), }, comments_before_operator: None, comments_after_operator: None, }), comments_after_type: None, span: (0, 8, 1), }, Type1 { type2: Type2::Typename { ident: Identifier { ident: "my..lower", socket: None, span: (0, 9, 1), }, generic_args: None, span: (0, 9, 1), }, operator: Some(Operator { operator: RangeCtlOp::RangeOp { is_inclusive: false, span: (10, 13, 1), }, type2: Type2::Typename { ident: Identifier { ident: "upper", socket: None, span: (14, 19, 1), }, generic_args: None, span: (14, 19, 1), }, comments_before_operator: None, comments_after_operator: None, }), comments_after_type: None, span: (0, 19, 1), }, Type1 { type2: Type2::Typename { ident: Identifier { ident: "target", socket: None, span: (0, 6, 1), }, generic_args: None, span: (0, 6, 1), }, operator: Some(Operator { operator: RangeCtlOp::CtlOp { ctrl: ".lt", span: (7, 10, 1), }, type2: Type2::Typename { ident: Identifier { ident: "controller", socket: None, span: (11, 21, 1), }, generic_args: None, span: (11, 21, 1), }, comments_before_operator: None, comments_after_operator: None, }), comments_after_type: None, span: (0, 21, 1), }, Type1 { type2: Type2::ParenthesizedType { pt: Type { type_choices: vec![ TypeChoice { type1: Type1 { type2: Type2::Typename { ident: Identifier { ident: "text", socket: None, span: (2, 6, 1), }, generic_args: None, span: (2, 6, 1), }, operator: None, comments_after_type: None, span: (2, 6, 1), }, comments_before_type: None, comments_after_type: None, }, TypeChoice { type1: Type1 { type2: Type2::Typename { ident: Identifier { ident: "tstr", socket: None, span: (9, 13, 1), }, generic_args: None, span: (9, 13, 1), }, operator: None, comments_after_type: None, span: (9, 13, 1), }, comments_before_type: None, comments_after_type: None, }, ], span: (2, 13, 1), }, comments_before_type: None, comments_after_type: None, span: (0, 15, 1), }, operator: Some(Operator { operator: RangeCtlOp::CtlOp { ctrl: ".eq", span: (16, 19, 1), }, type2: Type2::TextValue { value: "hello".into(), span: (20, 27, 1), }, comments_before_operator: None, comments_after_operator: None, }), comments_after_type: None, span: (0, 27, 1), }, ]; for (idx, expected_output) in expected_outputs.iter().enumerate() { let l = Lexer::new(inputs[idx]); let t1 = Parser::new(inputs[idx], Box::new(l.iter()))?.parse_type1(None)?; assert_eq!(&t1, expected_output); assert_eq!(t1.to_string(), expected_output.to_string()); } Ok(()) } #[test] fn verify_type2() -> Result<()> { let inputs = [ r#""myvalue""#, r#"message<"reboot", "now">"#, r#"$$tcp-option"#, r#"~group1"#, r#"#6.997(tstr)"#, r#"9.9"#, r#"#"#, r#"[*3 reputon]"#, r#"[+ reputon]"#, r#"&groupname"#, r#"&( inlinegroup )"#, r#"{ ? "optional-key" ^ => int, }"#, r#"[ ( a: int, b: tstr ) ]"#, ]; let expected_outputs = [ Type2::TextValue { value: "myvalue".into(), span: (0, 9, 1), }, Type2::Typename { ident: Identifier { ident: "message", socket: None, span: (0, 7, 1), }, generic_args: Some(GenericArgs { args: vec![ GenericArg { arg: Box::from(Type1 { type2: Type2::TextValue { value: "reboot".into(), span: (8, 16, 1), }, operator: None, comments_after_type: None, span: (8, 16, 1), }), comments_before_type: None, comments_after_type: None, }, GenericArg { arg: Box::from(Type1 { type2: Type2::TextValue { value: "now".into(), span: (18, 23, 1), }, operator: None, comments_after_type: None, span: (18, 23, 1), }), comments_before_type: None, comments_after_type: None, }, ], span: (7, 24, 1), }), span: (0, 24, 1), }, Type2::Typename { ident: Identifier { ident: "tcp-option", socket: Some(SocketPlug::GROUP), span: (0, 12, 1), }, generic_args: None, span: (0, 12, 1), }, Type2::Unwrap { ident: Identifier { ident: "group1", socket: None, span: (1, 7, 1), }, generic_args: None, comments: None, span: (0, 0, 0), }, Type2::TaggedData { tag: Some(997), t: Type { type_choices: vec![TypeChoice { type1: Type1 { type2: Type2::Typename { ident: Identifier { ident: "tstr", socket: None, span: (7, 11, 1), }, generic_args: None, span: (7, 11, 1), }, operator: None, comments_after_type: None, span: (7, 11, 1), }, comments_before_type: None, comments_after_type: None, }], span: (7, 11, 1), }, comments_before_type: None, comments_after_type: None, span: (0, 11, 1), }, Type2::FloatValue { value: 9.9, span: (0, 3, 1), }, Type2::Any { span: (0, 1, 1) }, Type2::Array { group: Group { group_choices: vec![GroupChoice { group_entries: vec![( GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur: Some(Occurrence { occur: Occur::Exact { lower: None, upper: Some(3), span: (1, 3, 1), }, comments: None, _a: PhantomData, }), name: Identifier { ident: "reputon", socket: None, span: (4, 11, 1), }, generic_args: None, }, leading_comments: None, trailing_comments: None, span: (1, 11, 1), }, OptionalComma { optional_comma: false, trailing_comments: None, _a: PhantomData, }, )], comments_before_grpchoice: None, span: (1, 11, 1), }], span: (1, 11, 1), }, comments_before_group: None, comments_after_group: None, span: (0, 12, 1), }, Type2::Array { group: Group { group_choices: vec![GroupChoice { group_entries: vec![( GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur: Some(Occurrence { occur: Occur::OneOrMore { span: (1, 2, 1) }, comments: None, _a: PhantomData, }), name: Identifier { ident: "reputon", socket: None, span: (3, 10, 1), }, generic_args: None, }, leading_comments: None, trailing_comments: None, span: (1, 10, 1), }, OptionalComma { optional_comma: false, trailing_comments: None, _a: PhantomData, }, )], comments_before_grpchoice: None, span: (1, 10, 1), }], span: (1, 10, 1), }, comments_before_group: None, comments_after_group: None, span: (0, 11, 1), }, Type2::ChoiceFromGroup { ident: Identifier { ident: "groupname", socket: None, span: (1, 10, 1), }, generic_args: None, comments: None, span: (0, 10, 1), }, Type2::ChoiceFromInlineGroup { group: Group { group_choices: vec![GroupChoice { group_entries: vec![( GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur: None, name: Identifier { ident: "inlinegroup", socket: None, span: (3, 14, 1), }, generic_args: None, }, leading_comments: None, trailing_comments: None, span: (3, 14, 1), }, OptionalComma { optional_comma: false, trailing_comments: None, _a: PhantomData, }, )], comments_before_grpchoice: None, span: (3, 14, 1), }], span: (3, 14, 1), }, comments: None, comments_before_group: None, comments_after_group: None, span: (0, 14, 1), }, Type2::Map { group: Group { group_choices: vec![GroupChoice { group_entries: vec![( GroupEntry::ValueMemberKey { ge: Box::from(ValueMemberKeyEntry { occur: Some(Occurrence { occur: Occur::Optional { span: (2, 3, 1) }, comments: None, _a: PhantomData, }), member_key: Some(MemberKey::Type1 { t1: Box::from(Type1 { type2: Type2::TextValue { value: "optional-key".into(), span: (4, 18, 1), }, operator: None, comments_after_type: None, span: (4, 18, 1), }), is_cut: true, comments_before_cut: None, comments_after_cut: None, comments_after_arrowmap: None, span: (4, 23, 1), }), entry_type: Type { type_choices: vec![TypeChoice { type1: Type1 { type2: Type2::Typename { ident: Identifier { ident: "int", socket: None, span: (24, 27, 1), }, generic_args: None, span: (24, 27, 1), }, operator: None, comments_after_type: None, span: (24, 27, 1), }, comments_before_type: None, comments_after_type: None, }], span: (24, 27, 1), }, }), leading_comments: None, trailing_comments: None, span: (2, 28, 1), }, OptionalComma { optional_comma: true, trailing_comments: None, _a: PhantomData, }, )], comments_before_grpchoice: None, span: (2, 28, 1), }], span: (2, 28, 1), }, comments_before_group: None, comments_after_group: None, span: (0, 30, 1), }, Type2::Array { group: Group { group_choices: vec![GroupChoice { group_entries: vec![( GroupEntry::InlineGroup { group: Group { group_choices: vec![GroupChoice { group_entries: vec![ ( GroupEntry::ValueMemberKey { ge: Box::from(ValueMemberKeyEntry { occur: None, member_key: Some(MemberKey::Bareword { ident: Identifier { ident: "a", socket: None, span: (4, 5, 1), }, comments: None, comments_after_colon: None, span: (4, 6, 1), }), entry_type: Type { type_choices: vec![TypeChoice { type1: Type1 { type2: Type2::Typename { ident: Identifier { ident: "int", socket: None, span: (7, 10, 1), }, generic_args: None, span: (7, 10, 1), }, operator: None, comments_after_type: None, span: (7, 10, 1), }, comments_before_type: None, comments_after_type: None, }], span: (7, 10, 1), }, }), leading_comments: None, trailing_comments: None, span: (4, 11, 1), }, OptionalComma { optional_comma: true, trailing_comments: None, _a: PhantomData, }, ), ( GroupEntry::ValueMemberKey { ge: Box::from(ValueMemberKeyEntry { occur: None, member_key: Some(MemberKey::Bareword { ident: Identifier { ident: "b", socket: None, span: (12, 13, 1), }, comments: None, comments_after_colon: None, span: (12, 14, 1), }), entry_type: Type { type_choices: vec![TypeChoice { type1: Type1 { type2: Type2::Typename { ident: Identifier { ident: "tstr", socket: None, span: (15, 19, 1), }, generic_args: None, span: (15, 19, 1), }, operator: None, comments_after_type: None, span: (15, 19, 1), }, comments_before_type: None, comments_after_type: None, }], span: (15, 19, 1), }, }), leading_comments: None, trailing_comments: None, span: (12, 19, 1), }, OptionalComma { optional_comma: false, trailing_comments: None, _a: PhantomData, }, ), ], comments_before_grpchoice: None, span: (4, 19, 1), }], span: (4, 19, 1), }, occur: None, comments_before_group: None, comments_after_group: None, span: (2, 21, 1), }, OptionalComma { optional_comma: false, trailing_comments: None, _a: PhantomData, }, )], comments_before_grpchoice: None, span: (2, 21, 1), }], span: (2, 21, 1), }, comments_before_group: None, comments_after_group: None, span: (0, 23, 1), }, ]; for (idx, expected_output) in expected_outputs.iter().enumerate() { let l = Lexer::new(inputs[idx]); let t2 = Parser::new(inputs[idx], Box::new(l.iter()))?.parse_type2()?; assert_eq!(&t2, expected_output); assert_eq!(t2.to_string(), expected_output.to_string()); } Ok(()) } #[test] fn verify_type2_complex() -> Result<()> { let inputs = [ r#"[ [* file-entry], [* directory-entry] ]"#, r#"{ int, int // int, tstr }"#, r#"{ int, int, int, tstr }"#, ]; let expected_ouputs = [ Type2::Array { group: Group { group_choices: vec![GroupChoice { group_entries: vec![ ( GroupEntry::ValueMemberKey { ge: Box::from(ValueMemberKeyEntry { occur: None, member_key: None, entry_type: Type { type_choices: vec![TypeChoice { type1: Type1 { type2: Type2::Array { group: Group { group_choices: vec![GroupChoice { group_entries: vec![( GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur: Some(Occurrence { occur: Occur::ZeroOrMore { span: (3, 4, 1) }, comments: None, _a: PhantomData, }), name: Identifier { ident: "file-entry", socket: None, span: (5, 15, 1), }, generic_args: None, }, leading_comments: None, trailing_comments: None, span: (3, 15, 1), }, OptionalComma { optional_comma: false, trailing_comments: None, _a: PhantomData, }, )], comments_before_grpchoice: None, span: (3, 15, 1), }], span: (3, 15, 1), }, comments_before_group: None, comments_after_group: None, span: (2, 16, 1), }, operator: None, comments_after_type: None, span: (2, 16, 1), }, comments_before_type: None, comments_after_type: None, }], span: (2, 16, 1), }, }), leading_comments: None, trailing_comments: None, span: (2, 17, 1), }, OptionalComma { optional_comma: true, trailing_comments: None, _a: PhantomData, }, ), ( GroupEntry::ValueMemberKey { ge: Box::from(ValueMemberKeyEntry { occur: None, member_key: None, entry_type: Type { type_choices: vec![TypeChoice { type1: Type1 { type2: Type2::Array { group: Group { group_choices: vec![GroupChoice { group_entries: vec![( GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur: Some(Occurrence { occur: Occur::ZeroOrMore { span: (19, 20, 1) }, comments: None, _a: PhantomData, }), name: Identifier { ident: "directory-entry", socket: None, span: (21, 36, 1), }, generic_args: None, }, leading_comments: None, trailing_comments: None, span: (19, 36, 1), }, OptionalComma { optional_comma: false, trailing_comments: None, _a: PhantomData, }, )], comments_before_grpchoice: None, span: (19, 36, 1), }], span: (19, 36, 1), }, comments_before_group: None, comments_after_group: None, span: (18, 37, 1), }, operator: None, comments_after_type: None, span: (18, 37, 1), }, comments_before_type: None, comments_after_type: None, }], span: (18, 37, 1), }, }), leading_comments: None, trailing_comments: None, span: (18, 37, 1), }, OptionalComma { optional_comma: false, trailing_comments: None, _a: PhantomData, }, ), ], comments_before_grpchoice: None, span: (2, 37, 1), }], span: (2, 37, 1), }, comments_before_group: None, comments_after_group: None, span: (0, 39, 1), }, Type2::Map { group: Group { group_choices: vec![ GroupChoice { group_entries: vec![ ( GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur: None, name: Identifier { ident: "int", socket: None, span: (2, 5, 1), }, generic_args: None, }, leading_comments: None, trailing_comments: None, span: (2, 6, 1), }, OptionalComma { optional_comma: true, trailing_comments: None, _a: PhantomData, }, ), ( GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur: None, name: Identifier { ident: "int", socket: None, span: (7, 10, 1), }, generic_args: None, }, leading_comments: None, trailing_comments: None, span: (7, 10, 1), }, OptionalComma { optional_comma: false, trailing_comments: None, _a: PhantomData, }, ), ], comments_before_grpchoice: None, span: (2, 10, 1), }, GroupChoice { group_entries: vec![ ( GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur: None, name: Identifier { ident: "int", socket: None, span: (14, 17, 1), }, generic_args: None, }, leading_comments: None, trailing_comments: None, span: (14, 18, 1), }, OptionalComma { optional_comma: true, trailing_comments: None, _a: PhantomData, }, ), ( GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur: None, name: Identifier { ident: "tstr", socket: None, span: (19, 23, 1), }, generic_args: None, }, leading_comments: None, trailing_comments: None, span: (19, 23, 1), }, OptionalComma { optional_comma: false, trailing_comments: None, _a: PhantomData, }, ), ], comments_before_grpchoice: None, span: (14, 23, 1), }, ], span: (2, 23, 1), }, comments_before_group: None, comments_after_group: None, span: (0, 25, 1), }, Type2::Map { group: Group { group_choices: vec![GroupChoice { group_entries: vec![ ( GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur: None, name: Identifier { ident: "int", socket: None, span: (2, 5, 1), }, generic_args: None, }, leading_comments: None, trailing_comments: None, span: (2, 6, 1), }, OptionalComma { optional_comma: true, trailing_comments: None, _a: PhantomData, }, ), ( GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur: None, name: Identifier { ident: "int", socket: None, span: (7, 10, 1), }, generic_args: None, }, leading_comments: None, trailing_comments: None, span: (7, 11, 1), }, OptionalComma { optional_comma: true, trailing_comments: None, _a: PhantomData, }, ), ( GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur: None, name: Identifier { ident: "int", socket: None, span: (12, 15, 1), }, generic_args: None, }, leading_comments: None, trailing_comments: None, span: (12, 16, 1), }, OptionalComma { optional_comma: true, trailing_comments: None, _a: PhantomData, }, ), ( GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur: None, name: Identifier { ident: "tstr", socket: None, span: (17, 21, 1), }, generic_args: None, }, leading_comments: None, trailing_comments: None, span: (17, 21, 1), }, OptionalComma { optional_comma: false, trailing_comments: None, _a: PhantomData, }, ), ], comments_before_grpchoice: None, span: (2, 21, 1), }], span: (2, 21, 1), }, comments_before_group: None, comments_after_group: None, span: (0, 23, 1), }, ]; for (idx, expected_output) in expected_ouputs.iter().enumerate() { let l = Lexer::new(inputs[idx]); let t2 = Parser::new(inputs[idx], Box::new(l.iter()))?.parse_type2()?; assert_eq!(&t2, expected_output); assert_eq!(t2.to_string(), expected_output.to_string()); } Ok(()) } #[test] fn verify_grpent() -> Result<()> { let inputs = [ r#"* type1 ^ => "value""#, r#"type1: type2"#, r#"typename"#, r#"? 0: addrdistr"#, r#"0: finite_set"#, r#"* [credential] => coin"#, ]; let expected_outputs = [ GroupEntry::ValueMemberKey { ge: Box::from(ValueMemberKeyEntry { occur: Some(Occurrence { occur: Occur::ZeroOrMore { span: (0, 1, 1) }, comments: None, _a: PhantomData, }), member_key: Some(MemberKey::Type1 { t1: Box::from(Type1 { type2: Type2::Typename { ident: Identifier { ident: "type1", socket: None, span: (2, 7, 1), }, generic_args: None, span: (2, 7, 1), }, operator: None, comments_after_type: None, span: (2, 7, 1), }), is_cut: true, comments_before_cut: None, comments_after_cut: None, comments_after_arrowmap: None, span: (2, 12, 1), }), entry_type: Type { type_choices: vec![TypeChoice { type1: Type1 { type2: Type2::TextValue { value: "value".into(), span: (13, 20, 1), }, operator: None, comments_after_type: None, span: (13, 20, 1), }, comments_before_type: None, comments_after_type: None, }], span: (13, 20, 1), }, }), leading_comments: None, trailing_comments: None, span: (0, 20, 1), }, GroupEntry::ValueMemberKey { ge: Box::from(ValueMemberKeyEntry { occur: None, member_key: Some(MemberKey::Bareword { ident: Identifier { ident: "type1", socket: None, span: (0, 5, 1), }, comments: None, comments_after_colon: None, span: (0, 6, 1), }), entry_type: Type { type_choices: vec![TypeChoice { type1: Type1 { type2: Type2::Typename { ident: Identifier { ident: "type2", socket: None, span: (7, 12, 1), }, generic_args: None, span: (7, 12, 1), }, operator: None, comments_after_type: None, span: (7, 12, 1), }, comments_before_type: None, comments_after_type: None, }], span: (7, 12, 1), }, }), leading_comments: None, trailing_comments: None, span: (0, 12, 1), }, GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur: None, name: Identifier { ident: "typename", socket: None, span: (0, 8, 1), }, generic_args: None, }, leading_comments: None, trailing_comments: None, span: (0, 8, 1), }, GroupEntry::ValueMemberKey { ge: Box::from(ValueMemberKeyEntry { occur: Some(Occurrence { occur: Occur::Optional { span: (0, 1, 1) }, comments: None, _a: PhantomData, }), member_key: Some(MemberKey::Value { value: token::Value::UINT(0), comments: None, comments_after_colon: None, span: (2, 4, 1), }), entry_type: Type { type_choices: vec![TypeChoice { type1: Type1 { type2: Type2::Typename { ident: Identifier { ident: "addrdistr", socket: None, span: (5, 14, 1), }, generic_args: None, span: (5, 14, 1), }, operator: None, comments_after_type: None, span: (5, 14, 1), }, comments_before_type: None, comments_after_type: None, }], span: (5, 14, 1), }, }), leading_comments: None, trailing_comments: None, span: (0, 14, 1), }, GroupEntry::ValueMemberKey { ge: Box::from(ValueMemberKeyEntry { occur: None, member_key: Some(MemberKey::Value { value: token::Value::UINT(0), comments: None, comments_after_colon: None, span: (0, 2, 1), }), entry_type: Type { type_choices: vec![TypeChoice { type1: Type1 { type2: Type2::Typename { ident: Identifier { ident: "finite_set", socket: None, span: (3, 13, 1), }, generic_args: Some(GenericArgs { args: vec![GenericArg { arg: Box::from(Type1 { type2: Type2::Typename { ident: Identifier { ident: "transaction_input", socket: None, span: (14, 31, 1), }, generic_args: None, span: (14, 31, 1), }, operator: None, comments_after_type: None, span: (14, 31, 1), }), comments_before_type: None, comments_after_type: None, }], span: (13, 32, 1), }), span: (3, 32, 1), }, operator: None, comments_after_type: None, span: (3, 32, 1), }, comments_before_type: None, comments_after_type: None, }], span: (3, 32, 1), }, }), leading_comments: None, trailing_comments: None, span: (0, 32, 1), }, GroupEntry::ValueMemberKey { ge: Box::from(ValueMemberKeyEntry { occur: Some(Occurrence { occur: Occur::ZeroOrMore { span: (0, 1, 1) }, comments: None, _a: PhantomData, }), member_key: Some(MemberKey::Type1 { t1: Box::from(Type1 { type2: Type2::Array { group: Group { group_choices: vec![GroupChoice { group_entries: vec![( GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur: None, name: Identifier { ident: "credential", socket: None, span: (3, 13, 1), }, generic_args: None, }, leading_comments: None, trailing_comments: None, span: (3, 13, 1), }, OptionalComma { optional_comma: false, trailing_comments: None, _a: PhantomData, }, )], comments_before_grpchoice: None, span: (3, 13, 1), }], span: (3, 13, 1), }, comments_before_group: None, comments_after_group: None, span: (2, 14, 1), }, operator: None, comments_after_type: None, span: (2, 14, 1), }), is_cut: false, comments_before_cut: None, comments_after_cut: None, comments_after_arrowmap: None, span: (2, 22, 1), }), entry_type: Type { type_choices: vec![TypeChoice { type1: Type1 { type2: Type2::Typename { ident: Identifier { ident: "coin", socket: None, span: (18, 22, 1), }, generic_args: None, span: (18, 22, 1), }, operator: None, comments_after_type: None, span: (18, 22, 1), }, comments_before_type: None, comments_after_type: None, }], span: (18, 22, 1), }, }), leading_comments: None, trailing_comments: None, span: (0, 22, 1), }, ]; for (idx, expected_output) in expected_outputs.iter().enumerate() { let l = Lexer::new(inputs[idx]); let grpent = Parser::new(inputs[idx], Box::new(l.iter()))?.parse_grpent(false)?; assert_eq!(&grpent, expected_output); assert_eq!(grpent.to_string(), expected_output.to_string()); } Ok(()) } #[test] fn verify_memberkey() -> Result<()> { let inputs = [ r#"type1 =>"#, r#"( "mytype1" / int ) ^ =>"#, r#"mybareword:"#, r#"my..bareword:"#, r#""myvalue": "#, r#"0:"#, ]; let expected_outputs = [ MemberKey::Type1 { t1: Box::from(Type1 { type2: Type2::Typename { ident: Identifier { ident: "type1", socket: None, span: (0, 5, 1), }, generic_args: None, span: (0, 5, 1), }, operator: None, comments_after_type: None, span: (0, 5, 1), }), is_cut: false, comments_before_cut: None, comments_after_cut: None, comments_after_arrowmap: None, span: (0, 8, 1), }, MemberKey::Type1 { t1: Box::from(Type1 { type2: Type2::ParenthesizedType { pt: Type { type_choices: vec![ TypeChoice { type1: Type1 { type2: Type2::TextValue { value: "mytype1".into(), span: (2, 11, 1), }, operator: None, span: (2, 11, 1), comments_after_type: None, }, comments_after_type: None, comments_before_type: None, }, TypeChoice { type1: Type1 { type2: Type2::Typename { ident: Identifier { ident: "int", span: (14, 17, 1), socket: None, }, span: (14, 17, 1), generic_args: None, }, span: (14, 17, 1), comments_after_type: None, operator: None, }, comments_before_type: None, comments_after_type: None, }, ], span: (2, 17, 1), }, span: (0, 19, 1), comments_before_type: None, comments_after_type: None, }, operator: None, comments_after_type: None, span: (0, 19, 1), }), is_cut: true, comments_before_cut: None, comments_after_cut: None, comments_after_arrowmap: None, span: (0, 24, 1), }, MemberKey::Bareword { ident: Identifier { ident: "mybareword", socket: None, span: (0, 10, 1), }, comments: None, comments_after_colon: None, span: (0, 11, 1), }, MemberKey::Bareword { ident: Identifier { ident: "my..bareword", socket: None, span: (0, 12, 1), }, comments: None, comments_after_colon: None, span: (0, 13, 1), }, MemberKey::Value { value: token::Value::TEXT("myvalue".into()), comments: None, comments_after_colon: None, span: (0, 10, 1), }, MemberKey::Value { value: token::Value::UINT(0), comments: None, comments_after_colon: None, span: (0, 2, 1), }, ]; for (idx, expected_output) in expected_outputs.iter().enumerate() { let l = Lexer::new(inputs[idx]); let mk = Parser::new(inputs[idx], Box::new(l.iter()))?.parse_memberkey(false)?; if let Some(mk) = mk { assert_eq!(&mk, expected_output); assert_eq!(mk.to_string(), expected_output.to_string()); } } Ok(()) } #[test] fn verify_occur() -> Result<()> { let inputs = [r#"1*3"#, r#"*"#, r#"+"#, r#"5*"#, r#"*3"#, r#"?"#]; let expected_outputs = [ Occurrence { occur: Occur::Exact { lower: Some(1), upper: Some(3), span: (0, 3, 1), }, comments: None, _a: PhantomData, }, Occurrence { occur: Occur::ZeroOrMore { span: (0, 1, 1) }, comments: None, _a: PhantomData, }, Occurrence { occur: Occur::OneOrMore { span: (0, 1, 1) }, comments: None, _a: PhantomData, }, Occurrence { occur: Occur::Exact { lower: Some(5), upper: None, span: (0, 2, 1), }, comments: None, _a: PhantomData, }, Occurrence { occur: Occur::Exact { lower: None, upper: Some(3), span: (0, 2, 1), }, comments: None, _a: PhantomData, }, Occurrence { occur: Occur::Optional { span: (0, 1, 1) }, comments: None, _a: PhantomData, }, ]; for (idx, expected_output) in expected_outputs.iter().enumerate() { let l = Lexer::new(inputs[idx]); let o = Parser::new(inputs[idx], Box::new(l.iter()))?.parse_occur(false)?; if let Some(o) = o { assert_eq!(&o, expected_output); assert_eq!(o.to_string(), expected_output.to_string()); } } Ok(()) } #[test] fn simple_type_choice_comments() -> Result<()> { let input = indoc!( r#" ; general_comment myrule = ; comments_after_assignt 1234 ; comments_after_type / ; comments_before_type 456 ; comments_after_type2 ; comments_after_rule "# ); let expected_output = CDDL { rules: vec![Rule::Type { rule: TypeRule { name: Identifier { ident: "myrule", socket: None, span: (19, 25, 3), }, generic_params: None, is_type_choice_alternate: false, value: Type { type_choices: vec![ TypeChoice { type1: Type1 { type2: Type2::UintValue { value: 1234, span: (57, 61, 5), }, operator: None, comments_after_type: Some(Comments(vec![" comments_after_type"])), span: (57, 61, 5), }, comments_before_type: None, comments_after_type: None, }, TypeChoice { type1: Type1 { type2: Type2::UintValue { value: 456, span: (117, 120, 9), }, operator: None, comments_after_type: Some(Comments(vec![" comments_after_type2"])), span: (117, 120, 9), }, comments_before_type: Some(Comments(vec![" comments_before_type"])), comments_after_type: None, }, ], span: (57, 120, 5), }, comments_before_assignt: None, comments_after_assignt: Some(Comments(vec![" comments_after_assignt"])), }, comments_after_rule: Some(Comments(vec![" comments_after_rule"])), span: (19, 120, 3), }], comments: Some(Comments(vec![" general_comment"])), }; let parser = Parser::new(input, Box::new(Lexer::new(input).iter()))?.parse_cddl()?; assert_eq!(parser, expected_output); assert_eq!(parser.to_string(), expected_output.to_string()); Ok(()) } #[test] fn simple_group_comments() -> Result<()> { let input = indoc!( r#" address = [ bytes, ; @name address uint ; @name checksum ] "# ); let expected_output = CDDL { rules: vec![Rule::Type { rule: TypeRule { name: Identifier { ident: "address", socket: None, span: (0, 7, 1), }, generic_params: None, is_type_choice_alternate: false, value: Type { type_choices: vec![TypeChoice { type1: Type1 { type2: Type2::Array { group: Group { group_choices: vec![GroupChoice { group_entries: vec![ ( GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { name: Identifier { ident: "bytes", socket: None, span: (14, 19, 2), }, occur: None, generic_args: None, }, span: (14, 20, 2), leading_comments: None, trailing_comments: None, }, OptionalComma { optional_comma: true, trailing_comments: Some(Comments(vec![" @name address"])), _a: PhantomData, }, ), ( GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { name: Identifier { ident: "uint", socket: None, span: (39, 43, 3), }, occur: None, generic_args: None, }, span: (39, 43, 3), leading_comments: None, trailing_comments: Some(Comments(vec![" @name checksum"])), }, OptionalComma { optional_comma: false, trailing_comments: None, _a: PhantomData, }, ), ], span: (11, 43, 1), comments_before_grpchoice: None, }], span: (11, 43, 1), }, span: (10, 64, 1), comments_before_group: None, comments_after_group: None, }, operator: None, span: (10, 64, 1), comments_after_type: None, }, comments_before_type: None, comments_after_type: None, }], span: (10, 64, 1), }, comments_before_assignt: None, comments_after_assignt: None, }, span: (0, 64, 1), comments_after_rule: None, }], comments: None, }; let parser = Parser::new(input, Box::new(Lexer::new(input).iter()))?.parse_cddl()?; assert_eq!(parser, expected_output); assert_eq!(parser.to_string(), expected_output.to_string()); Ok(()) } #[test] fn group_choice_comments() -> Result<()> { let input = indoc!( r#" block = [ ; comments_before_group ; comments_before_grpchoice1 0, text // ; comments_before_grpchoice2 1, bytes ] "# ); let expected_output = CDDL { rules: vec![Rule::Type { rule: TypeRule { name: Identifier { ident: "block", socket: None, span: (0, 5, 1), }, generic_params: None, is_type_choice_alternate: false, value: Type { type_choices: vec![TypeChoice { type1: Type1 { type2: Type2::Array { group: Group { group_choices: vec![ GroupChoice { group_entries: vec![ ( GroupEntry::ValueMemberKey { ge: Box::from(ValueMemberKeyEntry { occur: None, member_key: None, entry_type: Type { type_choices: vec![TypeChoice { type1: Type1 { type2: Type2::UintValue { value: 0, span: (75, 76, 4), }, operator: None, span: (75, 76, 4), comments_after_type: None, }, comments_before_type: None, comments_after_type: None, }], span: (75, 76, 4), }, }), span: (75, 77, 4), leading_comments: None, trailing_comments: None, }, OptionalComma { optional_comma: true, trailing_comments: None, _a: PhantomData, }, ), ( GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur: None, name: Identifier { ident: "text", socket: None, span: (78, 82, 4), }, generic_args: None, }, span: (78, 82, 4), leading_comments: None, trailing_comments: None, }, OptionalComma { optional_comma: false, trailing_comments: None, _a: PhantomData, }, ), ], span: (9, 82, 1), comments_before_grpchoice: Some(Comments(vec![ " comments_before_grpchoice1", ])), }, GroupChoice { group_entries: vec![ ( GroupEntry::ValueMemberKey { ge: Box::from(ValueMemberKeyEntry { occur: None, member_key: None, entry_type: Type { type_choices: vec![TypeChoice { type1: Type1 { type2: Type2::UintValue { value: 1, span: (127, 128, 7), }, operator: None, span: (127, 128, 7), comments_after_type: None, }, comments_before_type: None, comments_after_type: None, }], span: (127, 128, 7), }, }), span: (127, 129, 7), leading_comments: None, trailing_comments: None, }, OptionalComma { optional_comma: true, trailing_comments: None, _a: PhantomData, }, ), ( GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur: None, name: Identifier { ident: "bytes", socket: None, span: (130, 135, 7), }, generic_args: None, }, span: (130, 135, 7), leading_comments: None, trailing_comments: None, }, OptionalComma { optional_comma: false, trailing_comments: None, _a: PhantomData, }, ), ], span: (127, 135, 5), comments_before_grpchoice: Some(Comments(vec![ " comments_before_grpchoice2", ])), }, ], span: (9, 135, 1), }, span: (8, 137, 1), comments_before_group: Some(Comments(vec![" comments_before_group"])), comments_after_group: None, }, operator: None, span: (8, 137, 1), comments_after_type: None, }, comments_before_type: None, comments_after_type: None, }], span: (8, 137, 1), }, comments_before_assignt: None, comments_after_assignt: None, }, span: (0, 137, 1), comments_after_rule: None, }], comments: None, }; let parser = Parser::new(input, Box::new(Lexer::new(input).iter()))?.parse_cddl()?; assert_eq!(parser, expected_output); assert_eq!(parser.to_string(), expected_output.to_string()); Ok(()) } #[test] fn group_type_choice_comments() -> Result<()> { let input = indoc!( r#" block = [0, bytes] ; @name comments_after_type1 / [1, bytes] ; @name comments_after_type2 ; @name comments_after_type3 "# ); let expected_output = CDDL { rules: vec![Rule::Type { rule: TypeRule { name: Identifier { ident: "block", socket: None, span: (0, 5, 1), }, generic_params: None, is_type_choice_alternate: false, value: Type { type_choices: vec![ TypeChoice { type1: Type1 { type2: Type2::Array { group: Group { group_choices: vec![GroupChoice { group_entries: vec![ ( GroupEntry::ValueMemberKey { ge: Box::from(ValueMemberKeyEntry { occur: None, member_key: None, entry_type: Type { type_choices: vec![TypeChoice { type1: Type1 { type2: Type2::UintValue { value: 0, span: (11, 12, 2), }, operator: None, span: (11, 12, 2), comments_after_type: None, }, comments_before_type: None, comments_after_type: None, }], span: (11, 12, 2), }, }), span: (11, 13, 2), leading_comments: None, trailing_comments: None, }, OptionalComma { optional_comma: true, trailing_comments: None, _a: PhantomData, }, ), ( GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur: None, name: Identifier { ident: "bytes", socket: None, span: (14, 19, 2), }, generic_args: None, }, span: (14, 19, 2), leading_comments: None, trailing_comments: None, }, OptionalComma { optional_comma: false, trailing_comments: None, _a: PhantomData, }, ), ], span: (11, 19, 2), comments_before_grpchoice: None, }], span: (11, 19, 2), }, span: (10, 20, 2), comments_before_group: None, comments_after_group: None, }, operator: None, span: (10, 20, 2), comments_after_type: Some(Comments(vec![" @name comments_after_type1"])), }, comments_before_type: None, comments_after_type: None, }, TypeChoice { type1: Type1 { type2: Type2::Array { group: Group { group_choices: vec![GroupChoice { group_entries: vec![ ( GroupEntry::ValueMemberKey { ge: Box::from(ValueMemberKeyEntry { occur: None, member_key: None, entry_type: Type { type_choices: vec![TypeChoice { type1: Type1 { type2: Type2::UintValue { value: 1, span: (57, 58, 4), }, operator: None, span: (57, 58, 4), comments_after_type: None, }, comments_before_type: None, comments_after_type: None, }], span: (57, 58, 4), }, }), span: (57, 59, 4), leading_comments: None, trailing_comments: None, }, OptionalComma { optional_comma: true, trailing_comments: None, _a: PhantomData, }, ), ( GroupEntry::TypeGroupname { ge: TypeGroupnameEntry { occur: None, name: Identifier { ident: "bytes", socket: None, span: (60, 65, 4), }, generic_args: None, }, span: (60, 65, 4), leading_comments: None, trailing_comments: None, }, OptionalComma { optional_comma: false, trailing_comments: None, _a: PhantomData, }, ), ], span: (57, 65, 4), comments_before_grpchoice: None, }], span: (57, 65, 4), }, span: (56, 66, 4), comments_before_group: None, comments_after_group: None, }, operator: None, span: (56, 66, 4), comments_after_type: Some(Comments(vec![" @name comments_after_type2"])), }, comments_before_type: None, comments_after_type: None, }, ], span: (10, 66, 2), }, comments_before_assignt: None, comments_after_assignt: None, }, span: (0, 66, 1), comments_after_rule: Some(Comments(vec![" @name comments_after_type3"])), }], comments: None, }; let parser = Parser::new(input, Box::new(Lexer::new(input).iter()))?.parse_cddl()?; assert_eq!(parser, expected_output); assert_eq!(parser.to_string(), expected_output.to_string()); Ok(()) } } cddl-0.9.4/src/token.rs000064400000000000000000000442361046102023000130320ustar 00000000000000use std::{convert::TryFrom, fmt}; #[cfg(feature = "std")] use std::borrow::Cow; #[cfg(target_arch = "wasm32")] use serde::{Deserialize, Serialize}; #[cfg(not(feature = "std"))] use alloc::{borrow::Cow, string::String}; /// Token which represents a valid CDDL character or sequence #[derive(PartialEq, Debug, Clone)] pub enum Token<'a> { /// Illegal sequence of characters ILLEGAL(&'a str), /// End of file EOF, /// Identifier IDENT( /// Identifier &'a str, /// Socket/plug Option, ), /// Value VALUE(Value<'a>), /// CBOR tag '#' TAG( /// Major type Option, /// Optional constraint Option, ), // Operators /// Assignment operator '=' ASSIGN, /// Optional occurrence indicator '?' OPTIONAL, /// Zero or more occurrence indicator '*' ASTERISK, /// One or more occurrence indicator '+' ONEORMORE, /// Unwrap operator '~' UNWRAP, // Delimiters /// Comma ',' COMMA, /// Colon ':' COLON, /// Comment text COMMENT(&'a str), /// Type choice indicator '/' TCHOICE, /// Group choice indicator '//' GCHOICE, /// Type choice alternative '/=' TCHOICEALT, /// Group choice alternative '//=' GCHOICEALT, /// Arrow map '=>' ARROWMAP, /// Cut '^' CUT, /// Range operator. Inclusive '..' if true, otherwise exclusive '...'s RANGEOP(bool), /// Range RANGE( /// Lower bound RangeValue<'a>, /// Upper bound RangeValue<'a>, /// Inclusive bool, ), /// Left opening parend LPAREN, /// Right closing parend RPAREN, /// Left opening brace LBRACE, /// Right closing brace RBRACE, /// Left opening bracket LBRACKET, /// Right closing bracket RBRACKET, /// Left opening angle bracket LANGLEBRACKET, /// Right closing angle bracket RANGLEBRACKET, /// Control operator token ControlOperator(ControlOperator), /// group to choice enumeration '&' GTOCHOICE, // Standard prelude /// false FALSE, /// true TRUE, /// bool BOOL, /// nil NIL, /// null NULL, /// uint UINT, /// nint NINT, /// int INT, /// float16 FLOAT16, /// float32 FLOAT32, /// float64 FLOAT64, /// float16-32 FLOAT1632, /// float32-64 FLOAT3264, ///float FLOAT, /// bstr BSTR, /// tstr TSTR, /// any ANY, /// bytes BYTES, /// text TEXT, /// tdate TDATE, /// time TIME, /// number NUMBER, /// biguint BIGUINT, /// bignint BIGNINT, /// bigint BIGINT, /// integer INTEGER, /// unsigned UNSIGNED, /// decfrac DECFRAC, /// bigfloat BIGFLOAT, /// eb64url EB64URL, /// eb64legacy EB64LEGACY, /// eb16k EB16, /// encoded-cbor ENCODEDCBOR, /// uri URI, /// b64url B64URL, /// b64legacy B64LEGACY, /// regexp REGEXP, /// mime-message MIMEMESSAGE, /// cbor-any CBORANY, /// undefined UNDEFINED, /// newline (used only for comment formatting when compiled with the "lsp" /// feature) NEWLINE, } /// Control operator tokens #[cfg_attr(target_arch = "wasm32", derive(Serialize))] #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum ControlOperator { // Control operators /// .size control operator SIZE, /// .bits control operator BITS, /// .regexp control operator REGEXP, /// .cbor control operator CBOR, /// .cborseq control operator CBORSEQ, /// .within control operator WITHIN, /// .and control operator AND, /// .lt control operator LT, /// .le control operator LE, /// .gt control operator GT, /// .ge control operator GE, /// .eq control operator EQ, /// .ne control operator NE, /// .default control operator DEFAULT, /// .pcre control operator /// Proposed control extension to support Perl-Compatible Regular Expressions /// (PCREs). See PCRE, #[cfg(feature = "additional-controls")] /// .cat control operator (rfc 9165) CAT, #[cfg(feature = "additional-controls")] /// .det control operator (rfc 9165) DET, #[cfg(feature = "additional-controls")] /// .plus control operator (rfc 9165) PLUS, #[cfg(feature = "additional-controls")] /// .abnf control operator (rfc 9165) ABNF, #[cfg(feature = "additional-controls")] /// .abnfb control operator (rfc 9165) ABNFB, #[cfg(feature = "additional-controls")] /// .feature control operator (rfc 9165) FEATURE, } impl<'a> Token<'a> { /// Returns optional string literal of token if it is in the standard prelude /// /// # Example /// /// ``` /// use cddl::token::Token; /// /// let t = Token::ANY; /// assert_eq!(t.in_standard_prelude(), Some("any")); /// ``` pub fn in_standard_prelude(&self) -> Option<&'static str> { match self { Token::ANY => Some("any"), Token::UINT => Some("uint"), Token::NINT => Some("nint"), Token::INT => Some("int"), Token::BSTR => Some("bstr"), Token::BYTES => Some("bytes"), Token::TSTR => Some("tstr"), Token::TEXT => Some("text"), Token::TDATE => Some("tdate"), Token::TIME => Some("time"), Token::NUMBER => Some("number"), Token::BIGUINT => Some("biguint"), Token::BIGNINT => Some("bignint"), Token::BIGINT => Some("bigint"), Token::INTEGER => Some("integer"), Token::UNSIGNED => Some("unsigned"), Token::DECFRAC => Some("decfrac"), Token::BIGFLOAT => Some("bigfloat"), Token::EB64URL => Some("eb64url"), Token::EB64LEGACY => Some("eb64legacy"), Token::EB16 => Some("eb16"), Token::ENCODEDCBOR => Some("encoded-cbor"), Token::URI => Some("uri"), Token::B64URL => Some("b64url"), Token::B64LEGACY => Some("b64legacy"), Token::REGEXP => Some("regexp"), Token::MIMEMESSAGE => Some("mime-message"), Token::CBORANY => Some("cbor-any"), Token::FLOAT16 => Some("float16"), Token::FLOAT32 => Some("float32"), Token::FLOAT64 => Some("float64"), Token::FLOAT1632 => Some("float16-32"), Token::FLOAT3264 => Some("float32-64"), Token::FLOAT => Some("float"), Token::FALSE => Some("false"), Token::TRUE => Some("true"), Token::BOOL => Some("bool"), Token::NIL => Some("nil"), Token::NULL => Some("null"), Token::UNDEFINED => Some("undefined"), _ => None, } } } /// Range value #[derive(Debug, PartialEq, Clone)] pub enum RangeValue<'a> { /// Identifier IDENT( /// Identifier &'a str, /// Socket/plug Option, ), /// Integer INT(isize), /// Unsigned integer UINT(usize), /// Float FLOAT(f64), } impl<'a> TryFrom> for RangeValue<'a> { type Error = &'static str; fn try_from(t: Token<'a>) -> Result { match t { Token::IDENT(ident, socket) => Ok(RangeValue::IDENT(ident, socket)), Token::VALUE(value) => match value { Value::INT(i) => Ok(RangeValue::INT(i)), Value::UINT(ui) => Ok(RangeValue::UINT(ui)), Value::FLOAT(f) => Ok(RangeValue::FLOAT(f)), _ => Err("Invalid range token"), }, _ => Err("Invalid range token"), } } } impl<'a> RangeValue<'a> { /// Returns `Value` from given `RangeValue` pub fn as_value(&self) -> Option { match &self { RangeValue::UINT(ui) => Some(Value::UINT(*ui)), RangeValue::FLOAT(f) => Some(Value::FLOAT(*f)), _ => None, } } } impl<'a> fmt::Display for RangeValue<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { RangeValue::IDENT(ident, _) => write!(f, "{}", ident), RangeValue::INT(i) => write!(f, "{}", i), RangeValue::UINT(i) => write!(f, "{}", i), RangeValue::FLOAT(fl) => write!(f, "{}", fl), } } } /// Literal value // TODO: support hexfloat and exponent #[cfg_attr(target_arch = "wasm32", derive(Serialize, Deserialize))] #[derive(Debug, PartialEq, Clone)] pub enum Value<'a> { /// Integer value INT(isize), /// Unsigned integer value UINT(usize), /// Float value FLOAT(f64), /// Text value #[cfg_attr(target_arch = "wasm32", serde(borrow))] TEXT(Cow<'a, str>), /// Byte value #[cfg_attr(target_arch = "wasm32", serde(borrow))] BYTE(ByteValue<'a>), } /// Numeric value #[derive(Debug, PartialEq)] pub enum Numeric { /// Integer INT(isize), /// Unsigned integer UINT(usize), /// Float FLOAT(f64), } impl<'a> fmt::Display for Value<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Value::TEXT(text) => write!(f, "\"{}\"", text), Value::INT(i) => write!(f, "{}", i), Value::UINT(ui) => write!(f, "{}", ui), Value::FLOAT(float) => write!(f, "{}", float), Value::BYTE(bv) => write!(f, "{}", bv), } } } impl<'a> From<&'a str> for Value<'a> { fn from(value: &'a str) -> Self { Value::TEXT(value.into()) } } /// Byte string values #[cfg_attr(target_arch = "wasm32", derive(Serialize, Deserialize))] #[derive(Debug, PartialEq, Eq, Clone)] pub enum ByteValue<'a> { /// Unprefixed byte string value UTF8(Cow<'a, [u8]>), /// Prefixed base16 encoded byte string value B16(Cow<'a, [u8]>), /// Prefixed base64 encoded (URL safe) byte string value B64(Cow<'a, [u8]>), } impl<'a> fmt::Display for ByteValue<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { ByteValue::UTF8(b) => write!(f, "'{}'", std::str::from_utf8(b).map_err(|_| fmt::Error)?), ByteValue::B16(b) => write!( f, "h'{}'", String::from_utf8(b.to_vec()) .map_err(|_| fmt::Error)? .replace(' ', "") ), ByteValue::B64(b) => write!( f, "b64'{}'", String::from_utf8(b.to_vec()) .map_err(|_| fmt::Error)? .replace(' ', "") ), } } } /// Socket/plug prefix #[cfg_attr(target_arch = "wasm32", derive(Serialize, Deserialize))] #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum SocketPlug { /// Type socket `$` TYPE, /// Group socket `$$` GROUP, } impl std::str::FromStr for SocketPlug { type Err = &'static str; fn from_str(s: &str) -> Result { if let Some(c) = s.chars().next() { if c == '$' { if let Some(c) = s.chars().nth(1) { if c == '$' { return Ok(SocketPlug::GROUP); } } return Ok(SocketPlug::TYPE); } } Err("Malformed socket plug string") } } impl fmt::Display for SocketPlug { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { SocketPlug::TYPE => write!(f, "$"), SocketPlug::GROUP => write!(f, "$$"), } } } impl fmt::Display for ControlOperator { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { ControlOperator::SIZE => write!(f, ".size"), ControlOperator::BITS => write!(f, ".bits"), ControlOperator::REGEXP => write!(f, ".regexp"), ControlOperator::PCRE => write!(f, ".pcre"), ControlOperator::CBOR => write!(f, ".cbor"), ControlOperator::CBORSEQ => write!(f, ".cborseq"), ControlOperator::WITHIN => write!(f, ".within"), #[cfg(feature = "additional-controls")] ControlOperator::CAT => write!(f, ".cat"), #[cfg(feature = "additional-controls")] ControlOperator::DET => write!(f, ".det"), #[cfg(feature = "additional-controls")] ControlOperator::PLUS => write!(f, ".plus"), #[cfg(feature = "additional-controls")] ControlOperator::ABNF => write!(f, ".abnf"), #[cfg(feature = "additional-controls")] ControlOperator::ABNFB => write!(f, ".abnfb"), #[cfg(feature = "additional-controls")] ControlOperator::FEATURE => write!(f, ".feature"), ControlOperator::AND => write!(f, ".and"), ControlOperator::LT => write!(f, ".lt"), ControlOperator::LE => write!(f, ".le"), ControlOperator::GT => write!(f, ".gt"), ControlOperator::GE => write!(f, ".ge"), ControlOperator::EQ => write!(f, ".eq"), ControlOperator::NE => write!(f, ".ne"), ControlOperator::DEFAULT => write!(f, ".default"), } } } impl<'a> fmt::Display for Token<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Token::IDENT(ident, socket_plug) => { if let Some(sp) = socket_plug { return write!(f, "{}{}", sp, ident); } write!(f, "{}", ident) } Token::ILLEGAL(s) => write!(f, "ILLEGAL({})", s), Token::ASSIGN => write!(f, "="), Token::ONEORMORE => write!(f, "+"), Token::OPTIONAL => write!(f, "?"), Token::ASTERISK => write!(f, "*"), Token::LPAREN => write!(f, "("), Token::RPAREN => write!(f, ")"), Token::LBRACE => write!(f, "{{"), Token::RBRACE => write!(f, "}}"), Token::LBRACKET => write!(f, "["), Token::RBRACKET => write!(f, "]"), Token::TCHOICE => write!(f, "/"), Token::TCHOICEALT => write!(f, "/="), Token::GCHOICEALT => write!(f, "//="), Token::COMMA => write!(f, ","), Token::COMMENT(c) => write!(f, ";{}", c), Token::COLON => write!(f, ":"), Token::CUT => write!(f, "^"), Token::EOF => write!(f, ""), Token::TSTR => write!(f, "tstr"), Token::LANGLEBRACKET => write!(f, "<"), Token::RANGLEBRACKET => write!(f, ">"), Token::INT => write!(f, "int"), Token::UINT => write!(f, "uint"), Token::ARROWMAP => write!(f, "=>"), Token::ControlOperator(co) => write!(f, "{}", co), Token::NUMBER => write!(f, "number"), Token::BSTR => write!(f, "bstr"), Token::BYTES => write!(f, "bytes"), Token::GCHOICE => write!(f, "//"), Token::TRUE => write!(f, "true"), Token::GTOCHOICE => write!(f, "&"), Token::VALUE(value) => write!(f, "{}", value), Token::REGEXP => write!(f, "regexp"), Token::RANGEOP(i) => { if *i { write!(f, "..") } else { write!(f, "...") } } Token::RANGE(l, u, i) => match l { RangeValue::IDENT(..) if *i => write!(f, "{} .. {}", l, u), RangeValue::IDENT(..) => write!(f, "{} ... {}", l, u), _ => { if *i { write!(f, "{}..{}", l, u) } else { write!(f, "{}...{}", l, u) } } }, Token::TAG(mt, tag) => { if let Some(m) = mt { if let Some(t) = tag { return write!(f, "#{}.{}", m, t); } return write!(f, "#{}", m); } write!(f, "#") } _ => write!(f, ""), } } } /// Return an optional control token from a given string /// /// # Arguments /// /// `ident` - String slice with ident literal /// /// # Example /// /// ``` /// use cddl::token::{lookup_control_from_str, ControlOperator}; /// /// assert_eq!(lookup_control_from_str(".size"), Some(ControlOperator::SIZE)); /// ``` pub fn lookup_control_from_str(ident: &str) -> Option { match ident { ".size" => Some(ControlOperator::SIZE), ".bits" => Some(ControlOperator::BITS), ".regexp" => Some(ControlOperator::REGEXP), ".cbor" => Some(ControlOperator::CBOR), ".cborseq" => Some(ControlOperator::CBORSEQ), ".within" => Some(ControlOperator::WITHIN), ".and" => Some(ControlOperator::AND), ".lt" => Some(ControlOperator::LT), ".le" => Some(ControlOperator::LE), ".gt" => Some(ControlOperator::GT), ".ge" => Some(ControlOperator::GE), ".eq" => Some(ControlOperator::EQ), ".ne" => Some(ControlOperator::NE), ".default" => Some(ControlOperator::DEFAULT), ".pcre" => Some(ControlOperator::PCRE), #[cfg(feature = "additional-controls")] ".cat" => Some(ControlOperator::CAT), #[cfg(feature = "additional-controls")] ".det" => Some(ControlOperator::DET), #[cfg(feature = "additional-controls")] ".plus" => Some(ControlOperator::PLUS), #[cfg(feature = "additional-controls")] ".abnf" => Some(ControlOperator::ABNF), #[cfg(feature = "additional-controls")] ".abnfb" => Some(ControlOperator::ABNFB), #[cfg(feature = "additional-controls")] ".feature" => Some(ControlOperator::FEATURE), _ => None, } } /// Returns token in standard prelude from given string /// /// # Arguments /// /// `ident` - String slice with the token literal /// /// # Example /// /// ``` /// use cddl::token::{lookup_ident, Token}; /// /// assert_eq!(lookup_ident("false"), Token::FALSE); /// ``` pub fn lookup_ident(ident: &str) -> Token { match ident { "false" => Token::FALSE, "true" => Token::TRUE, "bool" => Token::BOOL, "nil" => Token::NIL, "null" => Token::NULL, "uint" => Token::UINT, "nint" => Token::NINT, "int" => Token::INT, "float16" => Token::FLOAT16, "float32" => Token::FLOAT32, "float64" => Token::FLOAT64, "float16-32" => Token::FLOAT1632, "float32-64" => Token::FLOAT3264, "float" => Token::FLOAT, "bstr" => Token::BSTR, "tstr" => Token::TSTR, "any" => Token::ANY, "bytes" => Token::BYTES, "text" => Token::TEXT, "tdate" => Token::TDATE, "time" => Token::TIME, "number" => Token::NUMBER, "biguint" => Token::BIGUINT, "bignint" => Token::BIGNINT, "bigint" => Token::BIGINT, "integer" => Token::INTEGER, "unsigned" => Token::UNSIGNED, "decfrac" => Token::DECFRAC, "bigfloat" => Token::BIGFLOAT, "eb64url" => Token::EB64URL, "eb64legacy" => Token::EB64LEGACY, "eb16" => Token::EB16, "encoded-cbor" => Token::ENCODEDCBOR, "uri" => Token::URI, "b64url" => Token::B64URL, "b64legacy" => Token::B64LEGACY, "regexp" => Token::REGEXP, "mime-message" => Token::MIMEMESSAGE, "cbor-any" => Token::CBORANY, "undefined" => Token::UNDEFINED, _ => { if let Some(c) = ident.chars().next() { if c == '$' { if let Some(c) = ident.chars().nth(1) { if c == '$' { return Token::IDENT(&ident[2..], Some(SocketPlug::GROUP)); } } return Token::IDENT(&ident[1..], Some(SocketPlug::TYPE)); } } Token::IDENT(ident, None) } } } /// If token is an opening delimiter, return its matching closing delimiter pub fn closing_delimiter<'a>(token: &Token) -> Option> { match token { Token::LBRACE => Some(Token::RBRACE), Token::LBRACKET => Some(Token::RBRACKET), Token::LPAREN => Some(Token::RPAREN), Token::LANGLEBRACKET => Some(Token::RANGLEBRACKET), _ => None, } } cddl-0.9.4/src/validator/cbor.rs000064400000000000000000004015171046102023000146230ustar 00000000000000#![cfg(feature = "std")] #![cfg(feature = "cbor")] #![cfg(not(feature = "lsp"))] use super::*; use crate::{ ast::*, token, visitor::{self, *}, }; use core::convert::TryInto; use std::{ borrow::Cow, collections::HashMap, convert::TryFrom, fmt::{self, Write}, }; use chrono::{TimeZone, Utc}; use ciborium::value::Value; use serde_json; #[cfg(feature = "additional-controls")] use crate::validator::control::{ abnf_from_complex_controller, cat_operation, plus_operation, validate_abnf, }; /// cbor validation Result pub type Result = std::result::Result<(), Error>; /// cbor validation error #[derive(Debug)] pub enum Error { /// Zero or more validation errors Validation(Vec), /// cbor parsing error CBORParsing(ciborium::de::Error), /// json parsing error. Used only for parsing regex controller strings JSONParsing(serde_json::Error), /// CDDL parsing error CDDLParsing(String), /// UTF8 parsing error, UTF8Parsing(std::str::Utf8Error), /// Base16 decoding error Base16Decoding(base16::DecodeError), /// Base64 decoding error Base64Decoding(data_encoding::DecodeError), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::Validation(errors) => { let mut error_str = String::new(); for e in errors.iter() { let _ = writeln!(error_str, "{}", e); } write!(f, "{}", error_str) } Error::CBORParsing(error) => write!(f, "error parsing cbor: {}", error), Error::JSONParsing(error) => write!(f, "error parsing json string: {}", error), Error::CDDLParsing(error) => write!(f, "error parsing CDDL: {}", error), Error::UTF8Parsing(error) => write!(f, "error parsing utf8: {}", error), Error::Base16Decoding(error) => write!(f, "error decoding base16: {}", error), Error::Base64Decoding(error) => write!(f, "error decoding base64: {}", error), } } } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Error::CBORParsing(error) => Some(error), _ => None, } } } /// cbor validation error #[derive(Clone, Debug)] pub struct ValidationError { /// Error message pub reason: String, /// Location in CDDL where error occurred pub cddl_location: String, /// Location in CBOR where error occurred pub cbor_location: String, /// Whether or not the error is associated with multiple type choices pub is_multi_type_choice: bool, /// Whether or not the error is associated with multiple group choices pub is_multi_group_choice: bool, /// Whether or not the error is associated with a group to choice enumeration pub is_group_to_choice_enum: bool, /// Error is associated with a type/group name group entry pub type_group_name_entry: Option, } impl fmt::Display for ValidationError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut error_str = String::from("error validating"); if self.is_multi_group_choice { error_str.push_str(" group choice"); } if self.is_multi_type_choice { error_str.push_str(" type choice"); } if self.is_group_to_choice_enum { error_str.push_str(" type choice in group to choice enumeration"); } if let Some(entry) = &self.type_group_name_entry { let _ = write!(error_str, " group entry associated with rule \"{}\"", entry); } write!( f, "{} at cbor location {}: {}", error_str, self.cbor_location, self.reason ) } } impl std::error::Error for ValidationError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } } impl Error { fn from_validator(cv: &CBORValidator, reason: String) -> Self { Error::Validation(vec![ValidationError { cddl_location: cv.cddl_location.clone(), cbor_location: cv.cbor_location.clone(), reason, is_multi_type_choice: cv.is_multi_type_choice, is_group_to_choice_enum: cv.is_group_to_choice_enum, type_group_name_entry: cv.type_group_name_entry.map(|e| e.to_string()), is_multi_group_choice: cv.is_multi_group_choice, }]) } } /// cbor validator type #[derive(Clone)] pub struct CBORValidator<'a> { cddl: &'a CDDL<'a>, cbor: Value, errors: Vec, cddl_location: String, cbor_location: String, // Occurrence indicator detected in current state of AST evaluation occurrence: Option, // Current group entry index detected in current state of AST evaluation group_entry_idx: Option, // cbor object value hoisted from previous state of AST evaluation object_value: Option, // Is member key detected in current state of AST evaluation is_member_key: bool, // Is a cut detected in current state of AST evaluation is_cut_present: bool, // Str value of cut detected in current state of AST evaluation cut_value: Option>, // Validate the generic rule given by str ident in current state of AST // evaluation eval_generic_rule: Option<&'a str>, // Aggregation of generic rules generic_rules: Vec>, // Control operator token detected in current state of AST evaluation ctrl: Option, // Is a group to choice enumeration detected in current state of AST // evaluation is_group_to_choice_enum: bool, // Are 2 or more type choices detected in current state of AST evaluation is_multi_type_choice: bool, // Are 2 or more group choices detected in current state of AST evaluation is_multi_group_choice: bool, // Type/group name entry detected in current state of AST evaluation. Used // only for providing more verbose error messages type_group_name_entry: Option<&'a str>, // Whether or not to advance to the next group entry if member key validation // fails as detected during the current state of AST evaluation advance_to_next_entry: bool, // Is validation checking for map quality is_ctrl_map_equality: bool, entry_counts: Option>, // Collect map entry keys that have already been validated validated_keys: Option>, // Collect map entry values that have yet to be validated values_to_validate: Option>, // Whether or not the validator is validating a map entry value validating_value: bool, // Collect valid array indices when entries are type choices valid_array_items: Option>, // Collect invalid array item errors where the key is the index of the invalid // array item array_errors: Option>>, is_colon_shortcut_present: bool, is_root: bool, is_multi_type_choice_type_rule_validating_array: bool, #[cfg(not(target_arch = "wasm32"))] #[cfg(feature = "additional-controls")] enabled_features: Option<&'a [&'a str]>, #[cfg(target_arch = "wasm32")] #[cfg(feature = "additional-controls")] enabled_features: Option>, #[cfg(feature = "additional-controls")] has_feature_errors: bool, #[cfg(feature = "additional-controls")] disabled_features: Option>, } #[derive(Clone, Debug)] struct GenericRule<'a> { name: &'a str, params: Vec<&'a str>, args: Vec>, } impl<'a> CBORValidator<'a> { #[cfg(not(target_arch = "wasm32"))] #[cfg(feature = "additional-controls")] /// New cborValidation from CDDL AST and cbor value pub fn new(cddl: &'a CDDL<'a>, cbor: Value, enabled_features: Option<&'a [&'a str]>) -> Self { CBORValidator { cddl, cbor, errors: Vec::default(), cddl_location: String::new(), cbor_location: String::new(), occurrence: None, group_entry_idx: None, object_value: None, is_member_key: false, is_cut_present: false, cut_value: None, eval_generic_rule: None, generic_rules: Vec::new(), ctrl: None, is_group_to_choice_enum: false, is_multi_type_choice: false, is_multi_group_choice: false, type_group_name_entry: None, advance_to_next_entry: false, is_ctrl_map_equality: false, entry_counts: None, validated_keys: None, values_to_validate: None, validating_value: false, valid_array_items: None, array_errors: None, is_colon_shortcut_present: false, is_root: false, is_multi_type_choice_type_rule_validating_array: false, enabled_features, has_feature_errors: false, disabled_features: None, } } #[cfg(not(target_arch = "wasm32"))] #[cfg(not(feature = "additional-controls"))] /// New cborValidation from CDDL AST and cbor value pub fn new(cddl: &'a CDDL<'a>, cbor: Value) -> Self { CBORValidator { cddl, cbor, errors: Vec::default(), cddl_location: String::new(), cbor_location: String::new(), occurrence: None, group_entry_idx: None, object_value: None, is_member_key: false, is_cut_present: false, cut_value: None, eval_generic_rule: None, generic_rules: Vec::new(), ctrl: None, is_group_to_choice_enum: false, is_multi_type_choice: false, is_multi_group_choice: false, type_group_name_entry: None, advance_to_next_entry: false, is_ctrl_map_equality: false, entry_counts: None, validated_keys: None, values_to_validate: None, validating_value: false, valid_array_items: None, array_errors: None, is_colon_shortcut_present: false, is_root: false, is_multi_type_choice_type_rule_validating_array: false, } } #[cfg(target_arch = "wasm32")] #[cfg(feature = "additional-controls")] /// New cborValidation from CDDL AST and cbor value pub fn new(cddl: &'a CDDL<'a>, cbor: Value, enabled_features: Option>) -> Self { CBORValidator { cddl, cbor, errors: Vec::default(), cddl_location: String::new(), cbor_location: String::new(), occurrence: None, group_entry_idx: None, object_value: None, is_member_key: false, is_cut_present: false, cut_value: None, eval_generic_rule: None, generic_rules: Vec::new(), ctrl: None, is_group_to_choice_enum: false, is_multi_type_choice: false, is_multi_group_choice: false, type_group_name_entry: None, advance_to_next_entry: false, is_ctrl_map_equality: false, entry_counts: None, validated_keys: None, values_to_validate: None, validating_value: false, valid_array_items: None, array_errors: None, is_colon_shortcut_present: false, is_root: false, is_multi_type_choice_type_rule_validating_array: false, enabled_features, has_feature_errors: false, disabled_features: None, } } #[cfg(target_arch = "wasm32")] #[cfg(not(feature = "additional-controls"))] /// New cborValidation from CDDL AST and cbor value pub fn new(cddl: &'a CDDL<'a>, cbor: Value) -> Self { CBORValidator { cddl, cbor, errors: Vec::default(), cddl_location: String::new(), cbor_location: String::new(), occurrence: None, group_entry_idx: None, object_value: None, is_member_key: false, is_cut_present: false, cut_value: None, eval_generic_rule: None, generic_rules: Vec::new(), ctrl: None, is_group_to_choice_enum: false, is_multi_type_choice: false, is_multi_group_choice: false, type_group_name_entry: None, advance_to_next_entry: false, is_ctrl_map_equality: false, entry_counts: None, validated_keys: None, values_to_validate: None, validating_value: false, valid_array_items: None, array_errors: None, is_colon_shortcut_present: false, is_root: false, is_multi_type_choice_type_rule_validating_array: false, } } fn validate_array_items( &mut self, token: &ArrayItemToken, ) -> visitor::Result> where cbor::Error: From>, { if let Value::Array(a) = &self.cbor { // Member keys are annotation only in an array context if self.is_member_key { return Ok(()); } match validate_array_occurrence( self.occurrence.as_ref(), self.entry_counts.as_ref().map(|ec| &ec[..]), a, ) { Ok((iter_items, allow_empty_array)) => { if iter_items { for (idx, v) in a.iter().enumerate() { if let Some(indices) = &self.valid_array_items { if self.is_multi_type_choice && indices.contains(&idx) { continue; } } #[cfg(all(feature = "additional-controls", target_arch = "wasm32"))] let mut cv = CBORValidator::new(self.cddl, v.clone(), self.enabled_features.clone()); #[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))] let mut cv = CBORValidator::new(self.cddl, v.clone(), self.enabled_features); #[cfg(not(feature = "additional-controls"))] let mut cv = CBORValidator::new(self.cddl, v.clone()); cv.generic_rules = self.generic_rules.clone(); cv.eval_generic_rule = self.eval_generic_rule; cv.ctrl = self.ctrl; cv.is_multi_type_choice = self.is_multi_type_choice; let _ = write!(cv.cbor_location, "{}/{}", self.cbor_location, idx); match token { ArrayItemToken::Value(value) => cv.visit_value(value)?, ArrayItemToken::Range(lower, upper, is_inclusive) => { cv.visit_range(lower, upper, *is_inclusive)? } ArrayItemToken::Group(group) => cv.visit_group(group)?, ArrayItemToken::Identifier(ident) => cv.visit_identifier(ident)?, ArrayItemToken::TaggedData(tagged_data) => cv.visit_type2(tagged_data)?, } if self.is_multi_type_choice && cv.errors.is_empty() { if let Some(indices) = &mut self.valid_array_items { indices.push(idx); } else { self.valid_array_items = Some(vec![idx]); } continue; } if let Some(errors) = &mut self.array_errors { if let Some(error) = errors.get_mut(&idx) { error.append(&mut cv.errors); } else { errors.insert(idx, cv.errors); } } else { let mut errors = HashMap::new(); errors.insert(idx, cv.errors); self.array_errors = Some(errors) } } } else { let idx = if !self.is_multi_type_choice { self.group_entry_idx.take() } else { self.group_entry_idx }; if let Some(idx) = idx { if let Some(v) = a.get(idx) { #[cfg(all(feature = "additional-controls", target_arch = "wasm32"))] let mut cv = CBORValidator::new(self.cddl, v.clone(), self.enabled_features.clone()); #[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))] let mut cv = CBORValidator::new(self.cddl, v.clone(), self.enabled_features); #[cfg(not(feature = "additional-controls"))] let mut cv = CBORValidator::new(self.cddl, v.clone()); cv.generic_rules = self.generic_rules.clone(); cv.eval_generic_rule = self.eval_generic_rule; cv.is_multi_type_choice = self.is_multi_type_choice; cv.ctrl = self.ctrl; let _ = write!(cv.cbor_location, "{}/{}", self.cbor_location, idx); match token { ArrayItemToken::Value(value) => cv.visit_value(value)?, ArrayItemToken::Range(lower, upper, is_inclusive) => { cv.visit_range(lower, upper, *is_inclusive)? } ArrayItemToken::Group(group) => cv.visit_group(group)?, ArrayItemToken::Identifier(ident) => cv.visit_identifier(ident)?, ArrayItemToken::TaggedData(tagged_data) => cv.visit_type2(tagged_data)?, } self.errors.append(&mut cv.errors); } else if !allow_empty_array { self.add_error(token.error_msg(Some(idx))); } } else if !self.is_multi_type_choice { self.add_error(format!("{}, got {:?}", token.error_msg(None), self.cbor)); } } } Err(errors) => { for e in errors.into_iter() { self.add_error(e); } } } } Ok(()) } } impl<'a, 'b, T: std::fmt::Debug + 'static> Validator<'a, 'b, cbor::Error> for CBORValidator<'a> where cbor::Error: From>, { fn validate(&mut self) -> std::result::Result<(), cbor::Error> { for r in self.cddl.rules.iter() { // First type rule is root if let Rule::Type { rule, .. } = r { if rule.generic_params.is_none() { self.is_root = true; self.visit_type_rule(rule)?; self.is_root = false; break; } } } if !self.errors.is_empty() { return Err(Error::Validation(self.errors.clone())); } Ok(()) } fn add_error(&mut self, reason: String) { self.errors.push(ValidationError { reason, cddl_location: self.cddl_location.clone(), cbor_location: self.cbor_location.clone(), is_multi_type_choice: self.is_multi_type_choice, is_multi_group_choice: self.is_multi_group_choice, is_group_to_choice_enum: self.is_group_to_choice_enum, type_group_name_entry: self.type_group_name_entry.map(|e| e.to_string()), }); } } impl<'a, 'b, T: std::fmt::Debug + 'static> Visitor<'a, 'b, Error> for CBORValidator<'a> where cbor::Error: From>, { fn visit_type_rule(&mut self, tr: &TypeRule<'a>) -> visitor::Result> { if let Some(gp) = &tr.generic_params { if let Some(gr) = self .generic_rules .iter_mut() .find(|r| r.name == tr.name.ident) { gr.params = gp.params.iter().map(|p| p.param.ident).collect(); } else { self.generic_rules.push(GenericRule { name: tr.name.ident, params: gp.params.iter().map(|p| p.param.ident).collect(), args: vec![], }); } } let type_choice_alternates = type_choice_alternates_from_ident(self.cddl, &tr.name); if !type_choice_alternates.is_empty() { self.is_multi_type_choice = true; if self.cbor.is_array() { self.is_multi_type_choice_type_rule_validating_array = true; } } let error_count = self.errors.len(); for t in type_choice_alternates { let cur_errors = self.errors.len(); self.visit_type(t)?; if self.errors.len() == cur_errors { for _ in 0..self.errors.len() - error_count { self.errors.pop(); } return Ok(()); } } if tr.value.type_choices.len() > 1 && self.cbor.is_array() { self.is_multi_type_choice_type_rule_validating_array = true; } self.visit_type(&tr.value) } fn visit_group_rule(&mut self, gr: &GroupRule<'a>) -> visitor::Result> { if let Some(gp) = &gr.generic_params { if let Some(gr) = self .generic_rules .iter_mut() .find(|r| r.name == gr.name.ident) { gr.params = gp.params.iter().map(|p| p.param.ident).collect(); } else { self.generic_rules.push(GenericRule { name: gr.name.ident, params: gp.params.iter().map(|p| p.param.ident).collect(), args: vec![], }); } } let group_choice_alternates = group_choice_alternates_from_ident(self.cddl, &gr.name); if !group_choice_alternates.is_empty() { self.is_multi_group_choice = true; } let error_count = self.errors.len(); for ge in group_choice_alternates { let cur_errors = self.errors.len(); self.visit_group_entry(ge)?; if self.errors.len() == cur_errors { for _ in 0..self.errors.len() - error_count { self.errors.pop(); } return Ok(()); } } self.visit_group_entry(&gr.entry) } fn visit_type(&mut self, t: &Type<'a>) -> visitor::Result> { if t.type_choices.len() > 1 { self.is_multi_type_choice = true; } let initial_error_count = self.errors.len(); for type_choice in t.type_choices.iter() { // If validating an array whose elements are type choices (i.e. [ 1* tstr // / integer ]), collect all errors and filter after the fact if matches!(self.cbor, Value::Array(_)) && !self.is_multi_type_choice_type_rule_validating_array { let error_count = self.errors.len(); self.visit_type_choice(type_choice)?; #[cfg(feature = "additional-controls")] if self.errors.len() == error_count && !self.has_feature_errors && self.disabled_features.is_none() { // Disregard invalid type choice validation errors if one of the // choices validates successfully let type_choice_error_count = self.errors.len() - initial_error_count; if type_choice_error_count > 0 { for _ in 0..type_choice_error_count { self.errors.pop(); } } } #[cfg(not(feature = "additional-controls"))] if self.errors.len() == error_count { // Disregard invalid type choice validation errors if one of the // choices validates successfully let type_choice_error_count = self.errors.len() - initial_error_count; if type_choice_error_count > 0 { for _ in 0..type_choice_error_count { self.errors.pop(); } } } continue; } let error_count = self.errors.len(); self.visit_type_choice(type_choice)?; #[cfg(feature = "additional-controls")] if self.errors.len() == error_count && !self.has_feature_errors && self.disabled_features.is_none() { // Disregard invalid type choice validation errors if one of the // choices validates successfully let type_choice_error_count = self.errors.len() - initial_error_count; if type_choice_error_count > 0 { for _ in 0..type_choice_error_count { self.errors.pop(); } } return Ok(()); } #[cfg(not(feature = "additional-controls"))] if self.errors.len() == error_count { // Disregard invalid type choice validation errors if one of the // choices validates successfully let type_choice_error_count = self.errors.len() - initial_error_count; if type_choice_error_count > 0 { for _ in 0..type_choice_error_count { self.errors.pop(); } } return Ok(()); } } Ok(()) } fn visit_group(&mut self, g: &Group<'a>) -> visitor::Result> { if g.group_choices.len() > 1 { self.is_multi_group_choice = true; } // Map equality/inequality validation if self.is_ctrl_map_equality { if let Some(t) = &self.ctrl { if let Value::Map(m) = &self.cbor { let entry_counts = entry_counts_from_group(self.cddl, g); let len = m.len(); if let ControlOperator::EQ | ControlOperator::NE = t { if !validate_entry_count(&entry_counts, len) { for ec in entry_counts.iter() { if let Some(occur) = &ec.entry_occurrence { self.add_error(format!( "expected array with length per occurrence {}", occur, )); } else { self.add_error(format!( "expected array with length {}, got {}", ec.count, len )); } } return Ok(()); } } } } } self.is_ctrl_map_equality = false; let initial_error_count = self.errors.len(); for group_choice in g.group_choices.iter() { let error_count = self.errors.len(); self.visit_group_choice(group_choice)?; if self.errors.len() == error_count { // Disregard invalid group choice validation errors if one of the // choices validates successfully let group_choice_error_count = self.errors.len() - initial_error_count; if group_choice_error_count > 0 { for _ in 0..group_choice_error_count { self.errors.pop(); } } return Ok(()); } } Ok(()) } fn visit_group_choice(&mut self, gc: &GroupChoice<'a>) -> visitor::Result> { if self.is_group_to_choice_enum { let initial_error_count = self.errors.len(); for tc in type_choices_from_group_choice(self.cddl, gc).iter() { let error_count = self.errors.len(); self.visit_type_choice(tc)?; if self.errors.len() == error_count { let type_choice_error_count = self.errors.len() - initial_error_count; if type_choice_error_count > 0 { for _ in 0..type_choice_error_count { self.errors.pop(); } } return Ok(()); } } return Ok(()); } for (idx, ge) in gc.group_entries.iter().enumerate() { self.group_entry_idx = Some(idx); self.visit_group_entry(&ge.0)?; } Ok(()) } fn visit_range( &mut self, lower: &Type2, upper: &Type2, is_inclusive: bool, ) -> visitor::Result> { if let Value::Array(_) = &self.cbor { return self.validate_array_items(&ArrayItemToken::Range(lower, upper, is_inclusive)); } match lower { Type2::IntValue { value: l, .. } => match upper { Type2::IntValue { value: u, .. } => { let error_str = if is_inclusive { format!( "expected integer to be in range {} <= value <= {}, got {:?}", l, u, self.cbor ) } else { format!( "expected integer to be in range {} < value < {}, got {:?}", l, u, self.cbor ) }; match &self.cbor { Value::Integer(i) => { if is_inclusive { if i128::from(*i) < *l as i128 || i128::from(*i) > *u as i128 { self.add_error(error_str); } else { return Ok(()); } } else if i128::from(*i) <= *l as i128 || i128::from(*i) >= *u as i128 { self.add_error(error_str); return Ok(()); } else { return Ok(()); } } _ => { self.add_error(error_str); return Ok(()); } } } Type2::UintValue { value: u, .. } => { let error_str = if is_inclusive { format!( "expected integer to be in range {} <= value <= {}, got {:?}", l, u, self.cbor ) } else { format!( "expected integer to be in range {} < value < {}, got {:?}", l, u, self.cbor ) }; match &self.cbor { Value::Integer(i) => { if is_inclusive { if i128::from(*i) < *l as i128 || i128::from(*i) > *u as i128 { self.add_error(error_str); } else { return Ok(()); } } else if i128::from(*i) <= *l as i128 || i128::from(*i) >= *u as i128 { self.add_error(error_str); return Ok(()); } else { return Ok(()); } } _ => { self.add_error(error_str); return Ok(()); } } } _ => { self.add_error(format!( "invalid cddl range. upper value must be an integer type. got {}", upper )); return Ok(()); } }, Type2::UintValue { value: l, .. } => match upper { Type2::UintValue { value: u, .. } => { let error_str = if is_inclusive { format!( "expected uint to be in range {} <= value <= {}, got {:?}", l, u, self.cbor ) } else { format!( "expected uint to be in range {} < value < {}, got {:?}", l, u, self.cbor ) }; match &self.cbor { Value::Integer(i) => { if is_inclusive { if i128::from(*i) < *l as i128 || i128::from(*i) > *u as i128 { self.add_error(error_str); } else { return Ok(()); } } else if i128::from(*i) <= *l as i128 || i128::from(*i) >= *u as i128 { self.add_error(error_str); return Ok(()); } else { return Ok(()); } } Value::Text(s) => match self.ctrl { Some(ControlOperator::SIZE) => { let len = s.len(); let s = s.clone(); if is_inclusive { if s.len() < *l || s.len() > *u { self.add_error(format!( "expected \"{}\" string length to be in the range {} <= value <= {}, got {}", s, l, u, len )); } return Ok(()); } else if s.len() <= *l || s.len() >= *u { self.add_error(format!( "expected \"{}\" string length to be in the range {} < value < {}, got {}", s, l, u, len )); return Ok(()); } } _ => { self.add_error("string value cannot be validated against a range without the .size control operator".to_string()); return Ok(()); } }, _ => { self.add_error(error_str); return Ok(()); } } } _ => { self.add_error(format!( "invalid cddl range. upper value must be a uint type. got {}", upper )); return Ok(()); } }, Type2::FloatValue { value: l, .. } => match upper { Type2::FloatValue { value: u, .. } => { let error_str = if is_inclusive { format!( "expected float to be in range {} <= value <= {}, got {:?}", l, u, self.cbor ) } else { format!( "expected float to be in range {} < value < {}, got {:?}", l, u, self.cbor ) }; match &self.cbor { Value::Float(f) => { if is_inclusive { if *f < *l || *f > *u { self.add_error(error_str); } else { return Ok(()); } } else if *f <= *l || *f >= *u { self.add_error(error_str); return Ok(()); } else { return Ok(()); } } _ => { self.add_error(error_str); return Ok(()); } } } _ => { self.add_error(format!( "invalid cddl range. upper value must be a float type. got {}", upper )); return Ok(()); } }, _ => { self.add_error( "invalid cddl range. upper and lower values must be either integers or floats" .to_string(), ); return Ok(()); } } Ok(()) } fn visit_control_operator( &mut self, target: &Type2<'a>, ctrl: ControlOperator, controller: &Type2<'a>, ) -> visitor::Result> { if let Type2::Typename { ident: target_ident, .. } = target { if let Type2::Typename { ident: controller_ident, .. } = controller { if let Some(name) = self.eval_generic_rule { if let Some(gr) = self .generic_rules .iter() .cloned() .find(|gr| gr.name == name) { for (idx, gp) in gr.params.iter().enumerate() { if let Some(arg) = gr.args.get(idx) { if *gp == target_ident.ident { let t2 = Type2::from(arg.clone()); if *gp == controller_ident.ident { return self.visit_control_operator(&t2, ctrl, &t2); } return self.visit_control_operator(&arg.type2, ctrl, controller); } } } } } } if let Some(name) = self.eval_generic_rule { if let Some(gr) = self .generic_rules .iter() .cloned() .find(|gr| gr.name == name) { for (idx, gp) in gr.params.iter().enumerate() { if let Some(arg) = gr.args.get(idx) { if *gp == target_ident.ident { let t2 = Type2::from(arg.clone()); return self.visit_control_operator(&t2, ctrl, controller); } } } } } } match ctrl { ControlOperator::EQ => { match target { Type2::Typename { ident, .. } => { if is_ident_string_data_type(self.cddl, ident) || is_ident_numeric_data_type(self.cddl, ident) { return self.visit_type2(controller); } } Type2::Array { group, .. } => { if let Value::Array(_) = &self.cbor { self.entry_counts = Some(entry_counts_from_group(self.cddl, group)); self.visit_type2(controller)?; self.entry_counts = None; return Ok(()); } } Type2::Map { .. } => { if let Value::Map(_) = &self.cbor { self.ctrl = Some(ctrl); self.is_ctrl_map_equality = true; self.visit_type2(controller)?; self.ctrl = None; self.is_ctrl_map_equality = false; return Ok(()); } } _ => self.add_error(format!( "target for .eq operator must be a string, numerical, array or map data type, got {}", target )), } Ok(()) } ControlOperator::NE => { match target { Type2::Typename { ident, .. } => { if is_ident_string_data_type(self.cddl, ident) || is_ident_numeric_data_type(self.cddl, ident) { self.ctrl = Some(ctrl); self.visit_type2(controller)?; self.ctrl = None; return Ok(()); } } Type2::Array { .. } => { if let Value::Array(_) = &self.cbor { self.ctrl = Some(ctrl); self.visit_type2(controller)?; self.ctrl = None; return Ok(()); } } Type2::Map { .. } => { if let Value::Map(_) = &self.cbor { self.ctrl = Some(ctrl); self.is_ctrl_map_equality = true; self.visit_type2(controller)?; self.ctrl = None; self.is_ctrl_map_equality = false; return Ok(()); } } _ => self.add_error(format!( "target for .ne operator must be a string, numerical, array or map data type, got {}", target )), } Ok(()) } ControlOperator::LT | ControlOperator::GT | ControlOperator::GE | ControlOperator::LE => { match target { Type2::Typename { ident, .. } if is_ident_numeric_data_type(self.cddl, ident) => { self.ctrl = Some(ctrl); self.visit_type2(controller)?; self.ctrl = None; Ok(()) } _ => { self.add_error(format!( "target for .lt, .gt, .ge or .le operator must be a numerical data type, got {}", target )); Ok(()) } } } ControlOperator::SIZE => match target { Type2::Typename { ident, .. } if is_ident_string_data_type(self.cddl, ident) || is_ident_uint_data_type(self.cddl, ident) || is_ident_byte_string_data_type(self.cddl, ident) => { self.ctrl = Some(ctrl); self.visit_type2(controller)?; self.ctrl = None; Ok(()) } _ => { self.add_error(format!( "target for .size must a string or uint data type, got {}", target )); Ok(()) } }, ControlOperator::AND => { self.ctrl = Some(ctrl); self.visit_type2(target)?; self.visit_type2(controller)?; self.ctrl = None; Ok(()) } ControlOperator::WITHIN => { self.ctrl = Some(ctrl); let error_count = self.errors.len(); self.visit_type2(target)?; let no_errors = self.errors.len() == error_count; self.visit_type2(controller)?; if no_errors && self.errors.len() > error_count { for _ in 0..self.errors.len() - error_count { self.errors.pop(); } self.add_error(format!( "expected type {} .within type {}, got {:?}", target, controller, self.cbor, )); } self.ctrl = None; Ok(()) } ControlOperator::DEFAULT => { self.ctrl = Some(ctrl); let error_count = self.errors.len(); self.visit_type2(target)?; if self.errors.len() != error_count { #[cfg(feature = "ast-span")] if let Some(Occur::Optional { .. }) = self.occurrence.take() { self.add_error(format!( "expected default value {}, got {:?}", controller, self.cbor )); } #[cfg(not(feature = "ast-span"))] if let Some(Occur::Optional {}) = self.occurrence.take() { self.add_error(format!( "expected default value {}, got {:?}", controller, self.cbor )); } } self.ctrl = None; Ok(()) } ControlOperator::REGEXP | ControlOperator::PCRE => { self.ctrl = Some(ctrl); match target { Type2::Typename { ident, .. } if is_ident_string_data_type(self.cddl, ident) => { match self.cbor { Value::Text(_) | Value::Array(_) => self.visit_type2(controller)?, _ => self.add_error(format!( ".regexp/.pcre control can only be matched against CBOR string, got {:?}", self.cbor )), } } _ => self.add_error(format!( ".regexp/.pcre control can only be matched against string data type, got {}", target )), } self.ctrl = None; Ok(()) } ControlOperator::CBOR | ControlOperator::CBORSEQ => { self.ctrl = Some(ctrl); match target { Type2::Typename { ident, .. } if is_ident_byte_string_data_type(self.cddl, ident) => { match &self.cbor { Value::Bytes(_) | Value::Array(_) => self.visit_type2(controller)?, _ => self.add_error(format!( "{} control can only be matched against a CBOR byte string, got {:?}", ctrl, self.cbor )), } } _ => self.add_error(format!( ".cbor control can only be matched against a byte string data type, got {}", target )), } self.ctrl = None; Ok(()) } ControlOperator::BITS => { self.ctrl = Some(ctrl); match target { Type2::Typename { ident, .. } if is_ident_byte_string_data_type(self.cddl, ident) || is_ident_uint_data_type(self.cddl, ident) => { match &self.cbor { Value::Bytes(_) | Value::Array(_) => self.visit_type2(controller)?, Value::Integer(i) if i128::from(*i) >= 0i128 => self.visit_type2(controller)?, _ => self.add_error(format!( "{} control can only be matched against a CBOR byte string or uint, got {:?}", ctrl, self.cbor, )), } } _ => self.add_error(format!( ".bits control can only be matched against a byte string data type, got {}", target )), } self.ctrl = None; Ok(()) } #[cfg(feature = "additional-controls")] ControlOperator::CAT => { self.ctrl = Some(ctrl); match cat_operation(self.cddl, target, controller, false) { Ok(values) => { let error_count = self.errors.len(); for v in values.iter() { let cur_errors = self.errors.len(); self.visit_type2(v)?; if self.errors.len() == cur_errors { for _ in 0..self.errors.len() - error_count { self.errors.pop(); } break; } } } Err(e) => self.add_error(e), } self.ctrl = None; Ok(()) } #[cfg(feature = "additional-controls")] ControlOperator::DET => { self.ctrl = Some(ctrl); match cat_operation(self.cddl, target, controller, true) { Ok(values) => { let error_count = self.errors.len(); for v in values.iter() { let cur_errors = self.errors.len(); self.visit_type2(v)?; if self.errors.len() == cur_errors { for _ in 0..self.errors.len() - error_count { self.errors.pop(); } break; } } } Err(e) => self.add_error(e), } self.ctrl = None; Ok(()) } #[cfg(feature = "additional-controls")] ControlOperator::PLUS => { self.ctrl = Some(ctrl); match plus_operation(self.cddl, target, controller) { Ok(values) => { let error_count = self.errors.len(); for v in values.iter() { let cur_errors = self.errors.len(); self.visit_type2(v)?; self.visit_type2(v)?; if self.errors.len() == cur_errors { for _ in 0..self.errors.len() - error_count { self.errors.pop(); } break; } } } Err(e) => self.add_error(e), } self.ctrl = None; Ok(()) } #[cfg(feature = "additional-controls")] ControlOperator::ABNF => { self.ctrl = Some(ctrl); match target { Type2::Typename { ident, .. } if is_ident_string_data_type(self.cddl, ident) => { match self.cbor { Value::Text(_) | Value::Array(_) => { if let Type2::ParenthesizedType { pt, .. } = controller { match abnf_from_complex_controller(self.cddl, pt) { Ok(values) => { let error_count = self.errors.len(); for v in values.iter() { let cur_errors = self.errors.len(); self.visit_type2(v)?; if self.errors.len() == cur_errors { for _ in 0..self.errors.len() - error_count { self.errors.pop(); } break; } } } Err(e) => self.add_error(e), } } else { self.visit_type2(controller)? } } _ => self.add_error(format!( ".abnf control can only be matched against a cbor string, got {:?}", self.cbor, )), } } _ => self.add_error(format!( ".abnf can only be matched against string data type, got {}", target, )), } self.ctrl = None; Ok(()) } #[cfg(feature = "additional-controls")] ControlOperator::ABNFB => { self.ctrl = Some(ctrl); match target { Type2::Typename { ident, .. } if is_ident_byte_string_data_type(self.cddl, ident) => { match self.cbor { Value::Bytes(_) | Value::Array(_) => { if let Type2::ParenthesizedType { pt, .. } = controller { match abnf_from_complex_controller(self.cddl, pt) { Ok(values) => { let error_count = self.errors.len(); for v in values.iter() { let cur_errors = self.errors.len(); self.visit_type2(v)?; if self.errors.len() == cur_errors { for _ in 0..self.errors.len() - error_count { self.errors.pop(); } break; } } } Err(e) => self.add_error(e), } } else { self.visit_type2(controller)? } } _ => self.add_error(format!( ".abnfb control can only be matched against cbor bytes, got {:?}", self.cbor, )), } } _ => self.add_error(format!( ".abnfb can only be matched against byte string target data type, got {}", target, )), } self.ctrl = None; Ok(()) } #[cfg(feature = "additional-controls")] #[cfg(not(target_arch = "wasm32"))] ControlOperator::FEATURE => { self.ctrl = Some(ctrl); if let Some(ef) = self.enabled_features { let tv = text_value_from_type2(self.cddl, controller); if let Some(Type2::TextValue { value, .. }) = tv { if ef.contains(&&**value) { let err_count = self.errors.len(); self.visit_type2(target)?; if self.errors.len() > err_count { self.has_feature_errors = true; } self.ctrl = None; } else { self .disabled_features .get_or_insert(vec![value.to_string()]) .push(value.to_string()); } } else if let Some(Type2::UTF8ByteString { value, .. }) = tv { let value = std::str::from_utf8(value).map_err(Error::UTF8Parsing)?; if ef.contains(&value) { let err_count = self.errors.len(); self.visit_type2(target)?; if self.errors.len() > err_count { self.has_feature_errors = true; } self.ctrl = None; } else { self .disabled_features .get_or_insert(vec![value.to_string()]) .push(value.to_string()); } } } self.ctrl = None; Ok(()) } #[cfg(feature = "additional-controls")] #[cfg(target_arch = "wasm32")] ControlOperator::FEATURE => { self.ctrl = Some(ctrl); if let Some(ef) = &self.enabled_features { let tv = text_value_from_type2(self.cddl, controller); if let Some(Type2::TextValue { value, .. }) = tv { if ef.contains(&JsValue::from(value.as_ref())) { let err_count = self.errors.len(); self.visit_type2(target)?; if self.errors.len() > err_count { self.has_feature_errors = true; } self.ctrl = None; } else { self .disabled_features .get_or_insert(vec![value.to_string()]) .push(value.to_string()); } } else if let Some(Type2::UTF8ByteString { value, .. }) = tv { let value = std::str::from_utf8(value).map_err(Error::UTF8Parsing)?; if ef.contains(&JsValue::from(value)) { let err_count = self.errors.len(); self.visit_type2(target)?; if self.errors.len() > err_count { self.has_feature_errors = true; } self.ctrl = None; } else { self .disabled_features .get_or_insert(vec![value.to_string()]) .push(value.to_string()); } } } self.ctrl = None; Ok(()) } } } fn visit_type2(&mut self, t2: &Type2<'a>) -> visitor::Result> { if matches!(self.ctrl, Some(ControlOperator::CBOR)) { if let Value::Bytes(b) = &self.cbor { let value = ciborium::de::from_reader(&b[..]); match value { Ok(value) => { let current_location = self.cbor_location.clone(); #[cfg(all(feature = "additional-controls", target_arch = "wasm32"))] let mut cv = CBORValidator::new(self.cddl, value, self.enabled_features.clone()); #[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))] let mut cv = CBORValidator::new(self.cddl, value, self.enabled_features); #[cfg(not(feature = "additional-controls"))] let mut cv = CBORValidator::new(self.cddl, value); cv.generic_rules = self.generic_rules.clone(); cv.eval_generic_rule = self.eval_generic_rule; cv.is_multi_type_choice = self.is_multi_type_choice; cv.is_multi_group_choice = self.is_multi_group_choice; cv.cbor_location.push_str(&self.cbor_location); cv.type_group_name_entry = self.type_group_name_entry; cv.visit_type2(t2)?; if cv.errors.is_empty() { self.cbor_location = current_location; return Ok(()); } self.errors.append(&mut cv.errors); } Err(e) => { self.add_error(format!("error decoding embedded CBOR, {}", e)); } } } return Ok(()); } else if matches!(self.ctrl, Some(ControlOperator::CBORSEQ)) { if let Value::Bytes(b) = &self.cbor { let value = ciborium::de::from_reader(&b[..]); match value { Ok(Value::Array(_)) => { let current_location = self.cbor_location.clone(); #[cfg(all(feature = "additional-controls", target_arch = "wasm32"))] let mut cv = CBORValidator::new( self.cddl, value.unwrap_or(Value::Null), self.enabled_features.clone(), ); #[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))] let mut cv = CBORValidator::new( self.cddl, value.unwrap_or(Value::Null), self.enabled_features, ); #[cfg(not(feature = "additional-controls"))] let mut cv = CBORValidator::new(self.cddl, value.unwrap_or(Value::Null)); cv.generic_rules = self.generic_rules.clone(); cv.eval_generic_rule = self.eval_generic_rule; cv.is_multi_type_choice = self.is_multi_type_choice; cv.is_multi_group_choice = self.is_multi_group_choice; cv.cbor_location.push_str(&self.cbor_location); cv.type_group_name_entry = self.type_group_name_entry; cv.visit_type2(t2)?; if cv.errors.is_empty() { self.cbor_location = current_location; return Ok(()); } self.errors.append(&mut cv.errors); } Err(e) => { self.add_error(format!("error decoding embedded CBOR, {}", e)); } Ok(v) => self.add_error(format!( "embedded CBOR must be a CBOR sequence, got {:?}", v )), } } return Ok(()); } match t2 { Type2::TextValue { value, .. } => self.visit_value(&token::Value::TEXT(value.clone())), Type2::Map { group, .. } => match &self.cbor { Value::Map(m) => { if self.is_member_key { let current_location = self.cbor_location.clone(); for (k, v) in m.iter() { #[cfg(feature = "additional-controls")] #[cfg(all(feature = "additional-controls", target_arch = "wasm32"))] let mut cv = CBORValidator::new(self.cddl, k.clone(), self.enabled_features.clone()); #[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))] let mut cv = CBORValidator::new(self.cddl, k.clone(), self.enabled_features); #[cfg(not(feature = "additional-controls"))] let mut cv = CBORValidator::new(self.cddl, k.clone()); cv.generic_rules = self.generic_rules.clone(); cv.eval_generic_rule = self.eval_generic_rule; cv.is_multi_type_choice = self.is_multi_type_choice; cv.is_multi_group_choice = self.is_multi_group_choice; cv.cbor_location.push_str(&self.cbor_location); cv.type_group_name_entry = self.type_group_name_entry; cv.visit_type2(t2)?; if cv.errors.is_empty() { self.object_value = Some(v.clone()); self .validated_keys .get_or_insert(vec![k.clone()]) .push(k.clone()); self.cbor_location = current_location; return Ok(()); } self.errors.append(&mut cv.errors); } return Ok(()); } #[allow(clippy::needless_collect)] let m = m.iter().map(|entry| entry.0.clone()).collect::>(); self.visit_group(group)?; // If extra map entries are detected, return validation error if self.values_to_validate.is_none() { for k in m.into_iter() { if let Some(keys) = &self.validated_keys { if !keys.contains(&k) { self.add_error(format!("unexpected key {:?}", k)); } } } } self.is_cut_present = false; self.cut_value = None; Ok(()) } Value::Array(_) => self.validate_array_items(&ArrayItemToken::Group(group)), _ => { self.add_error(format!("expected map object {}, got {:?}", t2, self.cbor)); Ok(()) } }, Type2::Array { group, .. } => match &self.cbor { Value::Array(a) => { if group.group_choices.len() == 1 && group.group_choices[0].group_entries.is_empty() && !a.is_empty() && !matches!( self.ctrl, Some(ControlOperator::NE) | Some(ControlOperator::DEFAULT) ) { self.add_error(format!("expected empty array, got {:?}", self.cbor)); return Ok(()); } self.entry_counts = Some(entry_counts_from_group(self.cddl, group)); self.visit_group(group)?; self.entry_counts = None; if let Some(errors) = &mut self.array_errors { if let Some(indices) = &self.valid_array_items { for idx in indices.iter() { errors.remove(idx); } } for error in errors.values_mut() { self.errors.append(error); } } self.valid_array_items = None; self.array_errors = None; Ok(()) } Value::Map(m) if self.is_member_key => { let current_location = self.cbor_location.clone(); self.entry_counts = Some(entry_counts_from_group(self.cddl, group)); for (k, v) in m.iter() { #[cfg(all(feature = "additional-controls", target_arch = "wasm32"))] let mut cv = CBORValidator::new(self.cddl, k.clone(), self.enabled_features.clone()); #[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))] let mut cv = CBORValidator::new(self.cddl, k.clone(), self.enabled_features); #[cfg(not(feature = "additional-controls"))] let mut cv = CBORValidator::new(self.cddl, k.clone()); cv.generic_rules = self.generic_rules.clone(); cv.entry_counts = self.entry_counts.clone(); cv.eval_generic_rule = self.eval_generic_rule; cv.is_multi_type_choice = self.is_multi_type_choice; cv.is_multi_group_choice = self.is_multi_group_choice; cv.cbor_location.push_str(&self.cbor_location); cv.type_group_name_entry = self.type_group_name_entry; cv.visit_type2(t2)?; if cv.errors.is_empty() { self.object_value = Some(v.clone()); self .validated_keys .get_or_insert(vec![k.clone()]) .push(k.clone()); self.cbor_location = current_location; return Ok(()); } self.errors.append(&mut cv.errors); } self.entry_counts = None; Ok(()) } _ => { self.add_error(format!("expected array type, got {:?}", self.cbor)); Ok(()) } }, Type2::ChoiceFromGroup { ident, generic_args, .. } => { if let Some(ga) = generic_args { if let Some(rule) = rule_from_ident(self.cddl, ident) { if let Some(gr) = self .generic_rules .iter_mut() .find(|gr| gr.name == ident.ident) { for arg in ga.args.iter() { gr.args.push((*arg.arg).clone()); } } else if let Some(params) = generic_params_from_rule(rule) { self.generic_rules.push(GenericRule { name: ident.ident, params, args: ga.args.iter().cloned().map(|arg| *arg.arg).collect(), }); } #[cfg(all(feature = "additional-controls", target_arch = "wasm32"))] let mut cv = CBORValidator::new(self.cddl, self.cbor.clone(), self.enabled_features.clone()); #[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))] let mut cv = CBORValidator::new(self.cddl, self.cbor.clone(), self.enabled_features); #[cfg(not(feature = "additional-controls"))] let mut cv = CBORValidator::new(self.cddl, self.cbor.clone()); cv.generic_rules = self.generic_rules.clone(); cv.eval_generic_rule = Some(ident.ident); cv.is_group_to_choice_enum = true; cv.is_multi_type_choice = self.is_multi_type_choice; cv.visit_rule(rule)?; self.errors.append(&mut cv.errors); return Ok(()); } } if group_rule_from_ident(self.cddl, ident).is_none() { self.add_error(format!( "rule {} must be a group rule to turn it into a choice", ident )); return Ok(()); } self.is_group_to_choice_enum = true; self.visit_identifier(ident)?; self.is_group_to_choice_enum = false; Ok(()) } Type2::ChoiceFromInlineGroup { group, .. } => { self.is_group_to_choice_enum = true; self.visit_group(group)?; self.is_group_to_choice_enum = false; Ok(()) } Type2::Typename { ident, generic_args, .. } => { if let Some(ga) = generic_args { if let Some(rule) = rule_from_ident(self.cddl, ident) { if let Some(gr) = self .generic_rules .iter_mut() .find(|gr| gr.name == ident.ident) { for arg in ga.args.iter() { gr.args.push((*arg.arg).clone()); } } else if let Some(params) = generic_params_from_rule(rule) { self.generic_rules.push(GenericRule { name: ident.ident, params, args: ga.args.iter().cloned().map(|arg| *arg.arg).collect(), }); } #[cfg(all(feature = "additional-controls", target_arch = "wasm32"))] let mut cv = CBORValidator::new(self.cddl, self.cbor.clone(), self.enabled_features.clone()); #[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))] let mut cv = CBORValidator::new(self.cddl, self.cbor.clone(), self.enabled_features); #[cfg(not(feature = "additional-controls"))] let mut cv = CBORValidator::new(self.cddl, self.cbor.clone()); cv.generic_rules = self.generic_rules.clone(); cv.eval_generic_rule = Some(ident.ident); cv.is_multi_type_choice = self.is_multi_type_choice; cv.visit_rule(rule)?; self.errors.append(&mut cv.errors); return Ok(()); } } let type_choice_alternates = type_choice_alternates_from_ident(self.cddl, ident); if !type_choice_alternates.is_empty() { self.is_multi_type_choice = true; } let error_count = self.errors.len(); for t in type_choice_alternates { let cur_errors = self.errors.len(); self.visit_type(t)?; if self.errors.len() == cur_errors { for _ in 0..self.errors.len() - error_count { self.errors.pop(); } return Ok(()); } } self.visit_identifier(ident) } Type2::IntValue { value, .. } => self.visit_value(&token::Value::INT(*value)), Type2::UintValue { value, .. } => self.visit_value(&token::Value::UINT(*value)), Type2::FloatValue { value, .. } => self.visit_value(&token::Value::FLOAT(*value)), Type2::UTF8ByteString { value, .. } => { self.visit_value(&token::Value::BYTE(ByteValue::UTF8(value.clone()))) } Type2::B16ByteString { value, .. } => { self.visit_value(&token::Value::BYTE(ByteValue::B16(value.clone()))) } Type2::ParenthesizedType { pt, .. } => self.visit_type(pt), Type2::Unwrap { ident, generic_args, .. } => { // Per // https://github.com/w3c/did-spec-registries/pull/138#issuecomment-719739215, // strip tag and validate underlying type if let Some(Type2::TaggedData { t, .. }) = tag_from_token(&lookup_ident(ident.ident)) { return self.visit_type(&t); } if let Some(ga) = generic_args { if let Some(rule) = unwrap_rule_from_ident(self.cddl, ident) { if let Some(gr) = self .generic_rules .iter_mut() .find(|gr| gr.name == ident.ident) { for arg in ga.args.iter() { gr.args.push((*arg.arg).clone()); } } else if let Some(params) = generic_params_from_rule(rule) { self.generic_rules.push(GenericRule { name: ident.ident, params, args: ga.args.iter().cloned().map(|arg| *arg.arg).collect(), }); } #[cfg(all(feature = "additional-controls", target_arch = "wasm32"))] let mut cv = CBORValidator::new(self.cddl, self.cbor.clone(), self.enabled_features.clone()); #[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))] let mut cv = CBORValidator::new(self.cddl, self.cbor.clone(), self.enabled_features); #[cfg(not(feature = "additional-controls"))] let mut cv = CBORValidator::new(self.cddl, self.cbor.clone()); cv.generic_rules = self.generic_rules.clone(); cv.eval_generic_rule = Some(ident.ident); cv.is_multi_type_choice = self.is_multi_type_choice; cv.visit_rule(rule)?; self.errors.append(&mut cv.errors); return Ok(()); } } if let Some(rule) = unwrap_rule_from_ident(self.cddl, ident) { return self.visit_rule(rule); } self.add_error(format!( "cannot unwrap identifier {}, rule not found", ident )); Ok(()) } Type2::TaggedData { tag, t, .. } => match &self.cbor { Value::Tag(actual_tag, value) => { if let Some(tag) = tag { if *tag as u64 != *actual_tag { self.add_error(format!( "expected tagged data #6.{}({}), got {:?}", tag, t, self.cbor )); return Ok(()); } } else if *actual_tag > 0 { self.add_error(format!( "expected tagged data #6({}), got {:?}", t, self.cbor )); return Ok(()); } #[cfg(all(feature = "additional-controls", target_arch = "wasm32"))] let mut cv = CBORValidator::new( self.cddl, value.as_ref().clone(), self.enabled_features.clone(), ); #[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))] let mut cv = CBORValidator::new(self.cddl, value.as_ref().clone(), self.enabled_features); #[cfg(not(feature = "additional-controls"))] let mut cv = CBORValidator::new(self.cddl, value.as_ref().clone()); cv.generic_rules = self.generic_rules.clone(); cv.eval_generic_rule = self.eval_generic_rule; cv.is_multi_type_choice = self.is_multi_type_choice; cv.is_multi_group_choice = self.is_multi_group_choice; cv.cbor_location.push_str(&self.cbor_location); cv.type_group_name_entry = self.type_group_name_entry; cv.visit_type(t)?; self.errors.append(&mut cv.errors); Ok(()) } Value::Array(_) => self.validate_array_items(&ArrayItemToken::TaggedData(t2)), _ => { if let Some(tag) = tag { self.add_error(format!( "expected tagged data #6.{}({}), got {:?}", tag, t, self.cbor )); } else { self.add_error(format!( "expected tagged data #6({}), got {:?}", t, self.cbor )); } Ok(()) } }, Type2::DataMajorType { mt, constraint, .. } => match &self.cbor { Value::Integer(i) => { match mt { 0u8 => match constraint { Some(c) if i128::from(*i) == *c as i128 && i128::from(*i) >= 0i128 => return Ok(()), Some(c) => { self.add_error(format!( "expected uint data type with constraint {} (#{}.{}), got {:?}", c, mt, c, self.cbor )); return Ok(()); } _ => { if i128::from(*i).is_negative() { self.add_error(format!( "expected uint data type (#{}), got {:?}", mt, self.cbor )); return Ok(()); } } }, 1u8 => match constraint { Some(c) if i128::from(*i) == 0i128 - *c as i128 => return Ok(()), Some(c) => { self.add_error(format!( "expected nint type with constraint {} (#{}.{}), got {:?}", c, mt, c, self.cbor )); return Ok(()); } _ => { if i128::from(*i) >= 0i128 { self.add_error(format!( "expected nint data type (#{}), got {:?}", mt, self.cbor )); return Ok(()); } } }, _ => self.add_error(format!( "expected major type {} with constraint {:?}, got {:?}", mt, constraint, self.cbor )), } Ok(()) } Value::Bytes(b) => { match mt { 2u8 => match constraint { Some(c) if *c == b.len() => return Ok(()), Some(c) => self.add_error(format!( "expected byte string type with constraint {} (#{}.{}), got {:?}", c, mt, c, self.cbor )), _ => return Ok(()), }, _ => self.add_error(format!( "expected major type {} with constraint {:?}, got {:?}", mt, constraint, self.cbor )), } Ok(()) } Value::Text(t) => { match mt { 3u8 => match constraint { Some(c) if *c == t.len() => return Ok(()), Some(c) => self.add_error(format!( "expected text string type with constraint {} (#{}.{}), got {:?}", c, mt, c, self.cbor )), _ => return Ok(()), }, _ => self.add_error(format!( "expected major type {} with constraint {:?}, got {:?}", mt, constraint, self.cbor )), } Ok(()) } Value::Array(a) => { match mt { 4u8 => match constraint { Some(c) if *c == a.len() => return Ok(()), Some(c) => self.add_error(format!( "expected array type with constraint {} (#{}.{}), got {:?}", c, mt, c, self.cbor )), _ => return Ok(()), }, _ => self.add_error(format!( "expected major type {} with constraint {:?}, got {:?}", mt, constraint, self.cbor )), } Ok(()) } Value::Map(m) => { match mt { 5u8 => match constraint { Some(c) if *c == m.len() => return Ok(()), Some(c) => self.add_error(format!( "expected map type with constraint {} (#{}.{}), got {:?}", c, mt, c, self.cbor )), _ => return Ok(()), }, _ => self.add_error(format!( "expected major type {} with constraint {:?}, got {:?}", mt, constraint, self.cbor )), } Ok(()) } Value::Float(_f) => { match mt { 7u8 => match constraint { Some(_c) => unimplemented!(), _ => return Ok(()), }, _ => self.add_error(format!( "expected major type {} with constraint {:?}, got {:?}", mt, constraint, self.cbor )), } Ok(()) } _ => { if let Some(constraint) = constraint { self.add_error(format!( "expected major type #{}.{}, got {:?}", mt, constraint, self.cbor )); } else { self.add_error(format!("expected major type #{}, got {:?}", mt, self.cbor)); } Ok(()) } }, #[cfg(feature = "ast-span")] Type2::Any { .. } => Ok(()), #[cfg(not(feature = "ast-span"))] Type2::Any {} => Ok(()), _ => { self.add_error(format!( "unsupported data type for validating cbor, got {}", t2 )); Ok(()) } } } fn visit_identifier(&mut self, ident: &Identifier<'a>) -> visitor::Result> { if let Some(name) = self.eval_generic_rule { if let Some(gr) = self .generic_rules .iter() .cloned() .find(|gr| gr.name == name) { for (idx, gp) in gr.params.iter().enumerate() { if *gp == ident.ident { if let Some(arg) = gr.args.get(idx) { return self.visit_type1(arg); } } } } } // self.is_colon_shortcut_present is only true when the ident is part of a // member key if !self.is_colon_shortcut_present { if let Some(r) = rule_from_ident(self.cddl, ident) { return self.visit_rule(r); } } if is_ident_any_type(self.cddl, ident) { return Ok(()); } match &self.cbor { Value::Null if is_ident_null_data_type(self.cddl, ident) => Ok(()), Value::Bytes(_) if is_ident_byte_string_data_type(self.cddl, ident) => Ok(()), Value::Bool(b) => { if is_ident_bool_data_type(self.cddl, ident) { return Ok(()); } if ident_matches_bool_value(self.cddl, ident, *b) { return Ok(()); } self.add_error(format!("expected type {}, got {:?}", ident, self.cbor)); Ok(()) } Value::Integer(i) => { if is_ident_uint_data_type(self.cddl, ident) { if i128::from(*i).is_negative() { self.add_error(format!("expected type {}, got {:?}", ident, self.cbor)); } Ok(()) } else if is_ident_integer_data_type(self.cddl, ident) { Ok(()) } else if is_ident_time_data_type(self.cddl, ident) { if let chrono::LocalResult::None = Utc.timestamp_millis_opt((i128::from(*i) * 1000) as i64) { let i = *i; self.add_error(format!( "expected time data type, invalid UNIX timestamp {:?}", i, )); } Ok(()) } else { self.add_error(format!("expected type {}, got {:?}", ident, self.cbor)); Ok(()) } } Value::Float(f) => { if is_ident_float_data_type(self.cddl, ident) { Ok(()) } else if is_ident_time_data_type(self.cddl, ident) { if let chrono::LocalResult::None = Utc.timestamp_millis_opt((*f * 1000f64) as i64) { let f = *f; self.add_error(format!( "expected time data type, invalid UNIX timestamp {:?}", f, )); } Ok(()) } else { self.add_error(format!("expected type {}, got {:?}", ident, self.cbor)); Ok(()) } } Value::Text(s) => { if is_ident_uri_data_type(self.cddl, ident) { if let Err(e) = uriparse::URI::try_from(&**s) { self.add_error(format!("expected URI data type, decoding error: {}", e)); } } else if is_ident_b64url_data_type(self.cddl, ident) { if let Err(e) = base64_url::decode(s) { self.add_error(format!( "expected base64 URL data type, decoding error: {}", e )); } } else if is_ident_tdate_data_type(self.cddl, ident) { if let Err(e) = chrono::DateTime::parse_from_rfc3339(s) { self.add_error(format!("expected tdate data type, decoding error: {}", e)); } } else if is_ident_string_data_type(self.cddl, ident) { return Ok(()); } else { self.add_error(format!("expected type {}, got {:?}", ident, self.cbor)); } Ok(()) } Value::Tag(tag, value) => { match *tag { 0 => { if is_ident_tdate_data_type(self.cddl, ident) { if let Value::Text(value) = value.as_ref() { if let Err(e) = chrono::DateTime::parse_from_rfc3339(value) { self.add_error(format!("expected tdate data type, decoding error: {}", e)); } } else { self.add_error(format!("expected type {}, got {:?}", ident, self.cbor)); } } else { self.add_error(format!("expected type {}, got {:?}", ident, self.cbor)); } } 1 => { if is_ident_time_data_type(self.cddl, ident) { if let Value::Integer(value) = *value.as_ref() { let dt = Utc.timestamp_opt(value.try_into().unwrap(), 0); if let chrono::LocalResult::None = dt { self.add_error(format!( "expected time data type, invalid UNIX timestamp {:?}", self.cbor )); } } else if let Value::Float(value) = value.as_ref() { let seconds = value.trunc() as i64; let nanoseconds = (value.fract() * 1e9) as u32; let dt = Utc.timestamp_opt(seconds, nanoseconds); if let chrono::LocalResult::None = dt { self.add_error(format!( "expected time data type, invalid UNIX timestamp {:?}", self.cbor )); } } else { self.add_error(format!("expected type {}, got {:?}", ident, self.cbor)); } } else { self.add_error(format!("expected type {}, got {:?}", ident, self.cbor)); } } _ => (), } Ok(()) } Value::Array(_) => self.validate_array_items(&ArrayItemToken::Identifier(ident)), Value::Map(m) => { match &self.occurrence { #[cfg(feature = "ast-span")] Some(Occur::Optional { .. }) | None => { if is_ident_string_data_type(self.cddl, ident) && !self.validating_value { if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Text(_))) { self .validated_keys .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); let _ = write!(self.cbor_location, "/{:?}", v); } else { self.add_error(format!("map requires entry key of type {}", ident)); } return Ok(()); } if is_ident_integer_data_type(self.cddl, ident) && !self.validating_value { if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Integer(_))) { self .validated_keys .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); let _ = write!(self.cbor_location, "/{:?}", v); } else { self.add_error(format!("map requires entry key of type {}", ident)); } return Ok(()); } if is_ident_bool_data_type(self.cddl, ident) && !self.validating_value { if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Bool(_))) { self .validated_keys .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); let _ = write!(self.cbor_location, "/{:?}", v); } else { self.add_error(format!("map requires entry key of type {}", ident)); } return Ok(()); } if is_ident_null_data_type(self.cddl, ident) && !self.validating_value { if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Null)) { self .validated_keys .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); let _ = write!(self.cbor_location, "/{:?}", v); } else { self.add_error(format!("map requires entry key of type {}", ident)); } return Ok(()); } if is_ident_byte_string_data_type(self.cddl, ident) && !self.validating_value { if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Bytes(_))) { self .validated_keys .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); let _ = write!(self.cbor_location, "/{:?}", v); } else { self.add_error(format!("map requires entry key of type {}", ident)); } return Ok(()); } if is_ident_float_data_type(self.cddl, ident) && !self.validating_value { if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Null)) { self .validated_keys .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); let _ = write!(self.cbor_location, "/{:?}", v); } else { self.add_error(format!("map requires entry key of type {}", ident)); } return Ok(()); } if token::lookup_ident(ident.ident) .in_standard_prelude() .is_some() { self.add_error(format!( "expected object value of type {}, got object", ident.ident )); return Ok(()); } self.visit_value(&token::Value::TEXT(ident.ident.into())) } #[cfg(not(feature = "ast-span"))] Some(Occur::Optional {}) | None => { if is_ident_string_data_type(self.cddl, ident) && !self.validating_value { if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Text(_))) { self .validated_keys .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); let _ = write!(self.cbor_location, "/{:?}", v); } else { self.add_error(format!("map requires entry key of type {}", ident)); } return Ok(()); } if is_ident_integer_data_type(self.cddl, ident) && !self.validating_value { if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Integer(_))) { self .validated_keys .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); let _ = write!(self.cbor_location, "/{:?}", v); } else { self.add_error(format!("map requires entry key of type {}", ident)); } return Ok(()); } if is_ident_bool_data_type(self.cddl, ident) && !self.validating_value { if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Bool(_))) { self .validated_keys .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); let _ = write!(self.cbor_location, "/{:?}", v); } else { self.add_error(format!("map requires entry key of type {}", ident)); } return Ok(()); } if is_ident_null_data_type(self.cddl, ident) && !self.validating_value { if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Null)) { self .validated_keys .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); let _ = write!(self.cbor_location, "/{:?}", v); } else { self.add_error(format!("map requires entry key of type {}", ident)); } return Ok(()); } if is_ident_byte_string_data_type(self.cddl, ident) && !self.validating_value { if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Bytes(_))) { self .validated_keys .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); let _ = write!(self.cbor_location, "/{:?}", v); } else { self.add_error(format!("map requires entry key of type {}", ident)); } return Ok(()); } if is_ident_float_data_type(self.cddl, ident) && !self.validating_value { if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Null)) { self .validated_keys .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); let _ = write!(self.cbor_location, "/{:?}", v); } else { self.add_error(format!("map requires entry key of type {}", ident)); } return Ok(()); } if token::lookup_ident(ident.ident) .in_standard_prelude() .is_some() { self.add_error(format!( "expected object value of type {}, got object", ident.ident )); return Ok(()); } self.visit_value(&token::Value::TEXT(ident.ident.into())) } Some(occur) => { let mut errors = Vec::new(); if is_ident_string_data_type(self.cddl, ident) { let values_to_validate = m .iter() .filter_map(|(k, v)| { if let Some(keys) = &self.validated_keys { if !keys.contains(k) { if matches!(k, Value::Text(_)) { Some(v.clone()) } else { errors.push(format!("key of type {} required, got {:?}", ident, k)); None } } else { None } } else if matches!(k, Value::Text(_)) { Some(v.clone()) } else { errors.push(format!("key of type {} required, got {:?}", ident, k)); None } }) .collect::>(); self.values_to_validate = Some(values_to_validate); } if is_ident_integer_data_type(self.cddl, ident) { let mut errors = Vec::new(); let values_to_validate = m .iter() .filter_map(|(k, v)| { if let Some(keys) = &self.validated_keys { if !keys.contains(k) { if matches!(k, Value::Integer(_)) { Some(v.clone()) } else { errors.push(format!("key of type {} required, got {:?}", ident, k)); None } } else { None } } else if matches!(k, Value::Integer(_)) { Some(v.clone()) } else { errors.push(format!("key of type {} required, got {:?}", ident, k)); None } }) .collect::>(); self.values_to_validate = Some(values_to_validate); } if is_ident_bool_data_type(self.cddl, ident) { let mut errors = Vec::new(); let values_to_validate = m .iter() .filter_map(|(k, v)| { if let Some(keys) = &self.validated_keys { if !keys.contains(k) { if matches!(k, Value::Bool(_)) { Some(v.clone()) } else { errors.push(format!("key of type {} required, got {:?}", ident, k)); None } } else { None } } else if matches!(k, Value::Bool(_)) { Some(v.clone()) } else { errors.push(format!("key of type {} required, got {:?}", ident, k)); None } }) .collect::>(); self.values_to_validate = Some(values_to_validate); } if is_ident_byte_string_data_type(self.cddl, ident) { let mut errors = Vec::new(); let values_to_validate = m .iter() .filter_map(|(k, v)| { if let Some(keys) = &self.validated_keys { if !keys.contains(k) { if matches!(k, Value::Bytes(_)) { Some(v.clone()) } else { errors.push(format!("key of type {} required, got {:?}", ident, k)); None } } else { None } } else if matches!(k, Value::Bytes(_)) { Some(v.clone()) } else { errors.push(format!("key of type {} required, got {:?}", ident, k)); None } }) .collect::>(); self.values_to_validate = Some(values_to_validate); } if is_ident_null_data_type(self.cddl, ident) { let mut errors = Vec::new(); let values_to_validate = m .iter() .filter_map(|(k, v)| { if let Some(keys) = &self.validated_keys { if !keys.contains(k) { if matches!(k, Value::Null) { Some(v.clone()) } else { errors.push(format!("key of type {} required, got {:?}", ident, k)); None } } else { None } } else if matches!(k, Value::Null) { Some(v.clone()) } else { errors.push(format!("key of type {} required, got {:?}", ident, k)); None } }) .collect::>(); self.values_to_validate = Some(values_to_validate); } if is_ident_float_data_type(self.cddl, ident) { let mut errors = Vec::new(); let values_to_validate = m .iter() .filter_map(|(k, v)| { if let Some(keys) = &self.validated_keys { if !keys.contains(k) { if matches!(k, Value::Float(_)) { Some(v.clone()) } else { errors.push(format!("key of type {} required, got {:?}", ident, k)); None } } else { None } } else if matches!(k, Value::Float(_)) { Some(v.clone()) } else { errors.push(format!("key of type {} required, got {:?}", ident, k)); None } }) .collect::>(); self.values_to_validate = Some(values_to_validate); } // If key validation error occurs, return early before checking occurrences if !errors.is_empty() { for e in errors.into_iter() { self.add_error(e); } return Ok(()); } #[cfg(feature = "ast-span")] if let Occur::ZeroOrMore { .. } | Occur::OneOrMore { .. } = occur { if let Occur::OneOrMore { .. } = occur { if m.is_empty() { self.add_error(format!( "map cannot be empty, one or more entries with key type {} required", ident )); return Ok(()); } } } else if let Occur::Exact { lower, upper, .. } = occur { if let Some(values_to_validate) = &self.values_to_validate { if let Some(lower) = lower { if let Some(upper) = upper { if values_to_validate.len() < *lower || values_to_validate.len() > *upper { if lower == upper { self.add_error(format!( "object must contain exactly {} entries of key of type {}", lower, ident, )); } else { self.add_error(format!( "object must contain between {} and {} entries of key of type {}", lower, upper, ident, )); } return Ok(()); } } if values_to_validate.len() < *lower { self.add_error(format!( "object must contain at least {} entries of key of type {}", lower, ident, )); return Ok(()); } } if let Some(upper) = upper { if values_to_validate.len() > *upper { self.add_error(format!( "object must contain no more than {} entries of key of type {}", upper, ident, )); return Ok(()); } } return Ok(()); } } #[cfg(not(feature = "ast-span"))] if let Occur::ZeroOrMore {} | Occur::OneOrMore {} = occur { if let Occur::OneOrMore {} = occur { if m.is_empty() { self.add_error(format!( "object cannot be empty, one or more entries with key type {} required", ident )); return Ok(()); } } } else if let Occur::Exact { lower, upper } = occur { if let Some(values_to_validate) = &self.values_to_validate { if let Some(lower) = lower { if let Some(upper) = upper { if values_to_validate.len() < *lower || values_to_validate.len() > *upper { if lower == upper { self.add_error(format!( "object must contain exactly {} entries of key of type {}", lower, ident, )); } else { self.add_error(format!( "object must contain between {} and {} entries of key of type {}", lower, upper, ident, )); } return Ok(()); } } if values_to_validate.len() < *lower { self.add_error(format!( "object must contain at least {} entries of key of type {}", lower, ident, )); return Ok(()); } } if let Some(upper) = upper { if values_to_validate.len() > *upper { self.add_error(format!( "object must contain no more than {} entries of key of type {}", upper, ident, )); return Ok(()); } } return Ok(()); } } if is_ident_string_data_type(self.cddl, ident) && !self.validating_value { if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Text(_))) { self .validated_keys .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); let _ = write!(self.cbor_location, "/{:?}", v); } else if (!matches!(occur, Occur::ZeroOrMore { .. }) && m.is_empty()) || (matches!(occur, Occur::ZeroOrMore { .. }) && !m.is_empty()) { self.add_error(format!("map requires entry key of type {}", ident)); } return Ok(()); } if is_ident_integer_data_type(self.cddl, ident) && !self.validating_value { if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Integer(_))) { self .validated_keys .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); let _ = write!(self.cbor_location, "/{:?}", v); } else if (!matches!(occur, Occur::ZeroOrMore { .. }) && m.is_empty()) || (matches!(occur, Occur::ZeroOrMore { .. }) && !m.is_empty()) { self.add_error(format!("map requires entry key of type {}", ident)); } return Ok(()); } if is_ident_bool_data_type(self.cddl, ident) && !self.validating_value { if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Bool(_))) { self .validated_keys .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); let _ = write!(self.cbor_location, "/{:?}", v); } else if (!matches!(occur, Occur::ZeroOrMore { .. }) && m.is_empty()) || (matches!(occur, Occur::ZeroOrMore { .. }) && !m.is_empty()) { self.add_error(format!("map requires entry key of type {}", ident)); } return Ok(()); } if is_ident_null_data_type(self.cddl, ident) && !self.validating_value { if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Null)) { self .validated_keys .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); let _ = write!(self.cbor_location, "/{:?}", v); } else if (!matches!(occur, Occur::ZeroOrMore { .. }) && m.is_empty()) || (matches!(occur, Occur::ZeroOrMore { .. }) && !m.is_empty()) { self.add_error(format!("map requires entry key of type {}", ident)); } return Ok(()); } if is_ident_byte_string_data_type(self.cddl, ident) && !self.validating_value { if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Bytes(_))) { self .validated_keys .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); let _ = write!(self.cbor_location, "/{:?}", v); } else if (!matches!(occur, Occur::ZeroOrMore { .. }) && m.is_empty()) || (matches!(occur, Occur::ZeroOrMore { .. }) && !m.is_empty()) { self.add_error(format!("map requires entry key of type {}", ident)); } return Ok(()); } if is_ident_float_data_type(self.cddl, ident) && !self.validating_value { if let Some((k, v)) = m.iter().find(|(k, _)| matches!(k, Value::Null)) { self .validated_keys .get_or_insert(vec![k.clone()]) .push(k.clone()); self.object_value = Some(v.clone()); let _ = write!(self.cbor_location, "/{:?}", v); } else if (!matches!(occur, Occur::ZeroOrMore { .. }) && m.is_empty()) || (matches!(occur, Occur::ZeroOrMore { .. }) && !m.is_empty()) { self.add_error(format!("map requires entry key of type {}", ident)); } return Ok(()); } if token::lookup_ident(ident.ident) .in_standard_prelude() .is_some() { self.add_error(format!( "expected object value of type {}, got object", ident.ident )); return Ok(()); } self.visit_value(&token::Value::TEXT(ident.ident.into())) } } } _ => { if let Some(cut_value) = self.cut_value.take() { self.add_error(format!( "cut present for member key {}. expected type {}, got {:?}", cut_value, ident, self.cbor )); } else { self.add_error(format!("expected type {}, got {:?}", ident, self.cbor)); } Ok(()) } } } fn visit_value_member_key_entry( &mut self, entry: &ValueMemberKeyEntry<'a>, ) -> visitor::Result> { if let Some(occur) = &entry.occur { self.visit_occurrence(occur)?; } let current_location = self.cbor_location.clone(); if let Some(mk) = &entry.member_key { let error_count = self.errors.len(); self.is_member_key = true; self.visit_memberkey(mk)?; self.is_member_key = false; // Move to next entry if member key validation fails if self.errors.len() != error_count { self.advance_to_next_entry = true; return Ok(()); } } if let Some(values) = &self.values_to_validate { for v in values.iter() { #[cfg(all(feature = "additional-controls", target_arch = "wasm32"))] let mut cv = CBORValidator::new(self.cddl, v.clone(), self.enabled_features.clone()); #[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))] let mut cv = CBORValidator::new(self.cddl, v.clone(), self.enabled_features); #[cfg(not(feature = "additional-controls"))] let mut cv = CBORValidator::new(self.cddl, v.clone()); cv.generic_rules = self.generic_rules.clone(); cv.eval_generic_rule = self.eval_generic_rule; cv.is_multi_type_choice = self.is_multi_type_choice; cv.is_multi_group_choice = self.is_multi_group_choice; cv.cbor_location.push_str(&self.cbor_location); cv.type_group_name_entry = self.type_group_name_entry; cv.validating_value = true; cv.visit_type(&entry.entry_type)?; self.cbor_location = current_location.clone(); self.errors.append(&mut cv.errors); if entry.occur.is_some() { self.occurrence = None; } } return Ok(()); } if let Some(v) = self.object_value.take() { #[cfg(all(feature = "additional-controls", target_arch = "wasm32"))] let mut cv = CBORValidator::new(self.cddl, v, self.enabled_features.clone()); #[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))] let mut cv = CBORValidator::new(self.cddl, v, self.enabled_features); #[cfg(not(feature = "additional-controls"))] let mut cv = CBORValidator::new(self.cddl, v); cv.generic_rules = self.generic_rules.clone(); cv.eval_generic_rule = self.eval_generic_rule; cv.is_multi_type_choice = self.is_multi_type_choice; cv.is_multi_group_choice = self.is_multi_group_choice; cv.cbor_location.push_str(&self.cbor_location); cv.type_group_name_entry = self.type_group_name_entry; cv.visit_type(&entry.entry_type)?; self.cbor_location = current_location; self.errors.append(&mut cv.errors); if entry.occur.is_some() { self.occurrence = None; } Ok(()) } else if !self.advance_to_next_entry { self.visit_type(&entry.entry_type) } else { Ok(()) } } fn visit_type_groupname_entry( &mut self, entry: &TypeGroupnameEntry<'a>, ) -> visitor::Result> { self.type_group_name_entry = Some(entry.name.ident); if let Some(ga) = &entry.generic_args { if let Some(rule) = rule_from_ident(self.cddl, &entry.name) { if let Some(gr) = self .generic_rules .iter_mut() .find(|gr| gr.name == entry.name.ident) { for arg in ga.args.iter() { gr.args.push((*arg.arg).clone()); } } else if let Some(params) = generic_params_from_rule(rule) { self.generic_rules.push(GenericRule { name: entry.name.ident, params, args: ga.args.iter().cloned().map(|arg| *arg.arg).collect(), }); } #[cfg(all(feature = "additional-controls", target_arch = "wasm32"))] let mut cv = CBORValidator::new(self.cddl, self.cbor.clone(), self.enabled_features.clone()); #[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))] let mut cv = CBORValidator::new(self.cddl, self.cbor.clone(), self.enabled_features); #[cfg(not(feature = "additional-controls"))] let mut cv = CBORValidator::new(self.cddl, self.cbor.clone()); cv.generic_rules = self.generic_rules.clone(); cv.eval_generic_rule = Some(entry.name.ident); cv.is_multi_type_choice = self.is_multi_type_choice; cv.visit_rule(rule)?; self.errors.append(&mut cv.errors); return Ok(()); } } let type_choice_alternates = type_choice_alternates_from_ident(self.cddl, &entry.name); if !type_choice_alternates.is_empty() { self.is_multi_type_choice = true; } let error_count = self.errors.len(); for t in type_choice_alternates { let cur_errors = self.errors.len(); self.visit_type(t)?; if self.errors.len() == cur_errors { for _ in 0..self.errors.len() - error_count { self.errors.pop(); } return Ok(()); } } let error_count = self.errors.len(); let group_choice_alternates = group_choice_alternates_from_ident(self.cddl, &entry.name); if !group_choice_alternates.is_empty() { self.is_multi_group_choice = true; } for ge in group_choice_alternates { let cur_errors = self.errors.len(); self.visit_group_entry(ge)?; if self.errors.len() == cur_errors { for _ in 0..self.errors.len() - error_count { self.errors.pop(); } return Ok(()); } } walk_type_groupname_entry(self, entry)?; self.type_group_name_entry = None; Ok(()) } fn visit_memberkey(&mut self, mk: &MemberKey<'a>) -> visitor::Result> { match mk { MemberKey::Type1 { is_cut, .. } => { self.is_cut_present = *is_cut; walk_memberkey(self, mk)?; self.is_cut_present = false; } MemberKey::Bareword { .. } => { self.is_colon_shortcut_present = true; walk_memberkey(self, mk)?; self.is_colon_shortcut_present = false; } _ => return walk_memberkey(self, mk), } Ok(()) } fn visit_value(&mut self, value: &token::Value<'a>) -> visitor::Result> { let error: Option = match &self.cbor { Value::Integer(i) => match value { token::Value::INT(v) => match &self.ctrl { Some(ControlOperator::NE) | Some(ControlOperator::DEFAULT) if i128::from(*i) != *v as i128 => { None } Some(ControlOperator::LT) if i128::from(*i) < *v as i128 => None, Some(ControlOperator::LE) if i128::from(*i) <= *v as i128 => None, Some(ControlOperator::GT) if i128::from(*i) > *v as i128 => None, Some(ControlOperator::GE) if i128::from(*i) >= *v as i128 => None, #[cfg(feature = "additional-controls")] Some(ControlOperator::PLUS) => { if i128::from(*i) == *v as i128 { None } else { Some(format!("expected computed .plus value {}, got {:?}", v, i)) } } #[cfg(feature = "additional-controls")] None | Some(ControlOperator::FEATURE) => { if i128::from(*i) == *v as i128 { None } else { Some(format!("expected value {}, got {:?}", v, i)) } } #[cfg(not(feature = "additional-controls"))] None => { if i128::from(*i) == *v as i128 { None } else { Some(format!("expected value {}, got {:?}", v, i)) } } _ => Some(format!( "expected value {} {}, got {:?}", self.ctrl.unwrap(), v, i )), }, token::Value::UINT(v) => match &self.ctrl { Some(ControlOperator::NE) | Some(ControlOperator::DEFAULT) if i128::from(*i) != *v as i128 => { None } Some(ControlOperator::LT) if i128::from(*i) < *v as i128 => None, Some(ControlOperator::LE) if i128::from(*i) <= *v as i128 => None, Some(ControlOperator::GT) if i128::from(*i) > *v as i128 => None, Some(ControlOperator::GE) if i128::from(*i) >= *v as i128 => None, Some(ControlOperator::SIZE) => match 256i128.checked_pow(*v as u32) { Some(n) if i128::from(*i) < n => None, _ => Some(format!("expected value .size {}, got {:?}", v, i)), }, Some(ControlOperator::BITS) => { if let Some(sv) = 1u32.checked_shl(*v as u32) { if (i128::from(*i) & sv as i128) != 0 { None } else { Some(format!("expected uint .bits {}, got {:?}", v, i)) } } else { Some(format!("expected uint .bits {}, got {:?}", v, i)) } } #[cfg(feature = "additional-controls")] Some(ControlOperator::PLUS) => { if i128::from(*i) == *v as i128 { None } else { Some(format!("expected computed .plus value {}, got {:?}", v, i)) } } #[cfg(feature = "additional-controls")] None | Some(ControlOperator::FEATURE) => { if i128::from(*i) == *v as i128 { None } else { Some(format!("expected value {}, got {:?}", v, i)) } } #[cfg(not(feature = "additional-controls"))] None => { if i128::from(*i) == *v as i128 { None } else { Some(format!("expected value {}, got {:?}", v, i)) } } _ => Some(format!( "expected value {} {}, got {:?}", self.ctrl.unwrap(), v, i )), }, _ => Some(format!("expected {}, got {:?}", value, i)), }, Value::Float(f) => match value { token::Value::FLOAT(v) => match &self.ctrl { Some(ControlOperator::NE) | Some(ControlOperator::DEFAULT) if (*f - *v).abs() > std::f64::EPSILON => { None } Some(ControlOperator::LT) if *f < *v => None, Some(ControlOperator::LE) if *f <= *v => None, Some(ControlOperator::GT) if *f > *v => None, Some(ControlOperator::GE) if *f >= *v => None, #[cfg(feature = "additional-controls")] Some(ControlOperator::PLUS) => { if (*f - *v).abs() < std::f64::EPSILON { None } else { Some(format!("expected computed .plus value {}, got {:?}", v, f)) } } #[cfg(feature = "additional-controls")] None | Some(ControlOperator::FEATURE) => { if (*f - *v).abs() < std::f64::EPSILON { None } else { Some(format!("expected value {}, got {:?}", v, f)) } } #[cfg(not(feature = "additional-controls"))] None => { if (*f - *v).abs() < std::f64::EPSILON { None } else { Some(format!("expected value {}, got {:?}", v, f)) } } _ => Some(format!( "expected value {} {}, got {:?}", self.ctrl.unwrap(), v, f )), }, _ => Some(format!("expected {}, got {:?}", value, f)), }, Value::Text(s) => match value { token::Value::TEXT(t) => match &self.ctrl { Some(ControlOperator::NE) | Some(ControlOperator::DEFAULT) => { if s != t { None } else { Some(format!("expected {} .ne to \"{}\"", value, s)) } } Some(ControlOperator::REGEXP) | Some(ControlOperator::PCRE) => { let re = regex::Regex::new( &format_regex( // Text strings must be JSON escaped per // https://datatracker.ietf.org/doc/html/rfc8610#section-3.1 serde_json::from_str::(&format!("\"{}\"", t)) .map_err(Error::JSONParsing)? .as_str() .ok_or_else(|| Error::from_validator(self, "malformed regex".to_string()))?, ) .ok_or_else(|| Error::from_validator(self, "malformed regex".to_string()))?, ) .map_err(|e| Error::from_validator(self, e.to_string()))?; if re.is_match(s) { None } else { Some(format!("expected \"{}\" to match regex \"{}\"", s, t)) } } #[cfg(feature = "additional-controls")] Some(ControlOperator::ABNF) => validate_abnf(t, s) .err() .map(|e| format!("\"{}\" is not valid against abnf: {}", s, e)), _ => { #[cfg(feature = "additional-controls")] if s == t { None } else if let Some(ControlOperator::CAT) | Some(ControlOperator::DET) = &self.ctrl { Some(format!( "expected value to match concatenated string {}, got \"{}\"", value, s )) } else if let Some(ctrl) = &self.ctrl { Some(format!("expected value {} {}, got \"{}\"", ctrl, value, s)) } else { Some(format!("expected value {} got \"{}\"", value, s)) } #[cfg(not(feature = "additional-controls"))] if s == t { None } else if let Some(ctrl) = &self.ctrl { Some(format!("expected value {} {}, got \"{}\"", ctrl, value, s)) } else { Some(format!("expected value {} got \"{}\"", value, s)) } } }, token::Value::UINT(u) => match &self.ctrl { Some(ControlOperator::SIZE) => { if s.len() == *u { None } else { Some(format!("expected \"{}\" .size {}, got {}", s, u, s.len())) } } _ => Some(format!("expected {}, got {}", u, s)), }, token::Value::BYTE(token::ByteValue::UTF8(b)) if s.as_bytes() == b.as_ref() => None, token::Value::BYTE(token::ByteValue::B16(b)) if s.as_bytes() == b.as_ref() => None, token::Value::BYTE(token::ByteValue::B64(b)) if s.as_bytes() == b.as_ref() => None, _ => Some(format!("expected {}, got \"{}\"", value, s)), }, Value::Bytes(b) => match value { token::Value::UINT(v) => match &self.ctrl { Some(ControlOperator::SIZE) => { if b.len() == *v { None } else { Some(format!("expected \"{:?}\" .size {}, got {}", b, v, b.len())) } } Some(ControlOperator::BITS) => { if let Some(rsv) = v.checked_shr(3) { if let Some(s) = b.get(rsv) { if let Some(lsv) = 1u32.checked_shl(*v as u32 & 7) { if (*s as u32 & lsv) != 0 { None } else { Some(format!( "expected value {} {}, got {:?}", self.ctrl.unwrap(), v, b )) } } else { Some(format!( "expected value {} {}, got {:?}", self.ctrl.unwrap(), v, b )) } } else { Some(format!( "expected value {} {}, got {:?}", self.ctrl.unwrap(), v, b )) } } else { Some(format!( "expected value {} {}, got {:?}", self.ctrl.unwrap(), v, b )) } } _ => { if let Some(ctrl) = self.ctrl { Some(format!("expected value {} {}, got {:?}", ctrl, v, b)) } else { Some(format!("expected value {}, got {:?}", v, b)) } } }, #[cfg(feature = "additional-controls")] token::Value::TEXT(t) => match &self.ctrl { Some(ControlOperator::ABNFB) => { validate_abnf(t, std::str::from_utf8(b).map_err(Error::UTF8Parsing)?) .err() .map(|e| { format!( "cbor bytes \"{:?}\" are not valid against abnf {}: {}", b, t, e ) }) } _ => Some(format!( "expected value {} {}, got {:?}", self.ctrl.unwrap(), t, b )), }, #[cfg(feature = "additional-controls")] token::Value::BYTE(bv) => match &self.ctrl { Some(ControlOperator::ABNFB) => match bv { ByteValue::UTF8(utf8bv) => validate_abnf( std::str::from_utf8(utf8bv).map_err(Error::UTF8Parsing)?, std::str::from_utf8(b).map_err(Error::UTF8Parsing)?, ) .err() .map(|e| { format!( "cbor bytes \"{:?}\" are not valid against abnf {}: {}", b, bv, e ) }), ByteValue::B16(b16bv) => validate_abnf( std::str::from_utf8(&base16::decode(b16bv).map_err(Error::Base16Decoding)?) .map_err(Error::UTF8Parsing)?, std::str::from_utf8(b).map_err(Error::UTF8Parsing)?, ) .err() .map(|e| { format!( "cbor bytes \"{:?}\" are not valid against abnf {}: {}", b, bv, e ) }), ByteValue::B64(b64bv) => validate_abnf( std::str::from_utf8( &data_encoding::BASE64URL .decode(b64bv) .map_err(Error::Base64Decoding)?, ) .map_err(Error::UTF8Parsing)?, std::str::from_utf8(b).map_err(Error::UTF8Parsing)?, ) .err() .map(|e| { format!( "cbor bytes \"{:?}\" are not valid against abnf {}: {}", b, bv, e ) }), }, _ => Some(format!( "expected value {} {}, got {:?}", self.ctrl.unwrap(), bv, b )), }, _ => Some(format!("expected {}, got {:?}", value, b)), }, Value::Array(_) => { self.validate_array_items(&ArrayItemToken::Value(value))?; None } Value::Map(o) => { if self.is_cut_present { self.cut_value = Some(Type1::from(value.clone())); } if let token::Value::TEXT(Cow::Borrowed("any")) = value { return Ok(()); } // Retrieve the value from key unless optional/zero or more, in which // case advance to next group entry let k = token_value_into_cbor_value(value.clone()); #[cfg(feature = "ast-span")] if let Some(v) = o .iter() .find_map(|entry| if entry.0 == k { Some(&entry.1) } else { None }) { self.validated_keys.get_or_insert(vec![k.clone()]).push(k); self.object_value = Some(v.clone()); let _ = write!(self.cbor_location, "/{}", value); None } else if let Some(Occur::Optional { .. }) | Some(Occur::ZeroOrMore { .. }) = &self.occurrence.take() { self.advance_to_next_entry = true; None } else if let Some(ControlOperator::NE) | Some(ControlOperator::DEFAULT) = &self.ctrl { None } else { Some(format!("object missing key: \"{}\"", value)) } #[cfg(not(feature = "ast-span"))] if let Some(v) = o .iter() .find_map(|entry| if entry.0 == k { Some(&entry.1) } else { None }) { self.validated_keys.get_or_insert(vec![k.clone()]).push(k); self.object_value = Some(v.clone()); self.cbor_location.push_str(&format!("/{}", value)); None } else if let Some(Occur::Optional {}) | Some(Occur::ZeroOrMore {}) = &self.occurrence.take() { self.advance_to_next_entry = true; None } else if let Some(Token::NE) | Some(Token::DEFAULT) = &self.ctrl { None } else { Some(format!("object missing key: \"{}\"", value)) } } _ => Some(format!("expected {}, got {:?}", value, self.cbor)), }; if let Some(e) = error { self.add_error(e); } Ok(()) } fn visit_occurrence(&mut self, o: &Occurrence<'a>) -> visitor::Result> { self.occurrence = Some(o.occur); Ok(()) } } /// Converts a CDDL value type to ciborium::value::Value pub fn token_value_into_cbor_value(value: token::Value) -> ciborium::value::Value { match value { token::Value::UINT(i) => ciborium::value::Value::Integer(i.into()), token::Value::INT(i) => ciborium::value::Value::Integer(i.into()), token::Value::FLOAT(f) => ciborium::value::Value::Float(f), token::Value::TEXT(t) => ciborium::value::Value::Text(t.to_string()), token::Value::BYTE(b) => match b { ByteValue::UTF8(b) | ByteValue::B16(b) | ByteValue::B64(b) => { ciborium::value::Value::Bytes(b.into_owned()) } }, } } #[cfg(test)] #[cfg(not(target_arch = "wasm32"))] mod tests { use super::*; use ciborium::cbor; use indoc::indoc; #[cfg(not(feature = "additional-controls"))] #[test] fn validate() -> std::result::Result<(), Box> { let cddl = indoc!( r#" tcpflagbytes = bstr .bits flags flags = &( fin: 8, syn: 9, rst: 10, psh: 11, ack: 12, urg: 13, ece: 14, cwr: 15, ns: 0, ) / (4..7) ; data offset bits "# ); let cbor = ciborium::value::Value::Bytes(vec![0x90, 0x6d]); let mut lexer = lexer_from_str(cddl); let cddl = cddl_from_str(&mut lexer, cddl, true)?; let mut cv = CBORValidator::new(&cddl, cbor); cv.validate()?; Ok(()) } #[cfg(feature = "additional-controls")] #[test] fn validate_abnfb_1() -> std::result::Result<(), Box> { let cddl = indoc!( r#" oid = bytes .abnfb ("oid" .det cbor-tags-oid) roid = bytes .abnfb ("roid" .det cbor-tags-oid) cbor-tags-oid = ' oid = 1*arc roid = *arc arc = [nlsb] %x00-7f nlsb = %x81-ff *%x80-ff ' "# ); let sha256_oid = "2.16.840.1.101.3.4.2.1"; let cbor = ciborium::value::Value::Bytes(sha256_oid.as_bytes().to_vec()); let cddl = cddl_from_str(cddl, true)?; let mut cv = CBORValidator::new(&cddl, cbor, None); cv.validate()?; Ok(()) } #[cfg(feature = "additional-controls")] #[test] fn validate_feature() -> std::result::Result<(), Box> { let cddl = indoc!( r#" v = JC<"v", 2> JC = J .feature "json" / C .feature "cbor" "# ); let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing); if let Err(e) = &cddl { println!("{}", e); } let cbor = ciborium::value::Value::Integer(2.into()); let cddl = cddl.unwrap(); let mut cv = CBORValidator::new(&cddl, cbor, Some(&["cbor"])); cv.validate()?; Ok(()) } #[test] fn validate_type_choice_alternate() -> std::result::Result<(), Box> { let cddl = indoc!( r#" tester = [ $vals ] $vals /= 12 $vals /= 13 "# ); let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing); if let Err(e) = &cddl { println!("{}", e); } let cbor = ciborium::cbor!([13]).unwrap(); let cddl = cddl.unwrap(); let mut cv = CBORValidator::new(&cddl, cbor, None); cv.validate()?; Ok(()) } #[test] fn validate_group_choice_alternate_in_array( ) -> std::result::Result<(), Box> { let cddl = indoc!( r#" tester = [$$val] $$val //= ( type: 10, data: uint ) $$val //= ( type: 11, data: tstr ) "# ); let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing); if let Err(e) = &cddl { println!("{}", e); } let cbor = ciborium::cbor!([11, "test"]).unwrap(); let cddl = cddl.unwrap(); let mut cv = CBORValidator::new(&cddl, cbor, None); cv.validate()?; Ok(()) } #[test] fn validate_tdate_tag() -> std::result::Result<(), Box> { let cddl = indoc!( r#" root = time "# ); let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing); if let Err(e) = &cddl { println!("{}", e); } let cbor = ciborium::value::Value::Tag( 1, Box::from(ciborium::value::Value::Float(1680965875.01_f64)), ); let cddl = cddl.unwrap(); let mut cv = CBORValidator::new(&cddl, cbor, None); cv.validate()?; Ok(()) } #[test] fn validate_abnfb_2() -> std::result::Result<(), Box> { let cddl = indoc!( r#" ; Binary ABNF Test Schema test_cbor = { 61285: sub_map } sub_map = { 1: signature_abnf } signature = bytes .size 64 signature_abnf = signature .abnfb ' ANYDATA ANYDATA = *OCTET OCTET = %x00-FF ' "# ); let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing); if let Err(e) = &cddl { println!("{}", e); } let cbor = ciborium::value::Value::Map(vec![( ciborium::value::Value::Integer(61285.into()), ciborium::value::Value::Map(vec![( ciborium::value::Value::Integer(1.into()), ciborium::value::Value::Bytes(b"test".to_vec()), )]), )]); let cddl = cddl.unwrap(); let mut cv = CBORValidator::new(&cddl, cbor, None); cv.validate()?; Ok(()) } #[test] fn multi_type_choice_type_rule_array_validation( ) -> std::result::Result<(), Box> { use ciborium::value::Value; let cddl = indoc!( r#" Ref = nil / refShort / refFull blobSize = uint hashID = uint .lt 23 hashName = text hashDigest = bytes refShort = [ blobSize, hashID, hashDigest ] refFull = { 1: blobSize, 2: hashName, 3: hashDigest } "# ); let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing); if let Err(e) = &cddl { println!("{}", e); } let cbor = Value::Array(vec![ Value::Integer(3.into()), Value::Integer(2.into()), Value::Bytes( base16::decode("BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD").unwrap(), ), ]); let cddl = cddl.unwrap(); let mut cv = CBORValidator::new(&cddl, cbor, None); cv.validate()?; Ok(()) } #[test] fn tagged_data_in_array_validation() -> std::result::Result<(), Box> { use ciborium::value::Value; let cddl = indoc!( r#" start = [ * help ] help = #6.123(bstr) "# ); let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing); if let Err(e) = &cddl { println!("{}", e); } let cbor = Value::Array(vec![Value::Tag( 123, Box::from(Value::Bytes(base16::decode("00").unwrap())), )]); let cddl = cddl.unwrap(); let mut cv = CBORValidator::new(&cddl, cbor, None); cv.validate()?; Ok(()) } } cddl-0.9.4/src/validator/control.rs000064400000000000000000000762561046102023000153660ustar 00000000000000#![cfg(any(feature = "json", feature = "cbor"))] #![cfg(not(feature = "lsp"))] use crate::{ ast::{Identifier, Operator, RangeCtlOp, Rule, Type2, CDDL}, token::ControlOperator, }; #[cfg(feature = "additional-controls")] use crate::{ast::Type, validator::ByteValue}; #[cfg(feature = "additional-controls")] use itertools::Itertools; #[cfg(feature = "additional-controls")] use pest_meta; /// Retrieve all text strings and byte string literals from a given rule /// identifier. Used for proposed .cat control operator. pub fn string_literals_from_ident<'a>( cddl: &'a CDDL<'a>, ident: &Identifier, ) -> Vec<&'a Type2<'a>> { let mut literals = Vec::new(); for r in cddl.rules.iter() { if let Rule::Type { rule, .. } = r { if rule.name == *ident { for tc in rule.value.type_choices.iter() { match &tc.type1.type2 { t @ Type2::TextValue { .. } | t @ Type2::UTF8ByteString { .. } | t @ Type2::B16ByteString { .. } | t @ Type2::B64ByteString { .. } => literals.push(t), Type2::Typename { ident, .. } => { literals.append(&mut string_literals_from_ident(cddl, ident)) } _ => continue, } } } } } literals } /// Retrieve all numeric values from a given rule identifier. Used for /// proposed .cat control operator. pub fn numeric_values_from_ident<'a>(cddl: &'a CDDL<'a>, ident: &Identifier) -> Vec<&'a Type2<'a>> { let mut literals = Vec::new(); for r in cddl.rules.iter() { if let Rule::Type { rule, .. } = r { if rule.name == *ident { for tc in rule.value.type_choices.iter() { match &tc.type1.type2 { t @ Type2::IntValue { .. } | t @ Type2::UintValue { .. } | t @ Type2::FloatValue { .. } => literals.push(t), Type2::Typename { ident, .. } => { literals.append(&mut numeric_values_from_ident(cddl, ident)) } _ => continue, } } } } } literals } #[cfg(feature = "additional-controls")] /// Concatenate target and controller. The Vec return type is to accomodate more /// than one type choice in the controller. pub fn cat_operation<'a>( cddl: &'a CDDL<'a>, target: &Type2, controller: &Type2, is_dedent: bool, ) -> Result>, String> { let mut literals = Vec::new(); let ctrl = if is_dedent { ControlOperator::DET } else { ControlOperator::CAT }; match target { Type2::TextValue { value, .. } => match controller { // "testing" .cat "123" Type2::TextValue { value: controller, .. } => { if is_dedent { literals.push(format!("{}{}", dedent_str(value), dedent_str(controller)).into()) } else { literals.push(format!("{}{}", value, controller).into()) } } // "testing" .cat a Type2::Typename { ident, .. } => { let sl = string_literals_from_ident(cddl, ident); if sl.is_empty() { return Err(format!( "controller of type rule {} is not a string literal", ident )); } for controller in sl.iter() { literals.append(&mut cat_operation(cddl, target, controller, is_dedent)?) } } // "testing" .cat '123' Type2::UTF8ByteString { value: controller, .. } => match std::str::from_utf8(controller) { Ok(controller) => { let controller = controller.trim_start_matches('\'').trim_end_matches('\''); if is_dedent { literals.push(format!("{}{}", dedent_str(value), dedent_str(controller)).into()) } else { literals.push(format!("{}{}", value, controller).into()) } } Err(e) => return Err(format!("error parsing byte string: {}", e)), }, // "testing" .cat h'313233' Type2::B16ByteString { value: controller, .. } => match base16::decode(controller) { Ok(controller) => match String::from_utf8(controller) { Ok(controller) => { if is_dedent { literals.push(format!("{}{}", dedent_str(value), dedent_str(&controller)).into()) } else { literals.push(format!("{}{}", value, controller).into()) } } Err(e) => return Err(format!("error decoding utf-8: {}", e)), }, Err(e) => return Err(format!("error decoding base16 byte string literal: {}", e)), }, // "testing" .cat b64'MTIz' Type2::B64ByteString { value: controller, .. } => match data_encoding::BASE64URL.decode(controller) { Ok(controller) => match String::from_utf8(controller) { Ok(controller) => { if is_dedent { literals.push(format!("{}{}", dedent_str(value), dedent_str(&controller)).into()) } else { literals.push(format!("{}{}", value, controller).into()) } } Err(e) => return Err(format!("error decoding utf-8: {}", e)), }, Err(e) => { return Err(format!( "error decoding base64 encoded byte string literal: {}", e )) } }, // "testing" .cat ( "123" / "1234" ) Type2::ParenthesizedType { pt: controller, .. } => { for controller in controller.type_choices.iter() { if controller.type1.operator.is_none() { literals.append(&mut cat_operation( cddl, target, &controller.type1.type2, is_dedent, )?); } } } _ => return Err(format!("invalid controller used for {} operation", ctrl)), }, // a .cat "123" Type2::Typename { ident, .. } => { // Only grab the first type choice literal from the target per // https://github.com/cbor-wg/cddl-control/issues/2#issuecomment-729253368 if let Some(value) = string_literals_from_ident(cddl, ident).first() { literals.append(&mut cat_operation(cddl, value, controller, is_dedent)?); } else { return Err(format!("invalid controller used for {} operation", ctrl)); } } // ( "test" / "testing" ) .cat "123" Type2::ParenthesizedType { pt: target, .. } => { // Only grab the first type choice literal from the target per // https://github.com/cbor-wg/cddl-control/issues/2#issuecomment-729253368 if let Some(tc) = target.type_choices.first() { // Ignore nested operator if tc.type1.operator.is_none() { literals.append(&mut cat_operation( cddl, &tc.type1.type2, controller, is_dedent, )?); } } return Err(format!("invalid target type in {} control operator", ctrl)); } Type2::UTF8ByteString { value, .. } => match std::str::from_utf8(value) { Ok(value) => match controller { // 'testing' .cat "123" Type2::TextValue { value: controller, .. } => { let value = value.trim_start_matches('\'').trim_end_matches('\''); if is_dedent { literals.push(format!("{}{}", dedent_str(value), dedent_str(controller)).into()) } else { literals.push(format!("{}{}", value, controller).into()) } } Type2::Typename { ident, .. } => { let sl = string_literals_from_ident(cddl, ident); if sl.is_empty() { return Err(format!( "controller of type rule {} is not a string literal", ident )); } for controller in sl.iter() { literals.append(&mut cat_operation(cddl, target, controller, is_dedent)?) } } // 'testing' .cat '123 Type2::UTF8ByteString { value: controller, .. } => match std::str::from_utf8(controller) { Ok(controller) => { let value = value.trim_start_matches('\'').trim_end_matches('\''); let controller = controller.trim_start_matches('\'').trim_end_matches('\''); if is_dedent { literals.push(format!("{}{}", dedent_str(value), dedent_str(controller)).into()) } else { literals.push(format!("{}{}", value, controller).into()) } } Err(e) => return Err(format!("error parsing byte string: {}", e)), }, // 'testing' .cat h'313233' Type2::B16ByteString { value: controller, .. } => match base16::decode(controller) { Ok(controller) => match String::from_utf8(controller) { Ok(controller) => { let value = value.trim_start_matches('\'').trim_end_matches('\''); if is_dedent { literals.push(format!("{}{}", dedent_str(value), dedent_str(&controller)).into()) } else { literals.push(format!("{}{}", value, controller).into()) } } Err(e) => return Err(format!("error decoding utf-8: {}", e)), }, Err(e) => return Err(format!("error decoding base16 byte string literal: {}", e)), }, // 'testing' .cat b64'MTIz' Type2::B64ByteString { value: controller, .. } => match data_encoding::BASE64URL.decode(controller) { Ok(controller) => match String::from_utf8(controller) { Ok(controller) => { let value = value.trim_start_matches('\'').trim_end_matches('\''); if is_dedent { literals.push(format!("{}{}", dedent_str(value), dedent_str(&controller)).into()) } else { literals.push(format!("{}{}", value, controller).into()) } } Err(e) => return Err(format!("error decoding utf-8: {}", e)), }, Err(e) => { return Err(format!( "error decoding base64 encoded byte string literal: {}", e )) } }, // 'testing' .cat ( "123" / "1234" ) Type2::ParenthesizedType { pt: controller, .. } => { for controller in controller.type_choices.iter() { if controller.type1.operator.is_none() { literals.append(&mut cat_operation( cddl, target, &controller.type1.type2, is_dedent, )?); } } } _ => return Err(format!("invalid controller used for {} operation", ctrl)), }, Err(e) => return Err(format!("error parsing byte string: {}", e)), }, Type2::B16ByteString { value, .. } => match controller { // h'74657374696E67' .cat "123" Type2::TextValue { value: controller, .. } => { let controller = if is_dedent { base16::encode_lower(dedent_str(controller).as_bytes()) } else { base16::encode_lower(controller.as_bytes()) }; let concat = if is_dedent { [&dedent_bytes(value, false)?[..], controller.as_bytes()].concat() } else { [&value[..], controller.as_bytes()].concat() }; match base16::decode(&concat) { // Ignore the decoded value Ok(_) => literals.push(ByteValue::B16(concat.into()).into()), Err(e) => return Err(format!("concatenated value is invalid base16: {}", e)), } } // h'74657374696E67' .cat b Type2::Typename { ident, .. } => { let sl = string_literals_from_ident(cddl, ident); if sl.is_empty() { return Err(format!( "controller of type rule {} is not a string literal", ident )); } for controller in sl.iter() { literals.append(&mut cat_operation(cddl, target, controller, is_dedent)?) } } // h'74657374696E67' .cat '123' Type2::UTF8ByteString { value: controller, .. } => { let controller = if is_dedent { base16::encode_lower(&dedent_bytes(controller, true)?) } else { base16::encode_lower(&controller[..]) }; let concat = if is_dedent { [&dedent_bytes(value, false)?[..], controller.as_bytes()].concat() } else { [&value[..], controller.as_bytes()].concat() }; match base16::decode(&concat) { // Ignore the decoded value Ok(_) => literals.push(ByteValue::B16(concat.into()).into()), Err(e) => return Err(format!("concatenated value is invalid base16: {}", e)), } } // h'74657374696E67' .cat h'313233' Type2::B16ByteString { value: controller, .. } => { let concat = if is_dedent { [ &dedent_bytes(value, false)?[..], &dedent_bytes(controller, false)?[..], ] .concat() } else { [&value[..], &controller[..]].concat() }; match base16::decode(&concat) { // Ignore the decoded value Ok(_) => literals.push(ByteValue::B16(concat.into()).into()), Err(e) => return Err(format!("concatenated value is invalid base16: {}", e)), } } // h'74657374696E67' .cat b64'MTIz' Type2::B64ByteString { value: controller, .. } => match data_encoding::BASE64URL.decode(controller) { Ok(controller) => { let controller = base16::encode_lower(&controller); let concat = if is_dedent { [ &dedent_bytes(value, false)?[..], dedent_str(&controller).as_bytes(), ] .concat() } else { [&value[..], controller.as_bytes()].concat() }; match base16::decode(&concat) { // Ignore the decoded value Ok(_) => literals.push(ByteValue::B16(concat.into()).into()), Err(e) => return Err(format!("concatenated value is invalid base16: {}", e)), } } Err(e) => return Err(format!("controller is invalid base64: {}", e)), }, // h'74657374696E67' .cat ( "123" / "1234" ) Type2::ParenthesizedType { pt: controller, .. } => { for controller in controller.type_choices.iter() { if controller.type1.operator.is_none() { literals.append(&mut cat_operation( cddl, target, &controller.type1.type2, is_dedent, )?); } } } _ => return Err(format!("invalid controller used for {} operation", ctrl)), }, Type2::B64ByteString { value, .. } => match controller { // b64'dGVzdGluZw==' .cat "123" Type2::TextValue { value: controller, .. } => match data_encoding::BASE64URL.decode(value) { Ok(value) => { let concat = if is_dedent { [ &dedent_bytes(&value, false)?[..], dedent_str(controller).as_bytes(), ] .concat() } else { [&value[..], controller.as_bytes()].concat() }; literals.push( ByteValue::B64(data_encoding::BASE64URL.encode(&concat).into_bytes().into()).into(), ) } Err(e) => return Err(format!("target is invalid base64: {}", e)), }, // b64'dGVzdGluZw==' .cat b Type2::Typename { ident, .. } => { let sl = string_literals_from_ident(cddl, ident); if sl.is_empty() { return Err(format!( "controller of type rule {} is not a string literal", ident )); } for controller in sl.iter() { literals.append(&mut cat_operation(cddl, target, controller, is_dedent)?) } } // b64'dGVzdGluZw==' .cat '123' Type2::UTF8ByteString { value: controller, .. } => match data_encoding::BASE64URL.decode(value) { Ok(value) => { let concat = if is_dedent { [ &dedent_bytes(&value, false)?[..], &dedent_bytes(controller, true)?[..], ] .concat() } else { [&value[..], &controller[..]].concat() }; literals.push( ByteValue::B64(data_encoding::BASE64URL.encode(&concat).into_bytes().into()).into(), ) } Err(e) => return Err(format!("target is invalid base64: {}", e)), }, // b64'dGVzdGluZw==' .cat h'313233' Type2::B16ByteString { value: controller, .. } => match data_encoding::BASE64URL.decode(value) { Ok(value) => match base16::decode(controller) { Ok(controller) => { let concat = if is_dedent { [ &dedent_bytes(&value, false)?[..], &dedent_bytes(&controller, false)?[..], ] .concat() } else { [&value[..], &controller[..]].concat() }; literals.push( ByteValue::B64(data_encoding::BASE64URL.encode(&concat).into_bytes().into()).into(), ) } Err(e) => return Err(format!("controller is invalid base16: {}", e)), }, Err(e) => return Err(format!("target is invalid base64: {}", e)), }, // b64'dGVzdGluZw==' .cat b64'MTIz' Type2::B64ByteString { value: controller, .. } => match data_encoding::BASE64URL.decode(value) { Ok(value) => match data_encoding::BASE64URL.decode(controller) { Ok(controller) => { let concat = if is_dedent { [ &dedent_bytes(&value, false)?[..], &dedent_bytes(&controller, false)?[..], ] .concat() } else { [&value[..], &controller[..]].concat() }; literals.push( ByteValue::B64(data_encoding::BASE64URL.encode(&concat).into_bytes().into()).into(), ) } Err(e) => return Err(format!("controller is invalid base64: {}", e)), }, Err(e) => return Err(format!("target is invalid base64: {}", e)), }, // b64'dGVzdGluZw==' .cat ( "123" / "1234" ) Type2::ParenthesizedType { pt: controller, .. } => { for controller in controller.type_choices.iter() { if controller.type1.operator.is_none() { literals.append(&mut cat_operation( cddl, target, &controller.type1.type2, is_dedent, )?); } } } _ => return Err(format!("invalid controller used for {} operation", ctrl)), }, _ => { return Err(format!( "invalid target used for {} operation, got {}", ctrl, target )) } } Ok(literals) } #[cfg(feature = "additional-controls")] fn dedent_str(source: &str) -> String { // #[cfg(windows)] // let line_ending = "\r\n"; // #[cfg(not(windows))] let line_ending = "\n"; source .split(line_ending) .map(|l| l.trim_start()) .join(line_ending) } #[cfg(feature = "additional-controls")] fn dedent_bytes(source: &[u8], is_utf8_byte_string: bool) -> Result, String> { #[cfg(windows)] let line_ending = "\r\n"; #[cfg(not(windows))] let line_ending = "\n"; if is_utf8_byte_string { return Ok( std::str::from_utf8(source) .map_err(|e| e.to_string())? .trim_start_matches('\'') .trim_end_matches('\'') .split(line_ending) .map(|l| l.trim_start()) .join(line_ending) .into_bytes(), ); } Ok( std::str::from_utf8(source) .map_err(|e| e.to_string())? .split(line_ending) .map(|l| l.trim_start()) .join(line_ending) .into_bytes(), ) } /// Numeric addition of target and controller. The Vec return type is to /// accommodate more than one type choice in the controller pub fn plus_operation<'a>( cddl: &'a CDDL<'a>, target: &Type2, controller: &Type2, ) -> Result>, String> { let mut values = Vec::new(); match target { Type2::UintValue { value, .. } => match controller { Type2::UintValue { value: controller, .. } => values.push((value + controller).into()), Type2::IntValue { value: controller, .. } => values.push(((*value as isize + controller) as usize).into()), Type2::FloatValue { value: controller, .. } => values.push(((*value as isize + *controller as isize) as usize).into()), Type2::Typename { ident, .. } => { let nv = numeric_values_from_ident(cddl, ident); if nv.is_empty() { return Err(format!( "controller of type rule {} is not a numeric value", ident )); } for controller in nv.iter() { println!("controller: {}", controller); values.append(&mut plus_operation(cddl, target, controller)?) } } Type2::ParenthesizedType { pt: controller, .. } => { for controller in controller.type_choices.iter() { match &controller.type1.operator { Some(Operator { operator: RangeCtlOp::CtlOp { ctrl: ControlOperator::PLUS, .. }, type2: nested_controller, .. }) => { for v in plus_operation(cddl, &controller.type1.type2, nested_controller)?.iter() { values.append(&mut plus_operation(cddl, target, v)?); } } None => values.append(&mut plus_operation(cddl, target, &controller.type1.type2)?), _ => return Err("nested operator must be .plus".to_string()), } } } _ => return Err("invalid controller used for .plus operation".to_string()), }, Type2::IntValue { value, .. } => match controller { Type2::IntValue { value: controller, .. } => values.push((value + controller).into()), Type2::UintValue { value: controller, .. } => values.push((value + *controller as isize).into()), Type2::FloatValue { value: controller, .. } => values.push((value + *controller as isize).into()), Type2::Typename { ident, .. } => { let nv = numeric_values_from_ident(cddl, ident); if nv.is_empty() { return Err(format!( "controller of type rule {} is not a numeric value", ident )); } for controller in nv.iter() { println!("controller: {}", controller); values.append(&mut plus_operation(cddl, target, controller)?) } } Type2::ParenthesizedType { pt: controller, .. } => { for controller in controller.type_choices.iter() { match &controller.type1.operator { Some(Operator { operator: RangeCtlOp::CtlOp { ctrl: ControlOperator::PLUS, .. }, type2: nested_controller, .. }) => { for v in plus_operation(cddl, &controller.type1.type2, nested_controller)?.iter() { values.append(&mut plus_operation(cddl, target, v)?); } } None => values.append(&mut plus_operation(cddl, target, &controller.type1.type2)?), _ => return Err("nested operator must be .plus".to_string()), } } } _ => return Err("invalid controller used for .plus operation".to_string()), }, Type2::FloatValue { value, .. } => match controller { Type2::IntValue { value: controller, .. } => values.push((value + *controller as f64).into()), Type2::FloatValue { value: controller, .. } => values.push((value + controller).into()), Type2::Typename { ident, .. } => { let nv = numeric_values_from_ident(cddl, ident); if nv.is_empty() { return Err(format!( "controller of type rule {} is not a numeric value", ident )); } for controller in nv.iter() { println!("controller: {}", controller); values.append(&mut plus_operation(cddl, target, controller)?) } } Type2::ParenthesizedType { pt: controller, .. } => { for controller in controller.type_choices.iter() { match &controller.type1.operator { Some(Operator { operator: RangeCtlOp::CtlOp { ctrl: ControlOperator::PLUS, .. }, type2: nested_controller, .. }) => { for v in plus_operation(cddl, &controller.type1.type2, nested_controller)?.iter() { values.append(&mut plus_operation(cddl, target, v)?); } } None => values.append(&mut plus_operation(cddl, target, &controller.type1.type2)?), _ => return Err("nested operator must be .plus".to_string()), } } } _ => return Err("invalid controller used for .plus operation".to_string()), }, Type2::Typename { ident, .. } => { // Only grab the first type choice value from the target per // https://github.com/cbor-wg/cddl-control/issues/2#issuecomment-729253368 if let Some(value) = numeric_values_from_ident(cddl, ident).first() { values.append(&mut plus_operation(cddl, value, controller)?); } else { return Err("invalid controller used for .plus operation".to_string()); } } Type2::ParenthesizedType { pt: target, .. } => { // Only grab the first type choice value from the target per // https://github.com/cbor-wg/cddl-control/issues/2#issuecomment-729253368 if let Some(tc) = target.type_choices.first() { match &tc.type1.operator { Some(Operator { operator: RangeCtlOp::CtlOp { ctrl: ControlOperator::PLUS, .. }, type2: nested_controller, .. }) => { for v in plus_operation(cddl, &tc.type1.type2, nested_controller)?.iter() { values.append(&mut plus_operation(cddl, v, controller)?); } } None => values.append(&mut plus_operation(cddl, &tc.type1.type2, controller)?), _ => return Err("nested operator must be .plus".to_string()), } } else { return Err("invalid target type in .plus control operator".to_string()); } } _ => { return Err(format!( "invalid target type in .plus control operator, got {}", target )) } } Ok(values) } #[cfg(feature = "additional-controls")] pub fn validate_abnf(abnf: &str, target: &str) -> Result<(), String> { let abnf = abnf.trim(); if let Some(idx) = abnf.find('\n') { let (rule, abnf) = abnf.split_at(idx); let rule = rule.trim(); let mut abnf = abnf.trim().to_string(); // Refer to https://docs.rs/abnf/0.13.0/abnf/fn.rulelist.html as to why it // needs to end with a newline abnf.push('\n'); let rules = abnf_to_pest::parse_abnf(&abnf).map_err(|e| e.to_string())?; let mut w = Vec::new(); abnf_to_pest::render_rules_to_pest(rules) .render(0, &mut w) .unwrap(); let pest = String::from_utf8(w).unwrap(); let pairs = pest_meta::parser::parse(pest_meta::parser::Rule::grammar_rules, &pest) .map_err(|e| e.to_string())?; let ast = pest_meta::parser::consume_rules(pairs).unwrap(); let vm = pest_vm::Vm::new(pest_meta::optimizer::optimize(ast)); let rule = rule.replace('-', "_"); let _ = vm.parse(&rule, target).map_err(|e| e.to_string())?; } Ok(()) } /// If the controller for an .abnf/.abnfb control operator is a parenthesized /// type with a nested .cat/.det, it needs to be parsed beforehand. The Vec /// return type is to accomodate more than one type choice in the controller. #[cfg(feature = "additional-controls")] pub fn abnf_from_complex_controller<'a>( cddl: &'a CDDL<'a>, controller: &Type, ) -> Result>, String> { if let Some(tc) = controller.type_choices.first() { if let Some(operator) = &tc.type1.operator { if let RangeCtlOp::CtlOp { ctrl, .. } = operator.operator { match ctrl { ControlOperator::CAT => { return cat_operation(cddl, &tc.type1.type2, &operator.type2, false) } ControlOperator::DET => { return cat_operation(cddl, &tc.type1.type2, &operator.type2, true) } _ => return Err("invalid_controller".to_string()), } } } } Err("invalid controller".to_string()) } #[cfg(test)] #[cfg(not(target_arch = "wasm32"))] mod tests { #![allow(unused_imports)] #[cfg(feature = "ast-span")] use crate::ast::Span; use crate::{cddl_from_str, lexer_from_str}; use super::*; use indoc::indoc; #[cfg(feature = "additional-controls")] #[test] fn test_cat() -> std::result::Result<(), Box> { let cddl_str = indoc!( r#" a = "foo" .cat ' bar baz ' "# ); let cddl = cddl_from_str(cddl_str, true)?; assert_eq!( cat_operation( &cddl, &Type2::from("foo".to_string()), &Type2::from(indoc!( r#"' bar baz '"#, )), false, )?, vec![Type2::TextValue { value: "foo\n bar\n baz\n".into(), #[cfg(feature = "ast-span")] span: Span::default(), }], ); Ok(()) } #[cfg(feature = "additional-controls")] #[test] fn test_cat_with_dedent() -> std::result::Result<(), Box> { let cddl_str = indoc!( r#" a = "foo" .det b b = ' bar baz ' "# ); let cddl = cddl_from_str(cddl_str, true)?; assert_eq!( cat_operation( &cddl, &Type2::from("foo".to_string()), &Type2::Typename { ident: "b".into(), generic_args: None, #[cfg(feature = "ast-span")] span: Span::default(), }, true, )?, vec![Type2::TextValue { value: "foo\nbar\nbaz\n".into(), #[cfg(feature = "ast-span")] span: Span::default(), }] ); Ok(()) } #[cfg(feature = "additional-controls")] #[test] fn test_abnf() -> std::result::Result<(), Box> { let abnf_str = indoc!( r#" date-fullyear date-fullyear = 4DIGIT date-month = 2DIGIT ; 01-12 date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on ; month/year time-hour = 2DIGIT ; 00-23 time-minute = 2DIGIT ; 00-59 time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap sec ; rules time-secfrac = "." 1*DIGIT time-numoffset = ("+" / "-") time-hour ":" time-minute time-offset = "Z" / time-numoffset partial-time = time-hour ":" time-minute ":" time-second [time-secfrac] full-date = date-fullyear "-" date-month "-" date-mday full-time = partial-time time-offset date-time = full-date "T" full-time DIGIT = %x30-39 ; 0-9 ; abbreviated here "# ); validate_abnf(abnf_str, "2009")?; Ok(()) } } cddl-0.9.4/src/validator/json.rs000064400000000000000000002600371046102023000146470ustar 00000000000000#![cfg(feature = "std")] #![cfg(feature = "json")] #![cfg(not(feature = "lsp"))] use super::*; use crate::{ ast::*, token, visitor::{self, *}, }; use std::{ borrow::Cow, collections::HashMap, convert::TryFrom, fmt::{self, Write}, }; use chrono::{TimeZone, Utc}; use serde_json::Value; #[cfg(feature = "additional-controls")] use control::{abnf_from_complex_controller, cat_operation, plus_operation, validate_abnf}; /// JSON validation Result pub type Result = std::result::Result<(), Error>; /// JSON validation error #[derive(Debug)] pub enum Error { /// Zero or more validation errors Validation(Vec), /// JSON parsing error JSONParsing(serde_json::Error), /// CDDL parsing error CDDLParsing(String), /// UTF8 parsing error, UTF8Parsing(std::str::Utf8Error), /// Disabled feature DisabledFeature(String), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::Validation(errors) => { let mut error_str = String::new(); for e in errors.iter() { let _ = writeln!(error_str, "{}", e); } write!(f, "{}", error_str) } Error::JSONParsing(error) => write!(f, "error parsing JSON: {}", error), Error::CDDLParsing(error) => write!(f, "error parsing CDDL: {}", error), Error::UTF8Parsing(error) => write!(f, "error pasing utf8: {}", error), Error::DisabledFeature(feature) => write!(f, "feature {} is not enabled", feature), } } } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Error::JSONParsing(error) => Some(error), _ => None, } } } impl Error { fn from_validator(jv: &JSONValidator, reason: String) -> Self { Error::Validation(vec![ValidationError { cddl_location: jv.cddl_location.clone(), json_location: jv.json_location.clone(), reason, is_multi_type_choice: jv.is_multi_type_choice, is_group_to_choice_enum: jv.is_group_to_choice_enum, type_group_name_entry: jv.type_group_name_entry.map(|e| e.to_string()), is_multi_group_choice: jv.is_multi_group_choice, }]) } } /// JSON validation error #[derive(Clone, Debug)] pub struct ValidationError { /// Error message pub reason: String, /// Location in CDDL where error occurred pub cddl_location: String, /// Location in JSON (in JSONPointer notation) where error occurred pub json_location: String, /// Whether or not the error is associated with multiple type choices pub is_multi_type_choice: bool, /// Whether or not the error is associated with multiple group choices pub is_multi_group_choice: bool, /// Whether or not the error is associated with a group to choice enumeration pub is_group_to_choice_enum: bool, /// Error is associated with a type/group name group entry pub type_group_name_entry: Option, } impl fmt::Display for ValidationError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut error_str = String::from("error validating"); if self.is_multi_group_choice { error_str.push_str(" group choice"); } if self.is_multi_type_choice { error_str.push_str(" type choice"); } if self.is_group_to_choice_enum { error_str.push_str(" type choice in group to choice enumeration"); } if let Some(entry) = &self.type_group_name_entry { let _ = write!(error_str, " group entry associated with rule \"{}\"", entry); } if self.json_location.is_empty() { return write!( f, "{} at the root of the JSON document: {}", error_str, self.reason ); } write!( f, "{} at JSON location {}: {}", error_str, self.json_location, self.reason ) } } impl std::error::Error for ValidationError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } } impl ValidationError { fn from_validator(jv: &JSONValidator, reason: String) -> Self { ValidationError { cddl_location: jv.cddl_location.clone(), json_location: jv.json_location.clone(), reason, is_multi_type_choice: jv.is_multi_type_choice, is_group_to_choice_enum: jv.is_group_to_choice_enum, type_group_name_entry: jv.type_group_name_entry.map(|e| e.to_string()), is_multi_group_choice: jv.is_multi_group_choice, } } } /// JSON validator type #[derive(Clone)] pub struct JSONValidator<'a> { cddl: &'a CDDL<'a>, json: Value, errors: Vec, cddl_location: String, json_location: String, // Occurrence indicator detected in current state of AST evaluation occurrence: Option, // Current group entry index detected in current state of AST evaluation group_entry_idx: Option, // JSON object value hoisted from previous state of AST evaluation object_value: Option, // Is member key detected in current state of AST evaluation is_member_key: bool, // Is a cut detected in current state of AST evaluation is_cut_present: bool, // Str value of cut detected in current state of AST evaluation cut_value: Option>, // Validate the generic rule given by str ident in current state of AST // evaluation eval_generic_rule: Option<&'a str>, // Aggregation of generic rules generic_rules: Vec>, // Control operator token detected in current state of AST evaluation ctrl: Option, // Is a group to choice enumeration detected in current state of AST // evaluation is_group_to_choice_enum: bool, // Are 2 or more type choices detected in current state of AST evaluation is_multi_type_choice: bool, // Are 2 or more group choices detected in current state of AST evaluation is_multi_group_choice: bool, // Type/group name entry detected in current state of AST evaluation. Used // only for providing more verbose error messages type_group_name_entry: Option<&'a str>, // Whether or not to advance to the next group entry if member key validation // fails as detected during the current state of AST evaluation advance_to_next_entry: bool, is_ctrl_map_equality: bool, entry_counts: Option>, // Collect map entry keys that have already been validated validated_keys: Option>, // Collect map entry values that have yet to be validated values_to_validate: Option>, // Collect valid array indices when entries are type choices valid_array_items: Option>, // Collect invalid array item errors where the key is the index of the invalid // array item array_errors: Option>>, is_colon_shortcut_present: bool, is_root: bool, is_multi_type_choice_type_rule_validating_array: bool, #[cfg(not(target_arch = "wasm32"))] #[cfg(feature = "additional-controls")] enabled_features: Option<&'a [&'a str]>, #[cfg(target_arch = "wasm32")] #[cfg(feature = "additional-controls")] enabled_features: Option>, #[cfg(feature = "additional-controls")] has_feature_errors: bool, #[cfg(feature = "additional-controls")] disabled_features: Option>, } #[derive(Clone, Debug)] struct GenericRule<'a> { name: &'a str, params: Vec<&'a str>, args: Vec>, } impl<'a> JSONValidator<'a> { #[cfg(not(target_arch = "wasm32"))] #[cfg(feature = "additional-controls")] /// New JSONValidation from CDDL AST and JSON value pub fn new(cddl: &'a CDDL<'a>, json: Value, enabled_features: Option<&'a [&'a str]>) -> Self { JSONValidator { cddl, json, errors: Vec::default(), cddl_location: String::new(), json_location: String::new(), occurrence: None, group_entry_idx: None, object_value: None, is_member_key: false, is_cut_present: false, cut_value: None, eval_generic_rule: None, generic_rules: Vec::new(), ctrl: None, is_group_to_choice_enum: false, is_multi_type_choice: false, is_multi_group_choice: false, type_group_name_entry: None, advance_to_next_entry: false, is_ctrl_map_equality: false, entry_counts: None, validated_keys: None, values_to_validate: None, valid_array_items: None, array_errors: None, is_colon_shortcut_present: false, is_root: false, is_multi_type_choice_type_rule_validating_array: false, enabled_features, has_feature_errors: false, disabled_features: None, } } #[cfg(not(target_arch = "wasm32"))] #[cfg(not(feature = "additional-controls"))] /// New JSONValidation from CDDL AST and JSON value pub fn new(cddl: &'a CDDL<'a>, json: Value) -> Self { JSONValidator { cddl, json, errors: Vec::default(), cddl_location: String::new(), json_location: String::new(), occurrence: None, group_entry_idx: None, object_value: None, is_member_key: false, is_cut_present: false, cut_value: None, eval_generic_rule: None, generic_rules: Vec::new(), ctrl: None, is_group_to_choice_enum: false, is_multi_type_choice: false, is_multi_group_choice: false, type_group_name_entry: None, advance_to_next_entry: false, is_ctrl_map_equality: false, entry_counts: None, validated_keys: None, values_to_validate: None, valid_array_items: None, array_errors: None, is_colon_shortcut_present: false, is_root: false, is_multi_type_choice_type_rule_validating_array: false, } } #[cfg(target_arch = "wasm32")] #[cfg(feature = "additional-controls")] /// New JSONValidation from CDDL AST and JSON value pub fn new(cddl: &'a CDDL<'a>, json: Value, enabled_features: Option>) -> Self { JSONValidator { cddl, json, errors: Vec::default(), cddl_location: String::new(), json_location: String::new(), occurrence: None, group_entry_idx: None, object_value: None, is_member_key: false, is_cut_present: false, cut_value: None, eval_generic_rule: None, generic_rules: Vec::new(), ctrl: None, is_group_to_choice_enum: false, is_multi_type_choice: false, is_multi_group_choice: false, type_group_name_entry: None, advance_to_next_entry: false, is_ctrl_map_equality: false, entry_counts: None, validated_keys: None, values_to_validate: None, valid_array_items: None, array_errors: None, is_colon_shortcut_present: false, is_root: false, is_multi_type_choice_type_rule_validating_array: false, enabled_features, has_feature_errors: false, disabled_features: None, } } #[cfg(target_arch = "wasm32")] #[cfg(not(feature = "additional-controls"))] /// New JSONValidation from CDDL AST and JSON value pub fn new(cddl: &'a CDDL<'a>, json: Value) -> Self { JSONValidator { cddl, json, errors: Vec::default(), cddl_location: String::new(), json_location: String::new(), occurrence: None, group_entry_idx: None, object_value: None, is_member_key: false, is_cut_present: false, cut_value: None, eval_generic_rule: None, generic_rules: Vec::new(), ctrl: None, is_group_to_choice_enum: false, is_multi_type_choice: false, is_multi_group_choice: false, type_group_name_entry: None, advance_to_next_entry: false, is_ctrl_map_equality: false, entry_counts: None, validated_keys: None, values_to_validate: None, valid_array_items: None, array_errors: None, is_colon_shortcut_present: false, is_root: false, is_multi_type_choice_type_rule_validating_array: false, } } fn validate_array_items(&mut self, token: &ArrayItemToken) -> visitor::Result { if let Value::Array(a) = &self.json { // Member keys are annotation only in an array context if self.is_member_key { return Ok(()); } match validate_array_occurrence( self.occurrence.as_ref(), self.entry_counts.as_ref().map(|ec| &ec[..]), a, ) { Ok((iter_items, allow_empty_array)) => { if iter_items { for (idx, v) in a.iter().enumerate() { if let Some(indices) = &self.valid_array_items { if self.is_multi_type_choice && indices.contains(&idx) { continue; } } #[cfg(all(feature = "additional-controls", target_arch = "wasm32"))] let mut jv = JSONValidator::new(self.cddl, v.clone(), self.enabled_features.clone()); #[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))] let mut jv = JSONValidator::new(self.cddl, v.clone(), self.enabled_features); #[cfg(not(feature = "additional-controls"))] let mut jv = JSONValidator::new(self.cddl, v.clone()); jv.generic_rules = self.generic_rules.clone(); jv.eval_generic_rule = self.eval_generic_rule; jv.is_multi_type_choice = self.is_multi_type_choice; jv.ctrl = self.ctrl; let _ = write!(jv.json_location, "{}/{}", self.json_location, idx); match token { ArrayItemToken::Value(value) => jv.visit_value(value)?, ArrayItemToken::Range(lower, upper, is_inclusive) => { jv.visit_range(lower, upper, *is_inclusive)? } ArrayItemToken::Group(group) => jv.visit_group(group)?, ArrayItemToken::Identifier(ident) => jv.visit_identifier(ident)?, _ => (), } if self.is_multi_type_choice && jv.errors.is_empty() { if let Some(indices) = &mut self.valid_array_items { indices.push(idx); } else { self.valid_array_items = Some(vec![idx]); } continue; } if let Some(errors) = &mut self.array_errors { if let Some(error) = errors.get_mut(&idx) { error.append(&mut jv.errors); } else { errors.insert(idx, jv.errors); } } else { let mut errors = HashMap::new(); errors.insert(idx, jv.errors); self.array_errors = Some(errors) } } } else if let Some(idx) = self.group_entry_idx { if let Some(v) = a.get(idx) { #[cfg(all(feature = "additional-controls", target_arch = "wasm32"))] let mut jv = JSONValidator::new(self.cddl, v.clone(), self.enabled_features.clone()); #[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))] let mut jv = JSONValidator::new(self.cddl, v.clone(), self.enabled_features); #[cfg(not(feature = "additional-controls"))] let mut jv = JSONValidator::new(self.cddl, v.clone()); jv.generic_rules = self.generic_rules.clone(); jv.eval_generic_rule = self.eval_generic_rule; jv.is_multi_type_choice = self.is_multi_type_choice; jv.ctrl = self.ctrl; let _ = write!(jv.json_location, "{}/{}", self.json_location, idx); match token { ArrayItemToken::Value(value) => jv.visit_value(value)?, ArrayItemToken::Range(lower, upper, is_inclusive) => { jv.visit_range(lower, upper, *is_inclusive)? } ArrayItemToken::Group(group) => jv.visit_group(group)?, ArrayItemToken::Identifier(ident) => jv.visit_identifier(ident)?, _ => (), } self.errors.append(&mut jv.errors); } else if !allow_empty_array { self.add_error(token.error_msg(Some(idx))); } } else if !self.is_multi_type_choice { self.add_error(format!("{}, got {}", token.error_msg(None), self.json)); } } Err(errors) => { for e in errors.into_iter() { self.add_error(e); } } } } Ok(()) } fn validate_object_value(&mut self, value: &token::Value<'a>) -> visitor::Result { if let Value::Object(o) = &self.json { // Bareword member keys are converted to text string values if let token::Value::TEXT(t) = value { if self.is_cut_present { self.cut_value = Some(t.clone()); } if *t == "any" { return Ok(()); } // Retrieve the value from key unless optional/zero or more, in which // case advance to next group entry #[cfg(feature = "ast-span")] if let Some(v) = o.get(t.as_ref()) { self .validated_keys .get_or_insert(vec![t.to_string()]) .push(t.to_string()); self.object_value = Some(v.clone()); let _ = write!(self.json_location, "/{}", t); return Ok(()); } else if let Some(Occur::Optional { .. }) | Some(Occur::ZeroOrMore { .. }) = &self.occurrence.take() { self.advance_to_next_entry = true; return Ok(()); } else if let Some(ControlOperator::NE) | Some(ControlOperator::DEFAULT) = &self.ctrl { return Ok(()); } else { self.add_error(format!("object missing key: \"{}\"", t)) } // Retrieve the value from key unless optional/zero or more, in which // case advance to next group entry #[cfg(not(feature = "ast-span"))] if let Some(v) = o.get(t.as_ref()) { self .validated_keys .get_or_insert(vec![t.to_string()]) .push(t.to_string()); self.object_value = Some(v.clone()); self.json_location.push_str(&format!("/{}", t)); return Ok(()); } else if let Some(Occur::Optional {}) | Some(Occur::ZeroOrMore {}) = &self.occurrence.take() { self.advance_to_next_entry = true; return Ok(()); } else if let Some(Token::NE) | Some(Token::DEFAULT) = &self.ctrl { return Ok(()); } else { self.add_error(format!("object missing key: \"{}\"", t)) } } else { self.add_error(format!( "CDDL member key must be string data type. got {}", value )) } } Ok(()) } } impl<'a, 'b> Validator<'a, 'b, Error> for JSONValidator<'a> { /// Validate fn validate(&mut self) -> std::result::Result<(), Error> { for r in self.cddl.rules.iter() { // First type rule is root if let Rule::Type { rule, .. } = r { if rule.generic_params.is_none() { self.is_root = true; self.visit_type_rule(rule)?; self.is_root = false; break; } } } if !self.errors.is_empty() { return Err(Error::Validation(self.errors.clone())); } Ok(()) } fn add_error(&mut self, reason: String) { self.errors.push(ValidationError { reason, cddl_location: self.cddl_location.clone(), json_location: self.json_location.clone(), is_multi_type_choice: self.is_multi_type_choice, is_multi_group_choice: self.is_multi_group_choice, is_group_to_choice_enum: self.is_group_to_choice_enum, type_group_name_entry: self.type_group_name_entry.map(|e| e.to_string()), }); } } impl<'a, 'b> Visitor<'a, 'b, Error> for JSONValidator<'a> { fn visit_type_rule(&mut self, tr: &TypeRule<'a>) -> visitor::Result { if let Some(gp) = &tr.generic_params { if let Some(gr) = self .generic_rules .iter_mut() .find(|r| r.name == tr.name.ident) { gr.params = gp.params.iter().map(|p| p.param.ident).collect(); } else { self.generic_rules.push(GenericRule { name: tr.name.ident, params: gp.params.iter().map(|p| p.param.ident).collect(), args: vec![], }); } } let type_choice_alternates = type_choice_alternates_from_ident(self.cddl, &tr.name); if !type_choice_alternates.is_empty() { self.is_multi_type_choice = true; if self.json.is_array() { self.is_multi_type_choice_type_rule_validating_array = true; } } let error_count = self.errors.len(); for t in type_choice_alternates { let cur_errors = self.errors.len(); self.visit_type(t)?; if self.errors.len() == cur_errors { for _ in 0..self.errors.len() - error_count { self.errors.pop(); } return Ok(()); } } if tr.value.type_choices.len() > 1 && self.json.is_array() { self.is_multi_type_choice_type_rule_validating_array = true; } self.visit_type(&tr.value) } fn visit_group_rule(&mut self, gr: &GroupRule<'a>) -> visitor::Result { if let Some(gp) = &gr.generic_params { if let Some(gr) = self .generic_rules .iter_mut() .find(|r| r.name == gr.name.ident) { gr.params = gp.params.iter().map(|p| p.param.ident).collect(); } else { self.generic_rules.push(GenericRule { name: gr.name.ident, params: gp.params.iter().map(|p| p.param.ident).collect(), args: vec![], }); } } let group_choice_alternates = group_choice_alternates_from_ident(self.cddl, &gr.name); if !group_choice_alternates.is_empty() { self.is_multi_group_choice = true; } let error_count = self.errors.len(); for ge in group_choice_alternates { let cur_errors = self.errors.len(); self.visit_group_entry(ge)?; if self.errors.len() == cur_errors { for _ in 0..self.errors.len() - error_count { self.errors.pop(); } return Ok(()); } } self.visit_group_entry(&gr.entry) } fn visit_type(&mut self, t: &Type<'a>) -> visitor::Result { if t.type_choices.len() > 1 { self.is_multi_type_choice = true; } let initial_error_count = self.errors.len(); for type_choice in t.type_choices.iter() { // If validating an array whose elements are type choices (i.e. [ 1* tstr // / integer ]), collect all errors and filter after the fact if matches!(self.json, Value::Array(_)) && !self.is_multi_type_choice_type_rule_validating_array { let error_count = self.errors.len(); self.visit_type_choice(type_choice)?; #[cfg(feature = "additional-controls")] if self.errors.len() == error_count && !self.has_feature_errors && self.disabled_features.is_none() { // Disregard invalid type choice validation errors if one of the // choices validates successfully let type_choice_error_count = self.errors.len() - initial_error_count; if type_choice_error_count > 0 { for _ in 0..type_choice_error_count { self.errors.pop(); } } } #[cfg(not(feature = "additional-controls"))] if self.errors.len() == error_count { // Disregard invalid type choice validation errors if one of the // choices validates successfully let type_choice_error_count = self.errors.len() - initial_error_count; if type_choice_error_count > 0 { for _ in 0..type_choice_error_count { self.errors.pop(); } } } continue; } let error_count = self.errors.len(); self.visit_type_choice(type_choice)?; #[cfg(feature = "additional-controls")] if self.errors.len() == error_count && !self.has_feature_errors && self.disabled_features.is_none() { // Disregard invalid type choice validation errors if one of the // choices validates successfully let type_choice_error_count = self.errors.len() - initial_error_count; if type_choice_error_count > 0 { for _ in 0..type_choice_error_count { self.errors.pop(); } } return Ok(()); } #[cfg(not(feature = "additional-controls"))] if self.errors.len() == error_count { // Disregard invalid type choice validation errors if one of the // choices validates successfully let type_choice_error_count = self.errors.len() - initial_error_count; if type_choice_error_count > 0 { for _ in 0..type_choice_error_count { self.errors.pop(); } } return Ok(()); } } Ok(()) } fn visit_group(&mut self, g: &Group<'a>) -> visitor::Result { if g.group_choices.len() > 1 { self.is_multi_group_choice = true; } // Map equality/inequality validation if self.is_ctrl_map_equality { if let Some(t) = &self.ctrl { if let Value::Object(o) = &self.json { let entry_counts = entry_counts_from_group(self.cddl, g); let len = o.len(); if let ControlOperator::EQ = t { if !validate_entry_count(&entry_counts, len) { for ec in entry_counts.iter() { if let Some(occur) = &ec.entry_occurrence { self.add_error(format!( "map equality error. expected object with number of entries per occurrence {}", occur, )); } else { self.add_error(format!( "map equality error, expected object with length {}, got {}", ec.count, len )); } } return Ok(()); } } else if let ControlOperator::NE | ControlOperator::DEFAULT = t { if !validate_entry_count(&entry_counts, len) { for ec in entry_counts.iter() { if let Some(occur) = &ec.entry_occurrence { self.add_error(format!( "map inequality error. expected object with number of entries not per occurrence {}", occur, )); } else { self.add_error(format!( "map inequality error, expected object not with length {}, got {}", ec.count, len )); } } return Ok(()); } } } } } self.is_ctrl_map_equality = false; let initial_error_count = self.errors.len(); for group_choice in g.group_choices.iter() { let error_count = self.errors.len(); self.visit_group_choice(group_choice)?; if self.errors.len() == error_count { // Disregard invalid group choice validation errors if one of the // choices validates successfully let group_choice_error_count = self.errors.len() - initial_error_count; if group_choice_error_count > 0 { for _ in 0..group_choice_error_count { self.errors.pop(); } } return Ok(()); } } Ok(()) } fn visit_group_choice(&mut self, gc: &GroupChoice<'a>) -> visitor::Result { if self.is_group_to_choice_enum { let initial_error_count = self.errors.len(); for tc in type_choices_from_group_choice(self.cddl, gc).iter() { let error_count = self.errors.len(); self.visit_type_choice(tc)?; if self.errors.len() == error_count { let type_choice_error_count = self.errors.len() - initial_error_count; if type_choice_error_count > 0 { for _ in 0..type_choice_error_count { self.errors.pop(); } } return Ok(()); } } return Ok(()); } for (idx, ge) in gc.group_entries.iter().enumerate() { if let Some(current_index) = self.group_entry_idx.as_mut() { if idx != 0 { *current_index += 1; } } else { self.group_entry_idx = Some(idx); } self.visit_group_entry(&ge.0)?; } Ok(()) } fn visit_range( &mut self, lower: &Type2, upper: &Type2, is_inclusive: bool, ) -> visitor::Result { if matches!(&self.json, Value::Array(_)) { return self.validate_array_items(&ArrayItemToken::Range(lower, upper, is_inclusive)); } match lower { Type2::IntValue { value: l, .. } => match upper { Type2::IntValue { value: u, .. } => { let error_str = if is_inclusive { format!( "expected integer to be in range {} <= value <= {}, got {}", l, u, self.json ) } else { format!( "expected integer to be in range {} < value < {}, got {}", l, u, self.json ) }; match &self.json { Value::Number(n) => { if let Some(i) = n.as_i64() { if is_inclusive { if i < *l as i64 || i > *u as i64 { self.add_error(error_str); } else { return Ok(()); } } else if i <= *l as i64 || i >= *u as i64 { self.add_error(error_str); return Ok(()); } else { return Ok(()); } } else { self.add_error(error_str); return Ok(()); } } _ => { self.add_error(error_str); return Ok(()); } } } Type2::UintValue { value: u, .. } => { let error_str = if is_inclusive { format!( "expected integer to be in range {} <= value <= {}, got {}", l, u, self.json ) } else { format!( "expected integer to be in range {} < value < {}, got {}", l, u, self.json ) }; match &self.json { Value::Number(n) => { if let Some(i) = n.as_i64() { if is_inclusive { if i < *l as i64 || i > *u as i64 { self.add_error(error_str); } else { return Ok(()); } } else if i <= *l as i64 || i >= *u as i64 { self.add_error(error_str); return Ok(()); } else { return Ok(()); } } else { self.add_error(error_str); return Ok(()); } } _ => { self.add_error(error_str); return Ok(()); } } } _ => { self.add_error(format!( "invalid cddl range. upper value must be an integer type. got {}", upper )); return Ok(()); } }, Type2::UintValue { value: l, .. } => match upper { Type2::UintValue { value: u, .. } => { let error_str = if is_inclusive { format!( "expected uint to be in range {} <= value <= {}, got {}", l, u, self.json ) } else { format!( "expected uint to be in range {} < value < {}, got {}", l, u, self.json ) }; match &self.json { Value::Number(n) => { if let Some(i) = n.as_u64() { if is_inclusive { if i < *l as u64 || i > *u as u64 { self.add_error(error_str); } else { return Ok(()); } } else if i <= *l as u64 || i >= *u as u64 { self.add_error(error_str); return Ok(()); } else { return Ok(()); } } else { self.add_error(error_str); return Ok(()); } } Value::String(s) => match self.ctrl { Some(ControlOperator::SIZE) => { let len = s.len(); let s = s.clone(); if is_inclusive { if s.len() < *l || s.len() > *u { self.add_error(format!( "expected \"{}\" string length to be in the range {} <= value <= {}, got {}", s, l, u, len )); } return Ok(()); } else if s.len() <= *l || s.len() >= *u { self.add_error(format!( "expected \"{}\" string length to be in the range {} < value < {}, got {}", s, l, u, len )); return Ok(()); } } _ => { self.add_error("string value cannot be validated against a range without the .size control operator".to_string()); return Ok(()); } }, _ => { self.add_error(error_str); return Ok(()); } } } _ => { self.add_error(format!( "invalid cddl range. upper value must be a uint type. got {}", upper )); return Ok(()); } }, Type2::FloatValue { value: l, .. } => match upper { Type2::FloatValue { value: u, .. } => { let error_str = if is_inclusive { format!( "expected float to be in range {} <= value <= {}, got {}", l, u, self.json ) } else { format!( "expected float to be in range {} < value < {}, got {}", l, u, self.json ) }; match &self.json { Value::Number(n) => { if let Some(f) = n.as_f64() { if is_inclusive { if f < *l || f > *u { self.add_error(error_str); } else { return Ok(()); } } else if f <= *l || f >= *u { self.add_error(error_str); return Ok(()); } else { return Ok(()); } } else { self.add_error(error_str); return Ok(()); } } _ => { self.add_error(error_str); return Ok(()); } } } _ => { self.add_error(format!( "invalid cddl range. upper value must be a float type. got {}", upper )); return Ok(()); } }, _ => { self.add_error( "invalid cddl range. upper and lower values must be either integers or floats" .to_string(), ); return Ok(()); } } Ok(()) } fn visit_control_operator( &mut self, target: &Type2<'a>, ctrl: ControlOperator, controller: &Type2<'a>, ) -> visitor::Result { if let Type2::Typename { ident: target_ident, .. } = target { if let Type2::Typename { ident: controller_ident, .. } = controller { if let Some(name) = self.eval_generic_rule { if let Some(gr) = self .generic_rules .iter() .cloned() .find(|gr| gr.name == name) { for (idx, gp) in gr.params.iter().enumerate() { if let Some(arg) = gr.args.get(idx) { if *gp == target_ident.ident { let t2 = Type2::from(arg.clone()); if *gp == controller_ident.ident { return self.visit_control_operator(&t2, ctrl, &t2); } return self.visit_control_operator(&arg.type2, ctrl, controller); } } } } } } if let Some(name) = self.eval_generic_rule { if let Some(gr) = self .generic_rules .iter() .cloned() .find(|gr| gr.name == name) { for (idx, gp) in gr.params.iter().enumerate() { if let Some(arg) = gr.args.get(idx) { if *gp == target_ident.ident { let t2 = Type2::from(arg.clone()); return self.visit_control_operator(&t2, ctrl, controller); } } } } } } match ctrl { ControlOperator::EQ => match target { Type2::Typename { ident, .. } => { if is_ident_string_data_type(self.cddl, ident) || is_ident_numeric_data_type(self.cddl, ident) { return self.visit_type2(controller); } } Type2::Array { group, .. } => { if let Value::Array(_) = &self.json { self.entry_counts = Some(entry_counts_from_group(self.cddl, group)); self.visit_type2(controller)?; self.entry_counts = None; return Ok(()); } } Type2::Map { .. } => { if let Value::Object(_) = &self.json { self.ctrl = Some(ctrl); self.is_ctrl_map_equality = true; self.visit_type2(controller)?; self.ctrl = None; self.is_ctrl_map_equality = false; return Ok(()); } } _ => self.add_error(format!( "target for .eq operator must be a string, numerical, array or map data type, got {}", target )), }, ControlOperator::NE => match target { Type2::Typename { ident, .. } => { if is_ident_string_data_type(self.cddl, ident) || is_ident_numeric_data_type(self.cddl, ident) { self.ctrl = Some(ctrl); self.visit_type2(controller)?; self.ctrl = None; return Ok(()); } } Type2::Array { .. } => { if let Value::Array(_) = &self.json { self.ctrl = Some(ctrl); self.visit_type2(controller)?; self.ctrl = None; return Ok(()); } } Type2::Map { .. } => { if let Value::Object(_) = &self.json { self.ctrl = Some(ctrl); self.is_ctrl_map_equality = true; self.visit_type2(controller)?; self.ctrl = None; self.is_ctrl_map_equality = false; return Ok(()); } } _ => self.add_error(format!( "target for .ne operator must be a string, numerical, array or map data type, got {}", target )), }, ControlOperator::LT | ControlOperator::GT | ControlOperator::GE | ControlOperator::LE => { match target { Type2::Typename { ident, .. } if is_ident_numeric_data_type(self.cddl, ident) => { self.ctrl = Some(ctrl); self.visit_type2(controller)?; self.ctrl = None; } _ => { self.add_error(format!( "target for .lt, .gt, .ge or .le operator must be a numerical data type, got {}", target )); } } } ControlOperator::SIZE => match target { Type2::Typename { ident, .. } if is_ident_string_data_type(self.cddl, ident) || is_ident_uint_data_type(self.cddl, ident) => { self.ctrl = Some(ctrl); self.visit_type2(controller)?; self.ctrl = None; } _ => { self.add_error(format!( "target for .size must a string or uint data type, got {}", target )); } }, ControlOperator::AND => { self.ctrl = Some(ctrl); self.visit_type2(target)?; self.visit_type2(controller)?; self.ctrl = None; } ControlOperator::WITHIN => { self.ctrl = Some(ctrl); let error_count = self.errors.len(); self.visit_type2(target)?; let no_errors = self.errors.len() == error_count; self.visit_type2(controller)?; if no_errors && self.errors.len() > error_count { for _ in 0..self.errors.len() - error_count { self.errors.pop(); } self.add_error(format!( "expected type {} .within type {}, got {}", target, controller, self.json, )); } self.ctrl = None; } ControlOperator::DEFAULT => { self.ctrl = Some(ctrl); let error_count = self.errors.len(); self.visit_type2(target)?; if self.errors.len() != error_count { #[cfg(feature = "ast-span")] if let Some(Occur::Optional { .. }) = self.occurrence.take() { self.add_error(format!( "expected default value {}, got {}", controller, self.json )); } #[cfg(not(feature = "ast-span"))] if let Some(Occur::Optional {}) = self.occurrence.take() { self.add_error(format!( "expected default value {}, got {}", controller, self.json )); } } self.ctrl = None; } ControlOperator::REGEXP | ControlOperator::PCRE => { self.ctrl = Some(ctrl); match target { Type2::Typename { ident, .. } if is_ident_string_data_type(self.cddl, ident) => { match self.json { Value::String(_) | Value::Array(_) => self.visit_type2(controller)?, _ => self.add_error(format!( ".regexp/.pcre control can only be matched against JSON string, got {}", self.json )), } } _ => self.add_error(format!( ".regexp/.pcre control can only be matched against string data type, got {}", target )), } self.ctrl = None; } #[cfg(feature = "additional-controls")] ControlOperator::CAT => { self.ctrl = Some(ctrl); match cat_operation(self.cddl, target, controller, false) { Ok(values) => { let error_count = self.errors.len(); for v in values.iter() { let cur_errors = self.errors.len(); self.visit_type2(v)?; if self.errors.len() == cur_errors { for _ in 0..self.errors.len() - error_count { self.errors.pop(); } break; } } } Err(e) => self.add_error(e), } self.ctrl = None; } #[cfg(feature = "additional-controls")] ControlOperator::DET => { self.ctrl = Some(ctrl); match cat_operation(self.cddl, target, controller, true) { Ok(values) => { let error_count = self.errors.len(); for v in values.iter() { let cur_errors = self.errors.len(); self.visit_type2(v)?; if self.errors.len() == cur_errors { for _ in 0..self.errors.len() - error_count { self.errors.pop(); } break; } } } Err(e) => self.add_error(e), } self.ctrl = None; } #[cfg(feature = "additional-controls")] ControlOperator::PLUS => { self.ctrl = Some(ctrl); match plus_operation(self.cddl, target, controller) { Ok(values) => { let error_count = self.errors.len(); for v in values.iter() { let cur_errors = self.errors.len(); self.visit_type2(v)?; if self.errors.len() == cur_errors { for _ in 0..self.errors.len() - error_count { self.errors.pop(); } break; } } } Err(e) => self.add_error(e), } self.ctrl = None; } #[cfg(feature = "additional-controls")] ControlOperator::ABNF => { self.ctrl = Some(ctrl); match target { Type2::Typename { ident, .. } if is_ident_string_data_type(self.cddl, ident) => { match self.json { Value::String(_) | Value::Array(_) => { if let Type2::ParenthesizedType { pt, .. } = controller { match abnf_from_complex_controller(self.cddl, pt) { Ok(values) => { let error_count = self.errors.len(); for v in values.iter() { let cur_errors = self.errors.len(); self.visit_type2(v)?; if self.errors.len() == cur_errors { for _ in 0..self.errors.len() - error_count { self.errors.pop(); } break; } } } Err(e) => self.add_error(e), } } else { self.visit_type2(controller)? } } _ => self.add_error(format!( ".abnf control can only be matched against a JSON string, got {}", self.json, )), } } _ => self.add_error(format!( ".abnf can only be matched against string data type, got {}", target, )), } self.ctrl = None; } #[cfg(feature = "additional-controls")] #[cfg(not(target_arch = "wasm32"))] ControlOperator::FEATURE => { self.ctrl = Some(ctrl); if let Some(ef) = self.enabled_features { let tv = text_value_from_type2(self.cddl, controller); if let Some(Type2::TextValue { value, .. }) = tv { if ef.contains(&&**value) { let err_count = self.errors.len(); self.visit_type2(target)?; if self.errors.len() > err_count { self.has_feature_errors = true; } self.ctrl = None; } else { self .disabled_features .get_or_insert(vec![value.to_string()]) .push(value.to_string()); } } else if let Some(Type2::UTF8ByteString { value, .. }) = tv { let value = std::str::from_utf8(value).map_err(Error::UTF8Parsing)?; if ef.contains(&value) { let err_count = self.errors.len(); self.visit_type2(target)?; if self.errors.len() > err_count { self.has_feature_errors = true; } self.ctrl = None; } else { self .disabled_features .get_or_insert(vec![value.to_string()]) .push(value.to_string()); } } } self.ctrl = None; } #[cfg(feature = "additional-controls")] #[cfg(target_arch = "wasm32")] ControlOperator::FEATURE => { self.ctrl = Some(ctrl); if let Some(ef) = &self.enabled_features { let tv = text_value_from_type2(self.cddl, controller); if let Some(Type2::TextValue { value, .. }) = tv { if ef.contains(&JsValue::from(value.as_ref())) { let err_count = self.errors.len(); self.visit_type2(target)?; if self.errors.len() > err_count { self.has_feature_errors = true; } self.ctrl = None; } else { self .disabled_features .get_or_insert(vec![value.to_string()]) .push(value.to_string()); } } else if let Some(Type2::UTF8ByteString { value, .. }) = tv { let value = std::str::from_utf8(value).map_err(Error::UTF8Parsing)?; if ef.contains(&JsValue::from(value)) { let err_count = self.errors.len(); self.visit_type2(target)?; if self.errors.len() > err_count { self.has_feature_errors = true; } self.ctrl = None; } else { self .disabled_features .get_or_insert(vec![value.to_string()]) .push(value.to_string()); } } } self.ctrl = None; } _ => { self.add_error(format!("unsupported control operator {}", ctrl)); } } Ok(()) } fn visit_type2(&mut self, t2: &Type2<'a>) -> visitor::Result { match t2 { Type2::TextValue { value, .. } => self.visit_value(&token::Value::TEXT(value.clone())), Type2::Map { group, .. } => match &self.json { Value::Object(o) => { #[allow(clippy::needless_collect)] let o = o.keys().cloned().collect::>(); self.visit_group(group)?; if self.values_to_validate.is_none() { for k in o.into_iter() { if let Some(keys) = &self.validated_keys { if !keys.contains(&k) { self.add_error(format!("unexpected key {:?}", k)); } } } } self.is_cut_present = false; self.cut_value = None; Ok(()) } Value::Array(_) => self.validate_array_items(&ArrayItemToken::Group(group)), _ => { self.add_error(format!("expected map object {}, got {}", t2, self.json)); Ok(()) } }, Type2::Array { group, .. } => match &self.json { Value::Array(a) => { if group.group_choices.len() == 1 && group.group_choices[0].group_entries.is_empty() && !a.is_empty() && !matches!( self.ctrl, Some(ControlOperator::NE) | Some(ControlOperator::DEFAULT) ) { self.add_error(format!("expected empty array, got {}", self.json)); return Ok(()); } self.entry_counts = Some(entry_counts_from_group(self.cddl, group)); self.visit_group(group)?; self.entry_counts = None; if let Some(errors) = &mut self.array_errors { if let Some(indices) = &self.valid_array_items { for idx in indices.iter() { errors.remove(idx); } } for error in errors.values_mut() { self.errors.append(error); } } self.valid_array_items = None; self.array_errors = None; Ok(()) } _ => { self.add_error(format!("expected array type, got {}", self.json)); Ok(()) } }, Type2::ChoiceFromGroup { ident, generic_args, .. } => { if let Some(ga) = generic_args { if let Some(rule) = rule_from_ident(self.cddl, ident) { if let Some(gr) = self .generic_rules .iter_mut() .find(|gr| gr.name == ident.ident) { for arg in ga.args.iter() { gr.args.push((*arg.arg).clone()); } } else if let Some(params) = generic_params_from_rule(rule) { self.generic_rules.push(GenericRule { name: ident.ident, params, args: ga.args.iter().cloned().map(|arg| *arg.arg).collect(), }); } #[cfg(all(feature = "additional-controls", target_arch = "wasm32"))] let mut jv = JSONValidator::new(self.cddl, self.json.clone(), self.enabled_features.clone()); #[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))] let mut jv = JSONValidator::new(self.cddl, self.json.clone(), self.enabled_features); #[cfg(not(feature = "additional-controls"))] let mut jv = JSONValidator::new(self.cddl, self.json.clone()); jv.generic_rules = self.generic_rules.clone(); jv.eval_generic_rule = Some(ident.ident); jv.is_group_to_choice_enum = true; jv.is_multi_type_choice = self.is_multi_type_choice; jv.visit_rule(rule)?; self.errors.append(&mut jv.errors); return Ok(()); } } if group_rule_from_ident(self.cddl, ident).is_none() { self.add_error(format!( "rule {} must be a group rule to turn it into a choice", ident )); return Ok(()); } self.is_group_to_choice_enum = true; self.visit_identifier(ident)?; self.is_group_to_choice_enum = false; Ok(()) } Type2::ChoiceFromInlineGroup { group, .. } => { self.is_group_to_choice_enum = true; self.visit_group(group)?; self.is_group_to_choice_enum = false; Ok(()) } Type2::Typename { ident, generic_args, .. } => { if let Some(ga) = generic_args { if let Some(rule) = rule_from_ident(self.cddl, ident) { if let Some(gr) = self .generic_rules .iter_mut() .find(|gr| gr.name == ident.ident) { for arg in ga.args.iter() { gr.args.push((*arg.arg).clone()); } } else if let Some(params) = generic_params_from_rule(rule) { self.generic_rules.push(GenericRule { name: ident.ident, params, args: ga.args.iter().cloned().map(|arg| *arg.arg).collect(), }); } #[cfg(all(feature = "additional-controls", target_arch = "wasm32"))] let mut jv = JSONValidator::new(self.cddl, self.json.clone(), self.enabled_features.clone()); #[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))] let mut jv = JSONValidator::new(self.cddl, self.json.clone(), self.enabled_features); #[cfg(not(feature = "additional-controls"))] let mut jv = JSONValidator::new(self.cddl, self.json.clone()); jv.generic_rules = self.generic_rules.clone(); jv.eval_generic_rule = Some(ident.ident); jv.is_multi_type_choice = self.is_multi_type_choice; jv.visit_rule(rule)?; self.errors.append(&mut jv.errors); return Ok(()); } } let type_choice_alternates = type_choice_alternates_from_ident(self.cddl, ident); if !type_choice_alternates.is_empty() { self.is_multi_type_choice = true; } let error_count = self.errors.len(); for t in type_choice_alternates { let cur_errors = self.errors.len(); self.visit_type(t)?; if self.errors.len() == cur_errors { for _ in 0..self.errors.len() - error_count { self.errors.pop(); } return Ok(()); } } self.visit_identifier(ident) } Type2::IntValue { value, .. } => self.visit_value(&token::Value::INT(*value)), Type2::UintValue { value, .. } => self.visit_value(&token::Value::UINT(*value)), Type2::FloatValue { value, .. } => self.visit_value(&token::Value::FLOAT(*value)), Type2::ParenthesizedType { pt, .. } => self.visit_type(pt), Type2::Unwrap { ident, generic_args, .. } => { // Per // https://github.com/w3c/did-spec-registries/pull/138#issuecomment-719739215, // strip tag and validate underlying type if let Some(Type2::TaggedData { t, .. }) = tag_from_token(&lookup_ident(ident.ident)) { return self.visit_type(&t); } if let Some(ga) = generic_args { if let Some(rule) = unwrap_rule_from_ident(self.cddl, ident) { if let Some(gr) = self .generic_rules .iter_mut() .find(|gr| gr.name == ident.ident) { for arg in ga.args.iter() { gr.args.push((*arg.arg).clone()); } } else if let Some(params) = generic_params_from_rule(rule) { self.generic_rules.push(GenericRule { name: ident.ident, params, args: ga.args.iter().cloned().map(|arg| *arg.arg).collect(), }); } #[cfg(all(feature = "additional-controls", target_arch = "wasm32"))] let mut jv = JSONValidator::new(self.cddl, self.json.clone(), self.enabled_features.clone()); #[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))] let mut jv = JSONValidator::new(self.cddl, self.json.clone(), self.enabled_features); #[cfg(not(feature = "additional-controls"))] let mut jv = JSONValidator::new(self.cddl, self.json.clone()); jv.generic_rules = self.generic_rules.clone(); jv.eval_generic_rule = Some(ident.ident); jv.is_multi_type_choice = self.is_multi_type_choice; jv.visit_rule(rule)?; self.errors.append(&mut jv.errors); return Ok(()); } } if let Some(rule) = unwrap_rule_from_ident(self.cddl, ident) { return self.visit_rule(rule); } self.add_error(format!( "cannot unwrap identifier {}, rule not found", ident )); Ok(()) } #[cfg(feature = "ast-span")] Type2::Any { .. } => Ok(()), #[cfg(not(feature = "ast-span"))] Type2::Any {} => Ok(()), _ => { self.add_error(format!( "unsupported data type for validating JSON, got {}", t2 )); Ok(()) } } } fn visit_identifier(&mut self, ident: &Identifier<'a>) -> visitor::Result { if let Some(name) = self.eval_generic_rule { if let Some(gr) = self .generic_rules .iter() .cloned() .find(|gr| gr.name == name) { for (idx, gp) in gr.params.iter().enumerate() { if *gp == ident.ident { if let Some(arg) = gr.args.get(idx) { return self.visit_type1(arg); } } } } } // self.is_colon_shortcut_present is only true when the ident is part of a // member key if !self.is_colon_shortcut_present { if let Some(r) = rule_from_ident(self.cddl, ident) { return self.visit_rule(r); } } if is_ident_any_type(self.cddl, ident) { return Ok(()); } match &self.json { Value::Null if is_ident_null_data_type(self.cddl, ident) => Ok(()), Value::Bool(b) => { if is_ident_bool_data_type(self.cddl, ident) { return Ok(()); } if ident_matches_bool_value(self.cddl, ident, *b) { return Ok(()); } self.add_error(format!("expected type {}, got {}", ident, self.json)); Ok(()) } Value::Number(n) => { if is_ident_uint_data_type(self.cddl, ident) && n.is_u64() { return Ok(()); } else if is_ident_nint_data_type(self.cddl, ident) { if let Some(n) = n.as_i64() { if n.is_negative() { return Ok(()); } } } else if is_ident_time_data_type(self.cddl, ident) { if let Some(n) = n.as_i64() { if let chrono::LocalResult::None = Utc.timestamp_millis_opt(n * 1000) { self.add_error(format!( "expected time data type, invalid UNIX timestamp {}", n, )); } return Ok(()); } else if let Some(n) = n.as_f64() { // truncates fractional milliseconds when validating if let chrono::LocalResult::None = Utc.timestamp_millis_opt((n * 1000f64) as i64) { self.add_error(format!( "expected time data type, invalid UNIX timestamp {}", n, )); } } } else if (is_ident_integer_data_type(self.cddl, ident) && n.is_i64()) || (is_ident_float_data_type(self.cddl, ident) && n.is_f64()) { return Ok(()); } self.add_error(format!("expected type {}, got {}", ident, self.json)); Ok(()) } Value::String(s) => { if is_ident_uri_data_type(self.cddl, ident) { if let Err(e) = uriparse::URI::try_from(&**s) { self.add_error(format!("expected URI data type, decoding error: {}", e)); } } else if is_ident_b64url_data_type(self.cddl, ident) { if let Err(e) = base64_url::decode(s) { self.add_error(format!( "expected base64 URL data type, decoding error: {}", e )); } } else if is_ident_tdate_data_type(self.cddl, ident) { if let Err(e) = chrono::DateTime::parse_from_rfc3339(s) { self.add_error(format!("expected tdate data type, decoding error: {}", e)); } } else if is_ident_string_data_type(self.cddl, ident) { return Ok(()); } else { self.add_error(format!("expected type {}, got {}", ident, self.json)); } Ok(()) } Value::Array(_) => self.validate_array_items(&ArrayItemToken::Identifier(ident)), Value::Object(o) => match &self.occurrence { #[cfg(feature = "ast-span")] Some(Occur::Optional { .. }) | None => { if token::lookup_ident(ident.ident) .in_standard_prelude() .is_some() { self.add_error(format!( "expected object value of type {}, got object", ident.ident )); return Ok(()); } self.visit_value(&token::Value::TEXT(ident.ident.into())) } #[cfg(not(feature = "ast-span"))] Some(Occur::Optional {}) | None => { if token::lookup_ident(ident.ident) .in_standard_prelude() .is_some() { self.add_error(format!( "expected object value of type {}, got object", ident.ident )); return Ok(()); } self.visit_value(&token::Value::TEXT(ident.ident.into())) } Some(occur) => { if is_ident_string_data_type(self.cddl, ident) { let values_to_validate = o .iter() .filter_map(|(k, v)| match &self.validated_keys { Some(keys) if !keys.contains(k) => Some(v.clone()), Some(_) => None, None => Some(v.clone()), }) .collect::>(); self.values_to_validate = Some(values_to_validate); } #[cfg(feature = "ast-span")] if let Occur::ZeroOrMore { .. } | Occur::OneOrMore { .. } = occur { if let Occur::OneOrMore { .. } = occur { if o.is_empty() { self.add_error(format!( "object cannot be empty, one or more entries with key type {} required", ident )); return Ok(()); } } } else if let Occur::Exact { lower, upper, .. } = occur { if let Some(values_to_validate) = &self.values_to_validate { if let Some(lower) = lower { if let Some(upper) = upper { if values_to_validate.len() < *lower || values_to_validate.len() > *upper { if lower == upper { self.add_error(format!( "object must contain exactly {} entries of key of type {}", lower, ident, )); } else { self.add_error(format!( "object must contain between {} and {} entries of key of type {}", lower, upper, ident, )); } return Ok(()); } } if values_to_validate.len() < *lower { self.add_error(format!( "object must contain at least {} entries of key of type {}", lower, ident, )); return Ok(()); } } if let Some(upper) = upper { if values_to_validate.len() > *upper { self.add_error(format!( "object must contain no more than {} entries of key of type {}", upper, ident, )); return Ok(()); } } return Ok(()); } } #[cfg(not(feature = "ast-span"))] if let Occur::ZeroOrMore {} | Occur::OneOrMore {} = occur { if let Occur::OneOrMore {} = occur { if o.is_empty() { self.add_error(format!( "object cannot be empty, one or more entries with key type {} required", ident )); return Ok(()); } } } else if let Occur::Exact { lower, upper } = occur { if let Some(values_to_validate) = &self.values_to_validate { if let Some(lower) = lower { if let Some(upper) = upper { if values_to_validate.len() < *lower || values_to_validate.len() > *upper { if lower == upper { self.add_error(format!( "object must contain exactly {} entries of key of type {}", lower, ident, )); } else { self.add_error(format!( "object must contain between {} and {} entries of key of type {}", lower, upper, ident, )); } return Ok(()); } } if values_to_validate.len() < *lower { self.add_error(format!( "object must contain at least {} entries of key of type {}", lower, ident, )); return Ok(()); } } if let Some(upper) = upper { if values_to_validate.len() > *upper { self.add_error(format!( "object must contain no more than {} entries of key of type {}", upper, ident, )); return Ok(()); } } return Ok(()); } } Ok(()) } }, _ => { if let Some(cut_value) = self.cut_value.take() { self.add_error(format!( "cut present for member key {}. expected type {}, got {}", cut_value, ident, self.json )); } else { self.add_error(format!("expected type {}, got {}", ident, self.json)); } Ok(()) } } } fn visit_value_member_key_entry( &mut self, entry: &ValueMemberKeyEntry<'a>, ) -> visitor::Result { if let Some(occur) = &entry.occur { self.visit_occurrence(occur)?; } let current_location = self.json_location.clone(); if let Some(mk) = &entry.member_key { let error_count = self.errors.len(); self.is_member_key = true; self.visit_memberkey(mk)?; self.is_member_key = false; // Move to next entry if member key validation fails if self.errors.len() != error_count { self.advance_to_next_entry = true; return Ok(()); } } if let Some(values) = &self.values_to_validate { for v in values.iter() { #[cfg(all(feature = "additional-controls", target_arch = "wasm32"))] let mut jv = JSONValidator::new(self.cddl, v.clone(), self.enabled_features.clone()); #[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))] let mut jv = JSONValidator::new(self.cddl, v.clone(), self.enabled_features); #[cfg(not(feature = "additional-controls"))] let mut jv = JSONValidator::new(self.cddl, v.clone()); jv.generic_rules = self.generic_rules.clone(); jv.eval_generic_rule = self.eval_generic_rule; jv.is_multi_type_choice = self.is_multi_type_choice; jv.is_multi_group_choice = self.is_multi_group_choice; jv.json_location.push_str(&self.json_location); jv.type_group_name_entry = self.type_group_name_entry; jv.visit_type(&entry.entry_type)?; self.json_location = current_location.clone(); self.errors.append(&mut jv.errors); if entry.occur.is_some() { self.occurrence = None; } } return Ok(()); } if let Some(v) = self.object_value.take() { #[cfg(all(feature = "additional-controls", target_arch = "wasm32"))] let mut jv = JSONValidator::new(self.cddl, v, self.enabled_features.clone()); #[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))] let mut jv = JSONValidator::new(self.cddl, v, self.enabled_features); #[cfg(not(feature = "additional-controls"))] let mut jv = JSONValidator::new(self.cddl, v); jv.generic_rules = self.generic_rules.clone(); jv.eval_generic_rule = self.eval_generic_rule; jv.is_multi_type_choice = self.is_multi_type_choice; jv.is_multi_group_choice = self.is_multi_group_choice; jv.json_location.push_str(&self.json_location); jv.type_group_name_entry = self.type_group_name_entry; jv.visit_type(&entry.entry_type)?; self.json_location = current_location; self.errors.append(&mut jv.errors); if entry.occur.is_some() { self.occurrence = None; } Ok(()) } else if !self.advance_to_next_entry { self.visit_type(&entry.entry_type) } else { Ok(()) } } fn visit_type_groupname_entry( &mut self, entry: &TypeGroupnameEntry<'a>, ) -> visitor::Result { self.type_group_name_entry = Some(entry.name.ident); if let Some(ga) = &entry.generic_args { if let Some(rule) = rule_from_ident(self.cddl, &entry.name) { if let Some(gr) = self .generic_rules .iter_mut() .find(|gr| gr.name == entry.name.ident) { for arg in ga.args.iter() { gr.args.push((*arg.arg).clone()); } } else if let Some(params) = generic_params_from_rule(rule) { self.generic_rules.push(GenericRule { name: entry.name.ident, params, args: ga.args.iter().cloned().map(|arg| *arg.arg).collect(), }); } #[cfg(all(feature = "additional-controls", target_arch = "wasm32"))] let mut jv = JSONValidator::new(self.cddl, self.json.clone(), self.enabled_features.clone()); #[cfg(all(feature = "additional-controls", not(target_arch = "wasm32")))] let mut jv = JSONValidator::new(self.cddl, self.json.clone(), self.enabled_features); #[cfg(not(feature = "additional-controls"))] let mut jv = JSONValidator::new(self.cddl, self.json.clone()); jv.generic_rules = self.generic_rules.clone(); jv.eval_generic_rule = Some(entry.name.ident); jv.is_multi_type_choice = self.is_multi_type_choice; jv.visit_rule(rule)?; self.errors.append(&mut jv.errors); return Ok(()); } } let type_choice_alternates = type_choice_alternates_from_ident(self.cddl, &entry.name); if !type_choice_alternates.is_empty() { self.is_multi_type_choice = true; } let error_count = self.errors.len(); for t in type_choice_alternates { let cur_errors = self.errors.len(); self.visit_type(t)?; if self.errors.len() == cur_errors { for _ in 0..self.errors.len() - error_count { self.errors.pop(); } return Ok(()); } } let error_count = self.errors.len(); let group_choice_alternates = group_choice_alternates_from_ident(self.cddl, &entry.name); if !group_choice_alternates.is_empty() { self.is_multi_group_choice = true; } for ge in group_choice_alternates { let cur_errors = self.errors.len(); self.visit_group_entry(ge)?; if self.errors.len() == cur_errors { for _ in 0..self.errors.len() - error_count { self.errors.pop(); } return Ok(()); } } walk_type_groupname_entry(self, entry)?; self.type_group_name_entry = None; Ok(()) } fn visit_memberkey(&mut self, mk: &MemberKey<'a>) -> visitor::Result { match mk { MemberKey::Type1 { is_cut, .. } => { self.is_cut_present = *is_cut; walk_memberkey(self, mk)?; self.is_cut_present = false; } MemberKey::Bareword { .. } => { self.is_colon_shortcut_present = true; walk_memberkey(self, mk)?; self.is_colon_shortcut_present = false; } _ => return walk_memberkey(self, mk), } Ok(()) } fn visit_value(&mut self, value: &token::Value<'a>) -> visitor::Result { // FIXME: If during traversal the type being validated is supposed to be a value, // this fails if let Value::Array(_) = &self.json { return self.validate_array_items(&ArrayItemToken::Value(value)); } if let Value::Object(_) = &self.json { return self.validate_object_value(value); } let error: Option = match value { token::Value::INT(v) => match &self.json { Value::Number(n) => match n.as_i64() { Some(i) => match &self.ctrl { Some(ControlOperator::NE) | Some(ControlOperator::DEFAULT) if i != *v as i64 => None, Some(ControlOperator::LT) if i < *v as i64 => None, Some(ControlOperator::LE) if i <= *v as i64 => None, Some(ControlOperator::GT) if i > *v as i64 => None, Some(ControlOperator::GE) if i >= *v as i64 => None, #[cfg(feature = "additional-controls")] Some(ControlOperator::PLUS) => { if i == *v as i64 { None } else { Some(format!("expected computed .plus value {}, got {}", v, n)) } } #[cfg(feature = "additional-controls")] None | Some(ControlOperator::FEATURE) => { if i == *v as i64 { None } else { Some(format!("expected value {}, got {}", v, n)) } } #[cfg(not(feature = "additional-controls"))] None => { if i == *v as i64 { None } else { Some(format!("expected value {}, got {}", v, n)) } } _ => Some(format!( "expected value {} {}, got {}", self.ctrl.unwrap(), v, n )), }, None => Some(format!("{} cannot be represented as an i64", n)), }, _ => Some(format!("expected value {}, got {}", v, self.json)), }, token::Value::UINT(v) => match &self.json { Value::Number(n) => match n.as_u64() { Some(i) => match &self.ctrl { Some(ControlOperator::NE) | Some(ControlOperator::DEFAULT) if i != *v as u64 => None, Some(ControlOperator::LT) if i < *v as u64 => None, Some(ControlOperator::LE) if i <= *v as u64 => None, Some(ControlOperator::GT) if i > *v as u64 => None, Some(ControlOperator::GE) if i >= *v as u64 => None, Some(ControlOperator::SIZE) => match 256u128.checked_pow(*v as u32) { Some(n) if (i as u128) < n => None, _ => Some(format!("expected value .size {}, got {}", v, n)), }, #[cfg(feature = "additional-controls")] Some(ControlOperator::PLUS) => { if i == *v as u64 { None } else { Some(format!("expected computed .plus value {}, got {}", v, n)) } } #[cfg(feature = "additional-controls")] None | Some(ControlOperator::FEATURE) => { if i == *v as u64 { None } else { Some(format!("expected value {}, got {}", v, n)) } } #[cfg(not(feature = "additional-controls"))] None => { if i == *v as u64 { None } else { Some(format!("expected value {}, got {}", v, n)) } } _ => Some(format!( "expected value {} {}, got {}", self.ctrl.unwrap(), v, n )), }, None => Some(format!("{} cannot be represented as a u64", n)), }, Value::String(s) => match &self.ctrl { Some(ControlOperator::SIZE) => { if s.len() == *v { None } else { Some(format!("expected \"{}\" .size {}, got {}", s, v, s.len())) } } _ => Some(format!("expected {}, got {}", v, s)), }, _ => Some(format!("expected value {}, got {}", v, self.json)), }, token::Value::FLOAT(v) => match &self.json { Value::Number(n) => match n.as_f64() { Some(f) => match &self.ctrl { Some(ControlOperator::NE) | Some(ControlOperator::DEFAULT) if (f - *v).abs() > std::f64::EPSILON => { None } Some(ControlOperator::LT) if f < *v => None, Some(ControlOperator::LE) if f <= *v => None, Some(ControlOperator::GT) if f > *v => None, Some(ControlOperator::GE) if f >= *v => None, #[cfg(feature = "additional-controls")] Some(ControlOperator::PLUS) => { if (f - *v).abs() < std::f64::EPSILON { None } else { Some(format!("expected computed .plus value {}, got {}", v, n)) } } #[cfg(feature = "additional-controls")] None | Some(ControlOperator::FEATURE) => { if (f - *v).abs() < std::f64::EPSILON { None } else { Some(format!("expected value {}, got {}", v, n)) } } #[cfg(not(feature = "additional-controls"))] None => { if (f - *v).abs() < std::f64::EPSILON { None } else { Some(format!("expected value {}, got {}", v, n)) } } _ => Some(format!( "expected value {} {}, got {}", self.ctrl.unwrap(), v, n )), }, None => Some(format!("{} cannot be represented as an i64", n)), }, _ => Some(format!("expected value {}, got {}", v, self.json)), }, token::Value::TEXT(t) => match &self.json { Value::String(s) => match &self.ctrl { Some(ControlOperator::NE) | Some(ControlOperator::DEFAULT) => { if s != t { None } else { Some(format!("expected {} .ne to \"{}\"", value, s)) } } Some(ControlOperator::REGEXP) | Some(ControlOperator::PCRE) => { let re = regex::Regex::new( &format_regex( // Text strings must be JSON escaped per // https://datatracker.ietf.org/doc/html/rfc8610#section-3.1 serde_json::from_str::(&format!("\"{}\"", t)) .map_err(Error::JSONParsing)? .as_str() .ok_or_else(|| Error::from_validator(self, "malformed regex".to_string()))?, ) .ok_or_else(|| Error::from_validator(self, "malformed regex".to_string()))?, ) .map_err(|e| Error::from_validator(self, e.to_string()))?; if re.is_match(s) { None } else { Some(format!("expected \"{}\" to match regex \"{}\"", s, t)) } } #[cfg(feature = "additional-controls")] Some(ControlOperator::ABNF) => validate_abnf(t, s) .err() .map(|e| format!("\"{}\" is not valid against abnf: {}", s, e)), _ => { #[cfg(feature = "additional-controls")] if s == t { None } else if let Some(ControlOperator::CAT) | Some(ControlOperator::DET) = &self.ctrl { Some(format!( "expected value to match concatenated string {}, got \"{}\"", value, s )) } else if let Some(ctrl) = &self.ctrl { Some(format!("expected value {} {}, got \"{}\"", ctrl, value, s)) } else { Some(format!("expected value {} got \"{}\"", value, s)) } #[cfg(not(feature = "additional-controls"))] if s == t { None } else if let Some(ctrl) = &self.ctrl { Some(format!("expected value {} {}, got \"{}\"", ctrl, value, s)) } else { Some(format!("expected value {} got \"{}\"", value, s)) } } }, _ => Some(format!("expected value {}, got {}", t, self.json)), }, token::Value::BYTE(token::ByteValue::UTF8(b)) => match &self.json { Value::String(s) if s.as_bytes() == b.as_ref() => None, _ => Some(format!("expected byte value {:?}, got {}", b, self.json)), }, token::Value::BYTE(token::ByteValue::B16(b)) => match &self.json { Value::String(s) if s.as_bytes() == b.as_ref() => None, _ => Some(format!("expected byte value {:?}, got {}", b, self.json)), }, token::Value::BYTE(token::ByteValue::B64(b)) => match &self.json { Value::String(s) if s.as_bytes() == b.as_ref() => None, _ => Some(format!("expected byte value {:?}, got {}", b, self.json)), }, }; if let Some(e) = error { self.add_error(e); } Ok(()) } fn visit_occurrence(&mut self, o: &Occurrence) -> visitor::Result { self.occurrence = Some(o.occur); Ok(()) } } #[cfg(test)] #[cfg(not(target_arch = "wasm32"))] mod tests { #![allow(unused_imports)] use super::*; use indoc::indoc; #[cfg(feature = "additional-controls")] #[test] fn validate_plus() -> std::result::Result<(), Box> { let cddl = indoc!( r#" interval = ( "test" => BASE .plus a ) rect = { interval } X = 0 a = 10 "# ); let json = r#"{ "test": 10 }"#; let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing); if let Err(e) = &cddl { println!("{}", e); } let json = serde_json::from_str::(json).map_err(json::Error::JSONParsing)?; let cddl = cddl.unwrap(); let mut jv = JSONValidator::new(&cddl, json, None); jv.validate()?; Ok(()) } #[cfg(feature = "additional-controls")] #[test] fn validate_feature() -> std::result::Result<(), Box> { let cddl = indoc!( r#" v = JC<"v", 2> JC = C .feature "cbor" / J .feature "json" "# ); let json = r#""v""#; let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing); if let Err(e) = &cddl { println!("{}", e); } let json = serde_json::from_str::(json).map_err(json::Error::JSONParsing)?; let cddl = cddl.unwrap(); let mut jv = JSONValidator::new(&cddl, json, Some(&["json"])); jv.validate()?; Ok(()) } #[test] fn validate_type_choice_alternate() -> std::result::Result<(), Box> { let cddl = indoc!( r#" tester = [ $vals ] $vals /= 12 $vals /= 13 "# ); let json = r#"[ 13 ]"#; let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing); if let Err(e) = &cddl { println!("{}", e); } let json = serde_json::from_str::(json).map_err(json::Error::JSONParsing)?; let cddl = cddl.unwrap(); let mut jv = JSONValidator::new(&cddl, json, None); jv.validate()?; Ok(()) } #[test] fn validate_group_choice_alternate() -> std::result::Result<(), Box> { let cddl = indoc!( r#" tester = $$vals $$vals //= 18 $$vals //= 12 "# ); let json = r#"15"#; let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing); if let Err(e) = &cddl { println!("{}", e); } let json = serde_json::from_str::(json).map_err(json::Error::JSONParsing)?; let cddl = cddl.unwrap(); let mut jv = JSONValidator::new(&cddl, json, None); jv.validate()?; Ok(()) } #[test] fn validate_group_choice_alternate_in_array_1( ) -> std::result::Result<(), Box> { let cddl = indoc!( r#" tester = [$$val] $$val //= ( type: 10, data: uint, t: 11 ) $$val //= ( type: 11, data: tstr ) "# ); let json = r#"[10, 11, 11]"#; let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing); if let Err(e) = &cddl { println!("{}", e); } let json = serde_json::from_str::(json).map_err(json::Error::JSONParsing)?; let cddl = cddl.unwrap(); let mut jv = JSONValidator::new(&cddl, json, None); jv.validate()?; Ok(()) } #[test] fn validate_group_choice_alternate_in_array_2( ) -> std::result::Result<(), Box> { let cddl = indoc!( r#" tester = [$$val] $$val //= ( type: 10, extra, ) extra = ( something: uint, ) "# ); let json = r#"[10, 1]"#; let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing); if let Err(e) = &cddl { println!("{}", e); } let json = serde_json::from_str::(json).map_err(json::Error::JSONParsing)?; let cddl = cddl.unwrap(); let mut jv = JSONValidator::new(&cddl, json, None); jv.validate()?; Ok(()) } #[test] fn size_control_validation_error() -> std::result::Result<(), Box> { let cddl = indoc!( r#" start = Record Record = { id: Id } Id = uint .size 8 "# ); let json = r#"{ "id": 5 }"#; let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing); if let Err(e) = &cddl { println!("{}", e); } let json = serde_json::from_str::(json).map_err(json::Error::JSONParsing)?; let cddl = cddl.unwrap(); let mut jv = JSONValidator::new(&cddl, json, None); jv.validate()?; Ok(()) } #[test] fn validate_occurrences_in_object() -> std::result::Result<(), Box> { let cddl = indoc!( r#" limited = { 1* tstr => tstr } "# ); let json = r#"{ "A": "B" }"#; let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing); if let Err(e) = &cddl { println!("{}", e); } let json = serde_json::from_str::(json).map_err(json::Error::JSONParsing)?; let cddl = cddl.unwrap(); let mut jv = JSONValidator::new(&cddl, json, None); jv.validate().unwrap(); Ok(()) } #[test] fn validate_optional_occurrences_in_object() -> std::result::Result<(), Box> { let cddl = indoc!( r#" argument = { name: text, ? valid: "yes" / "no", } "# ); let json = r#"{ "name": "foo", "valid": "no" }"#; let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing); if let Err(e) = &cddl { println!("{}", e); } let json = serde_json::from_str::(json).map_err(json::Error::JSONParsing)?; let cddl = cddl.unwrap(); let mut jv = JSONValidator::new(&cddl, json, None); jv.validate().unwrap(); Ok(()) } } cddl-0.9.4/src/validator/mod.rs000064400000000000000000001043521046102023000144520ustar 00000000000000#![cfg(not(feature = "lsp"))] /// CBOR validation implementation pub mod cbor; /// JSON validation implementation pub mod json; mod control; use crate::{ ast::{ Group, GroupChoice, GroupEntry, GroupRule, Identifier, Occur, Rule, Type, Type2, TypeChoice, TypeRule, CDDL, }, token::*, visitor::Visitor, }; use std::error::Error; #[cfg(feature = "cbor")] use cbor::CBORValidator; #[cfg(feature = "cbor")] use ciborium; #[cfg(feature = "json")] use json::JSONValidator; use serde::de::Deserialize; #[cfg(target_arch = "wasm32")] use crate::{ error::ErrorMsg, lexer::Position, parser::{self, Parser}, }; #[cfg(target_arch = "wasm32")] use serde::Serialize; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; #[cfg(not(target_arch = "wasm32"))] use crate::cddl_from_str; #[cfg(target_arch = "wasm32")] #[derive(Serialize)] struct ParserError { position: Position, msg: ErrorMsg, } /// Validator trait. Implemented for JSON documents and CBOR binaries pub trait Validator<'a, 'b, E: Error>: Visitor<'a, 'b, E> { /// Validate the target fn validate(&mut self) -> std::result::Result<(), E>; /// Collect validation errors fn add_error(&mut self, reason: String); } impl CDDL<'_> { /// Validate the given document against the CDDL definition fn validate_json( &self, document: &[u8], #[cfg(feature = "additional-controls")] #[cfg(not(target_arch = "wasm32"))] enabled_features: Option<&[&str]>, #[cfg(feature = "additional-controls")] #[cfg(target_arch = "wasm32")] enabled_features: Option>, ) -> Result<(), Box> { let json = serde_json::from_slice::(document).map_err(json::Error::JSONParsing)?; #[cfg(feature = "additional-controls")] let mut jv = JSONValidator::new(self, json, enabled_features); #[cfg(not(feature = "additional-controls"))] let mut jv = JSONValidator::new(&cddl, json); jv.validate().map_err(|e| e.into()) } fn validate_cbor( &self, document: &[u8], #[cfg(feature = "additional-controls")] #[cfg(not(target_arch = "wasm32"))] enabled_features: Option<&[&str]>, #[cfg(feature = "additional-controls")] #[cfg(target_arch = "wasm32")] enabled_features: Option>, ) -> Result<(), Box> { let cbor: ciborium::value::Value = ciborium::de::from_reader(document)?; let mut cv = CBORValidator::new(self, cbor, enabled_features); cv.validate().map_err(|e| e.into()) } } #[cfg(not(target_arch = "wasm32"))] #[cfg(feature = "json")] /// Validate JSON string from a given CDDL document string pub fn validate_json_from_str( cddl: &str, json: &str, #[cfg(feature = "additional-controls")] enabled_features: Option<&[&str]>, ) -> json::Result { let cddl = cddl_from_str(cddl, true).map_err(json::Error::CDDLParsing)?; let json = serde_json::from_str::(json).map_err(json::Error::JSONParsing)?; #[cfg(feature = "additional-controls")] let mut jv = JSONValidator::new(&cddl, json, enabled_features); #[cfg(not(feature = "additional-controls"))] let mut jv = JSONValidator::new(&cddl, json); jv.validate() } #[cfg(target_arch = "wasm32")] #[cfg(feature = "additional-controls")] #[cfg(feature = "json")] #[wasm_bindgen] /// Validate JSON string from a given CDDL document string pub fn validate_json_from_str( cddl: &str, json: &str, enabled_features: Option>, ) -> std::result::Result { let mut p = Parser::new(cddl, Box::new(crate::lexer::lexer_from_str(cddl).iter())) .map_err(|e| JsValue::from(e.to_string()))?; let c = p.parse_cddl().map_err(|e| JsValue::from(e.to_string()))?; if !p.errors.is_empty() { return Err( serde_wasm_bindgen::to_value( &p.errors .iter() .filter_map(|e| { if let parser::Error::PARSER { position, msg } = e { Some(ParserError { position: *position, msg: msg.clone(), }) } else { None } }) .collect::>(), ) .map_err(|e| JsValue::from(e.to_string()))?, ); } let json = serde_json::from_str::(json).map_err(|e| JsValue::from(e.to_string()))?; let mut jv = JSONValidator::new(&c, json, enabled_features); jv.validate() .map_err(|e| JsValue::from(e.to_string())) .map(|_| JsValue::default()) } #[cfg(target_arch = "wasm32")] #[cfg(feature = "json")] #[cfg(not(feature = "additional-controls"))] #[wasm_bindgen] /// Validate JSON string from a given CDDL document string pub fn validate_json_from_str(cddl: &str, json: &str) -> std::result::Result { let mut l = Lexer::new(cddl); let mut p = Parser::new((&mut l).iter(), cddl).map_err(|e| JsValue::from(e.to_string()))?; let c = p.parse_cddl().map_err(|e| JsValue::from(e.to_string()))?; if !p.errors.is_empty() { return Err( JsValue::from_serde( &p.errors .iter() .filter_map(|e| { if let parser::Error::PARSER { position, msg } = e { Some(ParserError { position: *position, msg: msg.clone(), }) } else { None } }) .collect::>(), ) .map_err(|e| JsValue::from(e.to_string()))?, ); } let json = serde_json::from_str::(json).map_err(|e| JsValue::from(e.to_string()))?; let mut jv = JSONValidator::new(&c, json); jv.validate() .map_err(|e| JsValue::from(e.to_string())) .map(|_| JsValue::default()) } #[cfg(not(target_arch = "wasm32"))] #[cfg(feature = "cbor")] #[cfg(feature = "additional-controls")] /// Validate CBOR slice from a given CDDL document string pub fn validate_cbor_from_slice( cddl: &str, cbor_slice: &[u8], enabled_features: Option<&[&str]>, ) -> cbor::Result { let cddl = cddl_from_str(cddl, true).map_err(cbor::Error::CDDLParsing)?; let cbor: ciborium::value::Value = ciborium::de::from_reader(cbor_slice).map_err(cbor::Error::CBORParsing)?; let mut cv = CBORValidator::new(&cddl, cbor, enabled_features); cv.validate() } #[cfg(not(target_arch = "wasm32"))] #[cfg(feature = "cbor")] #[cfg(not(feature = "additional-controls"))] /// Validate CBOR slice from a given CDDL document string pub fn validate_cbor_from_slice(cddl: &str, cbor_slice: &[u8]) -> cbor::Result { let mut lexer = lexer_from_str(cddl); let cddl = cddl_from_str(&mut lexer, cddl, true).map_err(cbor::Error::CDDLParsing)?; let cbor: ciborium::value::Value = ciborium::de::from_reader(cbor_slice).map_err(cbor::Error::CBORParsing)?; let mut cv = CBORValidator::new(&cddl, cbor); cv.validate() } #[cfg(target_arch = "wasm32")] #[cfg(feature = "cbor")] #[cfg(feature = "additional-controls")] #[wasm_bindgen] /// Validate CBOR slice from a given CDDL document string pub fn validate_cbor_from_slice( cddl: &str, cbor_slice: &[u8], enabled_features: Option>, ) -> std::result::Result { let mut p = Parser::new(cddl, Box::new(crate::lexer::lexer_from_str(cddl).iter())) .map_err(|e| JsValue::from(e.to_string()))?; let c = p.parse_cddl().map_err(|e| JsValue::from(e.to_string()))?; if !p.errors.is_empty() { return Err( serde_wasm_bindgen::to_value( &p.errors .iter() .filter_map(|e| { if let parser::Error::PARSER { position, msg } = e { Some(ParserError { position: *position, msg: msg.clone(), }) } else { None } }) .collect::>(), ) .map_err(|e| JsValue::from(e.to_string()))?, ); } let cbor: ciborium::value::Value = ciborium::de::from_reader(cbor_slice).map_err(|e| JsValue::from(e.to_string()))?; let mut cv = CBORValidator::new(&c, cbor, enabled_features); cv.validate() .map_err(|e| JsValue::from(e.to_string())) .map(|_| JsValue::default()) } #[cfg(target_arch = "wasm32")] #[cfg(feature = "cbor")] #[cfg(not(feature = "additional-controls"))] #[wasm_bindgen] /// Validate CBOR slice from a given CDDL document string pub fn validate_cbor_from_slice( cddl: &str, cbor_slice: &[u8], ) -> std::result::Result { let mut l = Lexer::new(cddl); let mut p = Parser::new((&mut l).iter(), cddl).map_err(|e| JsValue::from(e.to_string()))?; let c = p.parse_cddl().map_err(|e| JsValue::from(e.to_string()))?; if !p.errors.is_empty() { return Err( JsValue::from_serde( &p.errors .iter() .filter_map(|e| { if let parser::Error::PARSER { position, msg } = e { Some(ParserError { position: *position, msg: msg.clone(), }) } else { None } }) .collect::>(), ) .map_err(|e| JsValue::from(e.to_string()))?, ); } let cbor: ciborium::value::Value = ciborium::de::from_reader(cbor_slice).map_err(|e| JsValue::from(e.to_string()))?; let mut cv = CBORValidator::new(&c, cbor); cv.validate() .map_err(|e| JsValue::from(e.to_string())) .map(|_| JsValue::default()) } /// Find non-choice alternate rule from a given identifier pub fn rule_from_ident<'a>(cddl: &'a CDDL, ident: &Identifier) -> Option<&'a Rule<'a>> { cddl.rules.iter().find(|r| match r { Rule::Type { rule, .. } if rule.name == *ident && !rule.is_type_choice_alternate => true, Rule::Group { rule, .. } if rule.name == *ident && !rule.is_group_choice_alternate => true, _ => false, }) } /// Find text values from a given identifier pub fn text_value_from_ident<'a>(cddl: &'a CDDL, ident: &Identifier) -> Option<&'a Type2<'a>> { cddl.rules.iter().find_map(|r| match r { Rule::Type { rule, .. } if rule.name == *ident => { rule.value.type_choices.iter().find_map(|tc| { if tc.type1.operator.is_none() { match &tc.type1.type2 { Type2::TextValue { .. } | Type2::UTF8ByteString { .. } => Some(&tc.type1.type2), Type2::Typename { ident, .. } => text_value_from_ident(cddl, ident), Type2::ParenthesizedType { pt, .. } => pt.type_choices.iter().find_map(|tc| { if tc.type1.operator.is_none() { text_value_from_type2(cddl, &tc.type1.type2) } else { None } }), _ => None, } } else { None } }) } _ => None, }) } /// Find text values from a given Type2 pub fn text_value_from_type2<'a>(cddl: &'a CDDL, t2: &'a Type2<'a>) -> Option<&'a Type2<'a>> { match t2 { Type2::TextValue { .. } | Type2::UTF8ByteString { .. } => Some(t2), Type2::Typename { ident, .. } => text_value_from_ident(cddl, ident), Type2::Array { group, .. } => group.group_choices.iter().find_map(|gc| { if gc.group_entries.len() == 2 { if let Some(ge) = gc.group_entries.first() { if let GroupEntry::ValueMemberKey { ge, .. } = &ge.0 { if ge.member_key.is_none() { ge.entry_type.type_choices.iter().find_map(|tc| { if tc.type1.operator.is_none() { text_value_from_type2(cddl, &tc.type1.type2) } else { None } }) } else { None } } else { None } } else { None } } else { None } }), Type2::ParenthesizedType { pt, .. } => pt.type_choices.iter().find_map(|tc| { if tc.type1.operator.is_none() { text_value_from_type2(cddl, &tc.type1.type2) } else { None } }), _ => None, } } /// Unwrap array, map or tag type rule from ident pub fn unwrap_rule_from_ident<'a>(cddl: &'a CDDL, ident: &Identifier) -> Option<&'a Rule<'a>> { cddl.rules.iter().find_map(|r| match r { Rule::Type { rule: TypeRule { name, is_type_choice_alternate, value: Type { type_choices, .. }, .. }, .. } if name == ident && !is_type_choice_alternate => { let match_fn = |tc: &TypeChoice| { matches!( tc.type1.type2, Type2::Map { .. } | Type2::Array { .. } | Type2::TaggedData { .. } ) }; if type_choices.iter().any(match_fn) { Some(r) } else if let Some(ident) = type_choices.iter().find_map(|tc| { if let Type2::Typename { ident, generic_args: None, .. } = &tc.type1.type2 { Some(ident) } else { None } }) { unwrap_rule_from_ident(cddl, ident) } else { None } } _ => None, }) } /// Find non-group choice alternate rule from a given identifier pub fn group_rule_from_ident<'a>(cddl: &'a CDDL, ident: &Identifier) -> Option<&'a GroupRule<'a>> { cddl.rules.iter().find_map(|r| match r { Rule::Group { rule, .. } if rule.name == *ident && !rule.is_group_choice_alternate => { Some(rule.as_ref()) } _ => None, }) } /// Find non-group choice alternate rule from a given identifier pub fn type_rule_from_ident<'a>(cddl: &'a CDDL, ident: &Identifier) -> Option<&'a TypeRule<'a>> { cddl.rules.iter().find_map(|r| match r { Rule::Type { rule, .. } if rule.name == *ident && !rule.is_type_choice_alternate => Some(rule), _ => None, }) } /// Retrieve the list of generic parameters for a given rule pub fn generic_params_from_rule<'a>(rule: &Rule<'a>) -> Option> { match rule { Rule::Type { rule, .. } => rule .generic_params .as_ref() .map(|gp| gp.params.iter().map(|gp| gp.param.ident).collect()), Rule::Group { rule, .. } => rule .generic_params .as_ref() .map(|gp| gp.params.iter().map(|gp| gp.param.ident).collect()), } } /// Find all type choice alternate rules from a given identifier pub fn type_choice_alternates_from_ident<'a>( cddl: &'a CDDL, ident: &Identifier, ) -> Vec<&'a Type<'a>> { cddl .rules .iter() .filter_map(|r| match r { Rule::Type { rule, .. } if &rule.name == ident && rule.is_type_choice_alternate => { Some(&rule.value) } _ => None, }) .collect::>() } /// Find all group choice alternate rules from a given identifier pub fn group_choice_alternates_from_ident<'a>( cddl: &'a CDDL, ident: &Identifier, ) -> Vec<&'a GroupEntry<'a>> { cddl .rules .iter() .filter_map(|r| match r { Rule::Group { rule, .. } if &rule.name == ident && rule.is_group_choice_alternate => { Some(&rule.entry) } _ => None, }) .collect::>() } /// Convert a given group choice to a list of type choices pub fn type_choices_from_group_choice<'a>( cddl: &'a CDDL, grpchoice: &GroupChoice<'a>, ) -> Vec> { let mut type_choices = Vec::new(); for ge in grpchoice.group_entries.iter() { match &ge.0 { GroupEntry::ValueMemberKey { ge, .. } => { type_choices.append(&mut ge.entry_type.type_choices.clone()); } GroupEntry::TypeGroupname { ge, .. } => { // TODO: parse generic args if let Some(r) = rule_from_ident(cddl, &ge.name) { match r { Rule::Type { rule, .. } => type_choices.append(&mut rule.value.type_choices.clone()), Rule::Group { rule, .. } => type_choices.append(&mut type_choices_from_group_choice( cddl, &GroupChoice::new(vec![rule.entry.clone()]), )), } } } GroupEntry::InlineGroup { group, .. } => { for gc in group.group_choices.iter() { type_choices.append(&mut type_choices_from_group_choice(cddl, gc)); } } } } type_choices } /// Is the given identifier associated with a null data type pub fn is_ident_null_data_type(cddl: &CDDL, ident: &Identifier) -> bool { if let Token::NULL | Token::NIL = lookup_ident(ident.ident) { return true; } cddl.rules.iter().any(|r| match r { Rule::Type { rule, .. } if &rule.name == ident => rule.value.type_choices.iter().any(|tc| { if let Type2::Typename { ident, .. } = &tc.type1.type2 { is_ident_null_data_type(cddl, ident) } else { false } }), _ => false, }) } /// Is the given identifier associated with a boolean data type pub fn is_ident_bool_data_type(cddl: &CDDL, ident: &Identifier) -> bool { if let Token::BOOL = lookup_ident(ident.ident) { return true; } cddl.rules.iter().any(|r| match r { Rule::Type { rule, .. } if &rule.name == ident => rule.value.type_choices.iter().any(|tc| { if let Type2::Typename { ident, .. } = &tc.type1.type2 { is_ident_bool_data_type(cddl, ident) } else { false } }), _ => false, }) } /// Does the given boolean identifier match the boolean value pub fn ident_matches_bool_value(cddl: &CDDL, ident: &Identifier, value: bool) -> bool { if let Token::TRUE = lookup_ident(ident.ident) { if value { return true; } } if let Token::FALSE = lookup_ident(ident.ident) { if !value { return true; } } cddl.rules.iter().any(|r| match r { Rule::Type { rule, .. } if &rule.name == ident => rule.value.type_choices.iter().any(|tc| { if let Type2::Typename { ident, .. } = &tc.type1.type2 { ident_matches_bool_value(cddl, ident, value) } else { false } }), _ => false, }) } /// Is the given identifier associated with a URI data type pub fn is_ident_uri_data_type(cddl: &CDDL, ident: &Identifier) -> bool { if let Token::URI = lookup_ident(ident.ident) { return true; } cddl.rules.iter().any(|r| match r { Rule::Type { rule, .. } if &rule.name == ident => rule.value.type_choices.iter().any(|tc| { if let Type2::Typename { ident, .. } = &tc.type1.type2 { is_ident_uri_data_type(cddl, ident) } else { false } }), _ => false, }) } /// Is the given identifier associated with a b64url data type pub fn is_ident_b64url_data_type(cddl: &CDDL, ident: &Identifier) -> bool { if let Token::B64URL = lookup_ident(ident.ident) { return true; } cddl.rules.iter().any(|r| match r { Rule::Type { rule, .. } if &rule.name == ident => rule.value.type_choices.iter().any(|tc| { if let Type2::Typename { ident, .. } = &tc.type1.type2 { is_ident_b64url_data_type(cddl, ident) } else { false } }), _ => false, }) } /// Is the given identifier associated with a tdate data type pub fn is_ident_tdate_data_type(cddl: &CDDL, ident: &Identifier) -> bool { if let Token::TDATE = lookup_ident(ident.ident) { return true; } cddl.rules.iter().any(|r| match r { Rule::Type { rule, .. } if &rule.name == ident => rule.value.type_choices.iter().any(|tc| { if let Type2::Typename { ident, .. } = &tc.type1.type2 { is_ident_tdate_data_type(cddl, ident) } else { false } }), _ => false, }) } /// Is the given identifier associated with a time data type pub fn is_ident_time_data_type(cddl: &CDDL, ident: &Identifier) -> bool { if let Token::TIME = lookup_ident(ident.ident) { return true; } cddl.rules.iter().any(|r| match r { Rule::Type { rule, .. } if &rule.name == ident => rule.value.type_choices.iter().any(|tc| { if let Type2::Typename { ident, .. } = &tc.type1.type2 { is_ident_time_data_type(cddl, ident) } else { false } }), _ => false, }) } /// Is the given identifier associated with a numeric data type pub fn is_ident_numeric_data_type(cddl: &CDDL, ident: &Identifier) -> bool { if let Token::UINT | Token::NINT | Token::INTEGER | Token::INT | Token::NUMBER | Token::FLOAT | Token::FLOAT16 | Token::FLOAT32 | Token::FLOAT64 | Token::FLOAT1632 | Token::FLOAT3264 | Token::UNSIGNED = lookup_ident(ident.ident) { return true; } cddl.rules.iter().any(|r| match r { Rule::Type { rule, .. } if rule.name == *ident => rule.value.type_choices.iter().any(|tc| { if let Type2::Typename { ident, .. } = &tc.type1.type2 { is_ident_numeric_data_type(cddl, ident) } else { false } }), _ => false, }) } /// Is the given identifier associated with a uint data type pub fn is_ident_uint_data_type(cddl: &CDDL, ident: &Identifier) -> bool { if let Token::UINT = lookup_ident(ident.ident) { return true; } cddl.rules.iter().any(|r| match r { Rule::Type { rule, .. } if rule.name == *ident => rule.value.type_choices.iter().any(|tc| { if let Type2::Typename { ident, .. } = &tc.type1.type2 { is_ident_uint_data_type(cddl, ident) } else { false } }), _ => false, }) } /// Is the given identifier associated with a nint data type pub fn is_ident_nint_data_type(cddl: &CDDL, ident: &Identifier) -> bool { if let Token::NINT = lookup_ident(ident.ident) { return true; } cddl.rules.iter().any(|r| match r { Rule::Type { rule, .. } if rule.name == *ident => rule.value.type_choices.iter().any(|tc| { if let Type2::Typename { ident, .. } = &tc.type1.type2 { is_ident_nint_data_type(cddl, ident) } else { false } }), _ => false, }) } /// Is the given identifier associated with an integer data type pub fn is_ident_integer_data_type(cddl: &CDDL, ident: &Identifier) -> bool { if let Token::INT | Token::INTEGER | Token::NINT | Token::UINT | Token::NUMBER | Token::UNSIGNED = lookup_ident(ident.ident) { return true; } cddl.rules.iter().any(|r| match r { Rule::Type { rule, .. } if rule.name == *ident => rule.value.type_choices.iter().any(|tc| { if let Type2::Typename { ident, .. } = &tc.type1.type2 { is_ident_integer_data_type(cddl, ident) } else { false } }), _ => false, }) } /// Is the given identifier associated with a float data type pub fn is_ident_float_data_type(cddl: &CDDL, ident: &Identifier) -> bool { if let Token::FLOAT | Token::FLOAT16 | Token::FLOAT1632 | Token::FLOAT32 | Token::FLOAT3264 | Token::FLOAT64 = lookup_ident(ident.ident) { return true; } cddl.rules.iter().any(|r| match r { Rule::Type { rule, .. } if rule.name == *ident => rule.value.type_choices.iter().any(|tc| { if let Type2::Typename { ident, .. } = &tc.type1.type2 { is_ident_float_data_type(cddl, ident) } else { false } }), _ => false, }) } /// Is the given identifier associated with a string data type pub fn is_ident_string_data_type(cddl: &CDDL, ident: &Identifier) -> bool { if let Token::TEXT | Token::TSTR = lookup_ident(ident.ident) { return true; } cddl.rules.iter().any(|r| match r { Rule::Type { rule, .. } if rule.name == *ident => rule.value.type_choices.iter().any(|tc| { if let Type2::Typename { ident, .. } = &tc.type1.type2 { is_ident_string_data_type(cddl, ident) } else { false } }), _ => false, }) } /// Is the given identifier associated with the any type pub fn is_ident_any_type(cddl: &CDDL, ident: &Identifier) -> bool { if let Token::ANY = lookup_ident(ident.ident) { return true; } cddl.rules.iter().any(|r| match r { Rule::Type { rule, .. } if rule.name == *ident => rule.value.type_choices.iter().any(|tc| { if let Type2::Typename { ident, .. } = &tc.type1.type2 { is_ident_any_type(cddl, ident) } else { false } }), _ => false, }) } /// Is the given identifier associated with a byte string data type pub fn is_ident_byte_string_data_type(cddl: &CDDL, ident: &Identifier) -> bool { if let Token::BSTR | Token::BYTES = lookup_ident(ident.ident) { return true; } cddl.rules.iter().any(|r| match r { Rule::Type { rule, .. } if rule.name == *ident => rule.value.type_choices.iter().any(|tc| { if let Type2::Typename { ident, .. } = &tc.type1.type2 { is_ident_byte_string_data_type(cddl, ident) } else { false } }), _ => false, }) } /// Validate array length and \[non\]homogeneity based on a given optional /// occurrence indicator. The first bool in the returned tuple indicates whether /// or not a subsequent validation of the array's elements shouch be homogenous. /// The second bool in the returned tuple indicates whether or not an empty /// array is allowed during a subsequent validation of the array's elements. pub fn validate_array_occurrence<'de, T: Deserialize<'de>>( occurrence: Option<&Occur>, entry_counts: Option<&[EntryCount]>, values: &[T], ) -> std::result::Result<(bool, bool), Vec> { let mut iter_items = false; #[cfg(feature = "ast-span")] let allow_empty_array = matches!(occurrence, Some(Occur::Optional { .. })); #[cfg(not(feature = "ast-span"))] let allow_empty_array = matches!(occurrence, Some(Occur::Optional {})); let mut errors = Vec::new(); match occurrence { #[cfg(feature = "ast-span")] Some(Occur::ZeroOrMore { .. }) => iter_items = true, #[cfg(not(feature = "ast-span"))] Some(Occur::ZeroOrMore {}) => iter_items = true, #[cfg(feature = "ast-span")] Some(Occur::OneOrMore { .. }) => { if values.is_empty() { errors.push("array must have at least one item".to_string()); } else { iter_items = true; } } #[cfg(not(feature = "ast-span"))] Some(Occur::OneOrMore {}) => { if values.is_empty() { errors.push("array must have at least one item".to_string()); } else { iter_items = true; } } Some(Occur::Exact { lower, upper, .. }) => { if let Some(lower) = lower { if let Some(upper) = upper { if lower == upper && values.len() != *lower { errors.push(format!("array must have exactly {} items", lower)); } if values.len() < *lower || values.len() > *upper { errors.push(format!( "array must have between {} and {} items", lower, upper )); } } else if values.len() < *lower { errors.push(format!("array must have at least {} items", lower)); } } else if let Some(upper) = upper { if values.len() > *upper { errors.push(format!("array must have not more than {} items", upper)); } } iter_items = true; } #[cfg(feature = "ast-span")] Some(Occur::Optional { .. }) => { if values.len() > 1 { errors.push("array must have 0 or 1 items".to_string()); } iter_items = false; } #[cfg(not(feature = "ast-span"))] Some(Occur::Optional {}) => { if values.len() > 1 { errors.push("array must have 0 or 1 items".to_string()); } iter_items = false; } None => { if values.is_empty() { errors.push("array must have exactly one item".to_string()); } else { iter_items = false; } } } if !iter_items && !allow_empty_array { if let Some(entry_counts) = entry_counts { let len = values.len(); if !validate_entry_count(entry_counts, len) { for ec in entry_counts.iter() { if let Some(occur) = &ec.entry_occurrence { errors.push(format!( "expected array with length per occurrence {}", occur, )); } else { errors.push(format!( "expected array with length {}, got {}", ec.count, len )); } } } } } if !errors.is_empty() { return Err(errors); } Ok((iter_items, allow_empty_array)) } /// Retrieve number of group entries from a group. This is currently only used /// for determining map equality/inequality and for validating the number of /// entries in arrays, but may be useful in other contexts. The occurrence is /// only captured for the second element of the CDDL array to avoid ambiguity in /// non-homogenous array definitions pub fn entry_counts_from_group<'a, 'b: 'a>( cddl: &'a CDDL, group: &'b Group<'a>, ) -> Vec { // Each EntryCount is associated with a group choice in the given group let mut entry_counts = Vec::new(); for gc in group.group_choices.iter() { let mut count = 0; let mut entry_occurrence = None; for (idx, ge) in gc.group_entries.iter().enumerate() { match &ge.0 { GroupEntry::ValueMemberKey { ge, .. } => { if idx == 1 { if let Some(occur) = &ge.occur { entry_occurrence = Some(occur.occur) } } count += 1; } GroupEntry::InlineGroup { group, occur, .. } => { if idx == 1 { if let Some(occur) = occur { entry_occurrence = Some(occur.occur) } } entry_counts = entry_counts_from_group(cddl, group); } GroupEntry::TypeGroupname { ge, .. } => { if idx == 1 { if let Some(occur) = &ge.occur { entry_occurrence = Some(occur.occur) } } if let Some(gr) = group_rule_from_ident(cddl, &ge.name) { if let GroupEntry::InlineGroup { group, .. } = &gr.entry { if group.group_choices.len() == 1 { count += if let Some(ec) = entry_counts_from_group(cddl, group).first() { ec.count } else { 0 }; } else { entry_counts.append(&mut entry_counts_from_group(cddl, group)); } } else { entry_counts.append(&mut entry_counts_from_group(cddl, &gr.entry.clone().into())); } } else if group_choice_alternates_from_ident(cddl, &ge.name).is_empty() { count += 1; } else { for ge in group_choice_alternates_from_ident(cddl, &ge.name).into_iter() { entry_counts.append(&mut entry_counts_from_group(cddl, &ge.clone().into())); } } } } } entry_counts.push(EntryCount { count, entry_occurrence, }); } entry_counts } /// Validate the number of entries given an array of possible valid entry counts pub fn validate_entry_count(valid_entry_counts: &[EntryCount], num_entries: usize) -> bool { valid_entry_counts.iter().any(|ec| { num_entries == ec.count as usize || match ec.entry_occurrence { #[cfg(feature = "ast-span")] Some(Occur::ZeroOrMore { .. }) | Some(Occur::Optional { .. }) => true, #[cfg(not(feature = "ast-span"))] Some(Occur::ZeroOrMore {}) | Some(Occur::Optional {}) => true, #[cfg(feature = "ast-span")] Some(Occur::OneOrMore { .. }) if num_entries > 0 => true, #[cfg(not(feature = "ast-span"))] Some(Occur::OneOrMore {}) if num_entries > 0 => true, Some(Occur::Exact { lower, upper, .. }) => { if let Some(lower) = lower { if let Some(upper) = upper { num_entries >= lower && num_entries <= upper } else { num_entries >= lower } } else if let Some(upper) = upper { num_entries <= upper } else { false } } _ => false, } }) } /// Entry count #[derive(Clone, Debug)] pub struct EntryCount { /// Count pub count: u64, /// Optional occurrence pub entry_occurrence: Option, } /// Regex needs to be formatted in a certain way so it can be parsed. See /// pub fn format_regex(input: &str) -> Option { let mut formatted_regex = String::from(input); let mut unescape = Vec::new(); for (idx, c) in formatted_regex.char_indices() { if c == '\\' { if let Some(c) = formatted_regex.chars().nth(idx + 1) { if !regex_syntax::is_meta_character(c) && c != 'd' { unescape.push(format!("\\{}", c)); } } } } for replace in unescape.iter() { formatted_regex = formatted_regex.replace(replace, &replace.chars().nth(1).unwrap().to_string()); } for find in ["?=", "?!", "?<=", "? { Value(&'a Value<'a>), Range(&'a Type2<'a>, &'a Type2<'a>, bool), Group(&'a Group<'a>), Identifier(&'a Identifier<'a>), TaggedData(&'a Type2<'a>), } #[allow(missing_docs)] impl ArrayItemToken<'_> { pub fn error_msg(&self, idx: Option) -> String { match self { ArrayItemToken::Value(value) => { if let Some(idx) = idx { format!("expected value {} at index {}", value, idx) } else { format!("expected value {}", value) } } ArrayItemToken::Range(lower, upper, is_inclusive) => { if let Some(idx) = idx { format!( "expected range lower {} upper {} inclusive {} at index {}", lower, upper, is_inclusive, idx ) } else { format!( "expected range lower {} upper {} inclusive {}", lower, upper, is_inclusive ) } } ArrayItemToken::Group(group) => { if let Some(idx) = idx { format!("expected map object {} at index {}", group, idx) } else { format!("expected map object {}", group) } } ArrayItemToken::Identifier(ident) => { if let Some(idx) = idx { format!("expected type {} at index {}", ident, idx) } else { format!("expected type {}", ident) } } ArrayItemToken::TaggedData(tagged_data) => { if let Some(idx) = idx { format!( "expected tagged data tag {:?} at index {}", tagged_data, idx ) } else { format!("expected tagged data {:?}", tagged_data) } } } } } #[cfg(test)] mod tests { #![cfg(not(target_arch = "wasm32"))] use super::*; #[test] fn validate_json() { let cddl_schema = cddl_from_str( r#" foo = { bar: tstr } "#, true, ) .unwrap(); let documents = [r#"{ "bar": "foo" }"#, r#"{ "bar": "foo2" }"#]; documents .iter() .all(|doc| cddl_schema.validate_json(doc.as_bytes(), None).is_ok()); } } cddl-0.9.4/src/visitor.rs000064400000000000000000000303721046102023000134050ustar 00000000000000#![cfg(feature = "std")] use crate::{ ast::*, token::Value, token::{ByteValue, ControlOperator}, }; use std::error::Error; /// Visitor result pub type Result = std::result::Result<(), T>; /// CDDL AST visitor pub trait Visitor<'a, 'b, E: Error> { /// Visit CDDL fn visit_cddl(&mut self, cddl: &'b CDDL<'a>) -> Result { walk_cddl(self, cddl) } /// Visit rule fn visit_rule(&mut self, rule: &'b Rule<'a>) -> Result { walk_rule(self, rule) } /// Visit identifier fn visit_identifier(&mut self, _ident: &Identifier<'a>) -> Result { Ok(()) } /// Visit value fn visit_value(&mut self, _value: &Value<'a>) -> Result { Ok(()) } /// Visit type rule fn visit_type_rule(&mut self, tr: &'b TypeRule<'a>) -> Result { walk_type_rule(self, tr) } /// Visit group rule fn visit_group_rule(&mut self, gr: &'b GroupRule<'a>) -> Result { walk_group_rule(self, gr) } /// Visit type fn visit_type(&mut self, t: &'b Type<'a>) -> Result { walk_type(self, t) } /// Visit type choice fn visit_type_choice(&mut self, tc: &'b TypeChoice<'a>) -> Result { walk_type_choice(self, tc) } /// Visit type1 fn visit_type1(&mut self, t1: &'b Type1<'a>) -> Result { walk_type1(self, t1) } /// Visit operator fn visit_operator(&mut self, target: &'b Type1<'a>, o: &'b Operator<'a>) -> Result { walk_operator(self, target, o) } /// Visit rangectlop fn visit_rangectlop( &mut self, op: &'b RangeCtlOp, target: &'b Type1<'a>, controller: &'b Type2<'a>, ) -> Result { walk_rangectlop(self, op, target, controller) } /// Visit range fn visit_range( &mut self, lower: &'b Type2<'a>, upper: &'b Type2<'a>, _is_inclusive: bool, ) -> Result { walk_range(self, lower, upper) } /// Visit control operator fn visit_control_operator( &mut self, target: &'b Type2<'a>, _ctrl: ControlOperator, controller: &'b Type2<'a>, ) -> Result { walk_control_operator(self, target, controller) } /// Visit type2 fn visit_type2(&mut self, t2: &'b Type2<'a>) -> Result { walk_type2(self, t2) } /// Visit group fn visit_group(&mut self, g: &'b Group<'a>) -> Result { walk_group(self, g) } /// Visit group choice fn visit_group_choice(&mut self, gc: &'b GroupChoice<'a>) -> Result { walk_group_choice(self, gc) } /// Visit group entry fn visit_group_entry(&mut self, entry: &'b GroupEntry<'a>) -> Result { walk_group_entry(self, entry) } /// Visit value member key entry fn visit_value_member_key_entry(&mut self, entry: &'b ValueMemberKeyEntry<'a>) -> Result { walk_value_member_key_entry(self, entry) } /// Visit typename/groupname entry fn visit_type_groupname_entry(&mut self, entry: &'b TypeGroupnameEntry<'a>) -> Result { walk_type_groupname_entry(self, entry) } /// Visit inline group entry fn visit_inline_group_entry( &mut self, occur: Option<&'b Occurrence<'a>>, g: &'b Group<'a>, ) -> Result { walk_inline_group_entry(self, occur, g) } /// Visit occurrences fn visit_occurrence(&mut self, _o: &'b Occurrence<'a>) -> Result { Ok(()) } /// Visit memberkey fn visit_memberkey(&mut self, mk: &'b MemberKey<'a>) -> Result { walk_memberkey(self, mk) } /// Visit genericargs fn visit_generic_args(&mut self, args: &'b GenericArgs<'a>) -> Result { walk_generic_args(self, args) } /// Visit genericarg fn visit_generic_arg(&mut self, arg: &'b GenericArg<'a>) -> Result { walk_generic_arg(self, arg) } /// Visit genericparams fn visit_generic_params(&mut self, params: &'b GenericParams<'a>) -> Result { walk_generic_params(self, params) } /// visit genericparam fn visit_generic_param(&mut self, param: &'b GenericParam<'a>) -> Result { walk_generic_param(self, param) } /// Visit nonmemberkey fn visit_nonmemberkey(&mut self, nmk: &'b NonMemberKey<'a>) -> Result { walk_nonmemberkey(self, nmk) } } /// Walk CDDL pub fn walk_cddl<'a, 'b, E, V>(visitor: &mut V, cddl: &'b CDDL<'a>) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { for rule in cddl.rules.iter() { visitor.visit_rule(rule)?; } Ok(()) } /// Walk rule pub fn walk_rule<'a, 'b, E, V>(visitor: &mut V, rule: &'b Rule<'a>) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { match rule { Rule::Type { rule, .. } => visitor.visit_type_rule(rule), Rule::Group { rule, .. } => visitor.visit_group_rule(rule), } } /// Walk type rule pub fn walk_type_rule<'a, 'b, E, V>(visitor: &mut V, tr: &'b TypeRule<'a>) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { visitor.visit_type(&tr.value) } /// Walk group rule pub fn walk_group_rule<'a, 'b, E, V>(visitor: &mut V, gr: &'b GroupRule<'a>) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { visitor.visit_group_entry(&gr.entry) } /// Walk type pub fn walk_type<'a, 'b, E, V>(visitor: &mut V, t: &'b Type<'a>) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { for tc in t.type_choices.iter() { visitor.visit_type_choice(tc)?; } Ok(()) } /// Walk type choice pub fn walk_type_choice<'a, 'b, E, V>(visitor: &mut V, tc: &'b TypeChoice<'a>) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { visitor.visit_type1(&tc.type1) } /// Walk type1 pub fn walk_type1<'a, 'b, E, V>(visitor: &mut V, t1: &'b Type1<'a>) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { if let Some(o) = &t1.operator { return visitor.visit_operator(t1, o); } visitor.visit_type2(&t1.type2) } /// Walk operator pub fn walk_operator<'a, 'b, E, V>( visitor: &mut V, target: &'b Type1<'a>, o: &'b Operator<'a>, ) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { visitor.visit_rangectlop(&o.operator, target, &o.type2) } /// Walk rangectlop pub fn walk_rangectlop<'a, 'b, E, V>( visitor: &mut V, op: &'b RangeCtlOp, target: &'b Type1<'a>, controller: &'b Type2<'a>, ) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { match op { RangeCtlOp::RangeOp { is_inclusive, .. } => { visitor.visit_range(&target.type2, controller, *is_inclusive) } RangeCtlOp::CtlOp { ctrl, .. } => { visitor.visit_control_operator(&target.type2, *ctrl, controller) } } } /// Walk range pub fn walk_range<'a, 'b, E, V>( visitor: &mut V, lower: &'b Type2<'a>, upper: &'b Type2<'a>, ) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { visitor.visit_type2(lower)?; visitor.visit_type2(upper) } /// Walk control operator pub fn walk_control_operator<'a, 'b, E, V>( visitor: &mut V, target: &'b Type2<'a>, controller: &'b Type2<'a>, ) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { visitor.visit_type2(target)?; visitor.visit_type2(controller) } /// Walk type2 pub fn walk_type2<'a, 'b, E, V>(visitor: &mut V, t2: &'b Type2<'a>) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { match t2 { Type2::Array { group, .. } => visitor.visit_group(group), Type2::Map { group, .. } => visitor.visit_group(group), Type2::ChoiceFromGroup { generic_args, ident, .. } => { if let Some(ga) = generic_args { visitor.visit_generic_args(ga)?; } visitor.visit_identifier(ident) } Type2::ChoiceFromInlineGroup { group, .. } => visitor.visit_group(group), Type2::TaggedData { t, .. } => visitor.visit_type(t), Type2::Typename { ident, .. } => visitor.visit_identifier(ident), Type2::Unwrap { generic_args, ident, .. } => { if let Some(ga) = generic_args { visitor.visit_generic_args(ga)?; } visitor.visit_identifier(ident) } Type2::ParenthesizedType { pt, .. } => visitor.visit_type(pt), Type2::B16ByteString { value, .. } => { visitor.visit_value(&Value::BYTE(ByteValue::B16(value.clone()))) } Type2::B64ByteString { value, .. } => { visitor.visit_value(&Value::BYTE(ByteValue::B64(value.clone()))) } Type2::UTF8ByteString { value, .. } => { visitor.visit_value(&Value::BYTE(ByteValue::UTF8(value.clone()))) } Type2::FloatValue { value, .. } => visitor.visit_value(&Value::FLOAT(*value)), Type2::IntValue { value, .. } => visitor.visit_value(&Value::INT(*value)), Type2::UintValue { value, .. } => visitor.visit_value(&Value::UINT(*value)), Type2::TextValue { value, .. } => visitor.visit_value(&Value::TEXT(value.clone())), _ => Ok(()), } } /// Walk group pub fn walk_group<'a, 'b, E, V>(visitor: &mut V, g: &'b Group<'a>) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { for gc in &g.group_choices { visitor.visit_group_choice(gc)?; } Ok(()) } /// Walk group choice pub fn walk_group_choice<'a, 'b, E, V>(visitor: &mut V, gc: &'b GroupChoice<'a>) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { for ge in &gc.group_entries { visitor.visit_group_entry(&ge.0)?; } Ok(()) } /// Walk group entry pub fn walk_group_entry<'a, 'b, E, V>(visitor: &mut V, entry: &'b GroupEntry<'a>) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { match entry { GroupEntry::ValueMemberKey { ge, .. } => visitor.visit_value_member_key_entry(ge), GroupEntry::TypeGroupname { ge, .. } => visitor.visit_type_groupname_entry(ge), GroupEntry::InlineGroup { occur, group, .. } => { visitor.visit_inline_group_entry(occur.as_ref(), group) } } } /// Walk value member key entry pub fn walk_value_member_key_entry<'a, 'b, E, V>( visitor: &mut V, entry: &'b ValueMemberKeyEntry<'a>, ) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { if let Some(occur) = &entry.occur { visitor.visit_occurrence(occur)?; } if let Some(mk) = &entry.member_key { visitor.visit_memberkey(mk)?; } visitor.visit_type(&entry.entry_type) } /// Walk typename/groupname entry pub fn walk_type_groupname_entry<'a, 'b, E, V>( visitor: &mut V, entry: &'b TypeGroupnameEntry<'a>, ) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { if let Some(o) = &entry.occur { visitor.visit_occurrence(o)?; } if let Some(ga) = &entry.generic_args { visitor.visit_generic_args(ga)?; } visitor.visit_identifier(&entry.name) } /// Walk inline group entry pub fn walk_inline_group_entry<'a, 'b, E, V>( visitor: &mut V, occur: Option<&'b Occurrence<'a>>, g: &'b Group<'a>, ) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { if let Some(o) = occur { visitor.visit_occurrence(o)?; } visitor.visit_group(g) } /// Walk memberkey pub fn walk_memberkey<'a, 'b, E, V>(visitor: &mut V, mk: &'b MemberKey<'a>) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { match mk { MemberKey::Type1 { t1, .. } => visitor.visit_type1(t1), MemberKey::Bareword { ident, .. } => visitor.visit_identifier(ident), MemberKey::Value { value, .. } => visitor.visit_value(value), MemberKey::NonMemberKey { non_member_key, .. } => visitor.visit_nonmemberkey(non_member_key), } } /// Walk genericargs pub fn walk_generic_args<'a, 'b, E, V>(visitor: &mut V, args: &'b GenericArgs<'a>) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { for arg in args.args.iter() { visitor.visit_generic_arg(arg)?; } Ok(()) } /// Walk genericarg pub fn walk_generic_arg<'a, 'b, E, V>(visitor: &mut V, arg: &'b GenericArg<'a>) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { visitor.visit_type1(&arg.arg) } /// Walk genericparams pub fn walk_generic_params<'a, 'b, E, V>( visitor: &mut V, params: &'b GenericParams<'a>, ) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { for param in params.params.iter() { visitor.visit_generic_param(param)?; } Ok(()) } /// Walk genericparams pub fn walk_generic_param<'a, 'b, E, V>(visitor: &mut V, param: &GenericParam<'a>) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { visitor.visit_identifier(¶m.param) } /// Walk nonmemberkey pub fn walk_nonmemberkey<'a, 'b, E, V>(visitor: &mut V, nmk: &'b NonMemberKey<'a>) -> Result where E: Error, V: Visitor<'a, 'b, E> + ?Sized, { match nmk { NonMemberKey::Group(group) => visitor.visit_group(group), NonMemberKey::Type(t) => visitor.visit_type(t), } }