aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeongik Cha <jeongik@google.com>2023-09-27 12:20:59 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2023-09-27 12:20:59 +0000
commitf8c5a770322e49abd51c4bea12b3f132919fec47 (patch)
tree70c35d2503ee77c3b9f3092198ec8cfd808378cf
parent879cbbe667494645ae83103639dc1bae9660e9e2 (diff)
parent388c2d8f2de3cd5e96ebdf7b3cf85c92935c1eed (diff)
downloadtoml-f8c5a770322e49abd51c4bea12b3f132919fec47.tar.gz
Import toml am: 62114efc69 am: 70a7c2f186 am: bb81be2675 am: 23265c77db am: 388c2d8f2d
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/toml/+/2752230 Change-Id: I1a651ea6ba7da94488566489695a4e2e895a4207 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--.cargo_vcs_info.json6
-rw-r--r--Android.bp24
-rw-r--r--Cargo.lock891
-rw-r--r--Cargo.toml159
-rw-r--r--Cargo.toml.orig72
-rw-r--r--LICENSE229
-rw-r--r--LICENSE-APACHE201
-rw-r--r--LICENSE-MIT25
-rw-r--r--METADATA20
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--MODULE_LICENSE_MIT0
-rw-r--r--OWNERS1
-rw-r--r--README.md29
-rw-r--r--examples/decode.rs54
-rw-r--r--examples/enum_external.rs45
-rw-r--r--examples/toml2json.rs47
-rw-r--r--src/de.rs322
-rw-r--r--src/edit.rs91
-rw-r--r--src/fmt.rs60
-rw-r--r--src/lib.rs182
-rw-r--r--src/macros.rs460
-rw-r--r--src/map.rs609
-rw-r--r--src/ser.rs1087
-rw-r--r--src/table.rs114
-rw-r--r--src/value.rs1455
-rw-r--r--tests/decoder.rs67
-rw-r--r--tests/decoder_compliance.rs21
-rw-r--r--tests/encoder.rs81
-rw-r--r--tests/encoder_compliance.rs14
-rw-r--r--tests/testsuite/de_errors.rs460
-rw-r--r--tests/testsuite/display.rs116
-rw-r--r--tests/testsuite/display_tricky.rs55
-rw-r--r--tests/testsuite/enum_external_deserialize.rs320
-rw-r--r--tests/testsuite/float.rs80
-rw-r--r--tests/testsuite/formatting.rs54
-rw-r--r--tests/testsuite/macros.rs368
-rw-r--r--tests/testsuite/main.rs15
-rw-r--r--tests/testsuite/pretty.rs184
-rw-r--r--tests/testsuite/serde.rs1096
-rw-r--r--tests/testsuite/spanned.rs261
-rw-r--r--tests/testsuite/spanned_impls.rs41
-rw-r--r--tests/testsuite/tables_last.rs162
42 files changed, 9578 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..9ceba26
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,6 @@
+{
+ "git": {
+ "sha1": "59ae12d8c1b3c79df6ae7a29d1841ae860cd7fb6"
+ },
+ "path_in_vcs": "crates/toml"
+} \ No newline at end of file
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..4abb67c
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,24 @@
+// This file is generated by cargo2android.py --run.
+// Do not modify this file as changes will be overridden on upgrade.
+
+
+
+rust_library_host {
+ name: "libtoml",
+ crate_name: "toml",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.7.6",
+ srcs: ["src/lib.rs"],
+ edition: "2021",
+ features: [
+ "default",
+ "display",
+ "parse",
+ ],
+ rustlibs: [
+ "libserde",
+ "libserde_spanned",
+ "libtoml_datetime",
+ "libtoml_edit",
+ ],
+}
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..7bf0506
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,891 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anstream"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6342bd4f5a1205d7f41e94a41a901f5647c938cdfa96036338e8533c9d6c2450"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is-terminal",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
+dependencies = [
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
+dependencies = [
+ "anstyle",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bstr"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "clap"
+version = "4.0.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39"
+dependencies = [
+ "bitflags",
+ "clap_derive",
+ "clap_lex",
+ "is-terminal",
+ "once_cell",
+ "strsim",
+ "termcolor",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014"
+dependencies = [
+ "heck",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.105",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8"
+dependencies = [
+ "os_str_bytes",
+]
+
+[[package]]
+name = "colorchoice"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
+
+[[package]]
+name = "errno"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "globset"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a"
+dependencies = [
+ "aho-corasick",
+ "bstr",
+ "fnv",
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
+
+[[package]]
+name = "heck"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
+
+[[package]]
+name = "ignore"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d"
+dependencies = [
+ "crossbeam-utils",
+ "globset",
+ "lazy_static",
+ "log",
+ "memchr",
+ "regex",
+ "same-file",
+ "thread_local",
+ "walkdir",
+ "winapi-util",
+]
+
+[[package]]
+name = "include_dir"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e"
+dependencies = [
+ "include_dir_macros",
+]
+
+[[package]]
+name = "include_dir_macros"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "io-lifetimes"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c"
+dependencies = [
+ "libc",
+ "windows-sys 0.42.0",
+]
+
+[[package]]
+name = "is-terminal"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
+dependencies = [
+ "hermit-abi 0.3.1",
+ "io-lifetimes",
+ "rustix",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.142"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
+
+[[package]]
+name = "libtest-mimic"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7b603516767d1ab23d0de09d023e62966c3322f7148297c35cf3d97aa8b37fa"
+dependencies = [
+ "clap",
+ "termcolor",
+ "threadpool",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b64f40e5e03e0d54f03845c8197d0291253cdbedfb1cb46b13c2c117554a9f4c"
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "normalize-line-endings"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
+
+[[package]]
+name = "num-integer"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
+dependencies = [
+ "hermit-abi 0.1.19",
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
+
+[[package]]
+name = "os_str_bytes"
+version = "6.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.105",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "regex"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
+
+[[package]]
+name = "rustix"
+version = "0.37.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2aae838e49b3d63e9274e1c01833cc8139d3fec468c3b84688c628f44b1ae11d"
+dependencies = [
+ "bitflags",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.160"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.160"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.15",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.96"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "similar"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62ac7f900db32bf3fd12e0117dd3dc4da74bc52ebaac97f39668446d89694803"
+
+[[package]]
+name = "snapbox"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6bccd62078347f89a914e3004d94582e13824d4e3d8a816317862884c423835"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "normalize-line-endings",
+ "similar",
+ "snapbox-macros",
+]
+
+[[package]]
+name = "snapbox-macros"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eaaf09df9f0eeae82be96290918520214530e738a7fe5a351b0f24cf77c0ca31"
+dependencies = [
+ "anstream",
+]
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "syn"
+version = "1.0.105"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "threadpool"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
+dependencies = [
+ "num_cpus",
+]
+
+[[package]]
+name = "toml"
+version = "0.7.6"
+dependencies = [
+ "indexmap",
+ "serde",
+ "serde_json",
+ "serde_spanned",
+ "snapbox",
+ "toml-test-harness",
+ "toml_datetime",
+ "toml_edit",
+]
+
+[[package]]
+name = "toml-test"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37351256790aa1dbd6d60f4ff08e55e7f372e292f3e9040d6e077463d9a779c3"
+dependencies = [
+ "chrono",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "toml-test-data"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93f351b6d6005ee802b0d4a53ca1cdf05636f441df4d299e62cba57f1da52646"
+dependencies = [
+ "include_dir",
+]
+
+[[package]]
+name = "toml-test-harness"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e00fda5710922fe6b3005bf6a5050c303d6f9625249c37b7386e8818f4af675"
+dependencies = [
+ "ignore",
+ "libtest-mimic",
+ "toml-test",
+ "toml-test-data",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.19.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78"
+dependencies = [
+ "indexmap",
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "walkdir"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
+dependencies = [
+ "same-file",
+ "winapi",
+ "winapi-util",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.1",
+ "windows_aarch64_msvc 0.42.1",
+ "windows_i686_gnu 0.42.1",
+ "windows_i686_msvc 0.42.1",
+ "windows_x86_64_gnu 0.42.1",
+ "windows_x86_64_gnullvm 0.42.1",
+ "windows_x86_64_msvc 0.42.1",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.1",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.0",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.1",
+ "windows_aarch64_msvc 0.42.1",
+ "windows_i686_gnu 0.42.1",
+ "windows_i686_msvc 0.42.1",
+ "windows_x86_64_gnu 0.42.1",
+ "windows_x86_64_gnullvm 0.42.1",
+ "windows_x86_64_msvc 0.42.1",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.0",
+ "windows_aarch64_msvc 0.48.0",
+ "windows_i686_gnu 0.48.0",
+ "windows_i686_msvc 0.48.0",
+ "windows_x86_64_gnu 0.48.0",
+ "windows_x86_64_gnullvm 0.48.0",
+ "windows_x86_64_msvc 0.48.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
+
+[[package]]
+name = "winnow"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699"
+dependencies = [
+ "memchr",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..02f89eb
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,159 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+rust-version = "1.64.0"
+name = "toml"
+version = "0.7.6"
+authors = ["Alex Crichton <alex@alexcrichton.com>"]
+include = [
+ "build.rs",
+ "src/**/*",
+ "Cargo.toml",
+ "Cargo.lock",
+ "LICENSE*",
+ "README.md",
+ "benches/**/*",
+ "examples/**/*",
+ "tests/**/*",
+]
+description = """
+A native Rust encoder and decoder of TOML-formatted files and streams. Provides
+implementations of the standard Serialize/Deserialize traits for TOML data to
+facilitate deserializing and serializing Rust structures.
+"""
+homepage = "https://github.com/toml-rs/toml"
+readme = "README.md"
+keywords = [
+ "encoding",
+ "toml",
+]
+categories = [
+ "encoding",
+ "parser-implementations",
+ "parsing",
+ "config",
+]
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/toml-rs/toml"
+
+[package.metadata.docs.rs]
+rustdoc-args = [
+ "--cfg",
+ "docsrs",
+]
+
+[[package.metadata.release.pre-release-replacements]]
+file = "CHANGELOG.md"
+min = 1
+replace = "{{version}}"
+search = "Unreleased"
+
+[[package.metadata.release.pre-release-replacements]]
+exactly = 1
+file = "CHANGELOG.md"
+replace = "...{{tag_name}}"
+search = '\.\.\.HEAD'
+
+[[package.metadata.release.pre-release-replacements]]
+file = "CHANGELOG.md"
+min = 1
+replace = "{{date}}"
+search = "ReleaseDate"
+
+[[package.metadata.release.pre-release-replacements]]
+exactly = 1
+file = "CHANGELOG.md"
+replace = """
+<!-- next-header -->
+## [Unreleased] - ReleaseDate
+"""
+search = "<!-- next-header -->"
+
+[[package.metadata.release.pre-release-replacements]]
+exactly = 1
+file = "CHANGELOG.md"
+replace = """
+<!-- next-url -->
+[Unreleased]: https://github.com/toml-rs/toml/compare/{{tag_name}}...HEAD"""
+search = "<!-- next-url -->"
+
+[[example]]
+name = "decode"
+required-features = [
+ "parse",
+ "display",
+]
+
+[[example]]
+name = "enum_external"
+required-features = [
+ "parse",
+ "display",
+]
+
+[[example]]
+name = "toml2json"
+required-features = [
+ "parse",
+ "display",
+]
+
+[[test]]
+name = "decoder_compliance"
+harness = false
+
+[[test]]
+name = "encoder_compliance"
+harness = false
+
+[dependencies.indexmap]
+version = "2.0.0"
+optional = true
+
+[dependencies.serde]
+version = "1.0.145"
+
+[dependencies.serde_spanned]
+version = "0.6.3"
+features = ["serde"]
+
+[dependencies.toml_datetime]
+version = "0.6.3"
+features = ["serde"]
+
+[dependencies.toml_edit]
+version = "0.19.12"
+features = ["serde"]
+optional = true
+
+[dev-dependencies.serde]
+version = "1.0.160"
+features = ["derive"]
+
+[dev-dependencies.serde_json]
+version = "1.0.96"
+
+[dev-dependencies.snapbox]
+version = "0.4.11"
+
+[dev-dependencies.toml-test-harness]
+version = "0.4.3"
+
+[features]
+default = [
+ "parse",
+ "display",
+]
+display = ["dep:toml_edit"]
+parse = ["dep:toml_edit"]
+preserve_order = ["indexmap"]
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..424f035
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,72 @@
+[package]
+name = "toml"
+version = "0.7.6"
+keywords = ["encoding", "toml"]
+categories = ["encoding", "parser-implementations", "parsing", "config"]
+description = """
+A native Rust encoder and decoder of TOML-formatted files and streams. Provides
+implementations of the standard Serialize/Deserialize traits for TOML data to
+facilitate deserializing and serializing Rust structures.
+"""
+authors = ["Alex Crichton <alex@alexcrichton.com>"]
+repository.workspace = true
+homepage.workspace = true
+license.workspace = true
+edition.workspace = true
+rust-version.workspace = true
+include.workspace = true
+
+[package.metadata.release]
+pre-release-replacements = [
+ {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1},
+ {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1},
+ {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1},
+ {file="CHANGELOG.md", search="<!-- 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 = "2.0.0", optional = true }
+toml_edit = { version = "0.19.12", path = "../toml_edit", features = ["serde"], optional = true }
+toml_datetime = { version = "0.6.3", path = "../toml_datetime", features = ["serde"] }
+serde_spanned = { version = "0.6.3", path = "../serde_spanned", features = ["serde"] }
+
+[dev-dependencies]
+serde = { version = "1.0.160", features = ["derive"] }
+serde_json = "1.0.96"
+toml-test-harness = "0.4.3"
+snapbox = "0.4.11"
+
+[[test]]
+name = "decoder_compliance"
+harness = false
+
+[[test]]
+name = "encoder_compliance"
+harness = false
+
+[[example]]
+name = "decode"
+required-features = ["parse", "display"]
+
+[[example]]
+name = "enum_external"
+required-features = ["parse", "display"]
+
+[[example]]
+name = "toml2json"
+required-features = ["parse", "display"]
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..4e83254
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,229 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+---
+
+Copyright (c) 2014 Alex Crichton
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
new file mode 100644
index 0000000..16fe87b
--- /dev/null
+++ b/LICENSE-APACHE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/LICENSE-MIT b/LICENSE-MIT
new file mode 100644
index 0000000..39e0ed6
--- /dev/null
+++ b/LICENSE-MIT
@@ -0,0 +1,25 @@
+Copyright (c) 2014 Alex Crichton
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..fad1206
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,20 @@
+name: "toml"
+description: "()"
+third_party {
+ identifier {
+ type: "crates.io"
+ value: "https://crates.io/crates/toml"
+ }
+ identifier {
+ type: "Archive"
+ value: "https://static.crates.io/crates/toml/toml-0.7.6.crate"
+ }
+ version: "0.7.6"
+ # Dual-licensed, using the least restrictive per go/thirdpartylicenses#same.
+ license_type: NOTICE
+ last_upgrade_date {
+ year: 2023
+ month: 8
+ day: 23
+ }
+}
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_MIT
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..45dc4dd
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1 @@
+include platform/prebuilts/rust:master:/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..82960dd
--- /dev/null
+++ b/README.md
@@ -0,0 +1,29 @@
+# toml
+
+[![Latest Version](https://img.shields.io/crates/v/toml.svg)](https://crates.io/crates/toml)
+[![Documentation](https://docs.rs/toml/badge.svg)](https://docs.rs/toml)
+
+A [serde]-compatible [TOML][toml] decoder and encoder for Rust.
+
+For format-preserving edits or finer control over output, see [toml_edit]
+
+[serde]: https://serde.rs/
+[toml]: https://github.com/toml-lang/toml
+[toml_edit]: https://docs.rs/toml_edit
+
+# License
+
+This project is licensed under either of
+
+ * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
+ http://www.apache.org/licenses/LICENSE-2.0)
+ * MIT license ([LICENSE-MIT](LICENSE-MIT) or
+ http://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in toml-rs by you, as defined in the Apache-2.0 license, shall be
+dual licensed as above, without any additional terms or conditions.
diff --git a/examples/decode.rs b/examples/decode.rs
new file mode 100644
index 0000000..348e911
--- /dev/null
+++ b/examples/decode.rs
@@ -0,0 +1,54 @@
+//! An example showing off the usage of `Deserialize` to automatically decode
+//! TOML into a Rust `struct`
+
+#![deny(warnings)]
+#![allow(dead_code)]
+
+use serde::Deserialize;
+
+/// This is what we're going to decode into. Each field is optional, meaning
+/// that it doesn't have to be present in TOML.
+#[derive(Debug, Deserialize)]
+struct Config {
+ global_string: Option<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
index 0000000..edac7d6
--- /dev/null
+++ b/examples/enum_external.rs
@@ -0,0 +1,45 @@
+//! An example showing off the usage of `Deserialize` to automatically decode
+//! TOML into a Rust `struct`, with enums.
+
+#![deny(warnings)]
+#![allow(dead_code)]
+
+use serde::Deserialize;
+
+/// This is what we're going to decode into.
+#[derive(Debug, Deserialize)]
+struct Config {
+ plain: MyEnum,
+ plain_table: MyEnum,
+ tuple: MyEnum,
+ #[serde(rename = "struct")]
+ structv: MyEnum,
+ newtype: MyEnum,
+ my_enum: Vec<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
index 0000000..3660611
--- /dev/null
+++ b/examples/toml2json.rs
@@ -0,0 +1,47 @@
+#![deny(warnings)]
+
+use std::env;
+use std::fs::File;
+use std::io;
+use std::io::prelude::*;
+
+use serde_json::Value as Json;
+use toml::Value as Toml;
+
+fn main() {
+ let mut args = env::args();
+ let mut input = String::new();
+ if args.len() > 1 {
+ let name = args.nth(1).unwrap();
+ File::open(name)
+ .and_then(|mut f| f.read_to_string(&mut input))
+ .unwrap();
+ } else {
+ io::stdin().read_to_string(&mut input).unwrap();
+ }
+
+ match input.parse() {
+ Ok(toml) => {
+ let json = convert(toml);
+ println!("{}", serde_json::to_string_pretty(&json).unwrap());
+ }
+ Err(error) => println!("failed to parse TOML: {}", error),
+ }
+}
+
+fn convert(toml: Toml) -> Json {
+ match toml {
+ Toml::String(s) => Json::String(s),
+ Toml::Integer(i) => Json::Number(i.into()),
+ Toml::Float(f) => {
+ let n = serde_json::Number::from_f64(f).expect("float infinite and nan not allowed");
+ Json::Number(n)
+ }
+ Toml::Boolean(b) => Json::Bool(b),
+ Toml::Array(arr) => Json::Array(arr.into_iter().map(convert).collect()),
+ Toml::Table(table) => {
+ Json::Object(table.into_iter().map(|(k, v)| (k, convert(v))).collect())
+ }
+ Toml::Datetime(dt) => Json::String(dt.to_string()),
+ }
+}
diff --git a/src/de.rs b/src/de.rs
new file mode 100644
index 0000000..9eb4c41
--- /dev/null
+++ b/src/de.rs
@@ -0,0 +1,322 @@
+//! Deserializing TOML into Rust structures.
+//!
+//! This module contains all the Serde support for deserializing TOML documents
+//! into Rust structures. Note that some top-level functions here are also
+//! provided at the top of the crate.
+
+/// Deserializes a string into a type.
+///
+/// This function will attempt to interpret `s` as a TOML document and
+/// deserialize `T` from the document.
+///
+/// To deserializes TOML values, instead of documents, see [`ValueDeserializer`].
+///
+/// # Examples
+///
+/// ```
+/// use serde::Deserialize;
+///
+/// #[derive(Deserialize)]
+/// struct Config {
+/// title: String,
+/// owner: Owner,
+/// }
+///
+/// #[derive(Deserialize)]
+/// struct Owner {
+/// name: String,
+/// }
+///
+/// let config: Config = toml::from_str(r#"
+/// title = 'TOML Example'
+///
+/// [owner]
+/// name = 'Lisa'
+/// "#).unwrap();
+///
+/// assert_eq!(config.title, "TOML Example");
+/// assert_eq!(config.owner.name, "Lisa");
+/// ```
+#[cfg(feature = "parse")]
+pub fn from_str<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
index 0000000..90fb284
--- /dev/null
+++ b/src/edit.rs
@@ -0,0 +1,91 @@
+#[cfg(feature = "parse")]
+pub(crate) mod de {
+ pub(crate) use toml_edit::de::Error;
+}
+
+#[cfg(not(feature = "parse"))]
+pub(crate) mod de {
+ /// Errors that can occur when deserializing a type.
+ #[derive(Debug, Clone, PartialEq, Eq)]
+ pub struct Error {
+ inner: String,
+ }
+
+ impl Error {
+ /// Add key while unwinding
+ pub fn add_key(&mut self, _key: String) {}
+
+ /// What went wrong
+ pub fn message(&self) -> &str {
+ self.inner.as_str()
+ }
+ }
+
+ impl serde::de::Error for Error {
+ fn custom<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
index 0000000..0e96bf0
--- /dev/null
+++ b/src/fmt.rs
@@ -0,0 +1,60 @@
+#[derive(Copy, Clone, Default)]
+pub(crate) struct DocumentFormatter {
+ pub(crate) multiline_array: bool,
+}
+
+impl toml_edit::visit_mut::VisitMut for DocumentFormatter {
+ fn visit_document_mut(&mut self, node: &mut toml_edit::Document) {
+ toml_edit::visit_mut::visit_document_mut(self, node);
+ }
+
+ fn visit_item_mut(&mut self, node: &mut toml_edit::Item) {
+ let other = std::mem::take(node);
+ let other = match other.into_table().map(toml_edit::Item::Table) {
+ Ok(i) => i,
+ Err(i) => i,
+ };
+ let other = match other
+ .into_array_of_tables()
+ .map(toml_edit::Item::ArrayOfTables)
+ {
+ Ok(i) => i,
+ Err(i) => i,
+ };
+ *node = other;
+
+ toml_edit::visit_mut::visit_item_mut(self, node);
+ }
+
+ fn visit_table_mut(&mut self, node: &mut toml_edit::Table) {
+ node.decor_mut().clear();
+
+ // Empty tables could be semantically meaningful, so make sure they are not implicit
+ if !node.is_empty() {
+ node.set_implicit(true);
+ }
+
+ toml_edit::visit_mut::visit_table_mut(self, node);
+ }
+
+ fn visit_value_mut(&mut self, node: &mut toml_edit::Value) {
+ node.decor_mut().clear();
+
+ toml_edit::visit_mut::visit_value_mut(self, node);
+ }
+
+ fn visit_array_mut(&mut self, node: &mut toml_edit::Array) {
+ toml_edit::visit_mut::visit_array_mut(self, node);
+
+ if !self.multiline_array || (0..=1).contains(&node.len()) {
+ node.set_trailing("");
+ node.set_trailing_comma(false);
+ } else {
+ for item in node.iter_mut() {
+ item.decor_mut().set_prefix("\n ");
+ }
+ node.set_trailing("\n");
+ node.set_trailing_comma(true);
+ }
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..61e6a4c
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,182 @@
+//! A [serde]-compatible [TOML]-parsing library
+//!
+//! TOML itself is a simple, ergonomic, and readable configuration format:
+//!
+//! ```toml
+//! [package]
+//! name = "toml"
+//! version = "0.4.2"
+//! authors = ["Alex Crichton <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
index 0000000..d86cc52
--- /dev/null
+++ b/src/macros.rs
@@ -0,0 +1,460 @@
+pub use serde::de::{Deserialize, IntoDeserializer};
+
+use crate::value::{Array, Table, Value};
+
+/// Construct a [`Table`] from TOML syntax.
+///
+/// ```rust
+/// let cargo_toml = toml::toml! {
+/// [package]
+/// name = "toml"
+/// version = "0.4.5"
+/// authors = ["Alex Crichton <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
index 0000000..bd720a7
--- /dev/null
+++ b/src/map.rs
@@ -0,0 +1,609 @@
+// Copyright 2017 Serde Developers
+//
+// Licensed under the Apache License, Version 2.0 <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)
+ }
+
+ /// Retains only the elements specified by the `keep` predicate.
+ ///
+ /// In other words, remove all pairs `(k, v)` for which `keep(&k, &mut v)`
+ /// returns `false`.
+ ///
+ /// The elements are visited in iteration order.
+ #[inline]
+ pub fn retain<F>(&mut self, mut keep: F)
+ where
+ F: FnMut(&str, &mut Value) -> bool,
+ {
+ self.map.retain(|key, value| keep(key.as_str(), value));
+ }
+
+ /// Gets the given key's corresponding entry in the map for in-place
+ /// manipulation.
+ pub fn entry<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
index 0000000..f1ab24b
--- /dev/null
+++ b/src/ser.rs
@@ -0,0 +1,1087 @@
+//! Serializing Rust structures into TOML.
+//!
+//! This module contains all the Serde support for serializing Rust structures
+//! into TOML documents (as strings). Note that some top-level functions here
+//! are also provided at the top of the crate.
+
+/// Serialize the given data structure as a String of TOML.
+///
+/// Serialization can fail if `T`'s implementation of `Serialize` decides to
+/// fail, if `T` contains a map with non-string keys, or if `T` attempts to
+/// serialize an unsupported datatype such as an enum, tuple, or tuple struct.
+///
+/// To serialize TOML values, instead of documents, see [`ValueSerializer`].
+///
+/// # Examples
+///
+/// ```
+/// use serde::Serialize;
+///
+/// #[derive(Serialize)]
+/// struct Config {
+/// database: Database,
+/// }
+///
+/// #[derive(Serialize)]
+/// struct Database {
+/// ip: String,
+/// port: Vec<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
index 0000000..a2d392b
--- /dev/null
+++ b/src/table.rs
@@ -0,0 +1,114 @@
+use std::fmt;
+
+use serde::de;
+use serde::ser;
+
+use crate::map::Map;
+use crate::Value;
+
+/// Type representing a TOML table, payload of the `Value::Table` variant.
+/// By default it is backed by a BTreeMap, enable the `preserve_order` feature
+/// to use a LinkedHashMap instead.
+pub type Table = Map<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
index 0000000..2785d9d
--- /dev/null
+++ b/src/value.rs
@@ -0,0 +1,1455 @@
+//! Definition of a TOML [value][Value]
+
+use std::collections::{BTreeMap, HashMap};
+use std::fmt;
+use std::hash::Hash;
+use std::mem::discriminant;
+use std::ops;
+use std::vec;
+
+use serde::de;
+use serde::de::IntoDeserializer;
+use serde::ser;
+
+use toml_datetime::__unstable as datetime;
+pub use toml_datetime::{Date, Datetime, DatetimeParseError, Offset, Time};
+
+/// Type representing a TOML array, payload of the `Value::Array` variant
+pub type Array = Vec<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: &'static 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()),
+ Value::Table(variant) => {
+ use de::Error;
+ if variant.is_empty() {
+ Err(crate::de::Error::custom(
+ "wanted exactly 1 element, found 0 elements",
+ ))
+ } else if variant.len() != 1 {
+ Err(crate::de::Error::custom(
+ "wanted exactly 1 element, more than 1 element",
+ ))
+ } else {
+ let deserializer = MapDeserializer::new(variant);
+ visitor.visit_enum(deserializer)
+ }
+ }
+ _ => Err(de::Error::invalid_type(
+ de::Unexpected::UnitVariant,
+ &"string only",
+ )),
+ }
+ }
+
+ // `None` is interpreted as a missing field so be sure to implement `Some`
+ // as a present field.
+ fn deserialize_option<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::EnumAccess<'de> for MapDeserializer {
+ type Error = crate::de::Error;
+ type Variant = MapEnumDeserializer;
+
+ fn variant_seed<V>(mut self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error>
+ where
+ V: serde::de::DeserializeSeed<'de>,
+ {
+ use de::Error;
+ let (key, value) = match self.iter.next() {
+ Some(pair) => pair,
+ None => {
+ return Err(Error::custom(
+ "expected table with exactly 1 entry, found empty table",
+ ));
+ }
+ };
+
+ let val = seed.deserialize(key.into_deserializer())?;
+
+ let variant = MapEnumDeserializer::new(value);
+
+ Ok((val, variant))
+ }
+}
+
+/// Deserializes table values into enum variants.
+pub(crate) struct MapEnumDeserializer {
+ value: Value,
+}
+
+impl MapEnumDeserializer {
+ pub(crate) fn new(value: Value) -> Self {
+ MapEnumDeserializer { value }
+ }
+}
+
+impl<'de> serde::de::VariantAccess<'de> for MapEnumDeserializer {
+ type Error = crate::de::Error;
+
+ fn unit_variant(self) -> Result<(), Self::Error> {
+ use de::Error;
+ match self.value {
+ Value::Table(values) => {
+ if values.is_empty() {
+ Ok(())
+ } else {
+ Err(Error::custom("expected empty table"))
+ }
+ }
+ e => Err(Error::custom(format!(
+ "expected table, found {}",
+ e.type_str()
+ ))),
+ }
+ }
+
+ fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value, Self::Error>
+ where
+ T: serde::de::DeserializeSeed<'de>,
+ {
+ seed.deserialize(self.value.into_deserializer())
+ }
+
+ fn tuple_variant<V>(self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ use de::Error;
+ match self.value {
+ Value::Table(values) => {
+ let tuple_values = values
+ .into_iter()
+ .enumerate()
+ .map(|(index, (key, value))| match key.parse::<usize>() {
+ Ok(key_index) if key_index == index => Ok(value),
+ Ok(_) | Err(_) => Err(Error::custom(format!(
+ "expected table key `{}`, but was `{}`",
+ index, key
+ ))),
+ })
+ // Fold all values into a `Vec`, or return the first error.
+ .fold(Ok(Vec::with_capacity(len)), |result, value_result| {
+ result.and_then(move |mut tuple_values| match value_result {
+ Ok(value) => {
+ tuple_values.push(value);
+ Ok(tuple_values)
+ }
+ // `Result<de::Value, Self::Error>` to `Result<Vec<_>, Self::Error>`
+ Err(e) => Err(e),
+ })
+ })?;
+
+ if tuple_values.len() == len {
+ serde::de::Deserializer::deserialize_seq(
+ tuple_values.into_deserializer(),
+ visitor,
+ )
+ } else {
+ Err(Error::custom(format!("expected tuple with length {}", len)))
+ }
+ }
+ e => Err(Error::custom(format!(
+ "expected table, found {}",
+ e.type_str()
+ ))),
+ }
+ }
+
+ fn struct_variant<V>(
+ self,
+ fields: &'static [&'static str],
+ visitor: V,
+ ) -> Result<V::Value, Self::Error>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ serde::de::Deserializer::deserialize_struct(
+ self.value.into_deserializer(),
+ "", // TODO: this should be the variant name
+ fields,
+ visitor,
+ )
+ }
+}
+
+impl<'de> de::IntoDeserializer<'de, crate::de::Error> for Value {
+ type Deserializer = Self;
+
+ fn into_deserializer(self) -> Self {
+ self
+ }
+}
+
+struct ValueSerializer;
+
+impl ser::Serializer for ValueSerializer {
+ type Ok = Value;
+ type Error = crate::ser::Error;
+
+ type SerializeSeq = ValueSerializeVec;
+ type SerializeTuple = ValueSerializeVec;
+ type SerializeTupleStruct = ValueSerializeVec;
+ type SerializeTupleVariant = ValueSerializeVec;
+ type SerializeMap = ValueSerializeMap;
+ type SerializeStruct = ValueSerializeMap;
+ type SerializeStructVariant = ser::Impossible<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,
+ {
+ let value = value.serialize(ValueSerializer)?;
+ let mut table = Table::new();
+ table.insert(variant.to_owned(), value);
+ Ok(table.into())
+ }
+
+ 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,
+ {
+ let value = value.serialize(ValueSerializer)?;
+ let mut table = Table::new();
+ table.insert(variant.to_owned(), value);
+ Ok(table)
+ }
+
+ 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
index 0000000..fe6db3f
--- /dev/null
+++ b/tests/decoder.rs
@@ -0,0 +1,67 @@
+#![cfg(all(feature = "parse", feature = "display"))]
+
+#[derive(Copy, Clone)]
+pub struct Decoder;
+
+impl toml_test_harness::Decoder for Decoder {
+ fn name(&self) -> &str {
+ "toml"
+ }
+
+ fn decode(&self, data: &[u8]) -> Result<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
index 0000000..5d4fc2a
--- /dev/null
+++ b/tests/decoder_compliance.rs
@@ -0,0 +1,21 @@
+mod decoder;
+
+#[cfg(all(feature = "parse", feature = "display"))]
+fn main() {
+ let decoder = decoder::Decoder;
+ let mut harness = toml_test_harness::DecoderHarness::new(decoder);
+ harness
+ .ignore([
+ "valid/spec/float-0.toml",
+ // Unreleased
+ "valid/string/escape-esc.toml",
+ "valid/string/hex-escape.toml",
+ "valid/datetime/no-seconds.toml",
+ "valid/inline-table/newline.toml",
+ ])
+ .unwrap();
+ harness.test();
+}
+
+#[cfg(not(all(feature = "parse", feature = "display")))]
+fn main() {}
diff --git a/tests/encoder.rs b/tests/encoder.rs
new file mode 100644
index 0000000..eda6296
--- /dev/null
+++ b/tests/encoder.rs
@@ -0,0 +1,81 @@
+#![cfg(all(feature = "parse", feature = "display"))]
+
+#[derive(Copy, Clone)]
+pub struct Encoder;
+
+impl toml_test_harness::Encoder for Encoder {
+ fn name(&self) -> &str {
+ "toml"
+ }
+
+ fn encode(&self, data: toml_test_harness::Decoded) -> Result<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
index 0000000..3807248
--- /dev/null
+++ b/tests/encoder_compliance.rs
@@ -0,0 +1,14 @@
+mod decoder;
+mod encoder;
+
+#[cfg(all(feature = "parse", feature = "display"))]
+fn main() {
+ let encoder = encoder::Encoder;
+ let decoder = decoder::Decoder;
+ let mut harness = toml_test_harness::EncoderHarness::new(encoder, decoder);
+ harness.ignore(["valid/spec/float-0.toml"]).unwrap();
+ harness.test();
+}
+
+#[cfg(not(all(feature = "parse", feature = "display")))]
+fn main() {}
diff --git a/tests/testsuite/de_errors.rs b/tests/testsuite/de_errors.rs
new file mode 100644
index 0000000..b3630bd
--- /dev/null
+++ b/tests/testsuite/de_errors.rs
@@ -0,0 +1,460 @@
+use serde::{de, Deserialize};
+use std::fmt;
+
+macro_rules! bad {
+ ($toml:expr, $ty:ty, $msg:expr) => {
+ match toml::from_str::<$ty>($toml) {
+ Ok(s) => panic!("parsed to: {:#?}", s),
+ Err(e) => snapbox::assert_eq($msg, e.to_string()),
+ }
+ };
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+struct Parent<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
index 0000000..7430fac
--- /dev/null
+++ b/tests/testsuite/display.rs
@@ -0,0 +1,116 @@
+use toml::map::Map;
+use toml::Value::{Array, Boolean, Float, Integer, String, Table};
+
+macro_rules! map( ($($k:expr => $v:expr),*) => ({
+ let mut _m = Map::new();
+ $(_m.insert($k.to_string(), $v);)*
+ _m
+}) );
+
+#[test]
+fn simple_show() {
+ assert_eq!(String("foo".to_string()).to_string(), "\"foo\"");
+ assert_eq!(Integer(10).to_string(), "10");
+ assert_eq!(Float(10.0).to_string(), "10.0");
+ assert_eq!(Float(2.4).to_string(), "2.4");
+ assert_eq!(Boolean(true).to_string(), "true");
+ assert_eq!(Array(vec![]).to_string(), "[]");
+ assert_eq!(Array(vec![Integer(1), Integer(2)]).to_string(), "[1, 2]");
+}
+
+#[test]
+fn table() {
+ assert_eq!(map! {}.to_string(), "");
+ assert_eq!(
+ map! {
+ "test" => Integer(2),
+ "test2" => Integer(3) }
+ .to_string(),
+ "test = 2\ntest2 = 3\n"
+ );
+ assert_eq!(
+ map! {
+ "test" => Integer(2),
+ "test2" => Table(map! {
+ "test" => String("wut".to_string())
+ })
+ }
+ .to_string(),
+ "test = 2\n\
+ \n\
+ [test2]\n\
+ test = \"wut\"\n"
+ );
+ assert_eq!(
+ map! {
+ "test" => Integer(2),
+ "test2" => Table(map! {
+ "test" => String("wut".to_string())
+ })
+ }
+ .to_string(),
+ "test = 2\n\
+ \n\
+ [test2]\n\
+ test = \"wut\"\n"
+ );
+ assert_eq!(
+ map! {
+ "test" => Integer(2),
+ "test2" => Array(vec![Table(map! {
+ "test" => String("wut".to_string())
+ })])
+ }
+ .to_string(),
+ "test = 2\n\
+ \n\
+ [[test2]]\n\
+ test = \"wut\"\n"
+ );
+ #[cfg(feature = "preserve_order")]
+ assert_eq!(
+ map! {
+ "foo.bar" => Integer(2),
+ "foo\"bar" => Integer(2)
+ }
+ .to_string(),
+ "\"foo.bar\" = 2\n\
+ \"foo\\\"bar\" = 2\n"
+ );
+ assert_eq!(
+ map! {
+ "test" => Integer(2),
+ "test2" => Array(vec![Table(map! {
+ "test" => Array(vec![Integer(2)])
+ })])
+ }
+ .to_string(),
+ "test = 2\n\
+ \n\
+ [[test2]]\n\
+ test = [2]\n"
+ );
+ let table = map! {
+ "test" => Integer(2),
+ "test2" => Array(vec![Table(map! {
+ "test" => Array(vec![Array(vec![Integer(2), Integer(3)]),
+ Array(vec![String("foo".to_string()), String("bar".to_string())])])
+ })])
+ };
+ assert_eq!(
+ table.to_string(),
+ "test = 2\n\
+ \n\
+ [[test2]]\n\
+ test = [[2, 3], [\"foo\", \"bar\"]]\n"
+ );
+ assert_eq!(
+ map! {
+ "test" => Array(vec![Integer(2)]),
+ "test2" => Integer(2)
+ }
+ .to_string(),
+ "test = [2]\n\
+ test2 = 2\n"
+ );
+}
diff --git a/tests/testsuite/display_tricky.rs b/tests/testsuite/display_tricky.rs
new file mode 100644
index 0000000..379ae91
--- /dev/null
+++ b/tests/testsuite/display_tricky.rs
@@ -0,0 +1,55 @@
+use serde::Deserialize;
+use serde::Serialize;
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Recipe {
+ pub name: String,
+ pub description: Option<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
index 0000000..6e0c2f7
--- /dev/null
+++ b/tests/testsuite/enum_external_deserialize.rs
@@ -0,0 +1,320 @@
+use serde::Deserialize;
+
+#[derive(Debug, Deserialize, PartialEq)]
+struct OuterStruct {
+ inner: TheEnum,
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+enum TheEnum {
+ Plain,
+ Tuple(i64, bool),
+ NewType(String),
+ Struct { value: i64 },
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+struct Val {
+ val: TheEnum,
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+struct Multi {
+ enums: Vec<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
index 0000000..d008134
--- /dev/null
+++ b/tests/testsuite/float.rs
@@ -0,0 +1,80 @@
+use serde::Deserialize;
+use serde::Serialize;
+use toml::Value;
+
+#[rustfmt::skip] // appears to be a bug in rustfmt to make this converge...
+macro_rules! float_inf_tests {
+ ($ty:ty) => {{
+ #[derive(Serialize, Deserialize)]
+ struct S {
+ sf1: $ty,
+ sf2: $ty,
+ sf3: $ty,
+ sf4: $ty,
+ sf5: $ty,
+ sf6: $ty,
+ sf7: $ty,
+ sf8: $ty,
+ }
+ let inf: S = toml::from_str(
+ r"
+ # infinity
+ sf1 = inf # positive infinity
+ sf2 = +inf # positive infinity
+ sf3 = -inf # negative infinity
+
+ # not a number
+ sf4 = nan # actual sNaN/qNaN encoding is implementation specific
+ sf5 = +nan # same as `nan`
+ sf6 = -nan # valid, actual encoding is implementation specific
+
+ # zero
+ sf7 = +0.0
+ sf8 = -0.0
+ ",
+ )
+ .expect("Parse infinities.");
+
+ assert!(inf.sf1.is_infinite());
+ assert!(inf.sf1.is_sign_positive());
+ assert!(inf.sf2.is_infinite());
+ assert!(inf.sf2.is_sign_positive());
+ assert!(inf.sf3.is_infinite());
+ assert!(inf.sf3.is_sign_negative());
+
+ assert!(inf.sf4.is_nan());
+ assert!(inf.sf4.is_sign_positive());
+ assert!(inf.sf5.is_nan());
+ assert!(inf.sf5.is_sign_positive());
+ assert!(inf.sf6.is_nan());
+ assert!(inf.sf6.is_sign_negative());
+
+ assert_eq!(inf.sf7, 0.0);
+ assert!(inf.sf7.is_sign_positive());
+ assert_eq!(inf.sf8, 0.0);
+ assert!(inf.sf8.is_sign_negative());
+
+ let s = toml::to_string(&inf).unwrap();
+ assert_eq!(
+ s,
+ "\
+sf1 = inf
+sf2 = inf
+sf3 = -inf
+sf4 = nan
+sf5 = nan
+sf6 = -nan
+sf7 = 0.0
+sf8 = -0.0
+"
+ );
+
+ toml::from_str::<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
index 0000000..8240d1d
--- /dev/null
+++ b/tests/testsuite/formatting.rs
@@ -0,0 +1,54 @@
+use serde::Deserialize;
+use serde::Serialize;
+use toml::to_string;
+
+#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
+struct User {
+ pub name: String,
+ pub surname: String,
+}
+
+#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
+struct Users {
+ pub user: Vec<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
index 0000000..5100705
--- /dev/null
+++ b/tests/testsuite/macros.rs
@@ -0,0 +1,368 @@
+use std::f64;
+
+use toml::toml;
+
+macro_rules! table {
+ ($($key:expr => $value:expr,)*) => {{
+ // https://github.com/rust-lang/rust/issues/60643
+ #[allow(unused_mut)]
+ let mut table = toml::value::Table::new();
+ $(
+ table.insert($key.to_string(), $value.into());
+ )*
+ toml::Value::Table(table)
+ }};
+}
+
+macro_rules! array {
+ ($($element:expr,)*) => {{
+ // https://github.com/rust-lang/rust/issues/60643
+ #![allow(clippy::vec_init_then_push)]
+ #[allow(unused_mut)]
+ let mut array = toml::value::Array::new();
+ $(
+ array.push($element.into());
+ )*
+ toml::Value::Array(array)
+ }};
+}
+
+macro_rules! datetime {
+ ($s:tt) => {
+ $s.parse::<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
index 0000000..1473787
--- /dev/null
+++ b/tests/testsuite/main.rs
@@ -0,0 +1,15 @@
+#![recursion_limit = "256"]
+#![cfg(all(feature = "parse", feature = "display"))]
+
+mod de_errors;
+mod display;
+mod display_tricky;
+mod enum_external_deserialize;
+mod float;
+mod formatting;
+mod macros;
+mod pretty;
+mod serde;
+mod spanned;
+mod spanned_impls;
+mod tables_last;
diff --git a/tests/testsuite/pretty.rs b/tests/testsuite/pretty.rs
new file mode 100644
index 0000000..3ae772b
--- /dev/null
+++ b/tests/testsuite/pretty.rs
@@ -0,0 +1,184 @@
+use serde::ser::Serialize;
+use snapbox::assert_eq;
+
+const NO_PRETTY: &str = "\
+[example]
+array = [\"item 1\", \"item 2\"]
+empty = []
+oneline = \"this has no newlines.\"
+text = '''
+
+this is the first line\\nthis is the second line
+'''
+";
+
+#[test]
+fn no_pretty() {
+ let toml = NO_PRETTY;
+ let value: toml::Value = toml::from_str(toml).unwrap();
+ let mut result = String::with_capacity(128);
+ value.serialize(toml::Serializer::new(&mut result)).unwrap();
+ assert_eq(toml, &result);
+}
+
+const PRETTY_STD: &str = "\
+[example]
+array = [
+ \"item 1\",
+ \"item 2\",
+]
+empty = []
+one = [\"one\"]
+oneline = \"this has no newlines.\"
+text = \"\"\"
+this is the first line
+this is the second line
+\"\"\"
+";
+
+#[test]
+fn pretty_std() {
+ let toml = PRETTY_STD;
+ let value: toml::Value = toml::from_str(toml).unwrap();
+ let mut result = String::with_capacity(128);
+ value
+ .serialize(toml::Serializer::pretty(&mut result))
+ .unwrap();
+ assert_eq(toml, &result);
+}
+
+const PRETTY_TRICKY: &str = r##"[example]
+f = "\f"
+glass = """
+Nothing too unusual, except that I can eat glass in:
+- Greek: Μπορώ να φάω σπασμένα γυαλιά χωρίς να πάθω τίποτα.
+- Polish: Mogę jeść szkło, i mi nie szkodzi.
+- Hindi: मैं काँच खा सकता हूँ, मुझे उस से कोई पीडा नहीं होती.
+- Japanese: 私はガラスを食べられます。それは私を傷つけません。
+"""
+r = "\r"
+r_newline = """
+\r
+"""
+single = "this is a single line but has '' cuz it's tricky"
+single_tricky = "single line with ''' in it"
+tabs = """
+this is pretty standard
+\texcept for some \ttabs right here
+"""
+text = """
+this is the first line.
+This has a ''' in it and \"\"\" cuz it's tricky yo
+Also ' and \" because why not
+this is the fourth line
+"""
+"##;
+
+#[test]
+fn pretty_tricky() {
+ let toml = PRETTY_TRICKY;
+ let value: toml::Value = toml::from_str(toml).unwrap();
+ let mut result = String::with_capacity(128);
+ value
+ .serialize(toml::Serializer::pretty(&mut result))
+ .unwrap();
+ assert_eq(toml, &result);
+}
+
+const PRETTY_TABLE_ARRAY: &str = r##"[[array]]
+key = "foo"
+
+[[array]]
+key = "bar"
+
+[abc]
+doc = "this is a table"
+
+[example]
+single = "this is a single line string"
+"##;
+
+#[test]
+fn pretty_table_array() {
+ let toml = PRETTY_TABLE_ARRAY;
+ let value: toml::Value = toml::from_str(toml).unwrap();
+ let mut result = String::with_capacity(128);
+ value
+ .serialize(toml::Serializer::pretty(&mut result))
+ .unwrap();
+ assert_eq(toml, &result);
+}
+
+const TABLE_ARRAY: &str = r##"[[array]]
+key = "foo"
+
+[[array]]
+key = "bar"
+
+[abc]
+doc = "this is a table"
+
+[example]
+single = "this is a single line string"
+"##;
+
+#[test]
+fn table_array() {
+ let toml = TABLE_ARRAY;
+ let value: toml::Value = toml::from_str(toml).unwrap();
+ let mut result = String::with_capacity(128);
+ value.serialize(toml::Serializer::new(&mut result)).unwrap();
+ assert_eq(toml, &result);
+}
+
+const PRETTY_EMPTY_TABLE: &str = r#"[example]
+"#;
+
+#[test]
+fn pretty_empty_table() {
+ let toml = PRETTY_EMPTY_TABLE;
+ let value: toml::Value = toml::from_str(toml).unwrap();
+ let mut result = String::with_capacity(128);
+ value.serialize(toml::Serializer::new(&mut result)).unwrap();
+ assert_eq(toml, &result);
+}
+
+#[test]
+fn error_includes_key() {
+ #[derive(Debug, serde::Serialize, serde::Deserialize)]
+ struct Package {
+ name: String,
+ version: String,
+ authors: Vec<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
index 0000000..6e927f7
--- /dev/null
+++ b/tests/testsuite/serde.rs
@@ -0,0 +1,1096 @@
+use serde::Deserialize;
+use serde::Deserializer;
+use serde::Serialize;
+use std::collections::BTreeMap;
+
+use toml::map::Map;
+use toml::Table;
+use toml::Value;
+
+macro_rules! t {
+ ($e:expr) => {
+ match $e {
+ Ok(t) => t,
+ Err(e) => panic!("{} failed with {}", stringify!($e), e),
+ }
+ };
+}
+
+macro_rules! equivalent {
+ ($literal:expr, $toml:expr,) => {{
+ let toml = $toml;
+ let literal = $literal;
+
+ // In/out of Value is equivalent
+ println!("try_from");
+ assert_eq!(t!(Table::try_from(literal.clone())), toml);
+ println!("try_into");
+ assert_eq!(literal, t!(toml.clone().try_into()));
+
+ // Through a string equivalent
+ println!("to_string");
+ snapbox::assert_eq(t!(toml::to_string(&toml)), t!(toml::to_string(&literal)));
+ println!("literal, from_str(toml)");
+ assert_eq!(literal, t!(toml::from_str(&t!(toml::to_string(&toml)))));
+ println!("toml, from_str(toml)");
+ assert_eq!(toml, t!(toml::from_str(&t!(toml::to_string(&toml)))));
+ }};
+}
+
+macro_rules! error {
+ ($ty:ty, $toml:expr, $msg_parse:expr, $msg_decode:expr) => {{
+ println!("attempting parsing");
+ match toml::from_str::<$ty>(&$toml.to_string()) {
+ Ok(_) => panic!("successful"),
+ Err(e) => snapbox::assert_eq($msg_parse, e.to_string()),
+ }
+
+ println!("attempting toml decoding");
+ match $toml.try_into::<$ty>() {
+ Ok(_) => panic!("successful"),
+ Err(e) => snapbox::assert_eq($msg_decode, e.to_string()),
+ }
+ }};
+}
+
+macro_rules! map( ($($k:ident: $v:expr),*) => ({
+ let mut _m = Map::new();
+ $(_m.insert(stringify!($k).to_string(), t!(Value::try_from($v)));)*
+ _m
+}) );
+
+#[test]
+fn smoke() {
+ #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+ struct Foo {
+ a: isize,
+ }
+
+ equivalent!(Foo { a: 2 }, map! { a: Value::Integer(2) },);
+}
+
+#[test]
+fn smoke_hyphen() {
+ #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+ struct Foo {
+ a_b: isize,
+ }
+
+ equivalent! {
+ Foo { a_b: 2 },
+ map! { a_b: Value::Integer(2)},
+ }
+
+ #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+ struct Foo2 {
+ #[serde(rename = "a-b")]
+ a_b: isize,
+ }
+
+ let mut m = Map::new();
+ m.insert("a-b".to_string(), Value::Integer(2));
+ equivalent! {
+ Foo2 { a_b: 2 },
+ m,
+ }
+}
+
+#[test]
+fn nested() {
+ #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+ struct Foo {
+ a: isize,
+ b: Bar,
+ }
+ #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+ struct Bar {
+ a: String,
+ }
+
+ equivalent! {
+ Foo { a: 2, b: Bar { a: "test".to_string() } },
+ map! {
+ a: Value::Integer(2),
+ b: map! {
+ a: Value::String("test".to_string())
+ }
+ },
+ }
+}
+
+#[test]
+fn application_decode_error() {
+ #[derive(PartialEq, Debug)]
+ struct Range10(usize);
+ impl<'de> serde::Deserialize<'de> for Range10 {
+ fn deserialize<D: 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)
+ }
+ },
+ }
+}
+
+#[test]
+fn newtype_variant() {
+ #[derive(Copy, Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
+ struct Struct {
+ field: Enum,
+ }
+
+ #[derive(Copy, Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
+ enum Enum {
+ Variant(u8),
+ }
+
+ equivalent! {
+ Struct { field: Enum::Variant(21) },
+ map! {
+ field: map! {
+ Variant: Value::Integer(21)
+ }
+ },
+ }
+}
+
+#[derive(Debug, Default, PartialEq, Serialize, Deserialize)]
+struct CanBeEmpty {
+ a: Option<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
index 0000000..760c73a
--- /dev/null
+++ b/tests/testsuite/spanned.rs
@@ -0,0 +1,261 @@
+#![allow(renamed_and_removed_lints)]
+#![allow(clippy::blacklisted_name)]
+
+use std::collections::HashMap;
+use std::fmt::Debug;
+
+use serde::Deserialize;
+use toml::value::Datetime;
+use toml::Spanned;
+
+/// A set of good datetimes.
+pub fn good_datetimes() -> Vec<&'static str> {
+ vec![
+ "1997-09-09T09:09:09Z",
+ "1997-09-09T09:09:09+09:09",
+ "1997-09-09T09:09:09-09:09",
+ "1997-09-09T09:09:09",
+ "1997-09-09",
+ "09:09:09",
+ "1997-09-09T09:09:09.09Z",
+ "1997-09-09T09:09:09.09+09:09",
+ "1997-09-09T09:09:09.09-09:09",
+ "1997-09-09T09:09:09.09",
+ "09:09:09.09",
+ ]
+}
+
+#[test]
+fn test_spanned_field() {
+ #[derive(Deserialize)]
+ struct Foo<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
index 0000000..5e866f9
--- /dev/null
+++ b/tests/testsuite/spanned_impls.rs
@@ -0,0 +1,41 @@
+use std::cmp::{Ord, Ordering, PartialOrd};
+
+use serde::Deserialize;
+use toml::{from_str, Spanned};
+
+#[test]
+fn test_spans_impls() {
+ #[derive(Deserialize)]
+ struct Foo {
+ bar: Spanned<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
index 0000000..b003557
--- /dev/null
+++ b/tests/testsuite/tables_last.rs
@@ -0,0 +1,162 @@
+use std::collections::HashMap;
+
+use serde::Deserialize;
+use serde::Serialize;
+
+#[test]
+fn always_works() {
+ // Ensure this works without the removed "toml::ser::tables_last"
+ #[derive(Serialize)]
+ struct A {
+ vals: HashMap<&'static str, Value>,
+ }
+
+ #[derive(Serialize)]
+ #[serde(untagged)]
+ enum Value {
+ Map(HashMap<&'static str, &'static str>),
+ Int(i32),
+ }
+
+ let mut a = A {
+ vals: HashMap::new(),
+ };
+ a.vals.insert("foo", Value::Int(0));
+
+ let mut sub = HashMap::new();
+ sub.insert("foo", "bar");
+ a.vals.insert("bar", Value::Map(sub));
+
+ toml::to_string(&a).unwrap();
+}
+
+#[test]
+fn vec_of_vec_issue_387() {
+ #[derive(Deserialize, Serialize, Debug)]
+ struct Glyph {
+ components: Vec<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();
+}