From 62114efc694eda8ad8c729d0d14f3d2771ac38a4 Mon Sep 17 00:00:00 2001 From: Jeongik Cha Date: Thu, 14 Sep 2023 16:35:30 +0900 Subject: Import toml Bug: 277909042 Test: build Change-Id: Ic80f1a5397999a7e8e5737b651031007b4a75851 --- .cargo_vcs_info.json | 6 + Android.bp | 24 + Cargo.lock | 891 ++++++++++++++++ Cargo.toml | 159 +++ Cargo.toml.orig | 72 ++ LICENSE | 229 ++++ LICENSE-APACHE | 201 ++++ LICENSE-MIT | 25 + METADATA | 20 + MODULE_LICENSE_APACHE2 | 0 MODULE_LICENSE_MIT | 0 OWNERS | 1 + README.md | 29 + examples/decode.rs | 54 + examples/enum_external.rs | 45 + examples/toml2json.rs | 47 + src/de.rs | 322 ++++++ src/edit.rs | 91 ++ src/fmt.rs | 60 ++ src/lib.rs | 182 ++++ src/macros.rs | 460 ++++++++ src/map.rs | 609 +++++++++++ src/ser.rs | 1087 +++++++++++++++++++ src/table.rs | 114 ++ src/value.rs | 1455 ++++++++++++++++++++++++++ tests/decoder.rs | 67 ++ tests/decoder_compliance.rs | 21 + tests/encoder.rs | 81 ++ tests/encoder_compliance.rs | 14 + tests/testsuite/de_errors.rs | 460 ++++++++ tests/testsuite/display.rs | 116 ++ tests/testsuite/display_tricky.rs | 55 + tests/testsuite/enum_external_deserialize.rs | 320 ++++++ tests/testsuite/float.rs | 80 ++ tests/testsuite/formatting.rs | 54 + tests/testsuite/macros.rs | 368 +++++++ tests/testsuite/main.rs | 15 + tests/testsuite/pretty.rs | 184 ++++ tests/testsuite/serde.rs | 1096 +++++++++++++++++++ tests/testsuite/spanned.rs | 261 +++++ tests/testsuite/spanned_impls.rs | 41 + tests/testsuite/tables_last.rs | 162 +++ 42 files changed, 9578 insertions(+) create mode 100644 .cargo_vcs_info.json create mode 100644 Android.bp create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Cargo.toml.orig create mode 100644 LICENSE create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 METADATA create mode 100644 MODULE_LICENSE_APACHE2 create mode 100644 MODULE_LICENSE_MIT create mode 100644 OWNERS create mode 100644 README.md create mode 100644 examples/decode.rs create mode 100644 examples/enum_external.rs create mode 100644 examples/toml2json.rs create mode 100644 src/de.rs create mode 100644 src/edit.rs create mode 100644 src/fmt.rs create mode 100644 src/lib.rs create mode 100644 src/macros.rs create mode 100644 src/map.rs create mode 100644 src/ser.rs create mode 100644 src/table.rs create mode 100644 src/value.rs create mode 100644 tests/decoder.rs create mode 100644 tests/decoder_compliance.rs create mode 100644 tests/encoder.rs create mode 100644 tests/encoder_compliance.rs create mode 100644 tests/testsuite/de_errors.rs create mode 100644 tests/testsuite/display.rs create mode 100644 tests/testsuite/display_tricky.rs create mode 100644 tests/testsuite/enum_external_deserialize.rs create mode 100644 tests/testsuite/float.rs create mode 100644 tests/testsuite/formatting.rs create mode 100644 tests/testsuite/macros.rs create mode 100644 tests/testsuite/main.rs create mode 100644 tests/testsuite/pretty.rs create mode 100644 tests/testsuite/serde.rs create mode 100644 tests/testsuite/spanned.rs create mode 100644 tests/testsuite/spanned_impls.rs create mode 100644 tests/testsuite/tables_last.rs diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..9ceba26 --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "59ae12d8c1b3c79df6ae7a29d1841ae860cd7fb6" + }, + "path_in_vcs": "crates/toml" +} \ No newline at end of file diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..4abb67c --- /dev/null +++ b/Android.bp @@ -0,0 +1,24 @@ +// This file is generated by cargo2android.py --run. +// Do not modify this file as changes will be overridden on upgrade. + + + +rust_library_host { + name: "libtoml", + crate_name: "toml", + cargo_env_compat: true, + cargo_pkg_version: "0.7.6", + srcs: ["src/lib.rs"], + edition: "2021", + features: [ + "default", + "display", + "parse", + ], + rustlibs: [ + "libserde", + "libserde_spanned", + "libtoml_datetime", + "libtoml_edit", + ], +} diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..7bf0506 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,891 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6342bd4f5a1205d7f41e94a41a901f5647c938cdfa96036338e8533c9d6c2450" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "memchr", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[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.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "clap" +version = "4.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39" +dependencies = [ + "bitflags", + "clap_derive", + "clap_lex", + "is-terminal", + "once_cell", + "strsim", + "termcolor", +] + +[[package]] +name = "clap_derive" +version = "4.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.105", +] + +[[package]] +name = "clap_lex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "crossbeam-utils" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "equivalent" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "globset" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "ignore" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +dependencies = [ + "crossbeam-utils", + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + +[[package]] +name = "include_dir" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +dependencies = [ + "libc", + "windows-sys 0.42.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "itoa" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.142" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" + +[[package]] +name = "libtest-mimic" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7b603516767d1ab23d0de09d023e62966c3322f7148297c35cf3d97aa8b37fa" +dependencies = [ + "clap", + "termcolor", + "threadpool", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b64f40e5e03e0d54f03845c8197d0291253cdbedfb1cb46b13c2c117554a9f4c" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi 0.1.19", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" + +[[package]] +name = "os_str_bytes" +version = "6.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" + +[[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.105", + "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.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "rustix" +version = "0.37.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aae838e49b3d63e9274e1c01833cc8139d3fec468c3b84688c628f44b1ae11d" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", +] + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.160" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.160" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "serde_json" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + +[[package]] +name = "similar" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ac7f900db32bf3fd12e0117dd3dc4da74bc52ebaac97f39668446d89694803" + +[[package]] +name = "snapbox" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6bccd62078347f89a914e3004d94582e13824d4e3d8a816317862884c423835" +dependencies = [ + "anstream", + "anstyle", + "normalize-line-endings", + "similar", + "snapbox-macros", +] + +[[package]] +name = "snapbox-macros" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaaf09df9f0eeae82be96290918520214530e738a7fe5a351b0f24cf77c0ca31" +dependencies = [ + "anstream", +] + +[[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.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +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 = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "toml" +version = "0.7.6" +dependencies = [ + "indexmap", + "serde", + "serde_json", + "serde_spanned", + "snapbox", + "toml-test-harness", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml-test" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37351256790aa1dbd6d60f4ff08e55e7f372e292f3e9040d6e077463d9a779c3" +dependencies = [ + "chrono", + "serde", + "serde_json", +] + +[[package]] +name = "toml-test-data" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f351b6d6005ee802b0d4a53ca1cdf05636f441df4d299e62cba57f1da52646" +dependencies = [ + "include_dir", +] + +[[package]] +name = "toml-test-harness" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e00fda5710922fe6b3005bf6a5050c303d6f9625249c37b7386e8818f4af675" +dependencies = [ + "ignore", + "libtest-mimic", + "toml-test", + "toml-test-data", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +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-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.1", + "windows_aarch64_msvc 0.42.1", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm 0.42.1", + "windows_x86_64_msvc 0.42.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.1", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.1", + "windows_aarch64_msvc 0.42.1", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm 0.42.1", + "windows_x86_64_msvc 0.42.1", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winnow" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..02f89eb --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,159 @@ +# 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 = "2021" +rust-version = "1.64.0" +name = "toml" +version = "0.7.6" +authors = ["Alex Crichton "] +include = [ + "build.rs", + "src/**/*", + "Cargo.toml", + "Cargo.lock", + "LICENSE*", + "README.md", + "benches/**/*", + "examples/**/*", + "tests/**/*", +] +description = """ +A native Rust encoder and decoder of TOML-formatted files and streams. Provides +implementations of the standard Serialize/Deserialize traits for TOML data to +facilitate deserializing and serializing Rust structures. +""" +homepage = "https://github.com/toml-rs/toml" +readme = "README.md" +keywords = [ + "encoding", + "toml", +] +categories = [ + "encoding", + "parser-implementations", + "parsing", + "config", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/toml-rs/toml" + +[package.metadata.docs.rs] +rustdoc-args = [ + "--cfg", + "docsrs", +] + +[[package.metadata.release.pre-release-replacements]] +file = "CHANGELOG.md" +min = 1 +replace = "{{version}}" +search = "Unreleased" + +[[package.metadata.release.pre-release-replacements]] +exactly = 1 +file = "CHANGELOG.md" +replace = "...{{tag_name}}" +search = '\.\.\.HEAD' + +[[package.metadata.release.pre-release-replacements]] +file = "CHANGELOG.md" +min = 1 +replace = "{{date}}" +search = "ReleaseDate" + +[[package.metadata.release.pre-release-replacements]] +exactly = 1 +file = "CHANGELOG.md" +replace = """ + +## [Unreleased] - ReleaseDate +""" +search = "" + +[[package.metadata.release.pre-release-replacements]] +exactly = 1 +file = "CHANGELOG.md" +replace = """ + +[Unreleased]: https://github.com/toml-rs/toml/compare/{{tag_name}}...HEAD""" +search = "" + +[[example]] +name = "decode" +required-features = [ + "parse", + "display", +] + +[[example]] +name = "enum_external" +required-features = [ + "parse", + "display", +] + +[[example]] +name = "toml2json" +required-features = [ + "parse", + "display", +] + +[[test]] +name = "decoder_compliance" +harness = false + +[[test]] +name = "encoder_compliance" +harness = false + +[dependencies.indexmap] +version = "2.0.0" +optional = true + +[dependencies.serde] +version = "1.0.145" + +[dependencies.serde_spanned] +version = "0.6.3" +features = ["serde"] + +[dependencies.toml_datetime] +version = "0.6.3" +features = ["serde"] + +[dependencies.toml_edit] +version = "0.19.12" +features = ["serde"] +optional = true + +[dev-dependencies.serde] +version = "1.0.160" +features = ["derive"] + +[dev-dependencies.serde_json] +version = "1.0.96" + +[dev-dependencies.snapbox] +version = "0.4.11" + +[dev-dependencies.toml-test-harness] +version = "0.4.3" + +[features] +default = [ + "parse", + "display", +] +display = ["dep:toml_edit"] +parse = ["dep:toml_edit"] +preserve_order = ["indexmap"] diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..424f035 --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,72 @@ +[package] +name = "toml" +version = "0.7.6" +keywords = ["encoding", "toml"] +categories = ["encoding", "parser-implementations", "parsing", "config"] +description = """ +A native Rust encoder and decoder of TOML-formatted files and streams. Provides +implementations of the standard Serialize/Deserialize traits for TOML data to +facilitate deserializing and serializing Rust structures. +""" +authors = ["Alex Crichton "] +repository.workspace = true +homepage.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true +include.workspace = true + +[package.metadata.release] +pre-release-replacements = [ + {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1}, + {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1}, + {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1}, + {file="CHANGELOG.md", search="", replace="\n## [Unreleased] - ReleaseDate\n", exactly=1}, + {file="CHANGELOG.md", search="", replace="\n[Unreleased]: https://github.com/toml-rs/toml/compare/{{tag_name}}...HEAD", exactly=1}, +] + +[package.metadata.docs.rs] +rustdoc-args = ["--cfg", "docsrs"] + +[features] +default = ["parse", "display"] +parse = ["dep:toml_edit"] +display = ["dep:toml_edit"] + +# Use indexmap rather than BTreeMap as the map type of toml::Value. +# This allows data to be read into a Value and written back to a TOML string +# while preserving the order of map keys in the input. +preserve_order = ["indexmap"] + +[dependencies] +serde = "1.0.145" +indexmap = { version = "2.0.0", optional = true } +toml_edit = { version = "0.19.12", path = "../toml_edit", features = ["serde"], optional = true } +toml_datetime = { version = "0.6.3", path = "../toml_datetime", features = ["serde"] } +serde_spanned = { version = "0.6.3", path = "../serde_spanned", features = ["serde"] } + +[dev-dependencies] +serde = { version = "1.0.160", features = ["derive"] } +serde_json = "1.0.96" +toml-test-harness = "0.4.3" +snapbox = "0.4.11" + +[[test]] +name = "decoder_compliance" +harness = false + +[[test]] +name = "encoder_compliance" +harness = false + +[[example]] +name = "decode" +required-features = ["parse", "display"] + +[[example]] +name = "enum_external" +required-features = ["parse", "display"] + +[[example]] +name = "toml2json" +required-features = ["parse", "display"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4e83254 --- /dev/null +++ b/LICENSE @@ -0,0 +1,229 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +--- + +Copyright (c) 2014 Alex Crichton + +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. diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..39e0ed6 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2014 Alex Crichton + +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. diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..fad1206 --- /dev/null +++ b/METADATA @@ -0,0 +1,20 @@ +name: "toml" +description: "()" +third_party { + identifier { + type: "crates.io" + value: "https://crates.io/crates/toml" + } + identifier { + type: "Archive" + value: "https://static.crates.io/crates/toml/toml-0.7.6.crate" + } + version: "0.7.6" + # Dual-licensed, using the least restrictive per go/thirdpartylicenses#same. + license_type: NOTICE + last_upgrade_date { + year: 2023 + month: 8 + day: 23 + } +} diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT new file mode 100644 index 0000000..e69de29 diff --git a/OWNERS b/OWNERS new file mode 100644 index 0000000..45dc4dd --- /dev/null +++ b/OWNERS @@ -0,0 +1 @@ +include platform/prebuilts/rust:master:/OWNERS diff --git a/README.md b/README.md new file mode 100644 index 0000000..82960dd --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# toml + +[![Latest Version](https://img.shields.io/crates/v/toml.svg)](https://crates.io/crates/toml) +[![Documentation](https://docs.rs/toml/badge.svg)](https://docs.rs/toml) + +A [serde]-compatible [TOML][toml] decoder and encoder for Rust. + +For format-preserving edits or finer control over output, see [toml_edit] + +[serde]: https://serde.rs/ +[toml]: https://github.com/toml-lang/toml +[toml_edit]: https://docs.rs/toml_edit + +# License + +This project is licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or + http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in toml-rs by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/examples/decode.rs b/examples/decode.rs new file mode 100644 index 0000000..348e911 --- /dev/null +++ b/examples/decode.rs @@ -0,0 +1,54 @@ +//! An example showing off the usage of `Deserialize` to automatically decode +//! TOML into a Rust `struct` + +#![deny(warnings)] +#![allow(dead_code)] + +use serde::Deserialize; + +/// This is what we're going to decode into. Each field is optional, meaning +/// that it doesn't have to be present in TOML. +#[derive(Debug, Deserialize)] +struct Config { + global_string: Option, + global_integer: Option, + server: Option, + peers: Option>, +} + +/// Sub-structs are decoded from tables, so this will decode from the `[server]` +/// table. +/// +/// Again, each field is optional, meaning they don't have to be present. +#[derive(Debug, Deserialize)] +struct ServerConfig { + ip: Option, + port: Option, +} + +#[derive(Debug, Deserialize)] +struct PeerConfig { + ip: Option, + port: Option, +} + +fn main() { + let toml_str = r#" + global_string = "test" + global_integer = 5 + + [server] + ip = "127.0.0.1" + port = 80 + + [[peers]] + ip = "127.0.0.1" + port = 8080 + + [[peers]] + ip = "127.0.0.1" + "#; + + let decoded: Config = toml::from_str(toml_str).unwrap(); + println!("{:#?}", decoded); +} diff --git a/examples/enum_external.rs b/examples/enum_external.rs new file mode 100644 index 0000000..edac7d6 --- /dev/null +++ b/examples/enum_external.rs @@ -0,0 +1,45 @@ +//! An example showing off the usage of `Deserialize` to automatically decode +//! TOML into a Rust `struct`, with enums. + +#![deny(warnings)] +#![allow(dead_code)] + +use serde::Deserialize; + +/// This is what we're going to decode into. +#[derive(Debug, Deserialize)] +struct Config { + plain: MyEnum, + plain_table: MyEnum, + tuple: MyEnum, + #[serde(rename = "struct")] + structv: MyEnum, + newtype: MyEnum, + my_enum: Vec, +} + +#[derive(Debug, Deserialize)] +enum MyEnum { + Plain, + Tuple(i64, bool), + NewType(String), + Struct { value: i64 }, +} + +fn main() { + let toml_str = r#" + plain = "Plain" + plain_table = { Plain = {} } + tuple = { Tuple = { 0 = 123, 1 = true } } + struct = { Struct = { value = 123 } } + newtype = { NewType = "value" } + my_enum = [ + { Plain = {} }, + { Tuple = { 0 = 123, 1 = true } }, + { NewType = "value" }, + { Struct = { value = 123 } } + ]"#; + + let decoded: Config = toml::from_str(toml_str).unwrap(); + println!("{:#?}", decoded); +} diff --git a/examples/toml2json.rs b/examples/toml2json.rs new file mode 100644 index 0000000..3660611 --- /dev/null +++ b/examples/toml2json.rs @@ -0,0 +1,47 @@ +#![deny(warnings)] + +use std::env; +use std::fs::File; +use std::io; +use std::io::prelude::*; + +use serde_json::Value as Json; +use toml::Value as Toml; + +fn main() { + let mut args = env::args(); + let mut input = String::new(); + if args.len() > 1 { + let name = args.nth(1).unwrap(); + File::open(name) + .and_then(|mut f| f.read_to_string(&mut input)) + .unwrap(); + } else { + io::stdin().read_to_string(&mut input).unwrap(); + } + + match input.parse() { + Ok(toml) => { + let json = convert(toml); + println!("{}", serde_json::to_string_pretty(&json).unwrap()); + } + Err(error) => println!("failed to parse TOML: {}", error), + } +} + +fn convert(toml: Toml) -> Json { + match toml { + Toml::String(s) => Json::String(s), + Toml::Integer(i) => Json::Number(i.into()), + Toml::Float(f) => { + let n = serde_json::Number::from_f64(f).expect("float infinite and nan not allowed"); + Json::Number(n) + } + Toml::Boolean(b) => Json::Bool(b), + Toml::Array(arr) => Json::Array(arr.into_iter().map(convert).collect()), + Toml::Table(table) => { + Json::Object(table.into_iter().map(|(k, v)| (k, convert(v))).collect()) + } + Toml::Datetime(dt) => Json::String(dt.to_string()), + } +} diff --git a/src/de.rs b/src/de.rs new file mode 100644 index 0000000..9eb4c41 --- /dev/null +++ b/src/de.rs @@ -0,0 +1,322 @@ +//! Deserializing TOML into Rust structures. +//! +//! This module contains all the Serde support for deserializing TOML documents +//! into Rust structures. Note that some top-level functions here are also +//! provided at the top of the crate. + +/// Deserializes a string into a type. +/// +/// This function will attempt to interpret `s` as a TOML document and +/// deserialize `T` from the document. +/// +/// To deserializes TOML values, instead of documents, see [`ValueDeserializer`]. +/// +/// # Examples +/// +/// ``` +/// use serde::Deserialize; +/// +/// #[derive(Deserialize)] +/// struct Config { +/// title: String, +/// owner: Owner, +/// } +/// +/// #[derive(Deserialize)] +/// struct Owner { +/// name: String, +/// } +/// +/// let config: Config = toml::from_str(r#" +/// title = 'TOML Example' +/// +/// [owner] +/// name = 'Lisa' +/// "#).unwrap(); +/// +/// assert_eq!(config.title, "TOML Example"); +/// assert_eq!(config.owner.name, "Lisa"); +/// ``` +#[cfg(feature = "parse")] +pub fn from_str(s: &'_ str) -> Result +where + T: serde::de::DeserializeOwned, +{ + T::deserialize(Deserializer::new(s)) +} + +/// Errors that can occur when deserializing a type. +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Error { + inner: crate::edit::de::Error, +} + +impl Error { + fn new(inner: crate::edit::de::Error) -> Self { + Self { inner } + } + + pub(crate) fn add_key(&mut self, key: String) { + self.inner.add_key(key) + } + + /// What went wrong + pub fn message(&self) -> &str { + self.inner.message() + } + + /// The start/end index into the original document where the error occurred + #[cfg(feature = "parse")] + pub fn span(&self) -> Option> { + self.inner.span() + } +} + +impl serde::de::Error for Error { + fn custom(msg: T) -> Self + where + T: std::fmt::Display, + { + Error::new(crate::edit::de::Error::custom(msg)) + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.inner.fmt(f) + } +} + +impl std::error::Error for Error {} + +/// Deserialization TOML document +/// +/// To deserializes TOML values, instead of documents, see [`ValueDeserializer`]. +#[cfg(feature = "parse")] +pub struct Deserializer<'a> { + input: &'a str, +} + +#[cfg(feature = "parse")] +impl<'a> Deserializer<'a> { + /// Deserialization implementation for TOML. + pub fn new(input: &'a str) -> Self { + Self { input } + } +} + +#[cfg(feature = "parse")] +impl<'de, 'a> serde::Deserializer<'de> for Deserializer<'a> { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + let inner = self + .input + .parse::() + .map_err(Error::new)?; + inner.deserialize_any(visitor).map_err(Error::new) + } + + // `None` is interpreted as a missing field so be sure to implement `Some` + // as a present field. + fn deserialize_option(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + let inner = self + .input + .parse::() + .map_err(Error::new)?; + inner.deserialize_option(visitor).map_err(Error::new) + } + + fn deserialize_newtype_struct( + self, + name: &'static str, + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + let inner = self + .input + .parse::() + .map_err(Error::new)?; + inner + .deserialize_newtype_struct(name, visitor) + .map_err(Error::new) + } + + fn deserialize_struct( + self, + name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + let inner = self + .input + .parse::() + .map_err(Error::new)?; + inner + .deserialize_struct(name, fields, visitor) + .map_err(Error::new) + } + + // Called when the type to deserialize is an enum, as opposed to a field in the type. + fn deserialize_enum( + self, + name: &'static str, + variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + let inner = self + .input + .parse::() + .map_err(Error::new)?; + inner + .deserialize_enum(name, variants, visitor) + .map_err(Error::new) + } + + serde::forward_to_deserialize_any! { + bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq + bytes byte_buf map unit + ignored_any unit_struct tuple_struct tuple identifier + } +} + +/// Deserialization TOML [value][crate::Value] +/// +/// # Example +/// +/// ``` +/// use serde::Deserialize; +/// +/// #[derive(Deserialize)] +/// struct Config { +/// title: String, +/// owner: Owner, +/// } +/// +/// #[derive(Deserialize)] +/// struct Owner { +/// name: String, +/// } +/// +/// let config = Config::deserialize(toml::de::ValueDeserializer::new( +/// r#"{ title = 'TOML Example', owner = { name = 'Lisa' } }"# +/// )).unwrap(); +/// +/// assert_eq!(config.title, "TOML Example"); +/// assert_eq!(config.owner.name, "Lisa"); +/// ``` +#[cfg(feature = "parse")] +pub struct ValueDeserializer<'a> { + input: &'a str, +} + +#[cfg(feature = "parse")] +impl<'a> ValueDeserializer<'a> { + /// Deserialization implementation for TOML. + pub fn new(input: &'a str) -> Self { + Self { input } + } +} + +#[cfg(feature = "parse")] +impl<'de, 'a> serde::Deserializer<'de> for ValueDeserializer<'a> { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + let inner = self + .input + .parse::() + .map_err(Error::new)?; + inner.deserialize_any(visitor).map_err(Error::new) + } + + // `None` is interpreted as a missing field so be sure to implement `Some` + // as a present field. + fn deserialize_option(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + let inner = self + .input + .parse::() + .map_err(Error::new)?; + inner.deserialize_option(visitor).map_err(Error::new) + } + + fn deserialize_newtype_struct( + self, + name: &'static str, + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + let inner = self + .input + .parse::() + .map_err(Error::new)?; + inner + .deserialize_newtype_struct(name, visitor) + .map_err(Error::new) + } + + fn deserialize_struct( + self, + name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + let inner = self + .input + .parse::() + .map_err(Error::new)?; + inner + .deserialize_struct(name, fields, visitor) + .map_err(Error::new) + } + + // Called when the type to deserialize is an enum, as opposed to a field in the type. + fn deserialize_enum( + self, + name: &'static str, + variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + let inner = self + .input + .parse::() + .map_err(Error::new)?; + inner + .deserialize_enum(name, variants, visitor) + .map_err(Error::new) + } + + serde::forward_to_deserialize_any! { + bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq + bytes byte_buf map unit + ignored_any unit_struct tuple_struct tuple identifier + } +} diff --git a/src/edit.rs b/src/edit.rs new file mode 100644 index 0000000..90fb284 --- /dev/null +++ b/src/edit.rs @@ -0,0 +1,91 @@ +#[cfg(feature = "parse")] +pub(crate) mod de { + pub(crate) use toml_edit::de::Error; +} + +#[cfg(not(feature = "parse"))] +pub(crate) mod de { + /// Errors that can occur when deserializing a type. + #[derive(Debug, Clone, PartialEq, Eq)] + pub struct Error { + inner: String, + } + + impl Error { + /// Add key while unwinding + pub fn add_key(&mut self, _key: String) {} + + /// What went wrong + pub fn message(&self) -> &str { + self.inner.as_str() + } + } + + impl serde::de::Error for Error { + fn custom(msg: T) -> Self + where + T: std::fmt::Display, + { + Error { + inner: msg.to_string(), + } + } + } + + impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.inner.fmt(f) + } + } + + impl std::error::Error for Error {} +} + +#[cfg(feature = "display")] +pub(crate) mod ser { + pub(crate) use toml_edit::ser::Error; +} + +#[cfg(not(feature = "display"))] +pub(crate) mod ser { + #[derive(Debug, Clone, PartialEq, Eq)] + #[non_exhaustive] + pub(crate) enum Error { + UnsupportedType(Option<&'static str>), + UnsupportedNone, + KeyNotString, + Custom(String), + } + + impl Error { + pub(crate) fn custom(msg: T) -> Self + where + T: std::fmt::Display, + { + Error::Custom(msg.to_string()) + } + } + + impl serde::ser::Error for Error { + fn custom(msg: T) -> Self + where + T: std::fmt::Display, + { + Self::custom(msg) + } + } + + impl std::fmt::Display for Error { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::UnsupportedType(Some(t)) => write!(formatter, "unsupported {t} type"), + Self::UnsupportedType(None) => write!(formatter, "unsupported rust type"), + Self::UnsupportedNone => "unsupported None value".fmt(formatter), + Self::KeyNotString => "map key was not a string".fmt(formatter), + Self::Custom(s) => s.fmt(formatter), + } + } + } + + impl std::error::Error for Error {} +} diff --git a/src/fmt.rs b/src/fmt.rs new file mode 100644 index 0000000..0e96bf0 --- /dev/null +++ b/src/fmt.rs @@ -0,0 +1,60 @@ +#[derive(Copy, Clone, Default)] +pub(crate) struct DocumentFormatter { + pub(crate) multiline_array: bool, +} + +impl toml_edit::visit_mut::VisitMut for DocumentFormatter { + fn visit_document_mut(&mut self, node: &mut toml_edit::Document) { + toml_edit::visit_mut::visit_document_mut(self, node); + } + + fn visit_item_mut(&mut self, node: &mut toml_edit::Item) { + let other = std::mem::take(node); + let other = match other.into_table().map(toml_edit::Item::Table) { + Ok(i) => i, + Err(i) => i, + }; + let other = match other + .into_array_of_tables() + .map(toml_edit::Item::ArrayOfTables) + { + Ok(i) => i, + Err(i) => i, + }; + *node = other; + + toml_edit::visit_mut::visit_item_mut(self, node); + } + + fn visit_table_mut(&mut self, node: &mut toml_edit::Table) { + node.decor_mut().clear(); + + // Empty tables could be semantically meaningful, so make sure they are not implicit + if !node.is_empty() { + node.set_implicit(true); + } + + toml_edit::visit_mut::visit_table_mut(self, node); + } + + fn visit_value_mut(&mut self, node: &mut toml_edit::Value) { + node.decor_mut().clear(); + + toml_edit::visit_mut::visit_value_mut(self, node); + } + + fn visit_array_mut(&mut self, node: &mut toml_edit::Array) { + toml_edit::visit_mut::visit_array_mut(self, node); + + if !self.multiline_array || (0..=1).contains(&node.len()) { + node.set_trailing(""); + node.set_trailing_comma(false); + } else { + for item in node.iter_mut() { + item.decor_mut().set_prefix("\n "); + } + node.set_trailing("\n"); + node.set_trailing_comma(true); + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..61e6a4c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,182 @@ +//! A [serde]-compatible [TOML]-parsing library +//! +//! TOML itself is a simple, ergonomic, and readable configuration format: +//! +//! ```toml +//! [package] +//! name = "toml" +//! version = "0.4.2" +//! authors = ["Alex Crichton "] +//! +//! [dependencies] +//! serde = "1.0" +//! ``` +//! +//! The TOML format tends to be relatively common throughout the Rust community +//! for configuration, notably being used by [Cargo], Rust's package manager. +//! +//! ## TOML values +//! +//! A TOML document is represented with the [`Table`] type which maps `String` to the [`Value`] enum: +//! +//! ```rust +//! # use toml::value::{Datetime, Array, Table}; +//! pub enum Value { +//! String(String), +//! Integer(i64), +//! Float(f64), +//! Boolean(bool), +//! Datetime(Datetime), +//! Array(Array), +//! Table(Table), +//! } +//! ``` +//! +//! ## Parsing TOML +//! +//! The easiest way to parse a TOML document is via the [`Table`] type: +//! +#![cfg_attr(not(feature = "parse"), doc = " ```ignore")] +#![cfg_attr(feature = "parse", doc = " ```")] +//! use toml::Table; +//! +//! let value = "foo = 'bar'".parse::().unwrap(); +//! +//! assert_eq!(value["foo"].as_str(), Some("bar")); +//! ``` +//! +//! The [`Table`] type implements a number of convenience methods and +//! traits; the example above uses [`FromStr`] to parse a [`str`] into a +//! [`Table`]. +//! +//! ## Deserialization and Serialization +//! +//! This crate supports [`serde`] 1.0 with a number of +//! implementations of the `Deserialize`, `Serialize`, `Deserializer`, and +//! `Serializer` traits. Namely, you'll find: +//! +//! * `Deserialize for Table` +//! * `Serialize for Table` +//! * `Deserialize for Value` +//! * `Serialize for Value` +//! * `Deserialize for Datetime` +//! * `Serialize for Datetime` +//! * `Deserializer for de::Deserializer` +//! * `Serializer for ser::Serializer` +//! * `Deserializer for Table` +//! * `Deserializer for Value` +//! +//! This means that you can use Serde to deserialize/serialize the +//! [`Table`] type as well as [`Value`] and [`Datetime`] type in this crate. You can also +//! use the [`Deserializer`], [`Serializer`], or [`Table`] type itself to act as +//! a deserializer/serializer for arbitrary types. +//! +//! An example of deserializing with TOML is: +//! +#![cfg_attr(not(feature = "parse"), doc = " ```ignore")] +#![cfg_attr(feature = "parse", doc = " ```")] +//! use serde::Deserialize; +//! +//! #[derive(Deserialize)] +//! struct Config { +//! ip: String, +//! port: Option, +//! keys: Keys, +//! } +//! +//! #[derive(Deserialize)] +//! struct Keys { +//! github: String, +//! travis: Option, +//! } +//! +//! let config: Config = toml::from_str(r#" +//! ip = '127.0.0.1' +//! +//! [keys] +//! github = 'xxxxxxxxxxxxxxxxx' +//! travis = 'yyyyyyyyyyyyyyyyy' +//! "#).unwrap(); +//! +//! assert_eq!(config.ip, "127.0.0.1"); +//! assert_eq!(config.port, None); +//! assert_eq!(config.keys.github, "xxxxxxxxxxxxxxxxx"); +//! assert_eq!(config.keys.travis.as_ref().unwrap(), "yyyyyyyyyyyyyyyyy"); +//! ``` +//! +//! You can serialize types in a similar fashion: +//! +#![cfg_attr(not(feature = "display"), doc = " ```ignore")] +#![cfg_attr(feature = "display", doc = " ```")] +//! use serde::Serialize; +//! +//! #[derive(Serialize)] +//! struct Config { +//! ip: String, +//! port: Option, +//! keys: Keys, +//! } +//! +//! #[derive(Serialize)] +//! struct Keys { +//! github: String, +//! travis: Option, +//! } +//! +//! let config = Config { +//! ip: "127.0.0.1".to_string(), +//! port: None, +//! keys: Keys { +//! github: "xxxxxxxxxxxxxxxxx".to_string(), +//! travis: Some("yyyyyyyyyyyyyyyyy".to_string()), +//! }, +//! }; +//! +//! let toml = toml::to_string(&config).unwrap(); +//! ``` +//! +//! [TOML]: https://github.com/toml-lang/toml +//! [Cargo]: https://crates.io/ +//! [`serde`]: https://serde.rs/ +//! [serde]: https://serde.rs/ + +#![deny(missing_docs)] +#![warn(rust_2018_idioms)] +// Makes rustc abort compilation if there are any unsafe blocks in the crate. +// Presence of this annotation is picked up by tools such as cargo-geiger +// and lets them ensure that there is indeed no unsafe code as opposed to +// something they couldn't detect (e.g. unsafe added via macro expansion, etc). +#![forbid(unsafe_code)] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] + +pub mod map; +pub mod value; + +pub mod de; +pub mod ser; + +#[doc(hidden)] +pub mod macros; + +mod edit; +#[cfg(feature = "display")] +mod fmt; +mod table; + +#[cfg(feature = "parse")] +#[doc(inline)] +pub use crate::de::{from_str, Deserializer}; +#[cfg(feature = "display")] +#[doc(inline)] +pub use crate::ser::{to_string, to_string_pretty, Serializer}; +#[doc(inline)] +pub use crate::value::Value; + +pub use serde_spanned::Spanned; +pub use table::Table; + +// Shortcuts for the module doc-comment +#[allow(unused_imports)] +use core::str::FromStr; +#[allow(unused_imports)] +use toml_datetime::Datetime; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..d86cc52 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,460 @@ +pub use serde::de::{Deserialize, IntoDeserializer}; + +use crate::value::{Array, Table, Value}; + +/// Construct a [`Table`] from TOML syntax. +/// +/// ```rust +/// let cargo_toml = toml::toml! { +/// [package] +/// name = "toml" +/// version = "0.4.5" +/// authors = ["Alex Crichton "] +/// +/// [badges] +/// travis-ci = { repository = "alexcrichton/toml-rs" } +/// +/// [dependencies] +/// serde = "1.0" +/// +/// [dev-dependencies] +/// serde_derive = "1.0" +/// serde_json = "1.0" +/// }; +/// +/// println!("{:#?}", cargo_toml); +/// ``` +#[macro_export] +macro_rules! toml { + ($($toml:tt)+) => {{ + let table = $crate::value::Table::new(); + let mut root = $crate::Value::Table(table); + $crate::toml_internal!(@toplevel root [] $($toml)+); + match root { + $crate::Value::Table(table) => table, + _ => unreachable!(), + } + }}; +} + +// TT-muncher to parse TOML syntax into a toml::Value. +// +// @toplevel -- Parse tokens outside of an inline table or inline array. In +// this state, `[table headers]` and `[[array headers]]` are +// allowed and `key = value` pairs are not separated by commas. +// +// @topleveldatetime -- Helper to parse a Datetime from string and insert it +// into a table, continuing in the @toplevel state. +// +// @path -- Turn a path segment into a string. Segments that look like idents +// are stringified, while quoted segments like `"cfg(windows)"` +// are not. +// +// @value -- Parse the value part of a `key = value` pair, which may be a +// primitive or inline table or inline array. +// +// @table -- Parse the contents of an inline table, returning them as a +// toml::Value::Table. +// +// @tabledatetime -- Helper to parse a Datetime from string and insert it +// into a table, continuing in the @table state. +// +// @array -- Parse the contents of an inline array, returning them as a +// toml::Value::Array. +// +// @arraydatetime -- Helper to parse a Datetime from string and push it into +// an array, continuing in the @array state. +// +// @trailingcomma -- Helper to append a comma to a sequence of tokens if the +// sequence is non-empty and does not already end in a trailing +// comma. +// +#[macro_export] +#[doc(hidden)] +macro_rules! toml_internal { + // Base case, no elements remaining. + (@toplevel $root:ident [$($path:tt)*]) => {}; + + // Parse negative number `key = -value`. + (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = - $v:tt $($rest:tt)*) => { + $crate::toml_internal!(@toplevel $root [$($path)*] $($($k)-+).+ = (-$v) $($rest)*); + }; + + // Parse positive number `key = +value`. + (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = + $v:tt $($rest:tt)*) => { + $crate::toml_internal!(@toplevel $root [$($path)*] $($($k)-+).+ = ($v) $($rest)*); + }; + + // Parse offset datetime `key = 1979-05-27T00:32:00.999999-07:00`. + (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt . $frac:tt - $tzh:tt : $tzm:tt $($rest:tt)*) => { + $crate::toml_internal!(@topleveldatetime $root [$($path)*] $($($k)-+).+ = ($yr - $mo - $dhr : $min : $sec . $frac - $tzh : $tzm) $($rest)*); + }; + // Space instead of T. + (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt . $frac:tt - $tzh:tt : $tzm:tt $($rest:tt)*) => { + $crate::toml_internal!(@topleveldatetime $root [$($path)*] $($($k)-+).+ = ($yr - $mo - $day T $hr : $min : $sec . $frac - $tzh : $tzm) $($rest)*); + }; + + // Parse offset datetime `key = 1979-05-27T00:32:00-07:00`. + (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt - $tzh:tt : $tzm:tt $($rest:tt)*) => { + $crate::toml_internal!(@topleveldatetime $root [$($path)*] $($($k)-+).+ = ($yr - $mo - $dhr : $min : $sec - $tzh : $tzm) $($rest)*); + }; + // Space instead of T. + (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt - $tzh:tt : $tzm:tt $($rest:tt)*) => { + $crate::toml_internal!(@topleveldatetime $root [$($path)*] $($($k)-+).+ = ($yr - $mo - $day T $hr : $min : $sec - $tzh : $tzm) $($rest)*); + }; + + // Parse local datetime `key = 1979-05-27T00:32:00.999999`. + (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt . $frac:tt $($rest:tt)*) => { + $crate::toml_internal!(@topleveldatetime $root [$($path)*] $($($k)-+).+ = ($yr - $mo - $dhr : $min : $sec . $frac) $($rest)*); + }; + // Space instead of T. + (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt . $frac:tt $($rest:tt)*) => { + $crate::toml_internal!(@topleveldatetime $root [$($path)*] $($($k)-+).+ = ($yr - $mo - $day T $hr : $min : $sec . $frac) $($rest)*); + }; + + // Parse offset datetime `key = 1979-05-27T07:32:00Z` and local datetime `key = 1979-05-27T07:32:00`. + (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt $($rest:tt)*) => { + $crate::toml_internal!(@topleveldatetime $root [$($path)*] $($($k)-+).+ = ($yr - $mo - $dhr : $min : $sec) $($rest)*); + }; + // Space instead of T. + (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt $($rest:tt)*) => { + $crate::toml_internal!(@topleveldatetime $root [$($path)*] $($($k)-+).+ = ($yr - $mo - $day T $hr : $min : $sec) $($rest)*); + }; + + // Parse local date `key = 1979-05-27`. + (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $yr:tt - $mo:tt - $day:tt $($rest:tt)*) => { + $crate::toml_internal!(@topleveldatetime $root [$($path)*] $($($k)-+).+ = ($yr - $mo - $day) $($rest)*); + }; + + // Parse local time `key = 00:32:00.999999`. + (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $hr:tt : $min:tt : $sec:tt . $frac:tt $($rest:tt)*) => { + $crate::toml_internal!(@topleveldatetime $root [$($path)*] $($($k)-+).+ = ($hr : $min : $sec . $frac) $($rest)*); + }; + + // Parse local time `key = 07:32:00`. + (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $hr:tt : $min:tt : $sec:tt $($rest:tt)*) => { + $crate::toml_internal!(@topleveldatetime $root [$($path)*] $($($k)-+).+ = ($hr : $min : $sec) $($rest)*); + }; + + // Parse any other `key = value` including string, inline array, inline + // table, number, and boolean. + (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $v:tt $($rest:tt)*) => {{ + $crate::macros::insert_toml( + &mut $root, + &[$($path)* $(&concat!($("-", $crate::toml_internal!(@path $k),)+)[1..], )+], + $crate::toml_internal!(@value $v)); + $crate::toml_internal!(@toplevel $root [$($path)*] $($rest)*); + }}; + + // Parse array header `[[bin]]`. + (@toplevel $root:ident $oldpath:tt [[$($($path:tt)-+).+]] $($rest:tt)*) => { + $crate::macros::push_toml( + &mut $root, + &[$(&concat!($("-", $crate::toml_internal!(@path $path),)+)[1..],)+]); + $crate::toml_internal!(@toplevel $root [$(&concat!($("-", $crate::toml_internal!(@path $path),)+)[1..],)+] $($rest)*); + }; + + // Parse table header `[patch.crates-io]`. + (@toplevel $root:ident $oldpath:tt [$($($path:tt)-+).+] $($rest:tt)*) => { + $crate::macros::insert_toml( + &mut $root, + &[$(&concat!($("-", $crate::toml_internal!(@path $path),)+)[1..],)+], + $crate::Value::Table($crate::value::Table::new())); + $crate::toml_internal!(@toplevel $root [$(&concat!($("-", $crate::toml_internal!(@path $path),)+)[1..],)+] $($rest)*); + }; + + // Parse datetime from string and insert into table. + (@topleveldatetime $root:ident [$($path:tt)*] $($($k:tt)-+).+ = ($($datetime:tt)+) $($rest:tt)*) => { + $crate::macros::insert_toml( + &mut $root, + &[$($path)* $(&concat!($("-", $crate::toml_internal!(@path $k),)+)[1..], )+], + $crate::Value::Datetime(concat!($(stringify!($datetime)),+).parse().unwrap())); + $crate::toml_internal!(@toplevel $root [$($path)*] $($rest)*); + }; + + // Turn a path segment into a string. + (@path $ident:ident) => { + stringify!($ident) + }; + + // For a path segment that is not an ident, expect that it is already a + // quoted string, like in `[target."cfg(windows)".dependencies]`. + (@path $quoted:tt) => { + $quoted + }; + + // Construct a Value from an inline table. + (@value { $($inline:tt)* }) => {{ + let mut table = $crate::Value::Table($crate::value::Table::new()); + $crate::toml_internal!(@trailingcomma (@table table) $($inline)*); + table + }}; + + // Construct a Value from an inline array. + (@value [ $($inline:tt)* ]) => {{ + let mut array = $crate::value::Array::new(); + $crate::toml_internal!(@trailingcomma (@array array) $($inline)*); + $crate::Value::Array(array) + }}; + + (@value (-nan)) => { + $crate::Value::Float(-::std::f64::NAN) + }; + + (@value (nan)) => { + $crate::Value::Float(::std::f64::NAN) + }; + + (@value nan) => { + $crate::Value::Float(::std::f64::NAN) + }; + + (@value (-inf)) => { + $crate::Value::Float(::std::f64::NEG_INFINITY) + }; + + (@value (inf)) => { + $crate::Value::Float(::std::f64::INFINITY) + }; + + (@value inf) => { + $crate::Value::Float(::std::f64::INFINITY) + }; + + // Construct a Value from any other type, probably string or boolean or number. + (@value $v:tt) => {{ + // TODO: Implement this with something like serde_json::to_value instead. + let de = $crate::macros::IntoDeserializer::<$crate::de::Error>::into_deserializer($v); + <$crate::Value as $crate::macros::Deserialize>::deserialize(de).unwrap() + }}; + + // Base case of inline table. + (@table $root:ident) => {}; + + // Parse negative number `key = -value`. + (@table $root:ident $($($k:tt)-+).+ = - $v:tt , $($rest:tt)*) => { + $crate::toml_internal!(@table $root $($($k)-+).+ = (-$v) , $($rest)*); + }; + + // Parse positive number `key = +value`. + (@table $root:ident $($($k:tt)-+).+ = + $v:tt , $($rest:tt)*) => { + $crate::toml_internal!(@table $root $($($k)-+).+ = ($v) , $($rest)*); + }; + + // Parse offset datetime `key = 1979-05-27T00:32:00.999999-07:00`. + (@table $root:ident $($($k:tt)-+).+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt . $frac:tt - $tzh:tt : $tzm:tt , $($rest:tt)*) => { + $crate::toml_internal!(@tabledatetime $root $($($k)-+).+ = ($yr - $mo - $dhr : $min : $sec . $frac - $tzh : $tzm) $($rest)*); + }; + // Space instead of T. + (@table $root:ident $($($k:tt)-+).+ = $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt . $frac:tt - $tzh:tt : $tzm:tt , $($rest:tt)*) => { + $crate::toml_internal!(@tabledatetime $root $($($k)-+).+ = ($yr - $mo - $day T $hr : $min : $sec . $frac - $tzh : $tzm) $($rest)*); + }; + + // Parse offset datetime `key = 1979-05-27T00:32:00-07:00`. + (@table $root:ident $($($k:tt)-+).+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt - $tzh:tt : $tzm:tt , $($rest:tt)*) => { + $crate::toml_internal!(@tabledatetime $root $($($k)-+).+ = ($yr - $mo - $dhr : $min : $sec - $tzh : $tzm) $($rest)*); + }; + // Space instead of T. + (@table $root:ident $($($k:tt)-+).+ = $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt - $tzh:tt : $tzm:tt , $($rest:tt)*) => { + $crate::toml_internal!(@tabledatetime $root $($($k)-+).+ = ($yr - $mo - $day T $hr : $min : $sec - $tzh : $tzm) $($rest)*); + }; + + // Parse local datetime `key = 1979-05-27T00:32:00.999999`. + (@table $root:ident $($($k:tt)-+).+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt . $frac:tt , $($rest:tt)*) => { + $crate::toml_internal!(@tabledatetime $root $($($k)-+).+ = ($yr - $mo - $dhr : $min : $sec . $frac) $($rest)*); + }; + // Space instead of T. + (@table $root:ident $($($k:tt)-+).+ = $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt . $frac:tt , $($rest:tt)*) => { + $crate::toml_internal!(@tabledatetime $root $($($k)-+).+ = ($yr - $mo - $day T $hr : $min : $sec . $frac) $($rest)*); + }; + + // Parse offset datetime `key = 1979-05-27T07:32:00Z` and local datetime `key = 1979-05-27T07:32:00`. + (@table $root:ident $($($k:tt)-+).+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt , $($rest:tt)*) => { + $crate::toml_internal!(@tabledatetime $root $($($k)-+).+ = ($yr - $mo - $dhr : $min : $sec) $($rest)*); + }; + // Space instead of T. + (@table $root:ident $($($k:tt)-+).+ = $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt , $($rest:tt)*) => { + $crate::toml_internal!(@tabledatetime $root $($($k)-+).+ = ($yr - $mo - $day T $hr : $min : $sec) $($rest)*); + }; + + // Parse local date `key = 1979-05-27`. + (@table $root:ident $($($k:tt)-+).+ = $yr:tt - $mo:tt - $day:tt , $($rest:tt)*) => { + $crate::toml_internal!(@tabledatetime $root $($($k)-+).+ = ($yr - $mo - $day) $($rest)*); + }; + + // Parse local time `key = 00:32:00.999999`. + (@table $root:ident $($($k:tt)-+).+ = $hr:tt : $min:tt : $sec:tt . $frac:tt , $($rest:tt)*) => { + $crate::toml_internal!(@tabledatetime $root $($($k)-+).+ = ($hr : $min : $sec . $frac) $($rest)*); + }; + + // Parse local time `key = 07:32:00`. + (@table $root:ident $($($k:tt)-+).+ = $hr:tt : $min:tt : $sec:tt , $($rest:tt)*) => { + $crate::toml_internal!(@tabledatetime $root $($($k)-+).+ = ($hr : $min : $sec) $($rest)*); + }; + + // Parse any other type, probably string or boolean or number. + (@table $root:ident $($($k:tt)-+).+ = $v:tt , $($rest:tt)*) => { + $crate::macros::insert_toml( + &mut $root, + &[$(&concat!($("-", $crate::toml_internal!(@path $k),)+)[1..], )+], + $crate::toml_internal!(@value $v)); + $crate::toml_internal!(@table $root $($rest)*); + }; + + // Parse a Datetime from string and continue in @table state. + (@tabledatetime $root:ident $($($k:tt)-+).+ = ($($datetime:tt)*) $($rest:tt)*) => { + $crate::macros::insert_toml( + &mut $root, + &[$(&concat!($("-", $crate::toml_internal!(@path $k),)+)[1..], )+], + $crate::Value::Datetime(concat!($(stringify!($datetime)),+).parse().unwrap())); + $crate::toml_internal!(@table $root $($rest)*); + }; + + // Base case of inline array. + (@array $root:ident) => {}; + + // Parse negative number `-value`. + (@array $root:ident - $v:tt , $($rest:tt)*) => { + $crate::toml_internal!(@array $root (-$v) , $($rest)*); + }; + + // Parse positive number `+value`. + (@array $root:ident + $v:tt , $($rest:tt)*) => { + $crate::toml_internal!(@array $root ($v) , $($rest)*); + }; + + // Parse offset datetime `1979-05-27T00:32:00.999999-07:00`. + (@array $root:ident $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt . $frac:tt - $tzh:tt : $tzm:tt , $($rest:tt)*) => { + $crate::toml_internal!(@arraydatetime $root ($yr - $mo - $dhr : $min : $sec . $frac - $tzh : $tzm) $($rest)*); + }; + // Space instead of T. + (@array $root:ident $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt . $frac:tt - $tzh:tt : $tzm:tt , $($rest:tt)*) => { + $crate::toml_internal!(@arraydatetime $root ($yr - $mo - $day T $hr : $min : $sec . $frac - $tzh : $tzm) $($rest)*); + }; + + // Parse offset datetime `1979-05-27T00:32:00-07:00`. + (@array $root:ident $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt - $tzh:tt : $tzm:tt , $($rest:tt)*) => { + $crate::toml_internal!(@arraydatetime $root ($yr - $mo - $dhr : $min : $sec - $tzh : $tzm) $($rest)*); + }; + // Space instead of T. + (@array $root:ident $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt - $tzh:tt : $tzm:tt , $($rest:tt)*) => { + $crate::toml_internal!(@arraydatetime $root ($yr - $mo - $day T $hr : $min : $sec - $tzh : $tzm) $($rest)*); + }; + + // Parse local datetime `1979-05-27T00:32:00.999999`. + (@array $root:ident $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt . $frac:tt , $($rest:tt)*) => { + $crate::toml_internal!(@arraydatetime $root ($yr - $mo - $dhr : $min : $sec . $frac) $($rest)*); + }; + // Space instead of T. + (@array $root:ident $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt . $frac:tt , $($rest:tt)*) => { + $crate::toml_internal!(@arraydatetime $root ($yr - $mo - $day T $hr : $min : $sec . $frac) $($rest)*); + }; + + // Parse offset datetime `1979-05-27T07:32:00Z` and local datetime `1979-05-27T07:32:00`. + (@array $root:ident $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt , $($rest:tt)*) => { + $crate::toml_internal!(@arraydatetime $root ($yr - $mo - $dhr : $min : $sec) $($rest)*); + }; + // Space instead of T. + (@array $root:ident $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt , $($rest:tt)*) => { + $crate::toml_internal!(@arraydatetime $root ($yr - $mo - $day T $hr : $min : $sec) $($rest)*); + }; + + // Parse local date `1979-05-27`. + (@array $root:ident $yr:tt - $mo:tt - $day:tt , $($rest:tt)*) => { + $crate::toml_internal!(@arraydatetime $root ($yr - $mo - $day) $($rest)*); + }; + + // Parse local time `00:32:00.999999`. + (@array $root:ident $hr:tt : $min:tt : $sec:tt . $frac:tt , $($rest:tt)*) => { + $crate::toml_internal!(@arraydatetime $root ($hr : $min : $sec . $frac) $($rest)*); + }; + + // Parse local time `07:32:00`. + (@array $root:ident $hr:tt : $min:tt : $sec:tt , $($rest:tt)*) => { + $crate::toml_internal!(@arraydatetime $root ($hr : $min : $sec) $($rest)*); + }; + + // Parse any other type, probably string or boolean or number. + (@array $root:ident $v:tt , $($rest:tt)*) => { + $root.push($crate::toml_internal!(@value $v)); + $crate::toml_internal!(@array $root $($rest)*); + }; + + // Parse a Datetime from string and continue in @array state. + (@arraydatetime $root:ident ($($datetime:tt)*) $($rest:tt)*) => { + $root.push($crate::Value::Datetime(concat!($(stringify!($datetime)),+).parse().unwrap())); + $crate::toml_internal!(@array $root $($rest)*); + }; + + // No trailing comma required if the tokens are empty. + (@trailingcomma ($($args:tt)*)) => { + $crate::toml_internal!($($args)*); + }; + + // Tokens end with a trailing comma, do not append another one. + (@trailingcomma ($($args:tt)*) ,) => { + $crate::toml_internal!($($args)* ,); + }; + + // Tokens end with something other than comma, append a trailing comma. + (@trailingcomma ($($args:tt)*) $last:tt) => { + $crate::toml_internal!($($args)* $last ,); + }; + + // Not yet at the last token. + (@trailingcomma ($($args:tt)*) $first:tt $($rest:tt)+) => { + $crate::toml_internal!(@trailingcomma ($($args)* $first) $($rest)+); + }; +} + +// Called when parsing a `key = value` pair. +// Inserts an entry into the table at the given path. +pub fn insert_toml(root: &mut Value, path: &[&str], value: Value) { + *traverse(root, path) = value; +} + +// Called when parsing an `[[array header]]`. +// Pushes an empty table onto the array at the given path. +pub fn push_toml(root: &mut Value, path: &[&str]) { + let target = traverse(root, path); + if !target.is_array() { + *target = Value::Array(Array::new()); + } + target + .as_array_mut() + .unwrap() + .push(Value::Table(Table::new())); +} + +fn traverse<'a>(root: &'a mut Value, path: &[&str]) -> &'a mut Value { + let mut cur = root; + for &key in path { + // Lexical lifetimes :D + let cur1 = cur; + + // From the TOML spec: + // + // > Each double-bracketed sub-table will belong to the most recently + // > defined table element above it. + let cur2 = if cur1.is_array() { + cur1.as_array_mut().unwrap().last_mut().unwrap() + } else { + cur1 + }; + + // We are about to index into this value, so it better be a table. + if !cur2.is_table() { + *cur2 = Value::Table(Table::new()); + } + + if !cur2.as_table().unwrap().contains_key(key) { + // Insert an empty table for the next loop iteration to point to. + let empty = Value::Table(Table::new()); + cur2.as_table_mut().unwrap().insert(key.to_owned(), empty); + } + + // Step into the current table. + cur = cur2.as_table_mut().unwrap().get_mut(key).unwrap(); + } + cur +} diff --git a/src/map.rs b/src/map.rs new file mode 100644 index 0000000..bd720a7 --- /dev/null +++ b/src/map.rs @@ -0,0 +1,609 @@ +// Copyright 2017 Serde Developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! A map of `String` to [Value]. +//! +//! By default the map is backed by a [`BTreeMap`]. Enable the `preserve_order` +//! feature of toml-rs to use [`IndexMap`] instead. +//! +//! [`BTreeMap`]: https://doc.rust-lang.org/std/collections/struct.BTreeMap.html +//! [`IndexMap`]: https://docs.rs/indexmap + +use crate::value::Value; +use serde::{de, ser}; +use std::borrow::Borrow; +use std::fmt::{self, Debug}; +use std::hash::Hash; +use std::iter::FromIterator; +use std::ops; + +#[cfg(not(feature = "preserve_order"))] +use std::collections::{btree_map, BTreeMap}; + +#[cfg(feature = "preserve_order")] +use indexmap::{self, IndexMap}; + +/// Represents a TOML key/value type. +pub struct Map { + map: MapImpl, +} + +#[cfg(not(feature = "preserve_order"))] +type MapImpl = BTreeMap; +#[cfg(feature = "preserve_order")] +type MapImpl = IndexMap; + +impl Map { + /// Makes a new empty Map. + #[inline] + pub fn new() -> Self { + Map { + map: MapImpl::new(), + } + } + + #[cfg(not(feature = "preserve_order"))] + /// Makes a new empty Map with the given initial capacity. + #[inline] + pub fn with_capacity(capacity: usize) -> Self { + // does not support with_capacity + let _ = capacity; + Map { + map: BTreeMap::new(), + } + } + + #[cfg(feature = "preserve_order")] + /// Makes a new empty Map with the given initial capacity. + #[inline] + pub fn with_capacity(capacity: usize) -> Self { + Map { + map: IndexMap::with_capacity(capacity), + } + } + + /// Clears the map, removing all values. + #[inline] + pub fn clear(&mut self) { + self.map.clear() + } + + /// Returns a reference to the value corresponding to the key. + /// + /// The key may be any borrowed form of the map's key type, but the ordering + /// on the borrowed form *must* match the ordering on the key type. + #[inline] + pub fn get(&self, key: &Q) -> Option<&Value> + where + String: Borrow, + Q: Ord + Eq + Hash, + { + self.map.get(key) + } + + /// Returns true if the map contains a value for the specified key. + /// + /// The key may be any borrowed form of the map's key type, but the ordering + /// on the borrowed form *must* match the ordering on the key type. + #[inline] + pub fn contains_key(&self, key: &Q) -> bool + where + String: Borrow, + Q: Ord + Eq + Hash, + { + self.map.contains_key(key) + } + + /// Returns a mutable reference to the value corresponding to the key. + /// + /// The key may be any borrowed form of the map's key type, but the ordering + /// on the borrowed form *must* match the ordering on the key type. + #[inline] + pub fn get_mut(&mut self, key: &Q) -> Option<&mut Value> + where + String: Borrow, + Q: Ord + Eq + Hash, + { + self.map.get_mut(key) + } + + /// Inserts a key-value pair into the map. + /// + /// If the map did not have this key present, `None` is returned. + /// + /// If the map did have this key present, the value is updated, and the old + /// value is returned. The key is not updated, though; this matters for + /// types that can be `==` without being identical. + #[inline] + pub fn insert(&mut self, k: String, v: Value) -> Option { + self.map.insert(k, v) + } + + /// Removes a key from the map, returning the value at the key if the key + /// was previously in the map. + /// + /// The key may be any borrowed form of the map's key type, but the ordering + /// on the borrowed form *must* match the ordering on the key type. + #[inline] + pub fn remove(&mut self, key: &Q) -> Option + where + String: Borrow, + Q: Ord + Eq + Hash, + { + self.map.remove(key) + } + + /// Retains only the elements specified by the `keep` predicate. + /// + /// In other words, remove all pairs `(k, v)` for which `keep(&k, &mut v)` + /// returns `false`. + /// + /// The elements are visited in iteration order. + #[inline] + pub fn retain(&mut self, mut keep: F) + where + F: FnMut(&str, &mut Value) -> bool, + { + self.map.retain(|key, value| keep(key.as_str(), value)); + } + + /// Gets the given key's corresponding entry in the map for in-place + /// manipulation. + pub fn entry(&mut self, key: S) -> Entry<'_> + where + S: Into, + { + #[cfg(feature = "preserve_order")] + use indexmap::map::Entry as EntryImpl; + #[cfg(not(feature = "preserve_order"))] + use std::collections::btree_map::Entry as EntryImpl; + + match self.map.entry(key.into()) { + EntryImpl::Vacant(vacant) => Entry::Vacant(VacantEntry { vacant }), + EntryImpl::Occupied(occupied) => Entry::Occupied(OccupiedEntry { occupied }), + } + } + + /// Returns the number of elements in the map. + #[inline] + pub fn len(&self) -> usize { + self.map.len() + } + + /// Returns true if the map contains no elements. + #[inline] + pub fn is_empty(&self) -> bool { + self.map.is_empty() + } + + /// Gets an iterator over the entries of the map. + #[inline] + pub fn iter(&self) -> Iter<'_> { + Iter { + iter: self.map.iter(), + } + } + + /// Gets a mutable iterator over the entries of the map. + #[inline] + pub fn iter_mut(&mut self) -> IterMut<'_> { + IterMut { + iter: self.map.iter_mut(), + } + } + + /// Gets an iterator over the keys of the map. + #[inline] + pub fn keys(&self) -> Keys<'_> { + Keys { + iter: self.map.keys(), + } + } + + /// Gets an iterator over the values of the map. + #[inline] + pub fn values(&self) -> Values<'_> { + Values { + iter: self.map.values(), + } + } +} + +impl Default for Map { + #[inline] + fn default() -> Self { + Map { + map: MapImpl::new(), + } + } +} + +impl Clone for Map { + #[inline] + fn clone(&self) -> Self { + Map { + map: self.map.clone(), + } + } +} + +impl PartialEq for Map { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.map.eq(&other.map) + } +} + +/// Access an element of this map. Panics if the given key is not present in the +/// map. +impl<'a, Q: ?Sized> ops::Index<&'a Q> for Map +where + String: Borrow, + Q: Ord + Eq + Hash, +{ + type Output = Value; + + fn index(&self, index: &Q) -> &Value { + self.map.index(index) + } +} + +/// Mutably access an element of this map. Panics if the given key is not +/// present in the map. +impl<'a, Q: ?Sized> ops::IndexMut<&'a Q> for Map +where + String: Borrow, + Q: Ord + Eq + Hash, +{ + fn index_mut(&mut self, index: &Q) -> &mut Value { + self.map.get_mut(index).expect("no entry found for key") + } +} + +impl Debug for Map { + #[inline] + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + self.map.fmt(formatter) + } +} + +impl ser::Serialize for Map { + #[inline] + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + use serde::ser::SerializeMap; + let mut map = serializer.serialize_map(Some(self.len()))?; + for (k, v) in self { + map.serialize_key(k)?; + map.serialize_value(v)?; + } + map.end() + } +} + +impl<'de> de::Deserialize<'de> for Map { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = Map; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a map") + } + + #[inline] + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(Map::new()) + } + + #[inline] + fn visit_map(self, mut visitor: V) -> Result + where + V: de::MapAccess<'de>, + { + let mut values = Map::new(); + + while let Some((key, value)) = visitor.next_entry()? { + values.insert(key, value); + } + + Ok(values) + } + } + + deserializer.deserialize_map(Visitor) + } +} + +impl FromIterator<(String, Value)> for Map { + fn from_iter(iter: T) -> Self + where + T: IntoIterator, + { + Map { + map: FromIterator::from_iter(iter), + } + } +} + +impl Extend<(String, Value)> for Map { + fn extend(&mut self, iter: T) + where + T: IntoIterator, + { + self.map.extend(iter); + } +} + +macro_rules! delegate_iterator { + (($name:ident $($generics:tt)*) => $item:ty) => { + impl $($generics)* Iterator for $name $($generics)* { + type Item = $item; + #[inline] + fn next(&mut self) -> Option { + self.iter.next() + } + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } + } + + impl $($generics)* DoubleEndedIterator for $name $($generics)* { + #[inline] + fn next_back(&mut self) -> Option { + self.iter.next_back() + } + } + + impl $($generics)* ExactSizeIterator for $name $($generics)* { + #[inline] + fn len(&self) -> usize { + self.iter.len() + } + } + } +} + +////////////////////////////////////////////////////////////////////////////// + +/// A view into a single entry in a map, which may either be vacant or occupied. +/// This enum is constructed from the [`entry`] method on [`Map`]. +/// +/// [`entry`]: struct.Map.html#method.entry +/// [`Map`]: struct.Map.html +pub enum Entry<'a> { + /// A vacant Entry. + Vacant(VacantEntry<'a>), + /// An occupied Entry. + Occupied(OccupiedEntry<'a>), +} + +/// A vacant Entry. It is part of the [`Entry`] enum. +/// +/// [`Entry`]: enum.Entry.html +pub struct VacantEntry<'a> { + vacant: VacantEntryImpl<'a>, +} + +/// An occupied Entry. It is part of the [`Entry`] enum. +/// +/// [`Entry`]: enum.Entry.html +pub struct OccupiedEntry<'a> { + occupied: OccupiedEntryImpl<'a>, +} + +#[cfg(not(feature = "preserve_order"))] +type VacantEntryImpl<'a> = btree_map::VacantEntry<'a, String, Value>; +#[cfg(feature = "preserve_order")] +type VacantEntryImpl<'a> = indexmap::map::VacantEntry<'a, String, Value>; + +#[cfg(not(feature = "preserve_order"))] +type OccupiedEntryImpl<'a> = btree_map::OccupiedEntry<'a, String, Value>; +#[cfg(feature = "preserve_order")] +type OccupiedEntryImpl<'a> = indexmap::map::OccupiedEntry<'a, String, Value>; + +impl<'a> Entry<'a> { + /// Returns a reference to this entry's key. + pub fn key(&self) -> &String { + match *self { + Entry::Vacant(ref e) => e.key(), + Entry::Occupied(ref e) => e.key(), + } + } + + /// Ensures a value is in the entry by inserting the default if empty, and + /// returns a mutable reference to the value in the entry. + pub fn or_insert(self, default: Value) -> &'a mut Value { + match self { + Entry::Vacant(entry) => entry.insert(default), + Entry::Occupied(entry) => entry.into_mut(), + } + } + + /// Ensures a value is in the entry by inserting the result of the default + /// function if empty, and returns a mutable reference to the value in the + /// entry. + pub fn or_insert_with(self, default: F) -> &'a mut Value + where + F: FnOnce() -> Value, + { + match self { + Entry::Vacant(entry) => entry.insert(default()), + Entry::Occupied(entry) => entry.into_mut(), + } + } +} + +impl<'a> VacantEntry<'a> { + /// Gets a reference to the key that would be used when inserting a value + /// through the VacantEntry. + #[inline] + pub fn key(&self) -> &String { + self.vacant.key() + } + + /// Sets the value of the entry with the VacantEntry's key, and returns a + /// mutable reference to it. + #[inline] + pub fn insert(self, value: Value) -> &'a mut Value { + self.vacant.insert(value) + } +} + +impl<'a> OccupiedEntry<'a> { + /// Gets a reference to the key in the entry. + #[inline] + pub fn key(&self) -> &String { + self.occupied.key() + } + + /// Gets a reference to the value in the entry. + #[inline] + pub fn get(&self) -> &Value { + self.occupied.get() + } + + /// Gets a mutable reference to the value in the entry. + #[inline] + pub fn get_mut(&mut self) -> &mut Value { + self.occupied.get_mut() + } + + /// Converts the entry into a mutable reference to its value. + #[inline] + pub fn into_mut(self) -> &'a mut Value { + self.occupied.into_mut() + } + + /// Sets the value of the entry with the `OccupiedEntry`'s key, and returns + /// the entry's old value. + #[inline] + pub fn insert(&mut self, value: Value) -> Value { + self.occupied.insert(value) + } + + /// Takes the value of the entry out of the map, and returns it. + #[inline] + pub fn remove(self) -> Value { + self.occupied.remove() + } +} + +////////////////////////////////////////////////////////////////////////////// + +impl<'a> IntoIterator for &'a Map { + type Item = (&'a String, &'a Value); + type IntoIter = Iter<'a>; + #[inline] + fn into_iter(self) -> Self::IntoIter { + Iter { + iter: self.map.iter(), + } + } +} + +/// An iterator over a toml::Map's entries. +pub struct Iter<'a> { + iter: IterImpl<'a>, +} + +#[cfg(not(feature = "preserve_order"))] +type IterImpl<'a> = btree_map::Iter<'a, String, Value>; +#[cfg(feature = "preserve_order")] +type IterImpl<'a> = indexmap::map::Iter<'a, String, Value>; + +delegate_iterator!((Iter<'a>) => (&'a String, &'a Value)); + +////////////////////////////////////////////////////////////////////////////// + +impl<'a> IntoIterator for &'a mut Map { + type Item = (&'a String, &'a mut Value); + type IntoIter = IterMut<'a>; + #[inline] + fn into_iter(self) -> Self::IntoIter { + IterMut { + iter: self.map.iter_mut(), + } + } +} + +/// A mutable iterator over a toml::Map's entries. +pub struct IterMut<'a> { + iter: IterMutImpl<'a>, +} + +#[cfg(not(feature = "preserve_order"))] +type IterMutImpl<'a> = btree_map::IterMut<'a, String, Value>; +#[cfg(feature = "preserve_order")] +type IterMutImpl<'a> = indexmap::map::IterMut<'a, String, Value>; + +delegate_iterator!((IterMut<'a>) => (&'a String, &'a mut Value)); + +////////////////////////////////////////////////////////////////////////////// + +impl IntoIterator for Map { + type Item = (String, Value); + type IntoIter = IntoIter; + #[inline] + fn into_iter(self) -> Self::IntoIter { + IntoIter { + iter: self.map.into_iter(), + } + } +} + +/// An owning iterator over a toml::Map's entries. +pub struct IntoIter { + iter: IntoIterImpl, +} + +#[cfg(not(feature = "preserve_order"))] +type IntoIterImpl = btree_map::IntoIter; +#[cfg(feature = "preserve_order")] +type IntoIterImpl = indexmap::map::IntoIter; + +delegate_iterator!((IntoIter) => (String, Value)); + +////////////////////////////////////////////////////////////////////////////// + +/// An iterator over a toml::Map's keys. +pub struct Keys<'a> { + iter: KeysImpl<'a>, +} + +#[cfg(not(feature = "preserve_order"))] +type KeysImpl<'a> = btree_map::Keys<'a, String, Value>; +#[cfg(feature = "preserve_order")] +type KeysImpl<'a> = indexmap::map::Keys<'a, String, Value>; + +delegate_iterator!((Keys<'a>) => &'a String); + +////////////////////////////////////////////////////////////////////////////// + +/// An iterator over a toml::Map's values. +pub struct Values<'a> { + iter: ValuesImpl<'a>, +} + +#[cfg(not(feature = "preserve_order"))] +type ValuesImpl<'a> = btree_map::Values<'a, String, Value>; +#[cfg(feature = "preserve_order")] +type ValuesImpl<'a> = indexmap::map::Values<'a, String, Value>; + +delegate_iterator!((Values<'a>) => &'a Value); diff --git a/src/ser.rs b/src/ser.rs new file mode 100644 index 0000000..f1ab24b --- /dev/null +++ b/src/ser.rs @@ -0,0 +1,1087 @@ +//! Serializing Rust structures into TOML. +//! +//! This module contains all the Serde support for serializing Rust structures +//! into TOML documents (as strings). Note that some top-level functions here +//! are also provided at the top of the crate. + +/// Serialize the given data structure as a String of TOML. +/// +/// Serialization can fail if `T`'s implementation of `Serialize` decides to +/// fail, if `T` contains a map with non-string keys, or if `T` attempts to +/// serialize an unsupported datatype such as an enum, tuple, or tuple struct. +/// +/// To serialize TOML values, instead of documents, see [`ValueSerializer`]. +/// +/// # Examples +/// +/// ``` +/// use serde::Serialize; +/// +/// #[derive(Serialize)] +/// struct Config { +/// database: Database, +/// } +/// +/// #[derive(Serialize)] +/// struct Database { +/// ip: String, +/// port: Vec, +/// connection_max: u32, +/// enabled: bool, +/// } +/// +/// let config = Config { +/// database: Database { +/// ip: "192.168.1.1".to_string(), +/// port: vec![8001, 8002, 8003], +/// connection_max: 5000, +/// enabled: false, +/// }, +/// }; +/// +/// let toml = toml::to_string(&config).unwrap(); +/// println!("{}", toml) +/// ``` +#[cfg(feature = "display")] +pub fn to_string(value: &T) -> Result +where + T: serde::ser::Serialize, +{ + let mut output = String::new(); + let serializer = Serializer::new(&mut output); + value.serialize(serializer)?; + Ok(output) +} + +/// Serialize the given data structure as a "pretty" String of TOML. +/// +/// This is identical to `to_string` except the output string has a more +/// "pretty" output. See `Serializer::pretty` for more details. +/// +/// To serialize TOML values, instead of documents, see [`ValueSerializer`]. +/// +/// For greater customization, instead serialize to a +/// [`toml_edit::Document`](https://docs.rs/toml_edit/latest/toml_edit/struct.Document.html). +#[cfg(feature = "display")] +pub fn to_string_pretty(value: &T) -> Result +where + T: serde::ser::Serialize, +{ + let mut output = String::new(); + let serializer = Serializer::pretty(&mut output); + value.serialize(serializer)?; + Ok(output) +} + +/// Errors that can occur when serializing a type. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Error { + pub(crate) inner: crate::edit::ser::Error, +} + +impl Error { + pub(crate) fn new(inner: impl std::fmt::Display) -> Self { + Self { + inner: crate::edit::ser::Error::Custom(inner.to_string()), + } + } + + #[cfg(feature = "display")] + pub(crate) fn wrap(inner: crate::edit::ser::Error) -> Self { + Self { inner } + } + + pub(crate) fn unsupported_type(t: Option<&'static str>) -> Self { + Self { + inner: crate::edit::ser::Error::UnsupportedType(t), + } + } + + pub(crate) fn unsupported_none() -> Self { + Self { + inner: crate::edit::ser::Error::UnsupportedNone, + } + } + + pub(crate) fn key_not_string() -> Self { + Self { + inner: crate::edit::ser::Error::KeyNotString, + } + } +} + +impl serde::ser::Error for Error { + fn custom(msg: T) -> Self + where + T: std::fmt::Display, + { + Error::new(msg) + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.inner.fmt(f) + } +} + +impl std::error::Error for Error {} + +/// Serialization for TOML documents. +/// +/// This structure implements serialization support for TOML to serialize an +/// arbitrary type to TOML. Note that the TOML format does not support all +/// datatypes in Rust, such as enums, tuples, and tuple structs. These types +/// will generate an error when serialized. +/// +/// Currently a serializer always writes its output to an in-memory `String`, +/// which is passed in when creating the serializer itself. +/// +/// To serialize TOML values, instead of documents, see [`ValueSerializer`]. +#[non_exhaustive] +#[cfg(feature = "display")] +pub struct Serializer<'d> { + dst: &'d mut String, + settings: crate::fmt::DocumentFormatter, +} + +#[cfg(feature = "display")] +impl<'d> Serializer<'d> { + /// Creates a new serializer which will emit TOML into the buffer provided. + /// + /// The serializer can then be used to serialize a type after which the data + /// will be present in `dst`. + pub fn new(dst: &'d mut String) -> Self { + Self { + dst, + settings: Default::default(), + } + } + + /// Apply a default "pretty" policy to the document + /// + /// For greater customization, instead serialize to a + /// [`toml_edit::Document`](https://docs.rs/toml_edit/latest/toml_edit/struct.Document.html). + pub fn pretty(dst: &'d mut String) -> Self { + let mut ser = Serializer::new(dst); + ser.settings.multiline_array = true; + ser + } +} + +#[cfg(feature = "display")] +impl<'d> serde::ser::Serializer for Serializer<'d> { + type Ok = (); + type Error = Error; + type SerializeSeq = SerializeDocumentArray<'d>; + type SerializeTuple = SerializeDocumentArray<'d>; + type SerializeTupleStruct = SerializeDocumentArray<'d>; + type SerializeTupleVariant = SerializeDocumentArray<'d>; + type SerializeMap = SerializeDocumentTable<'d>; + type SerializeStruct = SerializeDocumentTable<'d>; + type SerializeStructVariant = serde::ser::Impossible; + + fn serialize_bool(self, v: bool) -> Result { + write_document( + self.dst, + self.settings, + toml_edit::ser::ValueSerializer::new().serialize_bool(v), + ) + } + + fn serialize_i8(self, v: i8) -> Result { + write_document( + self.dst, + self.settings, + toml_edit::ser::ValueSerializer::new().serialize_i8(v), + ) + } + + fn serialize_i16(self, v: i16) -> Result { + write_document( + self.dst, + self.settings, + toml_edit::ser::ValueSerializer::new().serialize_i16(v), + ) + } + + fn serialize_i32(self, v: i32) -> Result { + write_document( + self.dst, + self.settings, + toml_edit::ser::ValueSerializer::new().serialize_i32(v), + ) + } + + fn serialize_i64(self, v: i64) -> Result { + write_document( + self.dst, + self.settings, + toml_edit::ser::ValueSerializer::new().serialize_i64(v), + ) + } + + fn serialize_u8(self, v: u8) -> Result { + write_document( + self.dst, + self.settings, + toml_edit::ser::ValueSerializer::new().serialize_u8(v), + ) + } + + fn serialize_u16(self, v: u16) -> Result { + write_document( + self.dst, + self.settings, + toml_edit::ser::ValueSerializer::new().serialize_u16(v), + ) + } + + fn serialize_u32(self, v: u32) -> Result { + write_document( + self.dst, + self.settings, + toml_edit::ser::ValueSerializer::new().serialize_u32(v), + ) + } + + fn serialize_u64(self, v: u64) -> Result { + write_document( + self.dst, + self.settings, + toml_edit::ser::ValueSerializer::new().serialize_u64(v), + ) + } + + fn serialize_f32(self, v: f32) -> Result { + write_document( + self.dst, + self.settings, + toml_edit::ser::ValueSerializer::new().serialize_f32(v), + ) + } + + fn serialize_f64(self, v: f64) -> Result { + write_document( + self.dst, + self.settings, + toml_edit::ser::ValueSerializer::new().serialize_f64(v), + ) + } + + fn serialize_char(self, v: char) -> Result { + write_document( + self.dst, + self.settings, + toml_edit::ser::ValueSerializer::new().serialize_char(v), + ) + } + + fn serialize_str(self, v: &str) -> Result { + write_document( + self.dst, + self.settings, + toml_edit::ser::ValueSerializer::new().serialize_str(v), + ) + } + + fn serialize_bytes(self, v: &[u8]) -> Result { + write_document( + self.dst, + self.settings, + toml_edit::ser::ValueSerializer::new().serialize_bytes(v), + ) + } + + fn serialize_none(self) -> Result { + write_document( + self.dst, + self.settings, + toml_edit::ser::ValueSerializer::new().serialize_none(), + ) + } + + fn serialize_some(self, v: &T) -> Result + where + T: serde::ser::Serialize, + { + write_document( + self.dst, + self.settings, + toml_edit::ser::ValueSerializer::new().serialize_some(v), + ) + } + + fn serialize_unit(self) -> Result { + write_document( + self.dst, + self.settings, + toml_edit::ser::ValueSerializer::new().serialize_unit(), + ) + } + + fn serialize_unit_struct(self, name: &'static str) -> Result { + write_document( + self.dst, + self.settings, + toml_edit::ser::ValueSerializer::new().serialize_unit_struct(name), + ) + } + + fn serialize_unit_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + ) -> Result { + write_document( + self.dst, + self.settings, + toml_edit::ser::ValueSerializer::new().serialize_unit_variant( + name, + variant_index, + variant, + ), + ) + } + + fn serialize_newtype_struct( + self, + name: &'static str, + v: &T, + ) -> Result + where + T: serde::ser::Serialize, + { + write_document( + self.dst, + self.settings, + toml_edit::ser::ValueSerializer::new().serialize_newtype_struct(name, v), + ) + } + + fn serialize_newtype_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result + where + T: serde::ser::Serialize, + { + write_document( + self.dst, + self.settings, + toml_edit::ser::ValueSerializer::new().serialize_newtype_variant( + name, + variant_index, + variant, + value, + ), + ) + } + + fn serialize_seq(self, len: Option) -> Result { + let ser = toml_edit::ser::ValueSerializer::new() + .serialize_seq(len) + .map_err(Error::wrap)?; + let ser = SerializeDocumentArray::new(self, ser); + Ok(ser) + } + + fn serialize_tuple(self, len: usize) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_tuple_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + len: usize, + ) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_map(self, len: Option) -> Result { + let ser = toml_edit::ser::ValueSerializer::new() + .serialize_map(len) + .map_err(Error::wrap)?; + let ser = SerializeDocumentTable::new(self, ser); + Ok(ser) + } + + fn serialize_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + self.serialize_map(Some(len)) + } + + fn serialize_struct_variant( + self, + name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Err(Error::unsupported_type(Some(name))) + } +} + +/// Serialization for TOML [values][crate::Value]. +/// +/// This structure implements serialization support for TOML to serialize an +/// arbitrary type to TOML. Note that the TOML format does not support all +/// datatypes in Rust, such as enums, tuples, and tuple structs. These types +/// will generate an error when serialized. +/// +/// Currently a serializer always writes its output to an in-memory `String`, +/// which is passed in when creating the serializer itself. +/// +/// # Examples +/// +/// ``` +/// use serde::Serialize; +/// +/// #[derive(Serialize)] +/// struct Config { +/// database: Database, +/// } +/// +/// #[derive(Serialize)] +/// struct Database { +/// ip: String, +/// port: Vec, +/// connection_max: u32, +/// enabled: bool, +/// } +/// +/// let config = Config { +/// database: Database { +/// ip: "192.168.1.1".to_string(), +/// port: vec![8001, 8002, 8003], +/// connection_max: 5000, +/// enabled: false, +/// }, +/// }; +/// +/// let mut value = String::new(); +/// serde::Serialize::serialize( +/// &config, +/// toml::ser::ValueSerializer::new(&mut value) +/// ).unwrap(); +/// println!("{}", value) +/// ``` +#[non_exhaustive] +#[cfg(feature = "display")] +pub struct ValueSerializer<'d> { + dst: &'d mut String, +} + +#[cfg(feature = "display")] +impl<'d> ValueSerializer<'d> { + /// Creates a new serializer which will emit TOML into the buffer provided. + /// + /// The serializer can then be used to serialize a type after which the data + /// will be present in `dst`. + pub fn new(dst: &'d mut String) -> Self { + Self { dst } + } +} + +#[cfg(feature = "display")] +impl<'d> serde::ser::Serializer for ValueSerializer<'d> { + type Ok = (); + type Error = Error; + type SerializeSeq = SerializeValueArray<'d>; + type SerializeTuple = SerializeValueArray<'d>; + type SerializeTupleStruct = SerializeValueArray<'d>; + type SerializeTupleVariant = SerializeValueArray<'d>; + type SerializeMap = SerializeValueTable<'d>; + type SerializeStruct = SerializeValueTable<'d>; + type SerializeStructVariant = serde::ser::Impossible; + + fn serialize_bool(self, v: bool) -> Result { + write_value( + self.dst, + toml_edit::ser::ValueSerializer::new().serialize_bool(v), + ) + } + + fn serialize_i8(self, v: i8) -> Result { + write_value( + self.dst, + toml_edit::ser::ValueSerializer::new().serialize_i8(v), + ) + } + + fn serialize_i16(self, v: i16) -> Result { + write_value( + self.dst, + toml_edit::ser::ValueSerializer::new().serialize_i16(v), + ) + } + + fn serialize_i32(self, v: i32) -> Result { + write_value( + self.dst, + toml_edit::ser::ValueSerializer::new().serialize_i32(v), + ) + } + + fn serialize_i64(self, v: i64) -> Result { + write_value( + self.dst, + toml_edit::ser::ValueSerializer::new().serialize_i64(v), + ) + } + + fn serialize_u8(self, v: u8) -> Result { + write_value( + self.dst, + toml_edit::ser::ValueSerializer::new().serialize_u8(v), + ) + } + + fn serialize_u16(self, v: u16) -> Result { + write_value( + self.dst, + toml_edit::ser::ValueSerializer::new().serialize_u16(v), + ) + } + + fn serialize_u32(self, v: u32) -> Result { + write_value( + self.dst, + toml_edit::ser::ValueSerializer::new().serialize_u32(v), + ) + } + + fn serialize_u64(self, v: u64) -> Result { + write_value( + self.dst, + toml_edit::ser::ValueSerializer::new().serialize_u64(v), + ) + } + + fn serialize_f32(self, v: f32) -> Result { + write_value( + self.dst, + toml_edit::ser::ValueSerializer::new().serialize_f32(v), + ) + } + + fn serialize_f64(self, v: f64) -> Result { + write_value( + self.dst, + toml_edit::ser::ValueSerializer::new().serialize_f64(v), + ) + } + + fn serialize_char(self, v: char) -> Result { + write_value( + self.dst, + toml_edit::ser::ValueSerializer::new().serialize_char(v), + ) + } + + fn serialize_str(self, v: &str) -> Result { + write_value( + self.dst, + toml_edit::ser::ValueSerializer::new().serialize_str(v), + ) + } + + fn serialize_bytes(self, v: &[u8]) -> Result { + write_value( + self.dst, + toml_edit::ser::ValueSerializer::new().serialize_bytes(v), + ) + } + + fn serialize_none(self) -> Result { + write_value( + self.dst, + toml_edit::ser::ValueSerializer::new().serialize_none(), + ) + } + + fn serialize_some(self, v: &T) -> Result + where + T: serde::ser::Serialize, + { + write_value( + self.dst, + toml_edit::ser::ValueSerializer::new().serialize_some(v), + ) + } + + fn serialize_unit(self) -> Result { + write_value( + self.dst, + toml_edit::ser::ValueSerializer::new().serialize_unit(), + ) + } + + fn serialize_unit_struct(self, name: &'static str) -> Result { + write_value( + self.dst, + toml_edit::ser::ValueSerializer::new().serialize_unit_struct(name), + ) + } + + fn serialize_unit_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + ) -> Result { + write_value( + self.dst, + toml_edit::ser::ValueSerializer::new().serialize_unit_variant( + name, + variant_index, + variant, + ), + ) + } + + fn serialize_newtype_struct( + self, + name: &'static str, + v: &T, + ) -> Result + where + T: serde::ser::Serialize, + { + write_value( + self.dst, + toml_edit::ser::ValueSerializer::new().serialize_newtype_struct(name, v), + ) + } + + fn serialize_newtype_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result + where + T: serde::ser::Serialize, + { + write_value( + self.dst, + toml_edit::ser::ValueSerializer::new().serialize_newtype_variant( + name, + variant_index, + variant, + value, + ), + ) + } + + fn serialize_seq(self, len: Option) -> Result { + let ser = toml_edit::ser::ValueSerializer::new() + .serialize_seq(len) + .map_err(Error::wrap)?; + let ser = SerializeValueArray::new(self, ser); + Ok(ser) + } + + fn serialize_tuple(self, len: usize) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_tuple_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + len: usize, + ) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_map(self, len: Option) -> Result { + let ser = toml_edit::ser::ValueSerializer::new() + .serialize_map(len) + .map_err(Error::wrap)?; + let ser = SerializeValueTable::new(self, ser); + Ok(ser) + } + + fn serialize_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + self.serialize_map(Some(len)) + } + + fn serialize_struct_variant( + self, + name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Err(Error::unsupported_type(Some(name))) + } +} + +#[cfg(feature = "display")] +use internal::*; + +#[cfg(feature = "display")] +mod internal { + use super::*; + + use crate::fmt::DocumentFormatter; + + type InnerSerializeDocumentSeq = + ::SerializeSeq; + + #[doc(hidden)] + pub struct SerializeDocumentArray<'d> { + inner: InnerSerializeDocumentSeq, + dst: &'d mut String, + settings: DocumentFormatter, + } + + impl<'d> SerializeDocumentArray<'d> { + pub(crate) fn new(ser: Serializer<'d>, inner: InnerSerializeDocumentSeq) -> Self { + Self { + inner, + dst: ser.dst, + settings: ser.settings, + } + } + } + + impl<'d> serde::ser::SerializeSeq for SerializeDocumentArray<'d> { + type Ok = (); + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<(), Error> + where + T: serde::ser::Serialize, + { + self.inner.serialize_element(value).map_err(Error::wrap) + } + + fn end(self) -> Result { + write_document(self.dst, self.settings, self.inner.end()) + } + } + + impl<'d> serde::ser::SerializeTuple for SerializeDocumentArray<'d> { + type Ok = (); + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<(), Error> + where + T: serde::ser::Serialize, + { + self.inner.serialize_element(value).map_err(Error::wrap) + } + + fn end(self) -> Result { + write_document(self.dst, self.settings, self.inner.end()) + } + } + + impl<'d> serde::ser::SerializeTupleVariant for SerializeDocumentArray<'d> { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<(), Error> + where + T: serde::ser::Serialize, + { + self.inner.serialize_field(value).map_err(Error::wrap) + } + + fn end(self) -> Result { + write_document(self.dst, self.settings, self.inner.end()) + } + } + + impl<'d> serde::ser::SerializeTupleStruct for SerializeDocumentArray<'d> { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<(), Error> + where + T: serde::ser::Serialize, + { + self.inner.serialize_field(value).map_err(Error::wrap) + } + + fn end(self) -> Result { + write_document(self.dst, self.settings, self.inner.end()) + } + } + + type InnerSerializeDocumentTable = + ::SerializeMap; + + #[doc(hidden)] + pub struct SerializeDocumentTable<'d> { + inner: InnerSerializeDocumentTable, + dst: &'d mut String, + settings: DocumentFormatter, + } + + impl<'d> SerializeDocumentTable<'d> { + pub(crate) fn new(ser: Serializer<'d>, inner: InnerSerializeDocumentTable) -> Self { + Self { + inner, + dst: ser.dst, + settings: ser.settings, + } + } + } + + impl<'d> serde::ser::SerializeMap for SerializeDocumentTable<'d> { + type Ok = (); + type Error = Error; + + fn serialize_key(&mut self, input: &T) -> Result<(), Self::Error> + where + T: serde::ser::Serialize, + { + self.inner.serialize_key(input).map_err(Error::wrap) + } + + fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> + where + T: serde::ser::Serialize, + { + self.inner.serialize_value(value).map_err(Error::wrap) + } + + fn end(self) -> Result { + write_document(self.dst, self.settings, self.inner.end()) + } + } + + impl<'d> serde::ser::SerializeStruct for SerializeDocumentTable<'d> { + type Ok = (); + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> + where + T: serde::ser::Serialize, + { + self.inner.serialize_field(key, value).map_err(Error::wrap) + } + + fn end(self) -> Result { + write_document(self.dst, self.settings, self.inner.end()) + } + } + + pub(crate) fn write_document( + dst: &mut String, + mut settings: DocumentFormatter, + value: Result, + ) -> Result<(), Error> { + use std::fmt::Write; + + let value = value.map_err(Error::wrap)?; + let mut table = match toml_edit::Item::Value(value).into_table() { + Ok(i) => i, + Err(_) => { + return Err(Error::unsupported_type(None)); + } + }; + + use toml_edit::visit_mut::VisitMut as _; + settings.visit_table_mut(&mut table); + + let doc: toml_edit::Document = table.into(); + write!(dst, "{}", doc).unwrap(); + + Ok(()) + } + + type InnerSerializeValueSeq = + ::SerializeSeq; + + #[doc(hidden)] + pub struct SerializeValueArray<'d> { + inner: InnerSerializeValueSeq, + dst: &'d mut String, + } + + impl<'d> SerializeValueArray<'d> { + pub(crate) fn new(ser: ValueSerializer<'d>, inner: InnerSerializeValueSeq) -> Self { + Self { + inner, + dst: ser.dst, + } + } + } + + impl<'d> serde::ser::SerializeSeq for SerializeValueArray<'d> { + type Ok = (); + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<(), Error> + where + T: serde::ser::Serialize, + { + self.inner.serialize_element(value).map_err(Error::wrap) + } + + fn end(self) -> Result { + write_value(self.dst, self.inner.end()) + } + } + + impl<'d> serde::ser::SerializeTuple for SerializeValueArray<'d> { + type Ok = (); + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<(), Error> + where + T: serde::ser::Serialize, + { + self.inner.serialize_element(value).map_err(Error::wrap) + } + + fn end(self) -> Result { + write_value(self.dst, self.inner.end()) + } + } + + impl<'d> serde::ser::SerializeTupleVariant for SerializeValueArray<'d> { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<(), Error> + where + T: serde::ser::Serialize, + { + self.inner.serialize_field(value).map_err(Error::wrap) + } + + fn end(self) -> Result { + write_value(self.dst, self.inner.end()) + } + } + + impl<'d> serde::ser::SerializeTupleStruct for SerializeValueArray<'d> { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<(), Error> + where + T: serde::ser::Serialize, + { + self.inner.serialize_field(value).map_err(Error::wrap) + } + + fn end(self) -> Result { + write_value(self.dst, self.inner.end()) + } + } + + type InnerSerializeValueTable = + ::SerializeMap; + + #[doc(hidden)] + pub struct SerializeValueTable<'d> { + inner: InnerSerializeValueTable, + dst: &'d mut String, + } + + impl<'d> SerializeValueTable<'d> { + pub(crate) fn new(ser: ValueSerializer<'d>, inner: InnerSerializeValueTable) -> Self { + Self { + inner, + dst: ser.dst, + } + } + } + + impl<'d> serde::ser::SerializeMap for SerializeValueTable<'d> { + type Ok = (); + type Error = Error; + + fn serialize_key(&mut self, input: &T) -> Result<(), Self::Error> + where + T: serde::ser::Serialize, + { + self.inner.serialize_key(input).map_err(Error::wrap) + } + + fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> + where + T: serde::ser::Serialize, + { + self.inner.serialize_value(value).map_err(Error::wrap) + } + + fn end(self) -> Result { + write_value(self.dst, self.inner.end()) + } + } + + impl<'d> serde::ser::SerializeStruct for SerializeValueTable<'d> { + type Ok = (); + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> + where + T: serde::ser::Serialize, + { + self.inner.serialize_field(key, value).map_err(Error::wrap) + } + + fn end(self) -> Result { + write_value(self.dst, self.inner.end()) + } + } + + pub(crate) fn write_value( + dst: &mut String, + value: Result, + ) -> Result<(), Error> { + use std::fmt::Write; + + let value = value.map_err(Error::wrap)?; + + write!(dst, "{}", value).unwrap(); + + Ok(()) + } +} diff --git a/src/table.rs b/src/table.rs new file mode 100644 index 0000000..a2d392b --- /dev/null +++ b/src/table.rs @@ -0,0 +1,114 @@ +use std::fmt; + +use serde::de; +use serde::ser; + +use crate::map::Map; +use crate::Value; + +/// Type representing a TOML table, payload of the `Value::Table` variant. +/// By default it is backed by a BTreeMap, enable the `preserve_order` feature +/// to use a LinkedHashMap instead. +pub type Table = Map; + +impl Table { + /// Convert a `T` into `toml::Table`. + /// + /// This conversion can fail if `T`'s implementation of `Serialize` decides to + /// fail, or if `T` contains a map with non-string keys. + pub fn try_from(value: T) -> Result + where + T: ser::Serialize, + { + value.serialize(crate::value::TableSerializer) + } + + /// Interpret a `toml::Table` as an instance of type `T`. + /// + /// This conversion can fail if the structure of the `Table` does not match the structure + /// expected by `T`, for example if `T` is a bool which can't be mapped to a `Table`. It can + /// also fail if the structure is correct but `T`'s implementation of `Deserialize` decides + /// that something is wrong with the data, for example required struct fields are missing from + /// the TOML map or some number is too big to fit in the expected primitive type. + pub fn try_into<'de, T>(self) -> Result + where + T: de::Deserialize<'de>, + { + de::Deserialize::deserialize(self) + } +} + +#[cfg(feature = "display")] +impl fmt::Display for Table { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + crate::ser::to_string(self) + .expect("Unable to represent value as string") + .fmt(f) + } +} + +#[cfg(feature = "parse")] +impl std::str::FromStr for Table { + type Err = crate::de::Error; + fn from_str(s: &str) -> Result { + crate::from_str(s) + } +} + +impl<'de> de::Deserializer<'de> for Table { + type Error = crate::de::Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + Value::Table(self).deserialize_any(visitor) + } + + #[inline] + fn deserialize_enum( + self, + name: &'static str, + variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + Value::Table(self).deserialize_enum(name, variants, visitor) + } + + // `None` is interpreted as a missing field so be sure to implement `Some` + // as a present field. + fn deserialize_option(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + Value::Table(self).deserialize_option(visitor) + } + + fn deserialize_newtype_struct( + self, + name: &'static str, + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + Value::Table(self).deserialize_newtype_struct(name, visitor) + } + + serde::forward_to_deserialize_any! { + bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string unit seq + bytes byte_buf map unit_struct tuple_struct struct + tuple ignored_any identifier + } +} + +impl<'de> de::IntoDeserializer<'de, crate::de::Error> for Table { + type Deserializer = Self; + + fn into_deserializer(self) -> Self { + self + } +} diff --git a/src/value.rs b/src/value.rs new file mode 100644 index 0000000..2785d9d --- /dev/null +++ b/src/value.rs @@ -0,0 +1,1455 @@ +//! Definition of a TOML [value][Value] + +use std::collections::{BTreeMap, HashMap}; +use std::fmt; +use std::hash::Hash; +use std::mem::discriminant; +use std::ops; +use std::vec; + +use serde::de; +use serde::de::IntoDeserializer; +use serde::ser; + +use toml_datetime::__unstable as datetime; +pub use toml_datetime::{Date, Datetime, DatetimeParseError, Offset, Time}; + +/// Type representing a TOML array, payload of the `Value::Array` variant +pub type Array = Vec; + +#[doc(no_inline)] +pub use crate::Table; + +/// Representation of a TOML value. +#[derive(PartialEq, Clone, Debug)] +pub enum Value { + /// Represents a TOML string + String(String), + /// Represents a TOML integer + Integer(i64), + /// Represents a TOML float + Float(f64), + /// Represents a TOML boolean + Boolean(bool), + /// Represents a TOML datetime + Datetime(Datetime), + /// Represents a TOML array + Array(Array), + /// Represents a TOML table + Table(Table), +} + +impl Value { + /// Convert a `T` into `toml::Value` which is an enum that can represent + /// any valid TOML data. + /// + /// This conversion can fail if `T`'s implementation of `Serialize` decides to + /// fail, or if `T` contains a map with non-string keys. + pub fn try_from(value: T) -> Result + where + T: ser::Serialize, + { + value.serialize(ValueSerializer) + } + + /// Interpret a `toml::Value` as an instance of type `T`. + /// + /// This conversion can fail if the structure of the `Value` does not match the + /// structure expected by `T`, for example if `T` is a struct type but the + /// `Value` contains something other than a TOML table. It can also fail if the + /// structure is correct but `T`'s implementation of `Deserialize` decides that + /// something is wrong with the data, for example required struct fields are + /// missing from the TOML map or some number is too big to fit in the expected + /// primitive type. + pub fn try_into<'de, T>(self) -> Result + where + T: de::Deserialize<'de>, + { + de::Deserialize::deserialize(self) + } + + /// Index into a TOML array or map. A string index can be used to access a + /// value in a map, and a usize index can be used to access an element of an + /// array. + /// + /// Returns `None` if the type of `self` does not match the type of the + /// index, for example if the index is a string and `self` is an array or a + /// number. Also returns `None` if the given key does not exist in the map + /// or the given index is not within the bounds of the array. + pub fn get(&self, index: I) -> Option<&Value> { + index.index(self) + } + + /// Mutably index into a TOML array or map. A string index can be used to + /// access a value in a map, and a usize index can be used to access an + /// element of an array. + /// + /// Returns `None` if the type of `self` does not match the type of the + /// index, for example if the index is a string and `self` is an array or a + /// number. Also returns `None` if the given key does not exist in the map + /// or the given index is not within the bounds of the array. + pub fn get_mut(&mut self, index: I) -> Option<&mut Value> { + index.index_mut(self) + } + + /// Extracts the integer value if it is an integer. + pub fn as_integer(&self) -> Option { + match *self { + Value::Integer(i) => Some(i), + _ => None, + } + } + + /// Tests whether this value is an integer. + pub fn is_integer(&self) -> bool { + self.as_integer().is_some() + } + + /// Extracts the float value if it is a float. + pub fn as_float(&self) -> Option { + match *self { + Value::Float(f) => Some(f), + _ => None, + } + } + + /// Tests whether this value is a float. + pub fn is_float(&self) -> bool { + self.as_float().is_some() + } + + /// Extracts the boolean value if it is a boolean. + pub fn as_bool(&self) -> Option { + match *self { + Value::Boolean(b) => Some(b), + _ => None, + } + } + + /// Tests whether this value is a boolean. + pub fn is_bool(&self) -> bool { + self.as_bool().is_some() + } + + /// Extracts the string of this value if it is a string. + pub fn as_str(&self) -> Option<&str> { + match *self { + Value::String(ref s) => Some(&**s), + _ => None, + } + } + + /// Tests if this value is a string. + pub fn is_str(&self) -> bool { + self.as_str().is_some() + } + + /// Extracts the datetime value if it is a datetime. + /// + /// Note that a parsed TOML value will only contain ISO 8601 dates. An + /// example date is: + /// + /// ```notrust + /// 1979-05-27T07:32:00Z + /// ``` + pub fn as_datetime(&self) -> Option<&Datetime> { + match *self { + Value::Datetime(ref s) => Some(s), + _ => None, + } + } + + /// Tests whether this value is a datetime. + pub fn is_datetime(&self) -> bool { + self.as_datetime().is_some() + } + + /// Extracts the array value if it is an array. + pub fn as_array(&self) -> Option<&Vec> { + match *self { + Value::Array(ref s) => Some(s), + _ => None, + } + } + + /// Extracts the array value if it is an array. + pub fn as_array_mut(&mut self) -> Option<&mut Vec> { + match *self { + Value::Array(ref mut s) => Some(s), + _ => None, + } + } + + /// Tests whether this value is an array. + pub fn is_array(&self) -> bool { + self.as_array().is_some() + } + + /// Extracts the table value if it is a table. + pub fn as_table(&self) -> Option<&Table> { + match *self { + Value::Table(ref s) => Some(s), + _ => None, + } + } + + /// Extracts the table value if it is a table. + pub fn as_table_mut(&mut self) -> Option<&mut Table> { + match *self { + Value::Table(ref mut s) => Some(s), + _ => None, + } + } + + /// Tests whether this value is a table. + pub fn is_table(&self) -> bool { + self.as_table().is_some() + } + + /// Tests whether this and another value have the same type. + pub fn same_type(&self, other: &Value) -> bool { + discriminant(self) == discriminant(other) + } + + /// Returns a human-readable representation of the type of this value. + pub fn type_str(&self) -> &'static str { + match *self { + Value::String(..) => "string", + Value::Integer(..) => "integer", + Value::Float(..) => "float", + Value::Boolean(..) => "boolean", + Value::Datetime(..) => "datetime", + Value::Array(..) => "array", + Value::Table(..) => "table", + } + } +} + +impl ops::Index for Value +where + I: Index, +{ + type Output = Value; + + fn index(&self, index: I) -> &Value { + self.get(index).expect("index not found") + } +} + +impl ops::IndexMut for Value +where + I: Index, +{ + fn index_mut(&mut self, index: I) -> &mut Value { + self.get_mut(index).expect("index not found") + } +} + +impl<'a> From<&'a str> for Value { + #[inline] + fn from(val: &'a str) -> Value { + Value::String(val.to_string()) + } +} + +impl> From> for Value { + fn from(val: Vec) -> Value { + Value::Array(val.into_iter().map(|v| v.into()).collect()) + } +} + +impl, V: Into> From> for Value { + fn from(val: BTreeMap) -> Value { + let table = val.into_iter().map(|(s, v)| (s.into(), v.into())).collect(); + + Value::Table(table) + } +} + +impl + Hash + Eq, V: Into> From> for Value { + fn from(val: HashMap) -> Value { + let table = val.into_iter().map(|(s, v)| (s.into(), v.into())).collect(); + + Value::Table(table) + } +} + +macro_rules! impl_into_value { + ($variant:ident : $T:ty) => { + impl From<$T> for Value { + #[inline] + fn from(val: $T) -> Value { + Value::$variant(val.into()) + } + } + }; +} + +impl_into_value!(String: String); +impl_into_value!(Integer: i64); +impl_into_value!(Integer: i32); +impl_into_value!(Integer: i8); +impl_into_value!(Integer: u8); +impl_into_value!(Integer: u32); +impl_into_value!(Float: f64); +impl_into_value!(Float: f32); +impl_into_value!(Boolean: bool); +impl_into_value!(Datetime: Datetime); +impl_into_value!(Table: Table); + +/// Types that can be used to index a `toml::Value` +/// +/// Currently this is implemented for `usize` to index arrays and `str` to index +/// tables. +/// +/// This trait is sealed and not intended for implementation outside of the +/// `toml` crate. +pub trait Index: Sealed { + #[doc(hidden)] + fn index<'a>(&self, val: &'a Value) -> Option<&'a Value>; + #[doc(hidden)] + fn index_mut<'a>(&self, val: &'a mut Value) -> Option<&'a mut Value>; +} + +/// An implementation detail that should not be implemented, this will change in +/// the future and break code otherwise. +#[doc(hidden)] +pub trait Sealed {} +impl Sealed for usize {} +impl Sealed for str {} +impl Sealed for String {} +impl<'a, T: Sealed + ?Sized> Sealed for &'a T {} + +impl Index for usize { + fn index<'a>(&self, val: &'a Value) -> Option<&'a Value> { + match *val { + Value::Array(ref a) => a.get(*self), + _ => None, + } + } + + fn index_mut<'a>(&self, val: &'a mut Value) -> Option<&'a mut Value> { + match *val { + Value::Array(ref mut a) => a.get_mut(*self), + _ => None, + } + } +} + +impl Index for str { + fn index<'a>(&self, val: &'a Value) -> Option<&'a Value> { + match *val { + Value::Table(ref a) => a.get(self), + _ => None, + } + } + + fn index_mut<'a>(&self, val: &'a mut Value) -> Option<&'a mut Value> { + match *val { + Value::Table(ref mut a) => a.get_mut(self), + _ => None, + } + } +} + +impl Index for String { + fn index<'a>(&self, val: &'a Value) -> Option<&'a Value> { + self[..].index(val) + } + + fn index_mut<'a>(&self, val: &'a mut Value) -> Option<&'a mut Value> { + self[..].index_mut(val) + } +} + +impl<'s, T: ?Sized> Index for &'s T +where + T: Index, +{ + fn index<'a>(&self, val: &'a Value) -> Option<&'a Value> { + (**self).index(val) + } + + fn index_mut<'a>(&self, val: &'a mut Value) -> Option<&'a mut Value> { + (**self).index_mut(val) + } +} + +#[cfg(feature = "display")] +impl fmt::Display for Value { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use serde::Serialize as _; + + let mut output = String::new(); + let serializer = crate::ser::ValueSerializer::new(&mut output); + self.serialize(serializer).unwrap(); + output.fmt(f) + } +} + +#[cfg(feature = "parse")] +impl std::str::FromStr for Value { + type Err = crate::de::Error; + fn from_str(s: &str) -> Result { + crate::from_str(s) + } +} + +impl ser::Serialize for Value { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + use serde::ser::SerializeMap; + + match *self { + Value::String(ref s) => serializer.serialize_str(s), + Value::Integer(i) => serializer.serialize_i64(i), + Value::Float(f) => serializer.serialize_f64(f), + Value::Boolean(b) => serializer.serialize_bool(b), + Value::Datetime(ref s) => s.serialize(serializer), + Value::Array(ref a) => a.serialize(serializer), + Value::Table(ref t) => { + let mut map = serializer.serialize_map(Some(t.len()))?; + // Be sure to visit non-tables first (and also non + // array-of-tables) as all keys must be emitted first. + for (k, v) in t { + if !v.is_table() && !v.is_array() + || (v + .as_array() + .map(|a| !a.iter().any(|v| v.is_table())) + .unwrap_or(false)) + { + map.serialize_entry(k, v)?; + } + } + for (k, v) in t { + if v.as_array() + .map(|a| a.iter().any(|v| v.is_table())) + .unwrap_or(false) + { + map.serialize_entry(k, v)?; + } + } + for (k, v) in t { + if v.is_table() { + map.serialize_entry(k, v)?; + } + } + map.end() + } + } + } +} + +impl<'de> de::Deserialize<'de> for Value { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + struct ValueVisitor; + + impl<'de> de::Visitor<'de> for ValueVisitor { + type Value = Value; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("any valid TOML value") + } + + fn visit_bool(self, value: bool) -> Result { + Ok(Value::Boolean(value)) + } + + fn visit_i64(self, value: i64) -> Result { + Ok(Value::Integer(value)) + } + + fn visit_u64(self, value: u64) -> Result { + if value <= i64::max_value() as u64 { + Ok(Value::Integer(value as i64)) + } else { + Err(de::Error::custom("u64 value was too large")) + } + } + + fn visit_u32(self, value: u32) -> Result { + Ok(Value::Integer(value.into())) + } + + fn visit_i32(self, value: i32) -> Result { + Ok(Value::Integer(value.into())) + } + + fn visit_f64(self, value: f64) -> Result { + Ok(Value::Float(value)) + } + + fn visit_str(self, value: &str) -> Result { + Ok(Value::String(value.into())) + } + + fn visit_string(self, value: String) -> Result { + Ok(Value::String(value)) + } + + fn visit_some(self, deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + de::Deserialize::deserialize(deserializer) + } + + fn visit_seq(self, mut visitor: V) -> Result + where + V: de::SeqAccess<'de>, + { + let mut vec = Vec::new(); + while let Some(elem) = visitor.next_element()? { + vec.push(elem); + } + Ok(Value::Array(vec)) + } + + fn visit_map(self, mut visitor: V) -> Result + where + V: de::MapAccess<'de>, + { + let mut key = String::new(); + let datetime = visitor.next_key_seed(DatetimeOrTable { key: &mut key })?; + match datetime { + Some(true) => { + let date: datetime::DatetimeFromString = visitor.next_value()?; + return Ok(Value::Datetime(date.value)); + } + None => return Ok(Value::Table(Table::new())), + Some(false) => {} + } + let mut map = Table::new(); + map.insert(key, visitor.next_value()?); + while let Some(key) = visitor.next_key::()? { + if let crate::map::Entry::Vacant(vacant) = map.entry(&key) { + vacant.insert(visitor.next_value()?); + } else { + let msg = format!("duplicate key: `{}`", key); + return Err(de::Error::custom(msg)); + } + } + Ok(Value::Table(map)) + } + } + + deserializer.deserialize_any(ValueVisitor) + } +} + +// This is wrapped by `Table` and any trait methods implemented here need to be wrapped there. +impl<'de> de::Deserializer<'de> for Value { + type Error = crate::de::Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self { + Value::Boolean(v) => visitor.visit_bool(v), + Value::Integer(n) => visitor.visit_i64(n), + Value::Float(n) => visitor.visit_f64(n), + Value::String(v) => visitor.visit_string(v), + Value::Datetime(v) => visitor.visit_string(v.to_string()), + Value::Array(v) => { + let len = v.len(); + let mut deserializer = SeqDeserializer::new(v); + let seq = visitor.visit_seq(&mut deserializer)?; + let remaining = deserializer.iter.len(); + if remaining == 0 { + Ok(seq) + } else { + Err(de::Error::invalid_length(len, &"fewer elements in array")) + } + } + Value::Table(v) => { + let len = v.len(); + let mut deserializer = MapDeserializer::new(v); + let map = visitor.visit_map(&mut deserializer)?; + let remaining = deserializer.iter.len(); + if remaining == 0 { + Ok(map) + } else { + Err(de::Error::invalid_length(len, &"fewer elements in map")) + } + } + } + } + + #[inline] + fn deserialize_enum( + self, + _name: &'static str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + match self { + Value::String(variant) => visitor.visit_enum(variant.into_deserializer()), + Value::Table(variant) => { + use de::Error; + if variant.is_empty() { + Err(crate::de::Error::custom( + "wanted exactly 1 element, found 0 elements", + )) + } else if variant.len() != 1 { + Err(crate::de::Error::custom( + "wanted exactly 1 element, more than 1 element", + )) + } else { + let deserializer = MapDeserializer::new(variant); + visitor.visit_enum(deserializer) + } + } + _ => Err(de::Error::invalid_type( + de::Unexpected::UnitVariant, + &"string only", + )), + } + } + + // `None` is interpreted as a missing field so be sure to implement `Some` + // as a present field. + fn deserialize_option(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_some(self) + } + + fn deserialize_newtype_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_newtype_struct(self) + } + + serde::forward_to_deserialize_any! { + bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string unit seq + bytes byte_buf map unit_struct tuple_struct struct + tuple ignored_any identifier + } +} + +struct SeqDeserializer { + iter: vec::IntoIter, +} + +impl SeqDeserializer { + fn new(vec: Vec) -> Self { + SeqDeserializer { + iter: vec.into_iter(), + } + } +} + +impl<'de> de::SeqAccess<'de> for SeqDeserializer { + type Error = crate::de::Error; + + fn next_element_seed(&mut self, seed: T) -> Result, crate::de::Error> + where + T: de::DeserializeSeed<'de>, + { + match self.iter.next() { + Some(value) => seed.deserialize(value).map(Some), + None => Ok(None), + } + } + + fn size_hint(&self) -> Option { + match self.iter.size_hint() { + (lower, Some(upper)) if lower == upper => Some(upper), + _ => None, + } + } +} + +struct MapDeserializer { + iter:
::IntoIter, + value: Option<(String, Value)>, +} + +impl MapDeserializer { + fn new(map: Table) -> Self { + MapDeserializer { + iter: map.into_iter(), + value: None, + } + } +} + +impl<'de> de::MapAccess<'de> for MapDeserializer { + type Error = crate::de::Error; + + fn next_key_seed(&mut self, seed: T) -> Result, crate::de::Error> + where + T: de::DeserializeSeed<'de>, + { + match self.iter.next() { + Some((key, value)) => { + self.value = Some((key.clone(), value)); + seed.deserialize(Value::String(key)).map(Some) + } + None => Ok(None), + } + } + + fn next_value_seed(&mut self, seed: T) -> Result + where + T: de::DeserializeSeed<'de>, + { + let (key, res) = match self.value.take() { + Some((key, value)) => (key, seed.deserialize(value)), + None => return Err(de::Error::custom("value is missing")), + }; + res.map_err(|mut error| { + error.add_key(key); + error + }) + } + + fn size_hint(&self) -> Option { + match self.iter.size_hint() { + (lower, Some(upper)) if lower == upper => Some(upper), + _ => None, + } + } +} + +impl<'de> de::EnumAccess<'de> for MapDeserializer { + type Error = crate::de::Error; + type Variant = MapEnumDeserializer; + + fn variant_seed(mut self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> + where + V: serde::de::DeserializeSeed<'de>, + { + use de::Error; + let (key, value) = match self.iter.next() { + Some(pair) => pair, + None => { + return Err(Error::custom( + "expected table with exactly 1 entry, found empty table", + )); + } + }; + + let val = seed.deserialize(key.into_deserializer())?; + + let variant = MapEnumDeserializer::new(value); + + Ok((val, variant)) + } +} + +/// Deserializes table values into enum variants. +pub(crate) struct MapEnumDeserializer { + value: Value, +} + +impl MapEnumDeserializer { + pub(crate) fn new(value: Value) -> Self { + MapEnumDeserializer { value } + } +} + +impl<'de> serde::de::VariantAccess<'de> for MapEnumDeserializer { + type Error = crate::de::Error; + + fn unit_variant(self) -> Result<(), Self::Error> { + use de::Error; + match self.value { + Value::Table(values) => { + if values.is_empty() { + Ok(()) + } else { + Err(Error::custom("expected empty table")) + } + } + e => Err(Error::custom(format!( + "expected table, found {}", + e.type_str() + ))), + } + } + + fn newtype_variant_seed(self, seed: T) -> Result + where + T: serde::de::DeserializeSeed<'de>, + { + seed.deserialize(self.value.into_deserializer()) + } + + fn tuple_variant(self, len: usize, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + use de::Error; + match self.value { + Value::Table(values) => { + let tuple_values = values + .into_iter() + .enumerate() + .map(|(index, (key, value))| match key.parse::() { + Ok(key_index) if key_index == index => Ok(value), + Ok(_) | Err(_) => Err(Error::custom(format!( + "expected table key `{}`, but was `{}`", + index, key + ))), + }) + // Fold all values into a `Vec`, or return the first error. + .fold(Ok(Vec::with_capacity(len)), |result, value_result| { + result.and_then(move |mut tuple_values| match value_result { + Ok(value) => { + tuple_values.push(value); + Ok(tuple_values) + } + // `Result` to `Result, Self::Error>` + Err(e) => Err(e), + }) + })?; + + if tuple_values.len() == len { + serde::de::Deserializer::deserialize_seq( + tuple_values.into_deserializer(), + visitor, + ) + } else { + Err(Error::custom(format!("expected tuple with length {}", len))) + } + } + e => Err(Error::custom(format!( + "expected table, found {}", + e.type_str() + ))), + } + } + + fn struct_variant( + self, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + serde::de::Deserializer::deserialize_struct( + self.value.into_deserializer(), + "", // TODO: this should be the variant name + fields, + visitor, + ) + } +} + +impl<'de> de::IntoDeserializer<'de, crate::de::Error> for Value { + type Deserializer = Self; + + fn into_deserializer(self) -> Self { + self + } +} + +struct ValueSerializer; + +impl ser::Serializer for ValueSerializer { + type Ok = Value; + type Error = crate::ser::Error; + + type SerializeSeq = ValueSerializeVec; + type SerializeTuple = ValueSerializeVec; + type SerializeTupleStruct = ValueSerializeVec; + type SerializeTupleVariant = ValueSerializeVec; + type SerializeMap = ValueSerializeMap; + type SerializeStruct = ValueSerializeMap; + type SerializeStructVariant = ser::Impossible; + + fn serialize_bool(self, value: bool) -> Result { + Ok(Value::Boolean(value)) + } + + fn serialize_i8(self, value: i8) -> Result { + self.serialize_i64(value.into()) + } + + fn serialize_i16(self, value: i16) -> Result { + self.serialize_i64(value.into()) + } + + fn serialize_i32(self, value: i32) -> Result { + self.serialize_i64(value.into()) + } + + fn serialize_i64(self, value: i64) -> Result { + Ok(Value::Integer(value)) + } + + fn serialize_u8(self, value: u8) -> Result { + self.serialize_i64(value.into()) + } + + fn serialize_u16(self, value: u16) -> Result { + self.serialize_i64(value.into()) + } + + fn serialize_u32(self, value: u32) -> Result { + self.serialize_i64(value.into()) + } + + fn serialize_u64(self, value: u64) -> Result { + if value <= i64::max_value() as u64 { + self.serialize_i64(value as i64) + } else { + Err(ser::Error::custom("u64 value was too large")) + } + } + + fn serialize_f32(self, value: f32) -> Result { + self.serialize_f64(value.into()) + } + + fn serialize_f64(self, value: f64) -> Result { + Ok(Value::Float(value)) + } + + fn serialize_char(self, value: char) -> Result { + let mut s = String::new(); + s.push(value); + self.serialize_str(&s) + } + + fn serialize_str(self, value: &str) -> Result { + Ok(Value::String(value.to_owned())) + } + + fn serialize_bytes(self, value: &[u8]) -> Result { + let vec = value.iter().map(|&b| Value::Integer(b.into())).collect(); + Ok(Value::Array(vec)) + } + + fn serialize_unit(self) -> Result { + Err(crate::ser::Error::unsupported_type(Some("unit"))) + } + + fn serialize_unit_struct(self, name: &'static str) -> Result { + Err(crate::ser::Error::unsupported_type(Some(name))) + } + + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + ) -> Result { + self.serialize_str(_variant) + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> Result + where + T: ser::Serialize, + { + value.serialize(self) + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result + where + T: ser::Serialize, + { + let value = value.serialize(ValueSerializer)?; + let mut table = Table::new(); + table.insert(variant.to_owned(), value); + Ok(table.into()) + } + + fn serialize_none(self) -> Result { + Err(crate::ser::Error::unsupported_none()) + } + + fn serialize_some(self, value: &T) -> Result + where + T: ser::Serialize, + { + value.serialize(self) + } + + fn serialize_seq(self, len: Option) -> Result { + Ok(ValueSerializeVec { + vec: Vec::with_capacity(len.unwrap_or(0)), + }) + } + + fn serialize_tuple(self, len: usize) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_tuple_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + len: usize, + ) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_map(self, _len: Option) -> Result { + Ok(ValueSerializeMap { + ser: SerializeMap { + map: Table::new(), + next_key: None, + }, + }) + } + + fn serialize_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + self.serialize_map(Some(len)) + } + + fn serialize_struct_variant( + self, + name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Err(crate::ser::Error::unsupported_type(Some(name))) + } +} + +pub(crate) struct TableSerializer; + +impl ser::Serializer for TableSerializer { + type Ok = Table; + type Error = crate::ser::Error; + + type SerializeSeq = ser::Impossible; + type SerializeTuple = ser::Impossible; + type SerializeTupleStruct = ser::Impossible; + type SerializeTupleVariant = ser::Impossible; + type SerializeMap = SerializeMap; + type SerializeStruct = SerializeMap; + type SerializeStructVariant = ser::Impossible; + + fn serialize_bool(self, _value: bool) -> Result { + Err(crate::ser::Error::unsupported_type(None)) + } + + fn serialize_i8(self, _value: i8) -> Result { + Err(crate::ser::Error::unsupported_type(None)) + } + + fn serialize_i16(self, _value: i16) -> Result { + Err(crate::ser::Error::unsupported_type(None)) + } + + fn serialize_i32(self, _value: i32) -> Result { + Err(crate::ser::Error::unsupported_type(None)) + } + + fn serialize_i64(self, _value: i64) -> Result { + Err(crate::ser::Error::unsupported_type(None)) + } + + fn serialize_u8(self, _value: u8) -> Result { + Err(crate::ser::Error::unsupported_type(None)) + } + + fn serialize_u16(self, _value: u16) -> Result { + Err(crate::ser::Error::unsupported_type(None)) + } + + fn serialize_u32(self, _value: u32) -> Result { + Err(crate::ser::Error::unsupported_type(None)) + } + + fn serialize_u64(self, _value: u64) -> Result { + Err(crate::ser::Error::unsupported_type(None)) + } + + fn serialize_f32(self, _value: f32) -> Result { + Err(crate::ser::Error::unsupported_type(None)) + } + + fn serialize_f64(self, _value: f64) -> Result { + Err(crate::ser::Error::unsupported_type(None)) + } + + fn serialize_char(self, _value: char) -> Result { + Err(crate::ser::Error::unsupported_type(None)) + } + + fn serialize_str(self, _value: &str) -> Result { + Err(crate::ser::Error::unsupported_type(None)) + } + + fn serialize_bytes(self, _value: &[u8]) -> Result { + Err(crate::ser::Error::unsupported_type(None)) + } + + fn serialize_unit(self) -> Result { + Err(crate::ser::Error::unsupported_type(None)) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result { + Err(crate::ser::Error::unsupported_type(None)) + } + + fn serialize_unit_variant( + self, + name: &'static str, + _variant_index: u32, + _variant: &'static str, + ) -> Result { + Err(crate::ser::Error::unsupported_type(Some(name))) + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> Result + where + T: ser::Serialize, + { + value.serialize(self) + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result + where + T: ser::Serialize, + { + let value = value.serialize(ValueSerializer)?; + let mut table = Table::new(); + table.insert(variant.to_owned(), value); + Ok(table) + } + + fn serialize_none(self) -> Result { + Err(crate::ser::Error::unsupported_none()) + } + + fn serialize_some(self, value: &T) -> Result + where + T: ser::Serialize, + { + value.serialize(self) + } + + fn serialize_seq(self, _len: Option) -> Result { + Err(crate::ser::Error::unsupported_type(None)) + } + + fn serialize_tuple(self, _len: usize) -> Result { + Err(crate::ser::Error::unsupported_type(None)) + } + + fn serialize_tuple_struct( + self, + name: &'static str, + _len: usize, + ) -> Result { + Err(crate::ser::Error::unsupported_type(Some(name))) + } + + fn serialize_tuple_variant( + self, + name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Err(crate::ser::Error::unsupported_type(Some(name))) + } + + fn serialize_map(self, _len: Option) -> Result { + Ok(SerializeMap { + map: Table::new(), + next_key: None, + }) + } + + fn serialize_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + self.serialize_map(Some(len)) + } + + fn serialize_struct_variant( + self, + name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Err(crate::ser::Error::unsupported_type(Some(name))) + } +} + +struct ValueSerializeVec { + vec: Vec, +} + +impl ser::SerializeSeq for ValueSerializeVec { + type Ok = Value; + type Error = crate::ser::Error; + + fn serialize_element(&mut self, value: &T) -> Result<(), crate::ser::Error> + where + T: ser::Serialize, + { + self.vec.push(Value::try_from(value)?); + Ok(()) + } + + fn end(self) -> Result { + Ok(Value::Array(self.vec)) + } +} + +impl ser::SerializeTuple for ValueSerializeVec { + type Ok = Value; + type Error = crate::ser::Error; + + fn serialize_element(&mut self, value: &T) -> Result<(), crate::ser::Error> + where + T: ser::Serialize, + { + ser::SerializeSeq::serialize_element(self, value) + } + + fn end(self) -> Result { + ser::SerializeSeq::end(self) + } +} + +impl ser::SerializeTupleStruct for ValueSerializeVec { + type Ok = Value; + type Error = crate::ser::Error; + + fn serialize_field(&mut self, value: &T) -> Result<(), crate::ser::Error> + where + T: ser::Serialize, + { + ser::SerializeSeq::serialize_element(self, value) + } + + fn end(self) -> Result { + ser::SerializeSeq::end(self) + } +} + +impl ser::SerializeTupleVariant for ValueSerializeVec { + type Ok = Value; + type Error = crate::ser::Error; + + fn serialize_field(&mut self, value: &T) -> Result<(), crate::ser::Error> + where + T: ser::Serialize, + { + ser::SerializeSeq::serialize_element(self, value) + } + + fn end(self) -> Result { + ser::SerializeSeq::end(self) + } +} + +pub(crate) struct SerializeMap { + map: Table, + next_key: Option, +} + +impl ser::SerializeMap for SerializeMap { + type Ok = Table; + type Error = crate::ser::Error; + + fn serialize_key(&mut self, key: &T) -> Result<(), crate::ser::Error> + where + T: ser::Serialize, + { + match Value::try_from(key)? { + Value::String(s) => self.next_key = Some(s), + _ => return Err(crate::ser::Error::key_not_string()), + }; + Ok(()) + } + + fn serialize_value(&mut self, value: &T) -> Result<(), crate::ser::Error> + where + T: ser::Serialize, + { + let key = self.next_key.take(); + let key = key.expect("serialize_value called before serialize_key"); + match Value::try_from(value) { + Ok(value) => { + self.map.insert(key, value); + } + Err(crate::ser::Error { + inner: crate::edit::ser::Error::UnsupportedNone, + }) => {} + Err(e) => return Err(e), + } + Ok(()) + } + + fn end(self) -> Result { + Ok(self.map) + } +} + +impl ser::SerializeStruct for SerializeMap { + type Ok = Table; + type Error = crate::ser::Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), crate::ser::Error> + where + T: ser::Serialize, + { + ser::SerializeMap::serialize_key(self, key)?; + ser::SerializeMap::serialize_value(self, value) + } + + fn end(self) -> Result { + ser::SerializeMap::end(self) + } +} + +struct ValueSerializeMap { + ser: SerializeMap, +} + +impl ser::SerializeMap for ValueSerializeMap { + type Ok = Value; + type Error = crate::ser::Error; + + fn serialize_key(&mut self, key: &T) -> Result<(), crate::ser::Error> + where + T: ser::Serialize, + { + self.ser.serialize_key(key) + } + + fn serialize_value(&mut self, value: &T) -> Result<(), crate::ser::Error> + where + T: ser::Serialize, + { + self.ser.serialize_value(value) + } + + fn end(self) -> Result { + self.ser.end().map(Value::Table) + } +} + +impl ser::SerializeStruct for ValueSerializeMap { + type Ok = Value; + type Error = crate::ser::Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), crate::ser::Error> + where + T: ser::Serialize, + { + ser::SerializeMap::serialize_key(self, key)?; + ser::SerializeMap::serialize_value(self, value) + } + + fn end(self) -> Result { + ser::SerializeMap::end(self) + } +} + +struct DatetimeOrTable<'a> { + key: &'a mut String, +} + +impl<'a, 'de> de::DeserializeSeed<'de> for DatetimeOrTable<'a> { + type Value = bool; + + fn deserialize(self, deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_any(self) + } +} + +impl<'a, 'de> de::Visitor<'de> for DatetimeOrTable<'a> { + type Value = bool; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a string key") + } + + fn visit_str(self, s: &str) -> Result + where + E: de::Error, + { + if s == datetime::FIELD { + Ok(true) + } else { + self.key.push_str(s); + Ok(false) + } + } + + fn visit_string(self, s: String) -> Result + where + E: de::Error, + { + if s == datetime::FIELD { + Ok(true) + } else { + *self.key = s; + Ok(false) + } + } +} diff --git a/tests/decoder.rs b/tests/decoder.rs new file mode 100644 index 0000000..fe6db3f --- /dev/null +++ b/tests/decoder.rs @@ -0,0 +1,67 @@ +#![cfg(all(feature = "parse", feature = "display"))] + +#[derive(Copy, Clone)] +pub struct Decoder; + +impl toml_test_harness::Decoder for Decoder { + fn name(&self) -> &str { + "toml" + } + + fn decode(&self, data: &[u8]) -> Result { + let data = std::str::from_utf8(data).map_err(toml_test_harness::Error::new)?; + let document = data + .parse::() + .map_err(toml_test_harness::Error::new)?; + value_to_decoded(&document) + } +} + +fn value_to_decoded( + value: &toml::Value, +) -> Result { + match value { + toml::Value::Integer(v) => Ok(toml_test_harness::Decoded::Value( + toml_test_harness::DecodedValue::from(*v), + )), + toml::Value::String(v) => Ok(toml_test_harness::Decoded::Value( + toml_test_harness::DecodedValue::from(v), + )), + toml::Value::Float(v) => Ok(toml_test_harness::Decoded::Value( + toml_test_harness::DecodedValue::from(*v), + )), + toml::Value::Datetime(v) => { + let value = v.to_string(); + let value = match (v.date.is_some(), v.time.is_some(), v.offset.is_some()) { + (true, true, true) => toml_test_harness::DecodedValue::Datetime(value), + (true, true, false) => toml_test_harness::DecodedValue::DatetimeLocal(value), + (true, false, false) => toml_test_harness::DecodedValue::DateLocal(value), + (false, true, false) => toml_test_harness::DecodedValue::TimeLocal(value), + _ => unreachable!("Unsupported case"), + }; + Ok(toml_test_harness::Decoded::Value(value)) + } + toml::Value::Boolean(v) => Ok(toml_test_harness::Decoded::Value( + toml_test_harness::DecodedValue::from(*v), + )), + toml::Value::Array(v) => { + let v: Result<_, toml_test_harness::Error> = v.iter().map(value_to_decoded).collect(); + Ok(toml_test_harness::Decoded::Array(v?)) + } + toml::Value::Table(v) => table_to_decoded(v), + } +} + +fn table_to_decoded( + value: &toml::value::Table, +) -> Result { + let table: Result<_, toml_test_harness::Error> = value + .iter() + .map(|(k, v)| { + let k = k.to_owned(); + let v = value_to_decoded(v)?; + Ok((k, v)) + }) + .collect(); + Ok(toml_test_harness::Decoded::Table(table?)) +} diff --git a/tests/decoder_compliance.rs b/tests/decoder_compliance.rs new file mode 100644 index 0000000..5d4fc2a --- /dev/null +++ b/tests/decoder_compliance.rs @@ -0,0 +1,21 @@ +mod decoder; + +#[cfg(all(feature = "parse", feature = "display"))] +fn main() { + let decoder = decoder::Decoder; + let mut harness = toml_test_harness::DecoderHarness::new(decoder); + harness + .ignore([ + "valid/spec/float-0.toml", + // Unreleased + "valid/string/escape-esc.toml", + "valid/string/hex-escape.toml", + "valid/datetime/no-seconds.toml", + "valid/inline-table/newline.toml", + ]) + .unwrap(); + harness.test(); +} + +#[cfg(not(all(feature = "parse", feature = "display")))] +fn main() {} diff --git a/tests/encoder.rs b/tests/encoder.rs new file mode 100644 index 0000000..eda6296 --- /dev/null +++ b/tests/encoder.rs @@ -0,0 +1,81 @@ +#![cfg(all(feature = "parse", feature = "display"))] + +#[derive(Copy, Clone)] +pub struct Encoder; + +impl toml_test_harness::Encoder for Encoder { + fn name(&self) -> &str { + "toml" + } + + fn encode(&self, data: toml_test_harness::Decoded) -> Result { + let value = from_decoded(&data)?; + let s = toml::to_string(&value).map_err(toml_test_harness::Error::new)?; + Ok(s) + } +} + +fn from_decoded( + decoded: &toml_test_harness::Decoded, +) -> Result { + let value = match decoded { + toml_test_harness::Decoded::Value(value) => from_decoded_value(value)?, + toml_test_harness::Decoded::Table(value) => toml::Value::Table(from_table(value)?), + toml_test_harness::Decoded::Array(value) => toml::Value::Array(from_array(value)?), + }; + Ok(value) +} + +fn from_decoded_value( + decoded: &toml_test_harness::DecodedValue, +) -> Result { + match decoded { + toml_test_harness::DecodedValue::String(value) => Ok(toml::Value::String(value.clone())), + toml_test_harness::DecodedValue::Integer(value) => value + .parse::() + .map_err(toml_test_harness::Error::new) + .map(toml::Value::Integer), + toml_test_harness::DecodedValue::Float(value) => value + .parse::() + .map_err(toml_test_harness::Error::new) + .map(toml::Value::Float), + toml_test_harness::DecodedValue::Bool(value) => value + .parse::() + .map_err(toml_test_harness::Error::new) + .map(toml::Value::Boolean), + toml_test_harness::DecodedValue::Datetime(value) => value + .parse::() + .map_err(toml_test_harness::Error::new) + .map(toml::Value::Datetime), + toml_test_harness::DecodedValue::DatetimeLocal(value) => value + .parse::() + .map_err(toml_test_harness::Error::new) + .map(toml::Value::Datetime), + toml_test_harness::DecodedValue::DateLocal(value) => value + .parse::() + .map_err(toml_test_harness::Error::new) + .map(toml::Value::Datetime), + toml_test_harness::DecodedValue::TimeLocal(value) => value + .parse::() + .map_err(toml_test_harness::Error::new) + .map(toml::Value::Datetime), + } +} + +fn from_table( + decoded: &std::collections::HashMap, +) -> Result { + decoded + .iter() + .map(|(k, v)| { + let v = from_decoded(v)?; + Ok((k.to_owned(), v)) + }) + .collect() +} + +fn from_array( + decoded: &[toml_test_harness::Decoded], +) -> Result { + decoded.iter().map(from_decoded).collect() +} diff --git a/tests/encoder_compliance.rs b/tests/encoder_compliance.rs new file mode 100644 index 0000000..3807248 --- /dev/null +++ b/tests/encoder_compliance.rs @@ -0,0 +1,14 @@ +mod decoder; +mod encoder; + +#[cfg(all(feature = "parse", feature = "display"))] +fn main() { + let encoder = encoder::Encoder; + let decoder = decoder::Decoder; + let mut harness = toml_test_harness::EncoderHarness::new(encoder, decoder); + harness.ignore(["valid/spec/float-0.toml"]).unwrap(); + harness.test(); +} + +#[cfg(not(all(feature = "parse", feature = "display")))] +fn main() {} diff --git a/tests/testsuite/de_errors.rs b/tests/testsuite/de_errors.rs new file mode 100644 index 0000000..b3630bd --- /dev/null +++ b/tests/testsuite/de_errors.rs @@ -0,0 +1,460 @@ +use serde::{de, Deserialize}; +use std::fmt; + +macro_rules! bad { + ($toml:expr, $ty:ty, $msg:expr) => { + match toml::from_str::<$ty>($toml) { + Ok(s) => panic!("parsed to: {:#?}", s), + Err(e) => snapbox::assert_eq($msg, e.to_string()), + } + }; +} + +#[derive(Debug, Deserialize, PartialEq)] +struct Parent { + p_a: T, + p_b: Vec>, +} + +#[derive(Debug, Deserialize, PartialEq)] +#[serde(deny_unknown_fields)] +struct Child { + c_a: T, + c_b: T, +} + +#[derive(Debug, PartialEq)] +enum CasedString { + Lowercase(String), + Uppercase(String), +} + +impl<'de> de::Deserialize<'de> for CasedString { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + struct CasedStringVisitor; + + impl<'de> de::Visitor<'de> for CasedStringVisitor { + type Value = CasedString; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string") + } + + fn visit_str(self, s: &str) -> Result + where + E: de::Error, + { + if s.is_empty() { + Err(de::Error::invalid_length(0, &"a non-empty string")) + } else if s.chars().all(|x| x.is_ascii_lowercase()) { + Ok(CasedString::Lowercase(s.to_string())) + } else if s.chars().all(|x| x.is_ascii_uppercase()) { + Ok(CasedString::Uppercase(s.to_string())) + } else { + Err(de::Error::invalid_value( + de::Unexpected::Str(s), + &"all lowercase or all uppercase", + )) + } + } + } + + deserializer.deserialize_any(CasedStringVisitor) + } +} + +#[test] +fn custom_errors() { + toml::from_str::>( + " + p_a = 'a' + p_b = [{c_a = 'a', c_b = 'c'}] + ", + ) + .unwrap(); + + // Custom error at p_b value. + bad!( + " + p_a = '' + # ^ + ", + Parent, + "\ +TOML parse error at line 2, column 19 + | +2 | p_a = '' + | ^^ +invalid length 0, expected a non-empty string +" + ); + + // Missing field in table. + bad!( + " + p_a = 'a' + # ^ + ", + Parent, + "\ +TOML parse error at line 1, column 1 + | +1 | + | ^ +missing field `p_b` +" + ); + + // Invalid type in p_b. + bad!( + " + p_a = 'a' + p_b = 1 + # ^ + ", + Parent, + "\ +TOML parse error at line 3, column 19 + | +3 | p_b = 1 + | ^ +invalid type: integer `1`, expected a sequence +" + ); + + // Sub-table in Vec is missing a field. + bad!( + " + p_a = 'a' + p_b = [ + {c_a = 'a'} + # ^ + ] + ", + Parent, + "\ +TOML parse error at line 4, column 17 + | +4 | {c_a = 'a'} + | ^^^^^^^^^^^ +missing field `c_b` +" + ); + + // Sub-table in Vec has a field with a bad value. + bad!( + " + p_a = 'a' + p_b = [ + {c_a = 'a', c_b = '*'} + # ^ + ] + ", + Parent, + "\ +TOML parse error at line 4, column 35 + | +4 | {c_a = 'a', c_b = '*'} + | ^^^ +invalid value: string \"*\", expected all lowercase or all uppercase +" + ); + + // Sub-table in Vec is missing a field. + bad!( + " + p_a = 'a' + p_b = [ + {c_a = 'a', c_b = 'b'}, + {c_a = 'aa'} + # ^ + ] + ", + Parent, + "\ +TOML parse error at line 5, column 17 + | +5 | {c_a = 'aa'} + | ^^^^^^^^^^^^ +missing field `c_b` +" + ); + + // Sub-table in the middle of a Vec is missing a field. + bad!( + " + p_a = 'a' + p_b = [ + {c_a = 'a', c_b = 'b'}, + {c_a = 'aa'}, + # ^ + {c_a = 'aaa', c_b = 'bbb'}, + ] + ", + Parent, + "\ +TOML parse error at line 5, column 17 + | +5 | {c_a = 'aa'}, + | ^^^^^^^^^^^^ +missing field `c_b` +" + ); + + // Sub-table in the middle of a Vec has a field with a bad value. + bad!( + " + p_a = 'a' + p_b = [ + {c_a = 'a', c_b = 'b'}, + {c_a = 'aa', c_b = 1}, + # ^ + {c_a = 'aaa', c_b = 'bbb'}, + ] + ", + Parent, + "\ +TOML parse error at line 5, column 36 + | +5 | {c_a = 'aa', c_b = 1}, + | ^ +invalid type: integer `1`, expected a string +" + ); + + // Sub-table in the middle of a Vec has an extra field. + bad!( + " + p_a = 'a' + p_b = [ + {c_a = 'a', c_b = 'b'}, + {c_a = 'aa', c_b = 'bb', c_d = 'd'}, + # ^ + {c_a = 'aaa', c_b = 'bbb'}, + {c_a = 'aaaa', c_b = 'bbbb'}, + ] + ", + Parent, + "\ +TOML parse error at line 5, column 42 + | +5 | {c_a = 'aa', c_b = 'bb', c_d = 'd'}, + | ^^^ +unknown field `c_d`, expected `c_a` or `c_b` +" + ); + + // Sub-table in the middle of a Vec is missing a field. + // FIXME: This location is pretty off. + bad!( + " + p_a = 'a' + [[p_b]] + c_a = 'a' + c_b = 'b' + [[p_b]] + c_a = 'aa' + # c_b = 'bb' # <- missing field + [[p_b]] + c_a = 'aaa' + c_b = 'bbb' + [[p_b]] + # ^ + c_a = 'aaaa' + c_b = 'bbbb' + ", + Parent, + "\ +TOML parse error at line 6, column 13 + | +6 | [[p_b]] + | ^^^^^^^^^^^^^^^^^^^ +missing field `c_b` +" + ); + + // Sub-table in the middle of a Vec has a field with a bad value. + bad!( + " + p_a = 'a' + [[p_b]] + c_a = 'a' + c_b = 'b' + [[p_b]] + c_a = 'aa' + c_b = '*' + # ^ + [[p_b]] + c_a = 'aaa' + c_b = 'bbb' + ", + Parent, + "\ +TOML parse error at line 8, column 19 + | +8 | c_b = '*' + | ^^^ +invalid value: string \"*\", expected all lowercase or all uppercase +" + ); + + // Sub-table in the middle of a Vec has an extra field. + bad!( + " + p_a = 'a' + [[p_b]] + c_a = 'a' + c_b = 'b' + [[p_b]] + c_a = 'aa' + c_d = 'dd' # unknown field + # ^ + [[p_b]] + c_a = 'aaa' + c_b = 'bbb' + [[p_b]] + c_a = 'aaaa' + c_b = 'bbbb' + ", + Parent, + "\ +TOML parse error at line 8, column 13 + | +8 | c_d = 'dd' # unknown field + | ^^^ +unknown field `c_d`, expected `c_a` or `c_b` +" + ); +} + +#[test] +fn serde_derive_deserialize_errors() { + bad!( + " + p_a = '' + # ^ + ", + Parent, + "\ +TOML parse error at line 1, column 1 + | +1 | + | ^ +missing field `p_b` +" + ); + + bad!( + " + p_a = '' + p_b = [ + {c_a = ''} + # ^ + ] + ", + Parent, + "\ +TOML parse error at line 4, column 17 + | +4 | {c_a = ''} + | ^^^^^^^^^^ +missing field `c_b` +" + ); + + bad!( + " + p_a = '' + p_b = [ + {c_a = '', c_b = 1} + # ^ + ] + ", + Parent, + "\ +TOML parse error at line 4, column 34 + | +4 | {c_a = '', c_b = 1} + | ^ +invalid type: integer `1`, expected a string +" + ); + + // FIXME: This location could be better. + bad!( + " + p_a = '' + p_b = [ + {c_a = '', c_b = '', c_d = ''}, + # ^ + ] + ", + Parent, + "\ +TOML parse error at line 4, column 38 + | +4 | {c_a = '', c_b = '', c_d = ''}, + | ^^^ +unknown field `c_d`, expected `c_a` or `c_b` +" + ); + + bad!( + " + p_a = 'a' + p_b = [ + {c_a = '', c_b = 1, c_d = ''}, + # ^ + ] + ", + Parent, + "\ +TOML parse error at line 4, column 34 + | +4 | {c_a = '', c_b = 1, c_d = ''}, + | ^ +invalid type: integer `1`, expected a string +" + ); +} + +#[test] +fn error_handles_crlf() { + bad!( + "\r\n\ + [t1]\r\n\ + [t2]\r\n\ + a = 1\r\n\ + a = 2\r\n\ + ", + toml::Value, + "\ +TOML parse error at line 5, column 1 + | +5 | a = 2 + | ^ +duplicate key `a` in table `t2` +" + ); + + // Should be the same as above. + bad!( + "\n\ + [t1]\n\ + [t2]\n\ + a = 1\n\ + a = 2\n\ + ", + toml::Value, + "\ +TOML parse error at line 5, column 1 + | +5 | a = 2 + | ^ +duplicate key `a` in table `t2` +" + ); +} diff --git a/tests/testsuite/display.rs b/tests/testsuite/display.rs new file mode 100644 index 0000000..7430fac --- /dev/null +++ b/tests/testsuite/display.rs @@ -0,0 +1,116 @@ +use toml::map::Map; +use toml::Value::{Array, Boolean, Float, Integer, String, Table}; + +macro_rules! map( ($($k:expr => $v:expr),*) => ({ + let mut _m = Map::new(); + $(_m.insert($k.to_string(), $v);)* + _m +}) ); + +#[test] +fn simple_show() { + assert_eq!(String("foo".to_string()).to_string(), "\"foo\""); + assert_eq!(Integer(10).to_string(), "10"); + assert_eq!(Float(10.0).to_string(), "10.0"); + assert_eq!(Float(2.4).to_string(), "2.4"); + assert_eq!(Boolean(true).to_string(), "true"); + assert_eq!(Array(vec![]).to_string(), "[]"); + assert_eq!(Array(vec![Integer(1), Integer(2)]).to_string(), "[1, 2]"); +} + +#[test] +fn table() { + assert_eq!(map! {}.to_string(), ""); + assert_eq!( + map! { + "test" => Integer(2), + "test2" => Integer(3) } + .to_string(), + "test = 2\ntest2 = 3\n" + ); + assert_eq!( + map! { + "test" => Integer(2), + "test2" => Table(map! { + "test" => String("wut".to_string()) + }) + } + .to_string(), + "test = 2\n\ + \n\ + [test2]\n\ + test = \"wut\"\n" + ); + assert_eq!( + map! { + "test" => Integer(2), + "test2" => Table(map! { + "test" => String("wut".to_string()) + }) + } + .to_string(), + "test = 2\n\ + \n\ + [test2]\n\ + test = \"wut\"\n" + ); + assert_eq!( + map! { + "test" => Integer(2), + "test2" => Array(vec![Table(map! { + "test" => String("wut".to_string()) + })]) + } + .to_string(), + "test = 2\n\ + \n\ + [[test2]]\n\ + test = \"wut\"\n" + ); + #[cfg(feature = "preserve_order")] + assert_eq!( + map! { + "foo.bar" => Integer(2), + "foo\"bar" => Integer(2) + } + .to_string(), + "\"foo.bar\" = 2\n\ + \"foo\\\"bar\" = 2\n" + ); + assert_eq!( + map! { + "test" => Integer(2), + "test2" => Array(vec![Table(map! { + "test" => Array(vec![Integer(2)]) + })]) + } + .to_string(), + "test = 2\n\ + \n\ + [[test2]]\n\ + test = [2]\n" + ); + let table = map! { + "test" => Integer(2), + "test2" => Array(vec![Table(map! { + "test" => Array(vec![Array(vec![Integer(2), Integer(3)]), + Array(vec![String("foo".to_string()), String("bar".to_string())])]) + })]) + }; + assert_eq!( + table.to_string(), + "test = 2\n\ + \n\ + [[test2]]\n\ + test = [[2, 3], [\"foo\", \"bar\"]]\n" + ); + assert_eq!( + map! { + "test" => Array(vec![Integer(2)]), + "test2" => Integer(2) + } + .to_string(), + "test = [2]\n\ + test2 = 2\n" + ); +} diff --git a/tests/testsuite/display_tricky.rs b/tests/testsuite/display_tricky.rs new file mode 100644 index 0000000..379ae91 --- /dev/null +++ b/tests/testsuite/display_tricky.rs @@ -0,0 +1,55 @@ +use serde::Deserialize; +use serde::Serialize; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Recipe { + pub name: String, + pub description: Option, + #[serde(default)] + pub modules: Vec, + #[serde(default)] + pub packages: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Modules { + pub name: String, + pub version: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Packages { + pub name: String, + pub version: Option, +} + +#[test] +fn both_ends() { + let recipe_works = toml::from_str::( + r#" + name = "testing" + description = "example" + modules = [] + + [[packages]] + name = "base" + "#, + ) + .unwrap(); + toml::to_string(&recipe_works).unwrap(); + + let recipe_fails = toml::from_str::( + r#" + name = "testing" + description = "example" + packages = [] + + [[modules]] + name = "base" + "#, + ) + .unwrap(); + + let recipe_toml = toml::Table::try_from(recipe_fails).unwrap(); + recipe_toml.to_string(); +} diff --git a/tests/testsuite/enum_external_deserialize.rs b/tests/testsuite/enum_external_deserialize.rs new file mode 100644 index 0000000..6e0c2f7 --- /dev/null +++ b/tests/testsuite/enum_external_deserialize.rs @@ -0,0 +1,320 @@ +use serde::Deserialize; + +#[derive(Debug, Deserialize, PartialEq)] +struct OuterStruct { + inner: TheEnum, +} + +#[derive(Debug, Deserialize, PartialEq)] +enum TheEnum { + Plain, + Tuple(i64, bool), + NewType(String), + Struct { value: i64 }, +} + +#[derive(Debug, Deserialize, PartialEq)] +struct Val { + val: TheEnum, +} + +#[derive(Debug, Deserialize, PartialEq)] +struct Multi { + enums: Vec, +} + +fn value_from_str(s: &'_ str) -> Result +where + T: serde::de::DeserializeOwned, +{ + T::deserialize(toml::de::ValueDeserializer::new(s)) +} + +#[test] +fn invalid_variant_returns_error_with_good_message_string() { + let error = value_from_str::("\"NonExistent\"").unwrap_err(); + snapbox::assert_eq( + r#"unknown variant `NonExistent`, expected one of `Plain`, `Tuple`, `NewType`, `Struct` +"#, + error.to_string(), + ); + + let error = toml::from_str::("val = \"NonExistent\"").unwrap_err(); + snapbox::assert_eq( + r#"TOML parse error at line 1, column 7 + | +1 | val = "NonExistent" + | ^^^^^^^^^^^^^ +unknown variant `NonExistent`, expected one of `Plain`, `Tuple`, `NewType`, `Struct` +"#, + error.to_string(), + ); +} + +#[test] +fn invalid_variant_returns_error_with_good_message_inline_table() { + let error = value_from_str::("{ NonExistent = {} }").unwrap_err(); + snapbox::assert_eq( + r#"unknown variant `NonExistent`, expected one of `Plain`, `Tuple`, `NewType`, `Struct` +"#, + error.to_string(), + ); + + let error = toml::from_str::("val = { NonExistent = {} }").unwrap_err(); + snapbox::assert_eq( + r#"TOML parse error at line 1, column 9 + | +1 | val = { NonExistent = {} } + | ^^^^^^^^^^^ +unknown variant `NonExistent`, expected one of `Plain`, `Tuple`, `NewType`, `Struct` +"#, + error.to_string(), + ); +} + +#[test] +fn extra_field_returns_expected_empty_table_error() { + let error = value_from_str::("{ Plain = { extra_field = 404 } }").unwrap_err(); + snapbox::assert_eq( + r#"expected empty table +"#, + error.to_string(), + ); + + let error = toml::from_str::("val = { Plain = { extra_field = 404 } }").unwrap_err(); + snapbox::assert_eq( + r#"TOML parse error at line 1, column 17 + | +1 | val = { Plain = { extra_field = 404 } } + | ^^^^^^^^^^^^^^^^^^^^^ +expected empty table +"#, + error.to_string(), + ); +} + +#[test] +fn extra_field_returns_expected_empty_table_error_struct_variant() { + let error = value_from_str::("{ Struct = { value = 123, extra_0 = 0, extra_1 = 1 } }") + .unwrap_err(); + + snapbox::assert_eq( + r#"unexpected keys in table: extra_0, extra_1, available keys: value +"#, + error.to_string(), + ); + + let error = + toml::from_str::("val = { Struct = { value = 123, extra_0 = 0, extra_1 = 1 } }") + .unwrap_err(); + + snapbox::assert_eq( + r#"TOML parse error at line 1, column 33 + | +1 | val = { Struct = { value = 123, extra_0 = 0, extra_1 = 1 } } + | ^^^^^^^ +unexpected keys in table: extra_0, extra_1, available keys: value +"#, + error.to_string(), + ); +} + +mod enum_unit { + use super::*; + + #[test] + fn from_str() { + assert_eq!(TheEnum::Plain, value_from_str("\"Plain\"").unwrap()); + + assert_eq!( + Val { + val: TheEnum::Plain + }, + toml::from_str("val = \"Plain\"").unwrap() + ); + } + + #[test] + fn from_inline_table() { + assert_eq!(TheEnum::Plain, value_from_str("{ Plain = {} }").unwrap()); + assert_eq!( + Val { + val: TheEnum::Plain + }, + toml::from_str("val = { Plain = {} }").unwrap() + ); + } + + #[test] + fn from_std_table() { + assert_eq!(TheEnum::Plain, toml::from_str("[Plain]\n").unwrap()); + } +} + +mod enum_tuple { + use super::*; + + #[test] + fn from_inline_table() { + assert_eq!( + TheEnum::Tuple(-123, true), + value_from_str("{ Tuple = { 0 = -123, 1 = true } }").unwrap() + ); + assert_eq!( + Val { + val: TheEnum::Tuple(-123, true) + }, + toml::from_str("val = { Tuple = { 0 = -123, 1 = true } }").unwrap() + ); + } + + #[test] + fn from_std_table() { + assert_eq!( + TheEnum::Tuple(-123, true), + toml::from_str( + r#"[Tuple] + 0 = -123 + 1 = true + "# + ) + .unwrap() + ); + } +} + +mod enum_newtype { + use super::*; + + #[test] + fn from_inline_table() { + assert_eq!( + TheEnum::NewType("value".to_string()), + value_from_str(r#"{ NewType = "value" }"#).unwrap() + ); + assert_eq!( + Val { + val: TheEnum::NewType("value".to_string()), + }, + toml::from_str(r#"val = { NewType = "value" }"#).unwrap() + ); + } + + #[test] + fn from_std_table() { + assert_eq!( + TheEnum::NewType("value".to_string()), + toml::from_str(r#"NewType = "value""#).unwrap() + ); + assert_eq!( + Val { + val: TheEnum::NewType("value".to_string()), + }, + toml::from_str( + r#"[val] + NewType = "value" + "# + ) + .unwrap() + ); + } +} + +mod enum_struct { + use super::*; + + #[test] + fn from_inline_table() { + assert_eq!( + TheEnum::Struct { value: -123 }, + value_from_str("{ Struct = { value = -123 } }").unwrap() + ); + assert_eq!( + Val { + val: TheEnum::Struct { value: -123 } + }, + toml::from_str("val = { Struct = { value = -123 } }").unwrap() + ); + } + + #[test] + fn from_std_table() { + assert_eq!( + TheEnum::Struct { value: -123 }, + toml::from_str( + r#"[Struct] + value = -123 + "# + ) + .unwrap() + ); + } + + #[test] + fn from_nested_std_table() { + assert_eq!( + OuterStruct { + inner: TheEnum::Struct { value: -123 } + }, + toml::from_str( + r#"[inner.Struct] + value = -123 + "# + ) + .unwrap() + ); + } +} + +mod enum_array { + use super::*; + + #[test] + fn from_inline_tables() { + let toml_str = r#" + enums = [ + { Plain = {} }, + { Tuple = { 0 = -123, 1 = true } }, + { NewType = "value" }, + { Struct = { value = -123 } } + ]"#; + assert_eq!( + Multi { + enums: vec![ + TheEnum::Plain, + TheEnum::Tuple(-123, true), + TheEnum::NewType("value".to_string()), + TheEnum::Struct { value: -123 }, + ] + }, + toml::from_str(toml_str).unwrap() + ); + } + + #[test] + fn from_std_table() { + let toml_str = r#"[[enums]] + Plain = {} + + [[enums]] + Tuple = { 0 = -123, 1 = true } + + [[enums]] + NewType = "value" + + [[enums]] + Struct = { value = -123 } + "#; + assert_eq!( + Multi { + enums: vec![ + TheEnum::Plain, + TheEnum::Tuple(-123, true), + TheEnum::NewType("value".to_string()), + TheEnum::Struct { value: -123 }, + ] + }, + toml::from_str(toml_str).unwrap() + ); + } +} diff --git a/tests/testsuite/float.rs b/tests/testsuite/float.rs new file mode 100644 index 0000000..d008134 --- /dev/null +++ b/tests/testsuite/float.rs @@ -0,0 +1,80 @@ +use serde::Deserialize; +use serde::Serialize; +use toml::Value; + +#[rustfmt::skip] // appears to be a bug in rustfmt to make this converge... +macro_rules! float_inf_tests { + ($ty:ty) => {{ + #[derive(Serialize, Deserialize)] + struct S { + sf1: $ty, + sf2: $ty, + sf3: $ty, + sf4: $ty, + sf5: $ty, + sf6: $ty, + sf7: $ty, + sf8: $ty, + } + let inf: S = toml::from_str( + r" + # infinity + sf1 = inf # positive infinity + sf2 = +inf # positive infinity + sf3 = -inf # negative infinity + + # not a number + sf4 = nan # actual sNaN/qNaN encoding is implementation specific + sf5 = +nan # same as `nan` + sf6 = -nan # valid, actual encoding is implementation specific + + # zero + sf7 = +0.0 + sf8 = -0.0 + ", + ) + .expect("Parse infinities."); + + assert!(inf.sf1.is_infinite()); + assert!(inf.sf1.is_sign_positive()); + assert!(inf.sf2.is_infinite()); + assert!(inf.sf2.is_sign_positive()); + assert!(inf.sf3.is_infinite()); + assert!(inf.sf3.is_sign_negative()); + + assert!(inf.sf4.is_nan()); + assert!(inf.sf4.is_sign_positive()); + assert!(inf.sf5.is_nan()); + assert!(inf.sf5.is_sign_positive()); + assert!(inf.sf6.is_nan()); + assert!(inf.sf6.is_sign_negative()); + + assert_eq!(inf.sf7, 0.0); + assert!(inf.sf7.is_sign_positive()); + assert_eq!(inf.sf8, 0.0); + assert!(inf.sf8.is_sign_negative()); + + let s = toml::to_string(&inf).unwrap(); + assert_eq!( + s, + "\ +sf1 = inf +sf2 = inf +sf3 = -inf +sf4 = nan +sf5 = nan +sf6 = -nan +sf7 = 0.0 +sf8 = -0.0 +" + ); + + toml::from_str::(&s).expect("roundtrip"); + }}; +} + +#[test] +fn float_inf() { + float_inf_tests!(f32); + float_inf_tests!(f64); +} diff --git a/tests/testsuite/formatting.rs b/tests/testsuite/formatting.rs new file mode 100644 index 0000000..8240d1d --- /dev/null +++ b/tests/testsuite/formatting.rs @@ -0,0 +1,54 @@ +use serde::Deserialize; +use serde::Serialize; +use toml::to_string; + +#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] +struct User { + pub name: String, + pub surname: String, +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] +struct Users { + pub user: Vec, +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] +struct TwoUsers { + pub user0: User, + pub user1: User, +} + +#[test] +fn no_unnecessary_newlines_array() { + assert!(!to_string(&Users { + user: vec![ + User { + name: "John".to_string(), + surname: "Doe".to_string(), + }, + User { + name: "Jane".to_string(), + surname: "Dough".to_string(), + }, + ], + }) + .unwrap() + .starts_with('\n')); +} + +#[test] +fn no_unnecessary_newlines_table() { + assert!(!to_string(&TwoUsers { + user0: User { + name: "John".to_string(), + surname: "Doe".to_string(), + }, + user1: User { + name: "Jane".to_string(), + surname: "Dough".to_string(), + }, + }) + .unwrap() + .starts_with('\n')); +} diff --git a/tests/testsuite/macros.rs b/tests/testsuite/macros.rs new file mode 100644 index 0000000..5100705 --- /dev/null +++ b/tests/testsuite/macros.rs @@ -0,0 +1,368 @@ +use std::f64; + +use toml::toml; + +macro_rules! table { + ($($key:expr => $value:expr,)*) => {{ + // https://github.com/rust-lang/rust/issues/60643 + #[allow(unused_mut)] + let mut table = toml::value::Table::new(); + $( + table.insert($key.to_string(), $value.into()); + )* + toml::Value::Table(table) + }}; +} + +macro_rules! array { + ($($element:expr,)*) => {{ + // https://github.com/rust-lang/rust/issues/60643 + #![allow(clippy::vec_init_then_push)] + #[allow(unused_mut)] + let mut array = toml::value::Array::new(); + $( + array.push($element.into()); + )* + toml::Value::Array(array) + }}; +} + +macro_rules! datetime { + ($s:tt) => { + $s.parse::().unwrap() + }; +} + +#[test] +fn test_cargo_toml() { + // Simple sanity check of: + // + // - Ordinary tables + // - Inline tables + // - Inline arrays + // - String values + // - Table keys containing hyphen + // - Table headers containing hyphen + let actual = toml! { + [package] + name = "toml" + version = "0.4.5" + authors = ["Alex Crichton "] + + [badges] + travis-ci = { repository = "alexcrichton/toml-rs" } + + [dependencies] + serde = "1.0" + + [dev-dependencies] + serde_derive = "1.0" + serde_json = "1.0" + }; + + let expected = table! { + "package" => table! { + "name" => "toml".to_owned(), + "version" => "0.4.5".to_owned(), + "authors" => array! { + "Alex Crichton ".to_owned(), + }, + }, + "badges" => table! { + "travis-ci" => table! { + "repository" => "alexcrichton/toml-rs".to_owned(), + }, + }, + "dependencies" => table! { + "serde" => "1.0".to_owned(), + }, + "dev-dependencies" => table! { + "serde_derive" => "1.0".to_owned(), + "serde_json" => "1.0".to_owned(), + }, + }; + + assert_eq!(toml::Value::Table(actual), expected); +} + +#[test] +fn test_array() { + // Copied from the TOML spec. + let actual = toml! { + [[fruit]] + name = "apple" + + [fruit.physical] + color = "red" + shape = "round" + + [[fruit.variety]] + name = "red delicious" + + [[fruit.variety]] + name = "granny smith" + + [[fruit]] + name = "banana" + + [[fruit.variety]] + name = "plantain" + }; + + let expected = table! { + "fruit" => array! { + table! { + "name" => "apple", + "physical" => table! { + "color" => "red", + "shape" => "round", + }, + "variety" => array! { + table! { + "name" => "red delicious", + }, + table! { + "name" => "granny smith", + }, + }, + }, + table! { + "name" => "banana", + "variety" => array! { + table! { + "name" => "plantain", + }, + }, + }, + }, + }; + + assert_eq!(toml::Value::Table(actual), expected); +} + +#[test] +fn test_number() { + #![allow(clippy::unusual_byte_groupings)] // Verify the macro with odd formatting + + let actual = toml! { + positive = 1 + negative = -1 + table = { positive = 1, negative = -1 } + array = [ 1, -1 ] + neg_zero = -0 + pos_zero = +0 + float = 1.618 + + sf1 = inf + sf2 = +inf + sf3 = -inf + sf7 = +0.0 + sf8 = -0.0 + + hex = 0xa_b_c + oct = 0o755 + bin = 0b11010110 + }; + + let expected = table! { + "positive" => 1, + "negative" => -1, + "table" => table! { + "positive" => 1, + "negative" => -1, + }, + "array" => array! { + 1, + -1, + }, + "neg_zero" => -0, + "pos_zero" => 0, + "float" => 1.618, + "sf1" => f64::INFINITY, + "sf2" => f64::INFINITY, + "sf3" => f64::NEG_INFINITY, + "sf7" => 0.0, + "sf8" => -0.0, + "hex" => 2748, + "oct" => 493, + "bin" => 214, + }; + + assert_eq!(toml::Value::Table(actual), expected); +} + +#[test] +fn test_nan() { + let actual = toml! { + sf4 = nan + sf5 = +nan + sf6 = -nan + }; + assert!(actual["sf4"].as_float().unwrap().is_nan()); + assert!(actual["sf5"].as_float().unwrap().is_nan()); + assert!(actual["sf6"].as_float().unwrap().is_nan()); +} + +#[test] +fn test_datetime() { + let actual = toml! { + // Copied from the TOML spec. + odt1 = 1979-05-27T07:32:00Z + odt2 = 1979-05-27T00:32:00-07:00 + odt3 = 1979-05-27T00:32:00.999999-07:00 + odt4 = 1979-05-27 07:32:00Z + ldt1 = 1979-05-27T07:32:00 + ldt2 = 1979-05-27T00:32:00.999999 + ld1 = 1979-05-27 + lt1 = 07:32:00 + lt2 = 00:32:00.999999 + + table = { + odt1 = 1979-05-27T07:32:00Z, + odt2 = 1979-05-27T00:32:00-07:00, + odt3 = 1979-05-27T00:32:00.999999-07:00, + odt4 = 1979-05-27 07:32:00Z, + ldt1 = 1979-05-27T07:32:00, + ldt2 = 1979-05-27T00:32:00.999999, + ld1 = 1979-05-27, + lt1 = 07:32:00, + lt2 = 00:32:00.999999, + } + + array = [ + 1979-05-27T07:32:00Z, + 1979-05-27T00:32:00-07:00, + 1979-05-27T00:32:00.999999-07:00, + 1979-05-27 07:32:00Z, + 1979-05-27T07:32:00, + 1979-05-27T00:32:00.999999, + 1979-05-27, + 07:32:00, + 00:32:00.999999, + ] + }; + + let expected = table! { + "odt1" => datetime!("1979-05-27T07:32:00Z"), + "odt2" => datetime!("1979-05-27T00:32:00-07:00"), + "odt3" => datetime!("1979-05-27T00:32:00.999999-07:00"), + "odt4" => datetime!("1979-05-27 07:32:00Z"), + "ldt1" => datetime!("1979-05-27T07:32:00"), + "ldt2" => datetime!("1979-05-27T00:32:00.999999"), + "ld1" => datetime!("1979-05-27"), + "lt1" => datetime!("07:32:00"), + "lt2" => datetime!("00:32:00.999999"), + + "table" => table! { + "odt1" => datetime!("1979-05-27T07:32:00Z"), + "odt2" => datetime!("1979-05-27T00:32:00-07:00"), + "odt3" => datetime!("1979-05-27T00:32:00.999999-07:00"), + "odt4" => datetime!("1979-05-27 07:32:00Z"), + "ldt1" => datetime!("1979-05-27T07:32:00"), + "ldt2" => datetime!("1979-05-27T00:32:00.999999"), + "ld1" => datetime!("1979-05-27"), + "lt1" => datetime!("07:32:00"), + "lt2" => datetime!("00:32:00.999999"), + }, + + "array" => array! { + datetime!("1979-05-27T07:32:00Z"), + datetime!("1979-05-27T00:32:00-07:00"), + datetime!("1979-05-27T00:32:00.999999-07:00"), + datetime!("1979-05-27 07:32:00Z"), + datetime!("1979-05-27T07:32:00"), + datetime!("1979-05-27T00:32:00.999999"), + datetime!("1979-05-27"), + datetime!("07:32:00"), + datetime!("00:32:00.999999"), + }, + }; + + assert_eq!(toml::Value::Table(actual), expected); +} + +// This test requires rustc >= 1.20. +#[test] +fn test_quoted_key() { + let actual = toml! { + "quoted" = true + table = { "quoted" = true } + + [target."cfg(windows)".dependencies] + winapi = "0.2.8" + }; + + let expected = table! { + "quoted" => true, + "table" => table! { + "quoted" => true, + }, + "target" => table! { + "cfg(windows)" => table! { + "dependencies" => table! { + "winapi" => "0.2.8", + }, + }, + }, + }; + + assert_eq!(toml::Value::Table(actual), expected); +} + +#[test] +fn test_empty() { + let actual = toml! { + empty_inline_table = {} + empty_inline_array = [] + + [empty_table] + + [[empty_array]] + }; + + let expected = table! { + "empty_inline_table" => table! {}, + "empty_inline_array" => array! {}, + "empty_table" => table! {}, + "empty_array" => array! { + table! {}, + }, + }; + + assert_eq!(toml::Value::Table(actual), expected); +} + +#[test] +fn test_dotted_keys() { + let actual = toml! { + a.b = 123 + a.c = 1979-05-27T07:32:00Z + [table] + a.b.c = 1 + a . b . d = 2 + in = { type.name = "cat", type.color = "blue" } + }; + + let expected = table! { + "a" => table! { + "b" => 123, + "c" => datetime!("1979-05-27T07:32:00Z"), + }, + "table" => table! { + "a" => table! { + "b" => table! { + "c" => 1, + "d" => 2, + }, + }, + "in" => table! { + "type" => table! { + "name" => "cat", + "color" => "blue", + }, + }, + }, + }; + + assert_eq!(toml::Value::Table(actual), expected); +} diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs new file mode 100644 index 0000000..1473787 --- /dev/null +++ b/tests/testsuite/main.rs @@ -0,0 +1,15 @@ +#![recursion_limit = "256"] +#![cfg(all(feature = "parse", feature = "display"))] + +mod de_errors; +mod display; +mod display_tricky; +mod enum_external_deserialize; +mod float; +mod formatting; +mod macros; +mod pretty; +mod serde; +mod spanned; +mod spanned_impls; +mod tables_last; diff --git a/tests/testsuite/pretty.rs b/tests/testsuite/pretty.rs new file mode 100644 index 0000000..3ae772b --- /dev/null +++ b/tests/testsuite/pretty.rs @@ -0,0 +1,184 @@ +use serde::ser::Serialize; +use snapbox::assert_eq; + +const NO_PRETTY: &str = "\ +[example] +array = [\"item 1\", \"item 2\"] +empty = [] +oneline = \"this has no newlines.\" +text = ''' + +this is the first line\\nthis is the second line +''' +"; + +#[test] +fn no_pretty() { + let toml = NO_PRETTY; + let value: toml::Value = toml::from_str(toml).unwrap(); + let mut result = String::with_capacity(128); + value.serialize(toml::Serializer::new(&mut result)).unwrap(); + assert_eq(toml, &result); +} + +const PRETTY_STD: &str = "\ +[example] +array = [ + \"item 1\", + \"item 2\", +] +empty = [] +one = [\"one\"] +oneline = \"this has no newlines.\" +text = \"\"\" +this is the first line +this is the second line +\"\"\" +"; + +#[test] +fn pretty_std() { + let toml = PRETTY_STD; + let value: toml::Value = toml::from_str(toml).unwrap(); + let mut result = String::with_capacity(128); + value + .serialize(toml::Serializer::pretty(&mut result)) + .unwrap(); + assert_eq(toml, &result); +} + +const PRETTY_TRICKY: &str = r##"[example] +f = "\f" +glass = """ +Nothing too unusual, except that I can eat glass in: +- Greek: Μπορώ να φάω σπασμένα γυαλιά χωρίς να πάθω τίποτα. +- Polish: Mogę jeść szkło, i mi nie szkodzi. +- Hindi: मैं काँच खा सकता हूँ, मुझे उस से कोई पीडा नहीं होती. +- Japanese: 私はガラスを食べられます。それは私を傷つけません。 +""" +r = "\r" +r_newline = """ +\r +""" +single = "this is a single line but has '' cuz it's tricky" +single_tricky = "single line with ''' in it" +tabs = """ +this is pretty standard +\texcept for some \ttabs right here +""" +text = """ +this is the first line. +This has a ''' in it and \"\"\" cuz it's tricky yo +Also ' and \" because why not +this is the fourth line +""" +"##; + +#[test] +fn pretty_tricky() { + let toml = PRETTY_TRICKY; + let value: toml::Value = toml::from_str(toml).unwrap(); + let mut result = String::with_capacity(128); + value + .serialize(toml::Serializer::pretty(&mut result)) + .unwrap(); + assert_eq(toml, &result); +} + +const PRETTY_TABLE_ARRAY: &str = r##"[[array]] +key = "foo" + +[[array]] +key = "bar" + +[abc] +doc = "this is a table" + +[example] +single = "this is a single line string" +"##; + +#[test] +fn pretty_table_array() { + let toml = PRETTY_TABLE_ARRAY; + let value: toml::Value = toml::from_str(toml).unwrap(); + let mut result = String::with_capacity(128); + value + .serialize(toml::Serializer::pretty(&mut result)) + .unwrap(); + assert_eq(toml, &result); +} + +const TABLE_ARRAY: &str = r##"[[array]] +key = "foo" + +[[array]] +key = "bar" + +[abc] +doc = "this is a table" + +[example] +single = "this is a single line string" +"##; + +#[test] +fn table_array() { + let toml = TABLE_ARRAY; + let value: toml::Value = toml::from_str(toml).unwrap(); + let mut result = String::with_capacity(128); + value.serialize(toml::Serializer::new(&mut result)).unwrap(); + assert_eq(toml, &result); +} + +const PRETTY_EMPTY_TABLE: &str = r#"[example] +"#; + +#[test] +fn pretty_empty_table() { + let toml = PRETTY_EMPTY_TABLE; + let value: toml::Value = toml::from_str(toml).unwrap(); + let mut result = String::with_capacity(128); + value.serialize(toml::Serializer::new(&mut result)).unwrap(); + assert_eq(toml, &result); +} + +#[test] +fn error_includes_key() { + #[derive(Debug, serde::Serialize, serde::Deserialize)] + struct Package { + name: String, + version: String, + authors: Vec, + profile: Profile, + } + + #[derive(Debug, serde::Serialize, serde::Deserialize)] + struct Profile { + dev: Dev, + } + + #[derive(Debug, serde::Serialize, serde::Deserialize)] + struct Dev { + debug: U32OrBool, + } + + #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, Eq, PartialEq)] + #[serde(untagged, expecting = "expected a boolean or an integer")] + pub enum U32OrBool { + U32(u32), + Bool(bool), + } + + let raw = r#"name = "foo" +version = "0.0.0" +authors = [] + +[profile.dev] +debug = true +"#; + + let pkg: Package = toml::from_str(raw).unwrap(); + let pretty = toml::to_string_pretty(&pkg).unwrap(); + assert_eq(raw, pretty); +} diff --git a/tests/testsuite/serde.rs b/tests/testsuite/serde.rs new file mode 100644 index 0000000..6e927f7 --- /dev/null +++ b/tests/testsuite/serde.rs @@ -0,0 +1,1096 @@ +use serde::Deserialize; +use serde::Deserializer; +use serde::Serialize; +use std::collections::BTreeMap; + +use toml::map::Map; +use toml::Table; +use toml::Value; + +macro_rules! t { + ($e:expr) => { + match $e { + Ok(t) => t, + Err(e) => panic!("{} failed with {}", stringify!($e), e), + } + }; +} + +macro_rules! equivalent { + ($literal:expr, $toml:expr,) => {{ + let toml = $toml; + let literal = $literal; + + // In/out of Value is equivalent + println!("try_from"); + assert_eq!(t!(Table::try_from(literal.clone())), toml); + println!("try_into"); + assert_eq!(literal, t!(toml.clone().try_into())); + + // Through a string equivalent + println!("to_string"); + snapbox::assert_eq(t!(toml::to_string(&toml)), t!(toml::to_string(&literal))); + println!("literal, from_str(toml)"); + assert_eq!(literal, t!(toml::from_str(&t!(toml::to_string(&toml))))); + println!("toml, from_str(toml)"); + assert_eq!(toml, t!(toml::from_str(&t!(toml::to_string(&toml))))); + }}; +} + +macro_rules! error { + ($ty:ty, $toml:expr, $msg_parse:expr, $msg_decode:expr) => {{ + println!("attempting parsing"); + match toml::from_str::<$ty>(&$toml.to_string()) { + Ok(_) => panic!("successful"), + Err(e) => snapbox::assert_eq($msg_parse, e.to_string()), + } + + println!("attempting toml decoding"); + match $toml.try_into::<$ty>() { + Ok(_) => panic!("successful"), + Err(e) => snapbox::assert_eq($msg_decode, e.to_string()), + } + }}; +} + +macro_rules! map( ($($k:ident: $v:expr),*) => ({ + let mut _m = Map::new(); + $(_m.insert(stringify!($k).to_string(), t!(Value::try_from($v)));)* + _m +}) ); + +#[test] +fn smoke() { + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Foo { + a: isize, + } + + equivalent!(Foo { a: 2 }, map! { a: Value::Integer(2) },); +} + +#[test] +fn smoke_hyphen() { + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Foo { + a_b: isize, + } + + equivalent! { + Foo { a_b: 2 }, + map! { a_b: Value::Integer(2)}, + } + + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Foo2 { + #[serde(rename = "a-b")] + a_b: isize, + } + + let mut m = Map::new(); + m.insert("a-b".to_string(), Value::Integer(2)); + equivalent! { + Foo2 { a_b: 2 }, + m, + } +} + +#[test] +fn nested() { + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Foo { + a: isize, + b: Bar, + } + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Bar { + a: String, + } + + equivalent! { + Foo { a: 2, b: Bar { a: "test".to_string() } }, + map! { + a: Value::Integer(2), + b: map! { + a: Value::String("test".to_string()) + } + }, + } +} + +#[test] +fn application_decode_error() { + #[derive(PartialEq, Debug)] + struct Range10(usize); + impl<'de> serde::Deserialize<'de> for Range10 { + fn deserialize>(d: D) -> Result { + let x: usize = serde::Deserialize::deserialize(d)?; + if x > 10 { + Err(serde::de::Error::custom("more than 10")) + } else { + Ok(Range10(x)) + } + } + } + let d_good = Value::Integer(5); + let d_bad1 = Value::String("not an isize".to_string()); + let d_bad2 = Value::Integer(11); + + assert_eq!(Range10(5), d_good.try_into().unwrap()); + + let err1: Result = d_bad1.try_into(); + assert!(err1.is_err()); + let err2: Result = d_bad2.try_into(); + assert!(err2.is_err()); +} + +#[test] +fn array() { + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Foo { + a: Vec, + } + + equivalent! { + Foo { a: vec![1, 2, 3, 4] }, + map! { + a: Value::Array(vec![ + Value::Integer(1), + Value::Integer(2), + Value::Integer(3), + Value::Integer(4) + ]) + }, + }; +} + +#[test] +fn inner_structs_with_options() { + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Foo { + a: Option>, + b: Bar, + } + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Bar { + a: String, + b: f64, + } + + equivalent! { + Foo { + a: Some(Box::new(Foo { + a: None, + b: Bar { a: "foo".to_string(), b: 4.5 }, + })), + b: Bar { a: "bar".to_string(), b: 1.0 }, + }, + map! { + a: map! { + b: map! { + a: Value::String("foo".to_string()), + b: Value::Float(4.5) + } + }, + b: map! { + a: Value::String("bar".to_string()), + b: Value::Float(1.0) + } + }, + } +} + +#[test] +#[cfg(feature = "preserve_order")] +fn hashmap() { + use std::collections::HashSet; + + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Foo { + set: HashSet, + map: BTreeMap, + } + + equivalent! { + Foo { + set: { + let mut s = HashSet::new(); + s.insert('a'); + s + }, + map: { + let mut m = BTreeMap::new(); + m.insert("bar".to_string(), 4); + m.insert("foo".to_string(), 10); + m + } + }, + map! { + set: Value::Array(vec![Value::String("a".to_string())]), + map: map! { + bar: Value::Integer(4), + foo: Value::Integer(10) + } + }, + } +} + +#[test] +fn table_array() { + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Foo { + a: Vec, + } + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Bar { + a: isize, + } + + equivalent! { + Foo { a: vec![Bar { a: 1 }, Bar { a: 2 }] }, + map! { + a: Value::Array(vec![ + Value::Table(map!{ a: Value::Integer(1) }), + Value::Table(map!{ a: Value::Integer(2) }), + ]) + }, + } +} + +#[test] +fn type_errors() { + #[derive(Deserialize)] + #[allow(dead_code)] + struct Foo { + bar: isize, + } + + error! { + Foo, + map! { + bar: Value::String("a".to_string()) + }, + r#"TOML parse error at line 1, column 7 + | +1 | bar = "a" + | ^^^ +invalid type: string "a", expected isize +"#, + "invalid type: string \"a\", expected isize\nin `bar`\n" + } + + #[derive(Deserialize)] + #[allow(dead_code)] + struct Bar { + foo: Foo, + } + + error! { + Bar, + map! { + foo: map! { + bar: Value::String("a".to_string()) + } + }, + r#"TOML parse error at line 2, column 7 + | +2 | bar = "a" + | ^^^ +invalid type: string "a", expected isize +"#, + "invalid type: string \"a\", expected isize\nin `foo.bar`\n" + } +} + +#[test] +fn missing_errors() { + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct Foo { + bar: isize, + } + + error! { + Foo, + map! { }, + r#"TOML parse error at line 1, column 1 + | +1 | + | ^ +missing field `bar` +"#, + "missing field `bar`\n" + } +} + +#[test] +fn parse_enum() { + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Foo { + a: E, + } + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + #[serde(untagged)] + enum E { + Bar(isize), + Baz(String), + Last(Foo2), + } + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Foo2 { + test: String, + } + + equivalent! { + Foo { a: E::Bar(10) }, + map! { a: Value::Integer(10) }, + } + + equivalent! { + Foo { a: E::Baz("foo".to_string()) }, + map! { a: Value::String("foo".to_string()) }, + } + + equivalent! { + Foo { a: E::Last(Foo2 { test: "test".to_string() }) }, + map! { a: map! { test: Value::String("test".to_string()) } }, + } +} + +#[test] +fn parse_enum_string() { + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Foo { + a: Sort, + } + + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + #[serde(rename_all = "lowercase")] + enum Sort { + Asc, + Desc, + } + + equivalent! { + Foo { a: Sort::Desc }, + map! { a: Value::String("desc".to_string()) }, + } +} + +#[test] +#[cfg(feature = "preserve_order")] +fn map_key_unit_variants() { + #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, PartialOrd, Ord)] + enum Sort { + #[serde(rename = "ascending")] + Asc, + Desc, + } + + let mut map = BTreeMap::new(); + map.insert(Sort::Asc, 1); + map.insert(Sort::Desc, 2); + + equivalent! { + map, + map! { ascending: Value::Integer(1), Desc: Value::Integer(2) }, + } +} + +// #[test] +// fn unused_fields() { +// #[derive(Serialize, Deserialize, PartialEq, Debug)] +// struct Foo { a: isize } +// +// let v = Foo { a: 2 }; +// let mut d = Decoder::new(Table(map! { +// a, Integer(2), +// b, Integer(5) +// })); +// assert_eq!(v, t!(Deserialize::deserialize(&mut d))); +// +// assert_eq!(d.toml, Some(Table(map! { +// b, Integer(5) +// }))); +// } +// +// #[test] +// fn unused_fields2() { +// #[derive(Serialize, Deserialize, PartialEq, Debug)] +// struct Foo { a: Bar } +// #[derive(Serialize, Deserialize, PartialEq, Debug)] +// struct Bar { a: isize } +// +// let v = Foo { a: Bar { a: 2 } }; +// let mut d = Decoder::new(Table(map! { +// a, Table(map! { +// a, Integer(2), +// b, Integer(5) +// }) +// })); +// assert_eq!(v, t!(Deserialize::deserialize(&mut d))); +// +// assert_eq!(d.toml, Some(Table(map! { +// a, Table(map! { +// b, Integer(5) +// }) +// }))); +// } +// +// #[test] +// fn unused_fields3() { +// #[derive(Serialize, Deserialize, PartialEq, Debug)] +// struct Foo { a: Bar } +// #[derive(Serialize, Deserialize, PartialEq, Debug)] +// struct Bar { a: isize } +// +// let v = Foo { a: Bar { a: 2 } }; +// let mut d = Decoder::new(Table(map! { +// a, Table(map! { +// a, Integer(2) +// }) +// })); +// assert_eq!(v, t!(Deserialize::deserialize(&mut d))); +// +// assert_eq!(d.toml, None); +// } +// +// #[test] +// fn unused_fields4() { +// #[derive(Serialize, Deserialize, PartialEq, Debug)] +// struct Foo { a: BTreeMap } +// +// let v = Foo { a: map! { a, "foo".to_string() } }; +// let mut d = Decoder::new(Table(map! { +// a, Table(map! { +// a, Value::String("foo".to_string()) +// }) +// })); +// assert_eq!(v, t!(Deserialize::deserialize(&mut d))); +// +// assert_eq!(d.toml, None); +// } +// +// #[test] +// fn unused_fields5() { +// #[derive(Serialize, Deserialize, PartialEq, Debug)] +// struct Foo { a: Vec } +// +// let v = Foo { a: vec!["a".to_string()] }; +// let mut d = Decoder::new(Table(map! { +// a, Array(vec![Value::String("a".to_string())]) +// })); +// assert_eq!(v, t!(Deserialize::deserialize(&mut d))); +// +// assert_eq!(d.toml, None); +// } +// +// #[test] +// fn unused_fields6() { +// #[derive(Serialize, Deserialize, PartialEq, Debug)] +// struct Foo { a: Option> } +// +// let v = Foo { a: Some(vec![]) }; +// let mut d = Decoder::new(Table(map! { +// a, Array(vec![]) +// })); +// assert_eq!(v, t!(Deserialize::deserialize(&mut d))); +// +// assert_eq!(d.toml, None); +// } +// +// #[test] +// fn unused_fields7() { +// #[derive(Serialize, Deserialize, PartialEq, Debug)] +// struct Foo { a: Vec } +// #[derive(Serialize, Deserialize, PartialEq, Debug)] +// struct Bar { a: isize } +// +// let v = Foo { a: vec![Bar { a: 1 }] }; +// let mut d = Decoder::new(Table(map! { +// a, Array(vec![Table(map! { +// a, Integer(1), +// b, Integer(2) +// })]) +// })); +// assert_eq!(v, t!(Deserialize::deserialize(&mut d))); +// +// assert_eq!(d.toml, Some(Table(map! { +// a, Array(vec![Table(map! { +// b, Integer(2) +// })]) +// }))); +// } + +#[test] +fn empty_arrays() { + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Foo { + a: Vec, + } + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Bar; + + equivalent! { + Foo { a: vec![] }, + map! {a: Value::Array(Vec::new())}, + } +} + +#[test] +fn empty_arrays2() { + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Foo { + a: Option>, + } + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Bar; + + equivalent! { + Foo { a: None }, + map! {}, + } + + equivalent! { + Foo { a: Some(vec![]) }, + map! { a: Value::Array(vec![]) }, + } +} + +#[test] +fn extra_keys() { + #[derive(Serialize, Deserialize)] + struct Foo { + a: isize, + } + + let toml = map! { a: Value::Integer(2), b: Value::Integer(2) }; + assert!(toml.clone().try_into::().is_ok()); + assert!(toml::from_str::(&toml.to_string()).is_ok()); +} + +#[test] +fn newtypes() { + #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] + struct A { + b: B, + } + + #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] + struct B(u32); + + equivalent! { + A { b: B(2) }, + map! { b: Value::Integer(2) }, + } +} + +#[test] +fn newtypes2() { + #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] + struct A { + b: B, + } + + #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] + struct B(Option); + + #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] + struct C { + x: u32, + y: u32, + z: u32, + } + + equivalent! { + A { b: B(Some(C { x: 0, y: 1, z: 2 })) }, + map! { + b: map! { + x: Value::Integer(0), + y: Value::Integer(1), + z: Value::Integer(2) + } + }, + } +} + +#[test] +fn newtype_variant() { + #[derive(Copy, Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] + struct Struct { + field: Enum, + } + + #[derive(Copy, Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] + enum Enum { + Variant(u8), + } + + equivalent! { + Struct { field: Enum::Variant(21) }, + map! { + field: map! { + Variant: Value::Integer(21) + } + }, + } +} + +#[derive(Debug, Default, PartialEq, Serialize, Deserialize)] +struct CanBeEmpty { + a: Option, + b: Option, +} + +#[test] +fn table_structs_empty() { + let text = "[bar]\n\n[baz]\n\n[bazv]\na = \"foo\"\n\n[foo]\n"; + let value: BTreeMap = toml::from_str(text).unwrap(); + let mut expected: BTreeMap = BTreeMap::new(); + expected.insert("bar".to_string(), CanBeEmpty::default()); + expected.insert("baz".to_string(), CanBeEmpty::default()); + expected.insert( + "bazv".to_string(), + CanBeEmpty { + a: Some("foo".to_string()), + b: None, + }, + ); + expected.insert("foo".to_string(), CanBeEmpty::default()); + assert_eq!(value, expected); + snapbox::assert_eq(text, toml::to_string(&value).unwrap()); +} + +#[test] +fn fixed_size_array() { + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Entity { + pos: [i32; 2], + } + + equivalent! { + Entity { pos: [1, 2] }, + map! { + pos: Value::Array(vec![ + Value::Integer(1), + Value::Integer(2), + ]) + }, + } +} + +#[test] +fn homogeneous_tuple() { + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Collection { + elems: (i64, i64, i64), + } + + equivalent! { + Collection { elems: (0, 1, 2) }, + map! { + elems: Value::Array(vec![ + Value::Integer(0), + Value::Integer(1), + Value::Integer(2), + ]) + }, + } +} + +#[test] +fn homogeneous_tuple_struct() { + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Object(Vec, Vec, Vec); + + equivalent! { + map! { + obj: Object(vec!["foo".to_string()], vec![], vec!["bar".to_string(), "baz".to_string()]) + }, + map! { + obj: Value::Array(vec![ + Value::Array(vec![ + Value::String("foo".to_string()), + ]), + Value::Array(vec![]), + Value::Array(vec![ + Value::String("bar".to_string()), + Value::String("baz".to_string()), + ]), + ]) + }, + } +} + +#[test] +fn json_interoperability() { + #[derive(Serialize, Deserialize)] + struct Foo { + any: toml::Value, + } + + let _foo: Foo = serde_json::from_str( + r#" + {"any":1} + "#, + ) + .unwrap(); +} + +#[test] +fn error_includes_key() { + #[derive(Debug, Serialize, Deserialize)] + struct Package { + name: String, + version: String, + authors: Vec, + profile: Profile, + } + + #[derive(Debug, Serialize, Deserialize)] + struct Profile { + dev: Dev, + } + + #[derive(Debug, Serialize, Deserialize)] + struct Dev { + debug: U32OrBool, + } + + #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] + #[serde(untagged, expecting = "expected a boolean or an integer")] + pub enum U32OrBool { + U32(u32), + Bool(bool), + } + + let res: Result = toml::from_str( + r#" +[package] +name = "foo" +version = "0.0.0" +authors = [] + +[profile.dev] +debug = 'a' +"#, + ); + let err = res.unwrap_err(); + snapbox::assert_eq( + r#"TOML parse error at line 8, column 9 + | +8 | debug = 'a' + | ^^^ +expected a boolean or an integer +"#, + err.to_string(), + ); + + let res: Result = toml::from_str( + r#" +[package] +name = "foo" +version = "0.0.0" +authors = [] + +[profile] +dev = { debug = 'a' } +"#, + ); + let err = res.unwrap_err(); + snapbox::assert_eq( + r#"TOML parse error at line 8, column 17 + | +8 | dev = { debug = 'a' } + | ^^^ +expected a boolean or an integer +"#, + err.to_string(), + ); +} + +#[test] +fn newline_key_value() { + #[derive(Debug, Serialize, Deserialize)] + struct Package { + name: String, + } + + let package = Package { + name: "foo".to_owned(), + }; + let raw = toml::to_string_pretty(&package).unwrap(); + snapbox::assert_eq( + r#"name = "foo" +"#, + raw, + ); +} + +#[test] +fn newline_table() { + #[derive(Debug, Serialize, Deserialize)] + struct Manifest { + package: Package, + } + + #[derive(Debug, Serialize, Deserialize)] + struct Package { + name: String, + } + + let package = Manifest { + package: Package { + name: "foo".to_owned(), + }, + }; + let raw = toml::to_string_pretty(&package).unwrap(); + snapbox::assert_eq( + r#"[package] +name = "foo" +"#, + raw, + ); +} + +#[test] +fn newline_dotted_table() { + #[derive(Debug, Serialize, Deserialize)] + struct Manifest { + profile: Profile, + } + + #[derive(Debug, Serialize, Deserialize)] + struct Profile { + dev: Dev, + } + + #[derive(Debug, Serialize, Deserialize)] + struct Dev { + debug: U32OrBool, + } + + #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] + #[serde(untagged, expecting = "expected a boolean or an integer")] + pub enum U32OrBool { + U32(u32), + Bool(bool), + } + + let package = Manifest { + profile: Profile { + dev: Dev { + debug: U32OrBool::Bool(true), + }, + }, + }; + let raw = toml::to_string_pretty(&package).unwrap(); + snapbox::assert_eq( + r#"[profile.dev] +debug = true +"#, + raw, + ); +} + +#[test] +fn newline_mixed_tables() { + #[derive(Debug, Serialize, Deserialize)] + struct Manifest { + cargo_features: Vec, + package: Package, + profile: Profile, + } + + #[derive(Debug, Serialize, Deserialize)] + struct Package { + name: String, + version: String, + authors: Vec, + } + + #[derive(Debug, Serialize, Deserialize)] + struct Profile { + dev: Dev, + } + + #[derive(Debug, Serialize, Deserialize)] + struct Dev { + debug: U32OrBool, + } + + #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] + #[serde(untagged, expecting = "expected a boolean or an integer")] + pub enum U32OrBool { + U32(u32), + Bool(bool), + } + + let package = Manifest { + cargo_features: vec![], + package: Package { + name: "foo".to_owned(), + version: "1.0.0".to_owned(), + authors: vec![], + }, + profile: Profile { + dev: Dev { + debug: U32OrBool::Bool(true), + }, + }, + }; + let raw = toml::to_string_pretty(&package).unwrap(); + snapbox::assert_eq( + r#"cargo_features = [] + +[package] +name = "foo" +version = "1.0.0" +authors = [] + +[profile.dev] +debug = true +"#, + raw, + ); +} + +#[test] +fn integer_min() { + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Foo { + a_b: i64, + } + + equivalent! { + Foo { a_b: i64::MIN }, + map! { a_b: Value::Integer(i64::MIN) }, + } +} + +#[test] +fn integer_too_big() { + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Foo { + a_b: u64, + } + + let native = Foo { a_b: u64::MAX }; + let err = Table::try_from(native.clone()).unwrap_err(); + snapbox::assert_eq("u64 value was too large", err.to_string()); + let err = toml::to_string(&native).unwrap_err(); + snapbox::assert_eq("out-of-range value for u64 type", err.to_string()); +} + +#[test] +fn integer_max() { + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Foo { + a_b: i64, + } + + equivalent! { + Foo { a_b: i64::MAX }, + map! { a_b: Value::Integer(i64::MAX) }, + } +} + +#[test] +fn float_min() { + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Foo { + a_b: f64, + } + + equivalent! { + Foo { a_b: f64::MIN }, + map! { a_b: Value::Float(f64::MIN) }, + } +} + +#[test] +fn float_max() { + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] + struct Foo { + a_b: f64, + } + + equivalent! { + Foo { a_b: f64::MAX }, + map! { a_b: Value::Float(f64::MAX) }, + } +} + +#[test] +fn unsupported_root_type() { + let native = "value"; + let err = toml::to_string_pretty(&native).unwrap_err(); + snapbox::assert_eq("unsupported rust type", err.to_string()); +} + +#[test] +fn unsupported_nested_type() { + #[derive(Debug, Serialize, Deserialize)] + struct Foo { + unused: (), + } + + let native = Foo { unused: () }; + let err = toml::to_string_pretty(&native).unwrap_err(); + snapbox::assert_eq("unsupported unit type", err.to_string()); +} + +#[test] +fn table_type_enum_regression_issue_388() { + #[derive(Deserialize)] + struct DataFile { + #[allow(dead_code)] + data: Compare, + } + + #[derive(Deserialize)] + enum Compare { + Gt(u32), + } + + let dotted_table = r#" + data.Gt = 5 + "#; + assert!(toml::from_str::(dotted_table).is_ok()); + + let inline_table = r#" + data = { Gt = 5 } + "#; + assert!(toml::from_str::(inline_table).is_ok()); +} + +#[test] +fn serialize_datetime_issue_333() { + use toml::{to_string, value::Date, value::Datetime}; + + #[derive(Serialize)] + struct Struct { + date: Datetime, + } + + let toml = to_string(&Struct { + date: Datetime { + date: Some(Date { + year: 2022, + month: 1, + day: 1, + }), + time: None, + offset: None, + }, + }) + .unwrap(); + assert_eq!(toml, "date = 2022-01-01\n"); +} + +#[test] +fn datetime_offset_issue_496() { + let original = "value = 1911-01-01T10:11:12-00:36\n"; + let toml = original.parse::().unwrap(); + let output = toml.to_string(); + snapbox::assert_eq(original, output); +} diff --git a/tests/testsuite/spanned.rs b/tests/testsuite/spanned.rs new file mode 100644 index 0000000..760c73a --- /dev/null +++ b/tests/testsuite/spanned.rs @@ -0,0 +1,261 @@ +#![allow(renamed_and_removed_lints)] +#![allow(clippy::blacklisted_name)] + +use std::collections::HashMap; +use std::fmt::Debug; + +use serde::Deserialize; +use toml::value::Datetime; +use toml::Spanned; + +/// A set of good datetimes. +pub fn good_datetimes() -> Vec<&'static str> { + vec![ + "1997-09-09T09:09:09Z", + "1997-09-09T09:09:09+09:09", + "1997-09-09T09:09:09-09:09", + "1997-09-09T09:09:09", + "1997-09-09", + "09:09:09", + "1997-09-09T09:09:09.09Z", + "1997-09-09T09:09:09.09+09:09", + "1997-09-09T09:09:09.09-09:09", + "1997-09-09T09:09:09.09", + "09:09:09.09", + ] +} + +#[test] +fn test_spanned_field() { + #[derive(Deserialize)] + struct Foo { + foo: Spanned, + } + + #[derive(Deserialize)] + struct BareFoo { + foo: T, + } + + fn good(s: &str, expected: &str, end: Option) + where + T: serde::de::DeserializeOwned + Debug + PartialEq, + { + let foo: Foo = toml::from_str(s).unwrap(); + + assert_eq!(6, foo.foo.span().start); + if let Some(end) = end { + assert_eq!(end, foo.foo.span().end); + } else { + assert_eq!(s.len(), foo.foo.span().end); + } + assert_eq!(expected, &s[foo.foo.span()]); + + // Test for Spanned<> at the top level + let foo_outer: Spanned> = toml::from_str(s).unwrap(); + + assert_eq!(0, foo_outer.span().start); + assert_eq!(s.len(), foo_outer.span().end); + assert_eq!(foo.foo.into_inner(), foo_outer.into_inner().foo); + } + + good::("foo = \"foo\"", "\"foo\"", None); + good::("foo = 42", "42", None); + // leading plus + good::("foo = +42", "+42", None); + // table + good::>( + "foo = {\"foo\" = 42, \"bar\" = 42}", + "{\"foo\" = 42, \"bar\" = 42}", + None, + ); + // array + good::>("foo = [0, 1, 2, 3, 4]", "[0, 1, 2, 3, 4]", None); + // datetime + good::( + "foo = \"1997-09-09T09:09:09Z\"", + "\"1997-09-09T09:09:09Z\"", + None, + ); + + for expected in good_datetimes() { + let s = format!("foo = {}", expected); + good::(&s, expected, None); + } + // ending at something other than the absolute end + good::("foo = 42\nnoise = true", "42", Some(8)); +} + +#[test] +fn test_inner_spanned_table() { + #[derive(Deserialize)] + struct Foo { + foo: Spanned, Spanned>>, + } + + fn good(s: &str, zero: bool) { + let foo: Foo = toml::from_str(s).unwrap(); + + if zero { + assert_eq!(foo.foo.span().start, 0); + assert_eq!(foo.foo.span().end, 73); + } else { + assert_eq!(foo.foo.span().start, s.find('{').unwrap()); + assert_eq!(foo.foo.span().end, s.find('}').unwrap() + 1); + } + for (k, v) in foo.foo.as_ref().iter() { + assert_eq!(&s[k.span().start..k.span().end], k.as_ref()); + assert_eq!(&s[(v.span().start + 1)..(v.span().end - 1)], v.as_ref()); + } + } + + good( + "\ + [foo] + a = 'b' + bar = 'baz' + c = 'd' + e = \"f\" + ", + true, + ); + + good( + " + foo = { a = 'b', bar = 'baz', c = 'd', e = \"f\" }", + false, + ); +} + +#[test] +fn test_outer_spanned_table() { + #[derive(Deserialize)] + struct Foo { + foo: HashMap, Spanned>, + } + + fn good(s: &str) { + let foo: Foo = toml::from_str(s).unwrap(); + + for (k, v) in foo.foo.iter() { + assert_eq!(&s[k.span().start..k.span().end], k.as_ref()); + assert_eq!(&s[(v.span().start + 1)..(v.span().end - 1)], v.as_ref()); + } + } + + good( + " + [foo] + a = 'b' + bar = 'baz' + c = 'd' + e = \"f\" + ", + ); + + good( + " + foo = { a = 'b', bar = 'baz', c = 'd', e = \"f\" } + ", + ); +} + +#[test] +fn test_spanned_nested() { + #[derive(Deserialize)] + struct Foo { + foo: HashMap, HashMap, Spanned>>, + } + + fn good(s: &str) { + let foo: Foo = toml::from_str(s).unwrap(); + + for (k, v) in foo.foo.iter() { + assert_eq!(&s[k.span().start..k.span().end], k.as_ref()); + for (n_k, n_v) in v.iter() { + assert_eq!(&s[n_k.span().start..n_k.span().end], n_k.as_ref()); + assert_eq!( + &s[(n_v.span().start + 1)..(n_v.span().end - 1)], + n_v.as_ref() + ); + } + } + } + + good( + " + [foo.a] + a = 'b' + c = 'd' + e = \"f\" + [foo.bar] + baz = 'true' + ", + ); + + good( + " + [foo] + foo = { a = 'b', bar = 'baz', c = 'd', e = \"f\" } + bazz = {} + g = { h = 'i' } + ", + ); +} + +#[test] +fn test_spanned_array() { + #[derive(Deserialize)] + struct Foo { + foo: Vec, Spanned>>>, + } + + let toml = "\ + [[foo]] + a = 'b' + bar = 'baz' + c = 'd' + e = \"f\" + [[foo]] + a = 'c' + bar = 'baz' + c = 'g' + e = \"h\" + "; + let foo_list: Foo = toml::from_str(toml).unwrap(); + + for (foo, expected) in foo_list.foo.iter().zip([0..75, 84..159]) { + assert_eq!(foo.span(), expected); + for (k, v) in foo.as_ref().iter() { + assert_eq!(&toml[k.span().start..k.span().end], k.as_ref()); + assert_eq!(&toml[(v.span().start + 1)..(v.span().end - 1)], v.as_ref()); + } + } +} + +#[test] +fn deny_unknown_fields() { + #[derive(Debug, serde::Deserialize)] + #[serde(deny_unknown_fields)] + struct Example { + #[allow(dead_code)] + real: u32, + } + + let error = toml::from_str::( + r#"# my comment +# bla bla bla +fake = 1"#, + ) + .unwrap_err(); + snapbox::assert_eq( + "\ +TOML parse error at line 3, column 1 + | +3 | fake = 1 + | ^^^^ +unknown field `fake`, expected `real` +", + error.to_string(), + ); +} diff --git a/tests/testsuite/spanned_impls.rs b/tests/testsuite/spanned_impls.rs new file mode 100644 index 0000000..5e866f9 --- /dev/null +++ b/tests/testsuite/spanned_impls.rs @@ -0,0 +1,41 @@ +use std::cmp::{Ord, Ordering, PartialOrd}; + +use serde::Deserialize; +use toml::{from_str, Spanned}; + +#[test] +fn test_spans_impls() { + #[derive(Deserialize)] + struct Foo { + bar: Spanned, + baz: Spanned, + } + let f: Foo = from_str( + " + bar = true + baz = \"yes\" + ", + ) + .unwrap(); + let g: Foo = from_str( + " + baz = \"yes\" + bar = true + ", + ) + .unwrap(); + assert!(f.bar.span() != g.bar.span()); + assert!(f.baz.span() != g.baz.span()); + + // test that eq still holds + assert_eq!(f.bar, g.bar); + assert_eq!(f.baz, g.baz); + + // test that Ord returns equal order + assert_eq!(f.bar.cmp(&g.bar), Ordering::Equal); + assert_eq!(f.baz.cmp(&g.baz), Ordering::Equal); + + // test that PartialOrd returns equal order + assert_eq!(f.bar.partial_cmp(&g.bar), Some(Ordering::Equal)); + assert_eq!(f.baz.partial_cmp(&g.baz), Some(Ordering::Equal)); +} diff --git a/tests/testsuite/tables_last.rs b/tests/testsuite/tables_last.rs new file mode 100644 index 0000000..b003557 --- /dev/null +++ b/tests/testsuite/tables_last.rs @@ -0,0 +1,162 @@ +use std::collections::HashMap; + +use serde::Deserialize; +use serde::Serialize; + +#[test] +fn always_works() { + // Ensure this works without the removed "toml::ser::tables_last" + #[derive(Serialize)] + struct A { + vals: HashMap<&'static str, Value>, + } + + #[derive(Serialize)] + #[serde(untagged)] + enum Value { + Map(HashMap<&'static str, &'static str>), + Int(i32), + } + + let mut a = A { + vals: HashMap::new(), + }; + a.vals.insert("foo", Value::Int(0)); + + let mut sub = HashMap::new(); + sub.insert("foo", "bar"); + a.vals.insert("bar", Value::Map(sub)); + + toml::to_string(&a).unwrap(); +} + +#[test] +fn vec_of_vec_issue_387() { + #[derive(Deserialize, Serialize, Debug)] + struct Glyph { + components: Vec, + contours: Vec, + } + + #[derive(Deserialize, Serialize, Debug)] + struct Point { + x: f64, + y: f64, + pt_type: String, + } + + type Contour = Vec; + + #[derive(Deserialize, Serialize, Debug)] + struct Component { + base: String, + transform: (f64, f64, f64, f64, f64, f64), + } + + let comp1 = Component { + base: "b".to_string(), + transform: (1.0, 0.0, 0.0, 1.0, 0.0, 0.0), + }; + let comp2 = Component { + base: "c".to_string(), + transform: (1.0, 0.0, 0.0, 1.0, 0.0, 0.0), + }; + let components = vec![comp1, comp2]; + + let contours = vec![ + vec![ + Point { + x: 3.0, + y: 4.0, + pt_type: "line".to_string(), + }, + Point { + x: 5.0, + y: 6.0, + pt_type: "line".to_string(), + }, + ], + vec![ + Point { + x: 0.0, + y: 0.0, + pt_type: "move".to_string(), + }, + Point { + x: 7.0, + y: 9.0, + pt_type: "offcurve".to_string(), + }, + Point { + x: 8.0, + y: 10.0, + pt_type: "offcurve".to_string(), + }, + Point { + x: 11.0, + y: 12.0, + pt_type: "curve".to_string(), + }, + ], + ]; + let g1 = Glyph { + contours, + components, + }; + + let s = toml::to_string_pretty(&g1).unwrap(); + let _g2: Glyph = toml::from_str(&s).unwrap(); +} + +#[test] +fn vec_order_issue_356() { + #[derive(Serialize, Deserialize)] + struct Outer { + v1: Vec, + v2: Vec, + } + + #[derive(Serialize, Deserialize)] + struct Inner {} + + let outer = Outer { + v1: vec![Inner {}], + v2: vec![], + }; + let s = toml::to_string_pretty(&outer).unwrap(); + let _o: Outer = toml::from_str(&s).unwrap(); +} + +#[test] +fn values_before_tables_issue_403() { + #[derive(Serialize, Deserialize)] + struct A { + a: String, + b: String, + } + + #[derive(Serialize, Deserialize)] + struct B { + a: String, + b: Vec, + } + + #[derive(Serialize, Deserialize)] + struct C { + a: A, + b: Vec, + c: Vec, + } + toml::to_string(&C { + a: A { + a: "aa".to_string(), + b: "ab".to_string(), + }, + b: vec!["b".to_string()], + c: vec![B { + a: "cba".to_string(), + b: vec!["cbb".to_string()], + }], + }) + .unwrap(); +} -- cgit v1.2.3