Import toml 0.7.3 upstream upstream/0.7.3
authorWoohyun Jung <wh0705.jung@samsung.com>
Thu, 16 Mar 2023 09:22:47 +0000 (18:22 +0900)
committerWoohyun Jung <wh0705.jung@samsung.com>
Thu, 16 Mar 2023 09:22:47 +0000 (18:22 +0900)
36 files changed:
.cargo_vcs_info.json [new file with mode: 0644]
Cargo.lock [new file with mode: 0644]
Cargo.toml [new file with mode: 0644]
Cargo.toml.orig [new file with mode: 0644]
LICENSE-APACHE [new file with mode: 0644]
LICENSE-MIT [new file with mode: 0644]
README.md [new file with mode: 0644]
examples/decode.rs [new file with mode: 0644]
examples/enum_external.rs [new file with mode: 0644]
examples/toml2json.rs [new file with mode: 0644]
src/de.rs [new file with mode: 0644]
src/edit.rs [new file with mode: 0644]
src/fmt.rs [new file with mode: 0644]
src/lib.rs [new file with mode: 0644]
src/macros.rs [new file with mode: 0644]
src/map.rs [new file with mode: 0644]
src/ser.rs [new file with mode: 0644]
src/table.rs [new file with mode: 0644]
src/value.rs [new file with mode: 0644]
tests/decoder.rs [new file with mode: 0644]
tests/decoder_compliance.rs [new file with mode: 0644]
tests/encoder.rs [new file with mode: 0644]
tests/encoder_compliance.rs [new file with mode: 0644]
tests/testsuite/de_errors.rs [new file with mode: 0644]
tests/testsuite/display.rs [new file with mode: 0644]
tests/testsuite/display_tricky.rs [new file with mode: 0644]
tests/testsuite/enum_external_deserialize.rs [new file with mode: 0644]
tests/testsuite/float.rs [new file with mode: 0644]
tests/testsuite/formatting.rs [new file with mode: 0644]
tests/testsuite/macros.rs [new file with mode: 0644]
tests/testsuite/main.rs [new file with mode: 0644]
tests/testsuite/pretty.rs [new file with mode: 0644]
tests/testsuite/serde.rs [new file with mode: 0644]
tests/testsuite/spanned.rs [new file with mode: 0644]
tests/testsuite/spanned_impls.rs [new file with mode: 0644]
tests/testsuite/tables_last.rs [new file with mode: 0644]

diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644 (file)
index 0000000..9f90a63
--- /dev/null
@@ -0,0 +1,6 @@
+{
+  "git": {
+    "sha1": "74b57e74bf950a3e8962ec95df992dd29ecc8c92"
+  },
+  "path_in_vcs": "crates/toml"
+}
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644 (file)
index 0000000..f963afa
--- /dev/null
@@ -0,0 +1,767 @@
+# 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 = "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",
+]
+
+[[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 = "concolor"
+version = "0.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "318d6c16e73b3a900eb212ad6a82fc7d298c5ab8184c7a9998646455bc474a16"
+dependencies = [
+ "bitflags",
+ "concolor-query",
+ "is-terminal",
+]
+
+[[package]]
+name = "concolor-query"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82a90734b3d5dcf656e7624cca6bce9c3a90ee11f900e80141a7427ccfb3d317"
+
+[[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 = "errno"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "winapi",
+]
+
+[[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.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[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 = "1.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
+dependencies = [
+ "autocfg",
+ "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.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef"
+dependencies = [
+ "hermit-abi 0.3.1",
+ "io-lifetimes",
+ "rustix",
+ "windows-sys 0.45.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.133"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966"
+
+[[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.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f"
+
+[[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",
+ "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.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
+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.36.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb93e85278e08bb5788653183213d3a60fc242b10cb9be96586f5a73dcb67c23"
+dependencies = [
+ "bitflags",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.42.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.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
+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.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09c20d8ee8713199cfd44148b70e47cb94f3e8dc538d727d31788f49e67b623e"
+dependencies = [
+ "concolor",
+ "normalize-line-endings",
+ "similar",
+ "snapbox-macros",
+ "yansi",
+]
+
+[[package]]
+name = "snapbox-macros"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "485e65c1203eb37244465e857d15a26d3a85a5410648ccb53b18bd44cb3a7336"
+
+[[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 = "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.3"
+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.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.19.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08de71aa0d6e348f070457f85af8bd566e2bc452156a423ddf22861b3a953fae"
+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 = "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",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
+
+[[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_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
+
+[[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_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
+
+[[package]]
+name = "winnow"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "efdd927d1a3d5d98abcfc4cf8627371862ee6abfe52a988050621c50c66b4493"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "yansi"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644 (file)
index 0000000..128fb14
--- /dev/null
@@ -0,0 +1,158 @@
+# 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.60.0"
+name = "toml"
+version = "0.7.3"
+authors = ["Alex Crichton <alex@alexcrichton.com>"]
+include = [
+    "src/**/*",
+    "Cargo.toml",
+    "LICENSE*",
+    "README.md",
+    "examples/**/*",
+    "benches/**/*",
+    "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"
+documentation = "https://docs.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.release.pre-release-replacements]]
+file = "CHANGELOG.md"
+search = "Unreleased"
+replace = "{{version}}"
+min = 1
+
+[[package.metadata.release.pre-release-replacements]]
+file = "CHANGELOG.md"
+search = '\.\.\.HEAD'
+replace = "...{{tag_name}}"
+exactly = 1
+
+[[package.metadata.release.pre-release-replacements]]
+file = "CHANGELOG.md"
+search = "ReleaseDate"
+replace = "{{date}}"
+min = 1
+
+[[package.metadata.release.pre-release-replacements]]
+file = "CHANGELOG.md"
+search = "<!-- next-header -->"
+replace = """
+<!-- next-header -->
+## [Unreleased] - ReleaseDate
+"""
+exactly = 1
+
+[[package.metadata.release.pre-release-replacements]]
+file = "CHANGELOG.md"
+search = "<!-- next-url -->"
+replace = """
+<!-- next-url -->
+[Unreleased]: https://github.com/toml-rs/toml/compare/{{tag_name}}...HEAD"""
+exactly = 1
+
+[package.metadata.docs.rs]
+rustdoc-args = [
+    "--cfg",
+    "docsrs",
+]
+
+[[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 = "1.9.1"
+optional = true
+
+[dependencies.serde]
+version = "1.0.145"
+
+[dependencies.serde_spanned]
+version = "0.6.1"
+features = ["serde"]
+
+[dependencies.toml_datetime]
+version = "0.6.1"
+features = ["serde"]
+
+[dependencies.toml_edit]
+version = "0.19.6"
+features = ["serde"]
+optional = true
+
+[dev-dependencies.serde]
+version = "1.0.152"
+features = ["derive"]
+
+[dev-dependencies.serde_json]
+version = "1.0.93"
+
+[dev-dependencies.snapbox]
+version = "0.4.7"
+
+[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 (file)
index 0000000..e7cf598
--- /dev/null
@@ -0,0 +1,82 @@
+[package]
+name = "toml"
+version = "0.7.3"
+readme = "README.md"
+license = "MIT OR Apache-2.0"
+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 <alex@alexcrichton.com>"]
+repository = "https://github.com/toml-rs/toml"
+homepage = "https://github.com/toml-rs/toml"
+documentation = "https://docs.rs/toml"
+edition = "2021"
+rust-version = "1.60.0"  # MSRV
+include = [
+  "src/**/*",
+  "Cargo.toml",
+  "LICENSE*",
+  "README.md",
+  "examples/**/*",
+  "benches/**/*",
+  "tests/**/*"
+]
+
+[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="<!-- next-header -->", replace="<!-- next-header -->\n## [Unreleased] - ReleaseDate\n", exactly=1},
+  {file="CHANGELOG.md", search="<!-- next-url -->", replace="<!-- next-url -->\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 = "1.9.1", optional = true }
+toml_edit = { version = "0.19.6", path = "../toml_edit", features = ["serde"], optional = true }
+toml_datetime = { version = "0.6.1", path = "../toml_datetime", features = ["serde"] }
+serde_spanned = { version = "0.6.1", path = "../serde_spanned", features = ["serde"] }
+
+[dev-dependencies]
+serde = { version = "1.0.152", features = ["derive"] }
+serde_json = "1.0.93"
+toml-test-harness = "0.4.3"
+snapbox = "0.4.7"
+
+[[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-APACHE b/LICENSE-APACHE
new file mode 100644 (file)
index 0000000..16fe87b
--- /dev/null
@@ -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 (file)
index 0000000..39e0ed6
--- /dev/null
@@ -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/README.md b/README.md
new file mode 100644 (file)
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 (file)
index 0000000..348e911
--- /dev/null
@@ -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<String>,
+    global_integer: Option<u64>,
+    server: Option<ServerConfig>,
+    peers: Option<Vec<PeerConfig>>,
+}
+
+/// 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<String>,
+    port: Option<u64>,
+}
+
+#[derive(Debug, Deserialize)]
+struct PeerConfig {
+    ip: Option<String>,
+    port: Option<u64>,
+}
+
+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 (file)
index 0000000..edac7d6
--- /dev/null
@@ -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<MyEnum>,
+}
+
+#[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 (file)
index 0000000..3660611
--- /dev/null
@@ -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 (file)
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<T>(s: &'_ str) -> Result<T, Error>
+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<std::ops::Range<usize>> {
+        self.inner.span()
+    }
+}
+
+impl serde::de::Error for Error {
+    fn custom<T>(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<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: serde::de::Visitor<'de>,
+    {
+        let inner = self
+            .input
+            .parse::<toml_edit::de::Deserializer>()
+            .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<V>(self, visitor: V) -> Result<V::Value, Error>
+    where
+        V: serde::de::Visitor<'de>,
+    {
+        let inner = self
+            .input
+            .parse::<toml_edit::de::Deserializer>()
+            .map_err(Error::new)?;
+        inner.deserialize_option(visitor).map_err(Error::new)
+    }
+
+    fn deserialize_newtype_struct<V>(
+        self,
+        name: &'static str,
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: serde::de::Visitor<'de>,
+    {
+        let inner = self
+            .input
+            .parse::<toml_edit::de::Deserializer>()
+            .map_err(Error::new)?;
+        inner
+            .deserialize_newtype_struct(name, visitor)
+            .map_err(Error::new)
+    }
+
+    fn deserialize_struct<V>(
+        self,
+        name: &'static str,
+        fields: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: serde::de::Visitor<'de>,
+    {
+        let inner = self
+            .input
+            .parse::<toml_edit::de::Deserializer>()
+            .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<V>(
+        self,
+        name: &'static str,
+        variants: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: serde::de::Visitor<'de>,
+    {
+        let inner = self
+            .input
+            .parse::<toml_edit::de::Deserializer>()
+            .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<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: serde::de::Visitor<'de>,
+    {
+        let inner = self
+            .input
+            .parse::<toml_edit::de::ValueDeserializer>()
+            .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<V>(self, visitor: V) -> Result<V::Value, Error>
+    where
+        V: serde::de::Visitor<'de>,
+    {
+        let inner = self
+            .input
+            .parse::<toml_edit::de::ValueDeserializer>()
+            .map_err(Error::new)?;
+        inner.deserialize_option(visitor).map_err(Error::new)
+    }
+
+    fn deserialize_newtype_struct<V>(
+        self,
+        name: &'static str,
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: serde::de::Visitor<'de>,
+    {
+        let inner = self
+            .input
+            .parse::<toml_edit::de::ValueDeserializer>()
+            .map_err(Error::new)?;
+        inner
+            .deserialize_newtype_struct(name, visitor)
+            .map_err(Error::new)
+    }
+
+    fn deserialize_struct<V>(
+        self,
+        name: &'static str,
+        fields: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: serde::de::Visitor<'de>,
+    {
+        let inner = self
+            .input
+            .parse::<toml_edit::de::ValueDeserializer>()
+            .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<V>(
+        self,
+        name: &'static str,
+        variants: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: serde::de::Visitor<'de>,
+    {
+        let inner = self
+            .input
+            .parse::<toml_edit::de::ValueDeserializer>()
+            .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 (file)
index 0000000..90fb284
--- /dev/null
@@ -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<T>(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<T>(msg: T) -> Self
+        where
+            T: std::fmt::Display,
+        {
+            Error::Custom(msg.to_string())
+        }
+    }
+
+    impl serde::ser::Error for Error {
+        fn custom<T>(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 (file)
index 0000000..0e96bf0
--- /dev/null
@@ -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 (file)
index 0000000..61e6a4c
--- /dev/null
@@ -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 <alex@alexcrichton.com>"]
+//!
+//! [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::<Table>().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<u16>,
+//!     keys: Keys,
+//! }
+//!
+//! #[derive(Deserialize)]
+//! struct Keys {
+//!     github: String,
+//!     travis: Option<String>,
+//! }
+//!
+//! 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<u16>,
+//!     keys: Keys,
+//! }
+//!
+//! #[derive(Serialize)]
+//! struct Keys {
+//!     github: String,
+//!     travis: Option<String>,
+//! }
+//!
+//! 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 (file)
index 0000000..d86cc52
--- /dev/null
@@ -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 <alex@alexcrichton.com>"]
+///
+///     [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 (file)
index 0000000..5b3ff19
--- /dev/null
@@ -0,0 +1,595 @@
+// Copyright 2017 Serde Developers
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<K, V> {
+    map: MapImpl<K, V>,
+}
+
+#[cfg(not(feature = "preserve_order"))]
+type MapImpl<K, V> = BTreeMap<K, V>;
+#[cfg(feature = "preserve_order")]
+type MapImpl<K, V> = IndexMap<K, V>;
+
+impl Map<String, Value> {
+    /// 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<Q: ?Sized>(&self, key: &Q) -> Option<&Value>
+    where
+        String: Borrow<Q>,
+        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<Q: ?Sized>(&self, key: &Q) -> bool
+    where
+        String: Borrow<Q>,
+        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<Q: ?Sized>(&mut self, key: &Q) -> Option<&mut Value>
+    where
+        String: Borrow<Q>,
+        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<Value> {
+        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<Q: ?Sized>(&mut self, key: &Q) -> Option<Value>
+    where
+        String: Borrow<Q>,
+        Q: Ord + Eq + Hash,
+    {
+        self.map.remove(key)
+    }
+
+    /// Gets the given key's corresponding entry in the map for in-place
+    /// manipulation.
+    pub fn entry<S>(&mut self, key: S) -> Entry<'_>
+    where
+        S: Into<String>,
+    {
+        #[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<String, Value> {
+    #[inline]
+    fn default() -> Self {
+        Map {
+            map: MapImpl::new(),
+        }
+    }
+}
+
+impl Clone for Map<String, Value> {
+    #[inline]
+    fn clone(&self) -> Self {
+        Map {
+            map: self.map.clone(),
+        }
+    }
+}
+
+impl PartialEq for Map<String, Value> {
+    #[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<String, Value>
+where
+    String: Borrow<Q>,
+    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<String, Value>
+where
+    String: Borrow<Q>,
+    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<String, Value> {
+    #[inline]
+    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+        self.map.fmt(formatter)
+    }
+}
+
+impl ser::Serialize for Map<String, Value> {
+    #[inline]
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    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<String, Value> {
+    #[inline]
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: de::Deserializer<'de>,
+    {
+        struct Visitor;
+
+        impl<'de> de::Visitor<'de> for Visitor {
+            type Value = Map<String, Value>;
+
+            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+                formatter.write_str("a map")
+            }
+
+            #[inline]
+            fn visit_unit<E>(self) -> Result<Self::Value, E>
+            where
+                E: de::Error,
+            {
+                Ok(Map::new())
+            }
+
+            #[inline]
+            fn visit_map<V>(self, mut visitor: V) -> Result<Self::Value, V::Error>
+            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<String, Value> {
+    fn from_iter<T>(iter: T) -> Self
+    where
+        T: IntoIterator<Item = (String, Value)>,
+    {
+        Map {
+            map: FromIterator::from_iter(iter),
+        }
+    }
+}
+
+impl Extend<(String, Value)> for Map<String, Value> {
+    fn extend<T>(&mut self, iter: T)
+    where
+        T: IntoIterator<Item = (String, Value)>,
+    {
+        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::Item> {
+                self.iter.next()
+            }
+            #[inline]
+            fn size_hint(&self) -> (usize, Option<usize>) {
+                self.iter.size_hint()
+            }
+        }
+
+        impl $($generics)* DoubleEndedIterator for $name $($generics)* {
+            #[inline]
+            fn next_back(&mut self) -> Option<Self::Item> {
+                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<F>(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<String, Value> {
+    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<String, Value> {
+    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<String, Value> {
+    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<String, Value>;
+#[cfg(feature = "preserve_order")]
+type IntoIterImpl = indexmap::map::IntoIter<String, Value>;
+
+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 (file)
index 0000000..f1ab24b
--- /dev/null
@@ -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<u16>,
+///     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<T: ?Sized>(value: &T) -> Result<String, Error>
+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<T: ?Sized>(value: &T) -> Result<String, Error>
+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<T>(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<Self::Ok, Self::Error>;
+
+    fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error> {
+        write_document(
+            self.dst,
+            self.settings,
+            toml_edit::ser::ValueSerializer::new().serialize_bool(v),
+        )
+    }
+
+    fn serialize_i8(self, v: i8) -> Result<Self::Ok, Self::Error> {
+        write_document(
+            self.dst,
+            self.settings,
+            toml_edit::ser::ValueSerializer::new().serialize_i8(v),
+        )
+    }
+
+    fn serialize_i16(self, v: i16) -> Result<Self::Ok, Self::Error> {
+        write_document(
+            self.dst,
+            self.settings,
+            toml_edit::ser::ValueSerializer::new().serialize_i16(v),
+        )
+    }
+
+    fn serialize_i32(self, v: i32) -> Result<Self::Ok, Self::Error> {
+        write_document(
+            self.dst,
+            self.settings,
+            toml_edit::ser::ValueSerializer::new().serialize_i32(v),
+        )
+    }
+
+    fn serialize_i64(self, v: i64) -> Result<Self::Ok, Self::Error> {
+        write_document(
+            self.dst,
+            self.settings,
+            toml_edit::ser::ValueSerializer::new().serialize_i64(v),
+        )
+    }
+
+    fn serialize_u8(self, v: u8) -> Result<Self::Ok, Self::Error> {
+        write_document(
+            self.dst,
+            self.settings,
+            toml_edit::ser::ValueSerializer::new().serialize_u8(v),
+        )
+    }
+
+    fn serialize_u16(self, v: u16) -> Result<Self::Ok, Self::Error> {
+        write_document(
+            self.dst,
+            self.settings,
+            toml_edit::ser::ValueSerializer::new().serialize_u16(v),
+        )
+    }
+
+    fn serialize_u32(self, v: u32) -> Result<Self::Ok, Self::Error> {
+        write_document(
+            self.dst,
+            self.settings,
+            toml_edit::ser::ValueSerializer::new().serialize_u32(v),
+        )
+    }
+
+    fn serialize_u64(self, v: u64) -> Result<Self::Ok, Self::Error> {
+        write_document(
+            self.dst,
+            self.settings,
+            toml_edit::ser::ValueSerializer::new().serialize_u64(v),
+        )
+    }
+
+    fn serialize_f32(self, v: f32) -> Result<Self::Ok, Self::Error> {
+        write_document(
+            self.dst,
+            self.settings,
+            toml_edit::ser::ValueSerializer::new().serialize_f32(v),
+        )
+    }
+
+    fn serialize_f64(self, v: f64) -> Result<Self::Ok, Self::Error> {
+        write_document(
+            self.dst,
+            self.settings,
+            toml_edit::ser::ValueSerializer::new().serialize_f64(v),
+        )
+    }
+
+    fn serialize_char(self, v: char) -> Result<Self::Ok, Self::Error> {
+        write_document(
+            self.dst,
+            self.settings,
+            toml_edit::ser::ValueSerializer::new().serialize_char(v),
+        )
+    }
+
+    fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
+        write_document(
+            self.dst,
+            self.settings,
+            toml_edit::ser::ValueSerializer::new().serialize_str(v),
+        )
+    }
+
+    fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok, Self::Error> {
+        write_document(
+            self.dst,
+            self.settings,
+            toml_edit::ser::ValueSerializer::new().serialize_bytes(v),
+        )
+    }
+
+    fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
+        write_document(
+            self.dst,
+            self.settings,
+            toml_edit::ser::ValueSerializer::new().serialize_none(),
+        )
+    }
+
+    fn serialize_some<T: ?Sized>(self, v: &T) -> Result<Self::Ok, Self::Error>
+    where
+        T: serde::ser::Serialize,
+    {
+        write_document(
+            self.dst,
+            self.settings,
+            toml_edit::ser::ValueSerializer::new().serialize_some(v),
+        )
+    }
+
+    fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
+        write_document(
+            self.dst,
+            self.settings,
+            toml_edit::ser::ValueSerializer::new().serialize_unit(),
+        )
+    }
+
+    fn serialize_unit_struct(self, name: &'static str) -> Result<Self::Ok, Self::Error> {
+        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<Self::Ok, Self::Error> {
+        write_document(
+            self.dst,
+            self.settings,
+            toml_edit::ser::ValueSerializer::new().serialize_unit_variant(
+                name,
+                variant_index,
+                variant,
+            ),
+        )
+    }
+
+    fn serialize_newtype_struct<T: ?Sized>(
+        self,
+        name: &'static str,
+        v: &T,
+    ) -> Result<Self::Ok, Self::Error>
+    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<T: ?Sized>(
+        self,
+        name: &'static str,
+        variant_index: u32,
+        variant: &'static str,
+        value: &T,
+    ) -> Result<Self::Ok, Self::Error>
+    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<usize>) -> Result<Self::SerializeSeq, Self::Error> {
+        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::SerializeTuple, Self::Error> {
+        self.serialize_seq(Some(len))
+    }
+
+    fn serialize_tuple_struct(
+        self,
+        _name: &'static str,
+        len: usize,
+    ) -> Result<Self::SerializeTupleStruct, Self::Error> {
+        self.serialize_seq(Some(len))
+    }
+
+    fn serialize_tuple_variant(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        len: usize,
+    ) -> Result<Self::SerializeTupleVariant, Self::Error> {
+        self.serialize_seq(Some(len))
+    }
+
+    fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
+        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::SerializeStruct, Self::Error> {
+        self.serialize_map(Some(len))
+    }
+
+    fn serialize_struct_variant(
+        self,
+        name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        _len: usize,
+    ) -> Result<Self::SerializeStructVariant, Self::Error> {
+        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<u16>,
+///     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<Self::Ok, Self::Error>;
+
+    fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error> {
+        write_value(
+            self.dst,
+            toml_edit::ser::ValueSerializer::new().serialize_bool(v),
+        )
+    }
+
+    fn serialize_i8(self, v: i8) -> Result<Self::Ok, Self::Error> {
+        write_value(
+            self.dst,
+            toml_edit::ser::ValueSerializer::new().serialize_i8(v),
+        )
+    }
+
+    fn serialize_i16(self, v: i16) -> Result<Self::Ok, Self::Error> {
+        write_value(
+            self.dst,
+            toml_edit::ser::ValueSerializer::new().serialize_i16(v),
+        )
+    }
+
+    fn serialize_i32(self, v: i32) -> Result<Self::Ok, Self::Error> {
+        write_value(
+            self.dst,
+            toml_edit::ser::ValueSerializer::new().serialize_i32(v),
+        )
+    }
+
+    fn serialize_i64(self, v: i64) -> Result<Self::Ok, Self::Error> {
+        write_value(
+            self.dst,
+            toml_edit::ser::ValueSerializer::new().serialize_i64(v),
+        )
+    }
+
+    fn serialize_u8(self, v: u8) -> Result<Self::Ok, Self::Error> {
+        write_value(
+            self.dst,
+            toml_edit::ser::ValueSerializer::new().serialize_u8(v),
+        )
+    }
+
+    fn serialize_u16(self, v: u16) -> Result<Self::Ok, Self::Error> {
+        write_value(
+            self.dst,
+            toml_edit::ser::ValueSerializer::new().serialize_u16(v),
+        )
+    }
+
+    fn serialize_u32(self, v: u32) -> Result<Self::Ok, Self::Error> {
+        write_value(
+            self.dst,
+            toml_edit::ser::ValueSerializer::new().serialize_u32(v),
+        )
+    }
+
+    fn serialize_u64(self, v: u64) -> Result<Self::Ok, Self::Error> {
+        write_value(
+            self.dst,
+            toml_edit::ser::ValueSerializer::new().serialize_u64(v),
+        )
+    }
+
+    fn serialize_f32(self, v: f32) -> Result<Self::Ok, Self::Error> {
+        write_value(
+            self.dst,
+            toml_edit::ser::ValueSerializer::new().serialize_f32(v),
+        )
+    }
+
+    fn serialize_f64(self, v: f64) -> Result<Self::Ok, Self::Error> {
+        write_value(
+            self.dst,
+            toml_edit::ser::ValueSerializer::new().serialize_f64(v),
+        )
+    }
+
+    fn serialize_char(self, v: char) -> Result<Self::Ok, Self::Error> {
+        write_value(
+            self.dst,
+            toml_edit::ser::ValueSerializer::new().serialize_char(v),
+        )
+    }
+
+    fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
+        write_value(
+            self.dst,
+            toml_edit::ser::ValueSerializer::new().serialize_str(v),
+        )
+    }
+
+    fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok, Self::Error> {
+        write_value(
+            self.dst,
+            toml_edit::ser::ValueSerializer::new().serialize_bytes(v),
+        )
+    }
+
+    fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
+        write_value(
+            self.dst,
+            toml_edit::ser::ValueSerializer::new().serialize_none(),
+        )
+    }
+
+    fn serialize_some<T: ?Sized>(self, v: &T) -> Result<Self::Ok, Self::Error>
+    where
+        T: serde::ser::Serialize,
+    {
+        write_value(
+            self.dst,
+            toml_edit::ser::ValueSerializer::new().serialize_some(v),
+        )
+    }
+
+    fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
+        write_value(
+            self.dst,
+            toml_edit::ser::ValueSerializer::new().serialize_unit(),
+        )
+    }
+
+    fn serialize_unit_struct(self, name: &'static str) -> Result<Self::Ok, Self::Error> {
+        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<Self::Ok, Self::Error> {
+        write_value(
+            self.dst,
+            toml_edit::ser::ValueSerializer::new().serialize_unit_variant(
+                name,
+                variant_index,
+                variant,
+            ),
+        )
+    }
+
+    fn serialize_newtype_struct<T: ?Sized>(
+        self,
+        name: &'static str,
+        v: &T,
+    ) -> Result<Self::Ok, Self::Error>
+    where
+        T: serde::ser::Serialize,
+    {
+        write_value(
+            self.dst,
+            toml_edit::ser::ValueSerializer::new().serialize_newtype_struct(name, v),
+        )
+    }
+
+    fn serialize_newtype_variant<T: ?Sized>(
+        self,
+        name: &'static str,
+        variant_index: u32,
+        variant: &'static str,
+        value: &T,
+    ) -> Result<Self::Ok, Self::Error>
+    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<usize>) -> Result<Self::SerializeSeq, Self::Error> {
+        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::SerializeTuple, Self::Error> {
+        self.serialize_seq(Some(len))
+    }
+
+    fn serialize_tuple_struct(
+        self,
+        _name: &'static str,
+        len: usize,
+    ) -> Result<Self::SerializeTupleStruct, Self::Error> {
+        self.serialize_seq(Some(len))
+    }
+
+    fn serialize_tuple_variant(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        len: usize,
+    ) -> Result<Self::SerializeTupleVariant, Self::Error> {
+        self.serialize_seq(Some(len))
+    }
+
+    fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
+        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::SerializeStruct, Self::Error> {
+        self.serialize_map(Some(len))
+    }
+
+    fn serialize_struct_variant(
+        self,
+        name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        _len: usize,
+    ) -> Result<Self::SerializeStructVariant, Self::Error> {
+        Err(Error::unsupported_type(Some(name)))
+    }
+}
+
+#[cfg(feature = "display")]
+use internal::*;
+
+#[cfg(feature = "display")]
+mod internal {
+    use super::*;
+
+    use crate::fmt::DocumentFormatter;
+
+    type InnerSerializeDocumentSeq =
+        <toml_edit::ser::ValueSerializer as serde::Serializer>::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<T: ?Sized>(&mut self, value: &T) -> Result<(), Error>
+        where
+            T: serde::ser::Serialize,
+        {
+            self.inner.serialize_element(value).map_err(Error::wrap)
+        }
+
+        fn end(self) -> Result<Self::Ok, Self::Error> {
+            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<T: ?Sized>(&mut self, value: &T) -> Result<(), Error>
+        where
+            T: serde::ser::Serialize,
+        {
+            self.inner.serialize_element(value).map_err(Error::wrap)
+        }
+
+        fn end(self) -> Result<Self::Ok, Self::Error> {
+            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<T: ?Sized>(&mut self, value: &T) -> Result<(), Error>
+        where
+            T: serde::ser::Serialize,
+        {
+            self.inner.serialize_field(value).map_err(Error::wrap)
+        }
+
+        fn end(self) -> Result<Self::Ok, Self::Error> {
+            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<T: ?Sized>(&mut self, value: &T) -> Result<(), Error>
+        where
+            T: serde::ser::Serialize,
+        {
+            self.inner.serialize_field(value).map_err(Error::wrap)
+        }
+
+        fn end(self) -> Result<Self::Ok, Self::Error> {
+            write_document(self.dst, self.settings, self.inner.end())
+        }
+    }
+
+    type InnerSerializeDocumentTable =
+        <toml_edit::ser::ValueSerializer as serde::Serializer>::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<T: ?Sized>(&mut self, input: &T) -> Result<(), Self::Error>
+        where
+            T: serde::ser::Serialize,
+        {
+            self.inner.serialize_key(input).map_err(Error::wrap)
+        }
+
+        fn serialize_value<T: ?Sized>(&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<Self::Ok, Self::Error> {
+            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<T: ?Sized>(
+            &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<Self::Ok, Self::Error> {
+            write_document(self.dst, self.settings, self.inner.end())
+        }
+    }
+
+    pub(crate) fn write_document(
+        dst: &mut String,
+        mut settings: DocumentFormatter,
+        value: Result<toml_edit::Value, crate::edit::ser::Error>,
+    ) -> 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 =
+        <toml_edit::ser::ValueSerializer as serde::Serializer>::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<T: ?Sized>(&mut self, value: &T) -> Result<(), Error>
+        where
+            T: serde::ser::Serialize,
+        {
+            self.inner.serialize_element(value).map_err(Error::wrap)
+        }
+
+        fn end(self) -> Result<Self::Ok, Self::Error> {
+            write_value(self.dst, self.inner.end())
+        }
+    }
+
+    impl<'d> serde::ser::SerializeTuple for SerializeValueArray<'d> {
+        type Ok = ();
+        type Error = Error;
+
+        fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<(), Error>
+        where
+            T: serde::ser::Serialize,
+        {
+            self.inner.serialize_element(value).map_err(Error::wrap)
+        }
+
+        fn end(self) -> Result<Self::Ok, Self::Error> {
+            write_value(self.dst, self.inner.end())
+        }
+    }
+
+    impl<'d> serde::ser::SerializeTupleVariant for SerializeValueArray<'d> {
+        type Ok = ();
+        type Error = Error;
+
+        fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<(), Error>
+        where
+            T: serde::ser::Serialize,
+        {
+            self.inner.serialize_field(value).map_err(Error::wrap)
+        }
+
+        fn end(self) -> Result<Self::Ok, Self::Error> {
+            write_value(self.dst, self.inner.end())
+        }
+    }
+
+    impl<'d> serde::ser::SerializeTupleStruct for SerializeValueArray<'d> {
+        type Ok = ();
+        type Error = Error;
+
+        fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<(), Error>
+        where
+            T: serde::ser::Serialize,
+        {
+            self.inner.serialize_field(value).map_err(Error::wrap)
+        }
+
+        fn end(self) -> Result<Self::Ok, Self::Error> {
+            write_value(self.dst, self.inner.end())
+        }
+    }
+
+    type InnerSerializeValueTable =
+        <toml_edit::ser::ValueSerializer as serde::Serializer>::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<T: ?Sized>(&mut self, input: &T) -> Result<(), Self::Error>
+        where
+            T: serde::ser::Serialize,
+        {
+            self.inner.serialize_key(input).map_err(Error::wrap)
+        }
+
+        fn serialize_value<T: ?Sized>(&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<Self::Ok, Self::Error> {
+            write_value(self.dst, self.inner.end())
+        }
+    }
+
+    impl<'d> serde::ser::SerializeStruct for SerializeValueTable<'d> {
+        type Ok = ();
+        type Error = Error;
+
+        fn serialize_field<T: ?Sized>(
+            &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<Self::Ok, Self::Error> {
+            write_value(self.dst, self.inner.end())
+        }
+    }
+
+    pub(crate) fn write_value(
+        dst: &mut String,
+        value: Result<toml_edit::Value, crate::edit::ser::Error>,
+    ) -> 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 (file)
index 0000000..a2d392b
--- /dev/null
@@ -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<String, Value>;
+
+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<T>(value: T) -> Result<Self, crate::ser::Error>
+    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<T, crate::de::Error>
+    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<Self, Self::Err> {
+        crate::from_str(s)
+    }
+}
+
+impl<'de> de::Deserializer<'de> for Table {
+    type Error = crate::de::Error;
+
+    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, crate::de::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        Value::Table(self).deserialize_any(visitor)
+    }
+
+    #[inline]
+    fn deserialize_enum<V>(
+        self,
+        name: &'static str,
+        variants: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, crate::de::Error>
+    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<V>(self, visitor: V) -> Result<V::Value, crate::de::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        Value::Table(self).deserialize_option(visitor)
+    }
+
+    fn deserialize_newtype_struct<V>(
+        self,
+        name: &'static str,
+        visitor: V,
+    ) -> Result<V::Value, crate::de::Error>
+    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 (file)
index 0000000..8ed75e8
--- /dev/null
@@ -0,0 +1,1308 @@
+//! 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<Value>;
+
+#[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<T>(value: T) -> Result<Value, crate::ser::Error>
+    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<T, crate::de::Error>
+    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<I: Index>(&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<I: Index>(&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<i64> {
+        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<f64> {
+        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<bool> {
+        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<Value>> {
+        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<Value>> {
+        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<I> ops::Index<I> for Value
+where
+    I: Index,
+{
+    type Output = Value;
+
+    fn index(&self, index: I) -> &Value {
+        self.get(index).expect("index not found")
+    }
+}
+
+impl<I> ops::IndexMut<I> 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<V: Into<Value>> From<Vec<V>> for Value {
+    fn from(val: Vec<V>) -> Value {
+        Value::Array(val.into_iter().map(|v| v.into()).collect())
+    }
+}
+
+impl<S: Into<String>, V: Into<Value>> From<BTreeMap<S, V>> for Value {
+    fn from(val: BTreeMap<S, V>) -> Value {
+        let table = val.into_iter().map(|(s, v)| (s.into(), v.into())).collect();
+
+        Value::Table(table)
+    }
+}
+
+impl<S: Into<String> + Hash + Eq, V: Into<Value>> From<HashMap<S, V>> for Value {
+    fn from(val: HashMap<S, V>) -> 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<Value, Self::Err> {
+        crate::from_str(s)
+    }
+}
+
+impl ser::Serialize for Value {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    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<D>(deserializer: D) -> Result<Value, D::Error>
+    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<E>(self, value: bool) -> Result<Value, E> {
+                Ok(Value::Boolean(value))
+            }
+
+            fn visit_i64<E>(self, value: i64) -> Result<Value, E> {
+                Ok(Value::Integer(value))
+            }
+
+            fn visit_u64<E: de::Error>(self, value: u64) -> Result<Value, E> {
+                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<E>(self, value: u32) -> Result<Value, E> {
+                Ok(Value::Integer(value.into()))
+            }
+
+            fn visit_i32<E>(self, value: i32) -> Result<Value, E> {
+                Ok(Value::Integer(value.into()))
+            }
+
+            fn visit_f64<E>(self, value: f64) -> Result<Value, E> {
+                Ok(Value::Float(value))
+            }
+
+            fn visit_str<E>(self, value: &str) -> Result<Value, E> {
+                Ok(Value::String(value.into()))
+            }
+
+            fn visit_string<E>(self, value: String) -> Result<Value, E> {
+                Ok(Value::String(value))
+            }
+
+            fn visit_some<D>(self, deserializer: D) -> Result<Value, D::Error>
+            where
+                D: de::Deserializer<'de>,
+            {
+                de::Deserialize::deserialize(deserializer)
+            }
+
+            fn visit_seq<V>(self, mut visitor: V) -> Result<Value, V::Error>
+            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<V>(self, mut visitor: V) -> Result<Value, V::Error>
+            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::<String>()? {
+                    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<V>(self, visitor: V) -> Result<V::Value, crate::de::Error>
+    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<V>(
+        self,
+        _name: &str,
+        _variants: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, crate::de::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        match self {
+            Value::String(variant) => visitor.visit_enum(variant.into_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<V>(self, visitor: V) -> Result<V::Value, crate::de::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        visitor.visit_some(self)
+    }
+
+    fn deserialize_newtype_struct<V>(
+        self,
+        _name: &'static str,
+        visitor: V,
+    ) -> Result<V::Value, crate::de::Error>
+    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<Value>,
+}
+
+impl SeqDeserializer {
+    fn new(vec: Vec<Value>) -> Self {
+        SeqDeserializer {
+            iter: vec.into_iter(),
+        }
+    }
+}
+
+impl<'de> de::SeqAccess<'de> for SeqDeserializer {
+    type Error = crate::de::Error;
+
+    fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, 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<usize> {
+        match self.iter.size_hint() {
+            (lower, Some(upper)) if lower == upper => Some(upper),
+            _ => None,
+        }
+    }
+}
+
+struct MapDeserializer {
+    iter: <Table as IntoIterator>::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<T>(&mut self, seed: T) -> Result<Option<T::Value>, 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<T>(&mut self, seed: T) -> Result<T::Value, crate::de::Error>
+    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<usize> {
+        match self.iter.size_hint() {
+            (lower, Some(upper)) if lower == upper => Some(upper),
+            _ => None,
+        }
+    }
+}
+
+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<Value, crate::ser::Error>;
+
+    fn serialize_bool(self, value: bool) -> Result<Value, crate::ser::Error> {
+        Ok(Value::Boolean(value))
+    }
+
+    fn serialize_i8(self, value: i8) -> Result<Value, crate::ser::Error> {
+        self.serialize_i64(value.into())
+    }
+
+    fn serialize_i16(self, value: i16) -> Result<Value, crate::ser::Error> {
+        self.serialize_i64(value.into())
+    }
+
+    fn serialize_i32(self, value: i32) -> Result<Value, crate::ser::Error> {
+        self.serialize_i64(value.into())
+    }
+
+    fn serialize_i64(self, value: i64) -> Result<Value, crate::ser::Error> {
+        Ok(Value::Integer(value))
+    }
+
+    fn serialize_u8(self, value: u8) -> Result<Value, crate::ser::Error> {
+        self.serialize_i64(value.into())
+    }
+
+    fn serialize_u16(self, value: u16) -> Result<Value, crate::ser::Error> {
+        self.serialize_i64(value.into())
+    }
+
+    fn serialize_u32(self, value: u32) -> Result<Value, crate::ser::Error> {
+        self.serialize_i64(value.into())
+    }
+
+    fn serialize_u64(self, value: u64) -> Result<Value, crate::ser::Error> {
+        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<Value, crate::ser::Error> {
+        self.serialize_f64(value.into())
+    }
+
+    fn serialize_f64(self, value: f64) -> Result<Value, crate::ser::Error> {
+        Ok(Value::Float(value))
+    }
+
+    fn serialize_char(self, value: char) -> Result<Value, crate::ser::Error> {
+        let mut s = String::new();
+        s.push(value);
+        self.serialize_str(&s)
+    }
+
+    fn serialize_str(self, value: &str) -> Result<Value, crate::ser::Error> {
+        Ok(Value::String(value.to_owned()))
+    }
+
+    fn serialize_bytes(self, value: &[u8]) -> Result<Value, crate::ser::Error> {
+        let vec = value.iter().map(|&b| Value::Integer(b.into())).collect();
+        Ok(Value::Array(vec))
+    }
+
+    fn serialize_unit(self) -> Result<Value, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_type(Some("unit")))
+    }
+
+    fn serialize_unit_struct(self, name: &'static str) -> Result<Value, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_type(Some(name)))
+    }
+
+    fn serialize_unit_variant(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+    ) -> Result<Value, crate::ser::Error> {
+        self.serialize_str(_variant)
+    }
+
+    fn serialize_newtype_struct<T: ?Sized>(
+        self,
+        _name: &'static str,
+        value: &T,
+    ) -> Result<Value, crate::ser::Error>
+    where
+        T: ser::Serialize,
+    {
+        value.serialize(self)
+    }
+
+    fn serialize_newtype_variant<T: ?Sized>(
+        self,
+        name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        _value: &T,
+    ) -> Result<Value, crate::ser::Error>
+    where
+        T: ser::Serialize,
+    {
+        Err(crate::ser::Error::unsupported_type(Some(name)))
+    }
+
+    fn serialize_none(self) -> Result<Value, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_none())
+    }
+
+    fn serialize_some<T: ?Sized>(self, value: &T) -> Result<Value, crate::ser::Error>
+    where
+        T: ser::Serialize,
+    {
+        value.serialize(self)
+    }
+
+    fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, crate::ser::Error> {
+        Ok(ValueSerializeVec {
+            vec: Vec::with_capacity(len.unwrap_or(0)),
+        })
+    }
+
+    fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, crate::ser::Error> {
+        self.serialize_seq(Some(len))
+    }
+
+    fn serialize_tuple_struct(
+        self,
+        _name: &'static str,
+        len: usize,
+    ) -> Result<Self::SerializeTupleStruct, crate::ser::Error> {
+        self.serialize_seq(Some(len))
+    }
+
+    fn serialize_tuple_variant(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        len: usize,
+    ) -> Result<Self::SerializeTupleVariant, crate::ser::Error> {
+        self.serialize_seq(Some(len))
+    }
+
+    fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, crate::ser::Error> {
+        Ok(ValueSerializeMap {
+            ser: SerializeMap {
+                map: Table::new(),
+                next_key: None,
+            },
+        })
+    }
+
+    fn serialize_struct(
+        self,
+        _name: &'static str,
+        len: usize,
+    ) -> Result<Self::SerializeStruct, crate::ser::Error> {
+        self.serialize_map(Some(len))
+    }
+
+    fn serialize_struct_variant(
+        self,
+        name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        _len: usize,
+    ) -> Result<Self::SerializeStructVariant, crate::ser::Error> {
+        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<Table, crate::ser::Error>;
+    type SerializeTuple = ser::Impossible<Table, crate::ser::Error>;
+    type SerializeTupleStruct = ser::Impossible<Table, crate::ser::Error>;
+    type SerializeTupleVariant = ser::Impossible<Table, crate::ser::Error>;
+    type SerializeMap = SerializeMap;
+    type SerializeStruct = SerializeMap;
+    type SerializeStructVariant = ser::Impossible<Table, crate::ser::Error>;
+
+    fn serialize_bool(self, _value: bool) -> Result<Table, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_type(None))
+    }
+
+    fn serialize_i8(self, _value: i8) -> Result<Table, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_type(None))
+    }
+
+    fn serialize_i16(self, _value: i16) -> Result<Table, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_type(None))
+    }
+
+    fn serialize_i32(self, _value: i32) -> Result<Table, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_type(None))
+    }
+
+    fn serialize_i64(self, _value: i64) -> Result<Table, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_type(None))
+    }
+
+    fn serialize_u8(self, _value: u8) -> Result<Table, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_type(None))
+    }
+
+    fn serialize_u16(self, _value: u16) -> Result<Table, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_type(None))
+    }
+
+    fn serialize_u32(self, _value: u32) -> Result<Table, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_type(None))
+    }
+
+    fn serialize_u64(self, _value: u64) -> Result<Table, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_type(None))
+    }
+
+    fn serialize_f32(self, _value: f32) -> Result<Table, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_type(None))
+    }
+
+    fn serialize_f64(self, _value: f64) -> Result<Table, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_type(None))
+    }
+
+    fn serialize_char(self, _value: char) -> Result<Table, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_type(None))
+    }
+
+    fn serialize_str(self, _value: &str) -> Result<Table, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_type(None))
+    }
+
+    fn serialize_bytes(self, _value: &[u8]) -> Result<Table, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_type(None))
+    }
+
+    fn serialize_unit(self) -> Result<Table, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_type(None))
+    }
+
+    fn serialize_unit_struct(self, _name: &'static str) -> Result<Table, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_type(None))
+    }
+
+    fn serialize_unit_variant(
+        self,
+        name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+    ) -> Result<Table, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_type(Some(name)))
+    }
+
+    fn serialize_newtype_struct<T: ?Sized>(
+        self,
+        _name: &'static str,
+        value: &T,
+    ) -> Result<Table, crate::ser::Error>
+    where
+        T: ser::Serialize,
+    {
+        value.serialize(self)
+    }
+
+    fn serialize_newtype_variant<T: ?Sized>(
+        self,
+        name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        _value: &T,
+    ) -> Result<Table, crate::ser::Error>
+    where
+        T: ser::Serialize,
+    {
+        Err(crate::ser::Error::unsupported_type(Some(name)))
+    }
+
+    fn serialize_none(self) -> Result<Table, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_none())
+    }
+
+    fn serialize_some<T: ?Sized>(self, value: &T) -> Result<Table, crate::ser::Error>
+    where
+        T: ser::Serialize,
+    {
+        value.serialize(self)
+    }
+
+    fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_type(None))
+    }
+
+    fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_type(None))
+    }
+
+    fn serialize_tuple_struct(
+        self,
+        name: &'static str,
+        _len: usize,
+    ) -> Result<Self::SerializeTupleStruct, crate::ser::Error> {
+        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<Self::SerializeTupleVariant, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_type(Some(name)))
+    }
+
+    fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, crate::ser::Error> {
+        Ok(SerializeMap {
+            map: Table::new(),
+            next_key: None,
+        })
+    }
+
+    fn serialize_struct(
+        self,
+        _name: &'static str,
+        len: usize,
+    ) -> Result<Self::SerializeStruct, crate::ser::Error> {
+        self.serialize_map(Some(len))
+    }
+
+    fn serialize_struct_variant(
+        self,
+        name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        _len: usize,
+    ) -> Result<Self::SerializeStructVariant, crate::ser::Error> {
+        Err(crate::ser::Error::unsupported_type(Some(name)))
+    }
+}
+
+struct ValueSerializeVec {
+    vec: Vec<Value>,
+}
+
+impl ser::SerializeSeq for ValueSerializeVec {
+    type Ok = Value;
+    type Error = crate::ser::Error;
+
+    fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<(), crate::ser::Error>
+    where
+        T: ser::Serialize,
+    {
+        self.vec.push(Value::try_from(value)?);
+        Ok(())
+    }
+
+    fn end(self) -> Result<Value, crate::ser::Error> {
+        Ok(Value::Array(self.vec))
+    }
+}
+
+impl ser::SerializeTuple for ValueSerializeVec {
+    type Ok = Value;
+    type Error = crate::ser::Error;
+
+    fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<(), crate::ser::Error>
+    where
+        T: ser::Serialize,
+    {
+        ser::SerializeSeq::serialize_element(self, value)
+    }
+
+    fn end(self) -> Result<Value, crate::ser::Error> {
+        ser::SerializeSeq::end(self)
+    }
+}
+
+impl ser::SerializeTupleStruct for ValueSerializeVec {
+    type Ok = Value;
+    type Error = crate::ser::Error;
+
+    fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<(), crate::ser::Error>
+    where
+        T: ser::Serialize,
+    {
+        ser::SerializeSeq::serialize_element(self, value)
+    }
+
+    fn end(self) -> Result<Value, crate::ser::Error> {
+        ser::SerializeSeq::end(self)
+    }
+}
+
+impl ser::SerializeTupleVariant for ValueSerializeVec {
+    type Ok = Value;
+    type Error = crate::ser::Error;
+
+    fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<(), crate::ser::Error>
+    where
+        T: ser::Serialize,
+    {
+        ser::SerializeSeq::serialize_element(self, value)
+    }
+
+    fn end(self) -> Result<Value, crate::ser::Error> {
+        ser::SerializeSeq::end(self)
+    }
+}
+
+pub(crate) struct SerializeMap {
+    map: Table,
+    next_key: Option<String>,
+}
+
+impl ser::SerializeMap for SerializeMap {
+    type Ok = Table;
+    type Error = crate::ser::Error;
+
+    fn serialize_key<T: ?Sized>(&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<T: ?Sized>(&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<Table, crate::ser::Error> {
+        Ok(self.map)
+    }
+}
+
+impl ser::SerializeStruct for SerializeMap {
+    type Ok = Table;
+    type Error = crate::ser::Error;
+
+    fn serialize_field<T: ?Sized>(
+        &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<Table, crate::ser::Error> {
+        ser::SerializeMap::end(self)
+    }
+}
+
+struct ValueSerializeMap {
+    ser: SerializeMap,
+}
+
+impl ser::SerializeMap for ValueSerializeMap {
+    type Ok = Value;
+    type Error = crate::ser::Error;
+
+    fn serialize_key<T: ?Sized>(&mut self, key: &T) -> Result<(), crate::ser::Error>
+    where
+        T: ser::Serialize,
+    {
+        self.ser.serialize_key(key)
+    }
+
+    fn serialize_value<T: ?Sized>(&mut self, value: &T) -> Result<(), crate::ser::Error>
+    where
+        T: ser::Serialize,
+    {
+        self.ser.serialize_value(value)
+    }
+
+    fn end(self) -> Result<Value, crate::ser::Error> {
+        self.ser.end().map(Value::Table)
+    }
+}
+
+impl ser::SerializeStruct for ValueSerializeMap {
+    type Ok = Value;
+    type Error = crate::ser::Error;
+
+    fn serialize_field<T: ?Sized>(
+        &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<Value, crate::ser::Error> {
+        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<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
+    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<E>(self, s: &str) -> Result<bool, E>
+    where
+        E: de::Error,
+    {
+        if s == datetime::FIELD {
+            Ok(true)
+        } else {
+            self.key.push_str(s);
+            Ok(false)
+        }
+    }
+
+    fn visit_string<E>(self, s: String) -> Result<bool, E>
+    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 (file)
index 0000000..fe6db3f
--- /dev/null
@@ -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<toml_test_harness::Decoded, toml_test_harness::Error> {
+        let data = std::str::from_utf8(data).map_err(toml_test_harness::Error::new)?;
+        let document = data
+            .parse::<toml::Value>()
+            .map_err(toml_test_harness::Error::new)?;
+        value_to_decoded(&document)
+    }
+}
+
+fn value_to_decoded(
+    value: &toml::Value,
+) -> Result<toml_test_harness::Decoded, toml_test_harness::Error> {
+    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<toml_test_harness::Decoded, toml_test_harness::Error> {
+    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 (file)
index 0000000..5d4fc2a
--- /dev/null
@@ -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 (file)
index 0000000..eda6296
--- /dev/null
@@ -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<String, toml_test_harness::Error> {
+        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<toml::Value, toml_test_harness::Error> {
+    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<toml::Value, toml_test_harness::Error> {
+    match decoded {
+        toml_test_harness::DecodedValue::String(value) => Ok(toml::Value::String(value.clone())),
+        toml_test_harness::DecodedValue::Integer(value) => value
+            .parse::<i64>()
+            .map_err(toml_test_harness::Error::new)
+            .map(toml::Value::Integer),
+        toml_test_harness::DecodedValue::Float(value) => value
+            .parse::<f64>()
+            .map_err(toml_test_harness::Error::new)
+            .map(toml::Value::Float),
+        toml_test_harness::DecodedValue::Bool(value) => value
+            .parse::<bool>()
+            .map_err(toml_test_harness::Error::new)
+            .map(toml::Value::Boolean),
+        toml_test_harness::DecodedValue::Datetime(value) => value
+            .parse::<toml::value::Datetime>()
+            .map_err(toml_test_harness::Error::new)
+            .map(toml::Value::Datetime),
+        toml_test_harness::DecodedValue::DatetimeLocal(value) => value
+            .parse::<toml::value::Datetime>()
+            .map_err(toml_test_harness::Error::new)
+            .map(toml::Value::Datetime),
+        toml_test_harness::DecodedValue::DateLocal(value) => value
+            .parse::<toml::value::Datetime>()
+            .map_err(toml_test_harness::Error::new)
+            .map(toml::Value::Datetime),
+        toml_test_harness::DecodedValue::TimeLocal(value) => value
+            .parse::<toml::value::Datetime>()
+            .map_err(toml_test_harness::Error::new)
+            .map(toml::Value::Datetime),
+    }
+}
+
+fn from_table(
+    decoded: &std::collections::HashMap<String, toml_test_harness::Decoded>,
+) -> Result<toml::value::Table, toml_test_harness::Error> {
+    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<toml::value::Array, toml_test_harness::Error> {
+    decoded.iter().map(from_decoded).collect()
+}
diff --git a/tests/encoder_compliance.rs b/tests/encoder_compliance.rs
new file mode 100644 (file)
index 0000000..3807248
--- /dev/null
@@ -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 (file)
index 0000000..b3630bd
--- /dev/null
@@ -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<T> {
+    p_a: T,
+    p_b: Vec<Child<T>>,
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+#[serde(deny_unknown_fields)]
+struct Child<T> {
+    c_a: T,
+    c_b: T,
+}
+
+#[derive(Debug, PartialEq)]
+enum CasedString {
+    Lowercase(String),
+    Uppercase(String),
+}
+
+impl<'de> de::Deserialize<'de> for CasedString {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    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<E>(self, s: &str) -> Result<Self::Value, E>
+            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::<Parent<CasedString>>(
+        "
+            p_a = 'a'
+            p_b = [{c_a = 'a', c_b = 'c'}]
+        ",
+    )
+    .unwrap();
+
+    // Custom error at p_b value.
+    bad!(
+        "
+            p_a = ''
+                # ^
+        ",
+        Parent<CasedString>,
+        "\
+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<CasedString>,
+        "\
+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<CasedString>,
+        "\
+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<CasedString>,
+        "\
+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<CasedString>,
+        "\
+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<CasedString>,
+        "\
+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<CasedString>,
+        "\
+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<CasedString>,
+        "\
+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<CasedString>,
+        "\
+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<CasedString>,
+        "\
+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<CasedString>,
+        "\
+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<CasedString>,
+        "\
+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<String>,
+        "\
+TOML parse error at line 1, column 1
+  |
+1 | 
+  | ^
+missing field `p_b`
+"
+    );
+
+    bad!(
+        "
+            p_a = ''
+            p_b = [
+                {c_a = ''}
+              # ^
+            ]
+        ",
+        Parent<String>,
+        "\
+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<String>,
+        "\
+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<String>,
+        "\
+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<String>,
+        "\
+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 (file)
index 0000000..7430fac
--- /dev/null
@@ -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 (file)
index 0000000..379ae91
--- /dev/null
@@ -0,0 +1,55 @@
+use serde::Deserialize;
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Recipe {
+    pub name: String,
+    pub description: Option<String>,
+    #[serde(default)]
+    pub modules: Vec<Modules>,
+    #[serde(default)]
+    pub packages: Vec<Packages>,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Modules {
+    pub name: String,
+    pub version: Option<String>,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Packages {
+    pub name: String,
+    pub version: Option<String>,
+}
+
+#[test]
+fn both_ends() {
+    let recipe_works = toml::from_str::<Recipe>(
+        r#"
+        name = "testing"
+        description = "example"
+        modules = []
+
+        [[packages]]
+        name = "base"
+    "#,
+    )
+    .unwrap();
+    toml::to_string(&recipe_works).unwrap();
+
+    let recipe_fails = toml::from_str::<Recipe>(
+        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 (file)
index 0000000..6e0c2f7
--- /dev/null
@@ -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<TheEnum>,
+}
+
+fn value_from_str<T>(s: &'_ str) -> Result<T, toml::de::Error>
+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::<TheEnum>("\"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>("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::<TheEnum>("{ 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>("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::<TheEnum>("{ Plain = { extra_field = 404 } }").unwrap_err();
+    snapbox::assert_eq(
+        r#"expected empty table
+"#,
+        error.to_string(),
+    );
+
+    let error = toml::from_str::<Val>("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::<TheEnum>("{ 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>("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 (file)
index 0000000..d008134
--- /dev/null
@@ -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::<Value>(&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 (file)
index 0000000..8240d1d
--- /dev/null
@@ -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<User>,
+}
+
+#[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 (file)
index 0000000..5100705
--- /dev/null
@@ -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::<toml::value::Datetime>().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 <alex@alexcrichton.com>"]
+
+        [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 <alex@alexcrichton.com>".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 (file)
index 0000000..1473787
--- /dev/null
@@ -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 (file)
index 0000000..3ae772b
--- /dev/null
@@ -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<String>,
+        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 (file)
index 0000000..3b9f65a
--- /dev/null
@@ -0,0 +1,1074 @@
+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: Deserializer<'de>>(d: D) -> Result<Range10, D::Error> {
+            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<Range10, _> = d_bad1.try_into();
+    assert!(err1.is_err());
+    let err2: Result<Range10, _> = d_bad2.try_into();
+    assert!(err2.is_err());
+}
+
+#[test]
+fn array() {
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Foo {
+        a: Vec<isize>,
+    }
+
+    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<Box<Foo>>,
+        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<char>,
+        map: BTreeMap<String, isize>,
+    }
+
+    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<Bar>,
+    }
+    #[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<String, String> }
+//
+//     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<String> }
+//
+//     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<Vec<String>> }
+//
+//     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<Bar> }
+//     #[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<Bar>,
+    }
+    #[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<Vec<Bar>>,
+    }
+    #[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::<Foo>().is_ok());
+    assert!(toml::from_str::<Foo>(&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<C>);
+
+    #[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)
+            }
+        },
+    }
+}
+
+#[derive(Debug, Default, PartialEq, Serialize, Deserialize)]
+struct CanBeEmpty {
+    a: Option<String>,
+    b: Option<String>,
+}
+
+#[test]
+fn table_structs_empty() {
+    let text = "[bar]\n\n[baz]\n\n[bazv]\na = \"foo\"\n\n[foo]\n";
+    let value: BTreeMap<String, CanBeEmpty> = toml::from_str(text).unwrap();
+    let mut expected: BTreeMap<String, CanBeEmpty> = 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<String>, Vec<String>, Vec<String>);
+
+    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<String>,
+        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<Package, _> = 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<Package, _> = 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<String>,
+        package: Package,
+        profile: Profile,
+    }
+
+    #[derive(Debug, Serialize, Deserialize)]
+    struct Package {
+        name: String,
+        version: String,
+        authors: Vec<String>,
+    }
+
+    #[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::<DataFile>(dotted_table).is_ok());
+
+    let inline_table = r#"
+        data = { Gt = 5 }
+        "#;
+    assert!(toml::from_str::<DataFile>(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::<toml::Table>().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 (file)
index 0000000..760c73a
--- /dev/null
@@ -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<T> {
+        foo: Spanned<T>,
+    }
+
+    #[derive(Deserialize)]
+    struct BareFoo<T> {
+        foo: T,
+    }
+
+    fn good<T>(s: &str, expected: &str, end: Option<usize>)
+    where
+        T: serde::de::DeserializeOwned + Debug + PartialEq,
+    {
+        let foo: Foo<T> = 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<BareFoo<T>> = 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::<String>("foo = \"foo\"", "\"foo\"", None);
+    good::<u32>("foo = 42", "42", None);
+    // leading plus
+    good::<u32>("foo = +42", "+42", None);
+    // table
+    good::<HashMap<String, u32>>(
+        "foo = {\"foo\" = 42, \"bar\" = 42}",
+        "{\"foo\" = 42, \"bar\" = 42}",
+        None,
+    );
+    // array
+    good::<Vec<u32>>("foo = [0, 1, 2, 3, 4]", "[0, 1, 2, 3, 4]", None);
+    // datetime
+    good::<String>(
+        "foo = \"1997-09-09T09:09:09Z\"",
+        "\"1997-09-09T09:09:09Z\"",
+        None,
+    );
+
+    for expected in good_datetimes() {
+        let s = format!("foo = {}", expected);
+        good::<Datetime>(&s, expected, None);
+    }
+    // ending at something other than the absolute end
+    good::<u32>("foo = 42\nnoise = true", "42", Some(8));
+}
+
+#[test]
+fn test_inner_spanned_table() {
+    #[derive(Deserialize)]
+    struct Foo {
+        foo: Spanned<HashMap<Spanned<String>, Spanned<String>>>,
+    }
+
+    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<String>, Spanned<String>>,
+    }
+
+    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<Spanned<String>, HashMap<Spanned<String>, Spanned<String>>>,
+    }
+
+    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<HashMap<Spanned<String>, Spanned<String>>>>,
+    }
+
+    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::<Example>(
+        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 (file)
index 0000000..5e866f9
--- /dev/null
@@ -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<bool>,
+        baz: Spanned<String>,
+    }
+    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 (file)
index 0000000..b003557
--- /dev/null
@@ -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<Component>,
+        contours: Vec<Contour>,
+    }
+
+    #[derive(Deserialize, Serialize, Debug)]
+    struct Point {
+        x: f64,
+        y: f64,
+        pt_type: String,
+    }
+
+    type Contour = Vec<Point>;
+
+    #[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<Inner>,
+        v2: Vec<Inner>,
+    }
+
+    #[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<String>,
+    }
+
+    #[derive(Serialize, Deserialize)]
+    struct C {
+        a: A,
+        b: Vec<String>,
+        c: Vec<B>,
+    }
+    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();
+}