Skip to main content

toml_example/
lib.rs

1//! This crate provides the [`TomlExample`] trait and an accompanying derive macro.
2//!
3//! Deriving [`TomlExample`] on a struct will generate functions `toml_example()`, `to_toml_example(file_name)` for generating toml example content.
4//!
5//! The following code shows how `toml-example` can be used.
6//! ```rust
7//! use toml_example::TomlExample;
8//!
9//! /// Config is to arrange something or change the controls on a computer or other device
10//! /// so that it can be used in a particular way
11//! #[derive(TomlExample)]
12//! struct Config {
13//!     /// Config.a should be a number
14//!     a: usize,
15//!     /// Config.b should be a string
16//!     b: String,
17//!     /// Optional Config.c is a number
18//!     c: Option<usize>,
19//!     /// Config.d is a list of number
20//!     d: Vec<usize>,
21//!     #[toml_example(default =7)]
22//!     e: usize,
23//!     /// Config.f should be a string
24//!     #[toml_example(default = "seven")]
25//!     f: String,
26//! }
27//! assert_eq!( Config::toml_example(),
28//! r#"# Config is to arrange something or change the controls on a computer or other device
29//! ## so that it can be used in a particular way
30//! ## Config.a should be a number
31//! a = 0
32//!
33//! ## Config.b should be a string
34//! b = ""
35//!
36//! ## Optional Config.c is a number
37//! ## c = 0
38//!
39//! ## Config.d is a list of number
40//! d = [ 0, ]
41//!
42//! e = 7
43//!
44//! ## Config.f should be a string
45//! f = "seven"
46//!
47//! "#);
48//! ```
49//!
50//! Also, toml-example will use `#[serde(default)]`, `#[serde(default = "default_fn")]` for the
51//! example value.
52//!
53//! With nesting structure, `#[toml_example(nesting)]` should set on the field as following
54//! example.
55//!
56//! ```rust
57//! use std::collections::HashMap;
58//! use toml_example::TomlExample;
59//!
60//! /// Service with specific port
61//! #[derive(TomlExample)]
62//! struct Service {
63//!     /// port should be a number
64//!     #[toml_example(default = 80)]
65//!     port: usize,
66//! }
67//! #[derive(TomlExample)]
68//! #[allow(dead_code)]
69//! struct Node {
70//!     /// Services are running in the node
71//!     #[toml_example(default = http, nesting)]
72//!     services: HashMap<String, Service>,
73//! }
74//!
75//! assert_eq!(Node::toml_example(),
76//! r#"# Services are running in the node
77//! ## Service with specific port
78//! [services.http]
79//! ## port should be a number
80//! port = 80
81//!
82//! "#);
83//! ```
84//!
85//! Flattened items are supported as well.
86//!
87//! ```rust
88//! use toml_example::TomlExample;
89//!
90//! #[derive(TomlExample)]
91//! struct ItemWrapper {
92//!     #[toml_example(flatten, nesting)]
93//!     item: Item,
94//! }
95//! #[derive(TomlExample)]
96//! struct Item {
97//!     value: String,
98//! }
99//!
100//! assert_eq!(ItemWrapper::toml_example(), Item::toml_example());
101//! ```
102//!
103//! Flattening works with maps too!
104//!
105//! ```rust
106//! use serde::Deserialize;
107//! use toml_example::TomlExample;
108//! # use std::collections::HashMap;
109//!
110//! #[derive(TomlExample, Deserialize)]
111//! struct MainConfig {
112//!     #[serde(flatten)]
113//!     #[toml_example(nesting)]
114//!     nested: HashMap<String, ConfigItem>,
115//! }
116//! #[derive(TomlExample, Deserialize)]
117//! struct ConfigItem {
118//!     #[toml_example(default = false)]
119//!     enabled: bool,
120//! }
121//!
122//! let example = MainConfig::toml_example();
123//! assert!(toml::from_str::<MainConfig>(&example).is_ok());
124//! assert_eq!(example, r#"[example]
125//! enabled = false
126//!
127//! "#);
128//! ```
129//!
130//! The fields of a struct can inherit their defaults from the parent struct when the
131//! `#[toml_example(default)]`, `#[serde(default)]` or `#[serde(default = "default_fn")]`
132//! attribute is set as an outer attribute of the parent struct:
133//!
134//! ```rust
135//! use serde::Serialize;
136//! use toml_example::TomlExample;
137//!
138//! #[derive(TomlExample, Serialize)]
139//! #[serde(default)]
140//! struct Config {
141//!     /// Name of the theme to use
142//!     theme: String,
143//! }
144//!
145//! impl Default for Config {
146//!     fn default() -> Self {
147//!         Self {
148//!             theme: String::from("Dark"),
149//!         }
150//!     }
151//! }
152//!
153//! assert_eq!(Config::toml_example(),
154//! r#"# Name of the theme to use
155//! theme = "Dark"
156//!
157//! "#);
158//! ```
159//!
160//! If you want an optional field become a required field in example,
161//! place the `#[toml_example(require)]` on the field.
162//! If you want to skip some field you can use `#[toml_example(skip)]`,
163//! the `#[serde(skip)]`, `#[serde(skip_deserializing)]` also works.
164//! ```rust
165//! use toml_example::TomlExample;
166//! #[derive(TomlExample)]
167//! struct Config {
168//!     /// Config.a is an optional number
169//!     #[toml_example(require)]
170//!     a: Option<usize>,
171//!     /// Config.b is an optional string
172//!     #[toml_example(require)]
173//!     b: Option<String>,
174//!     #[toml_example(require, default = "third")]
175//!     c: Option<String>,
176//!     #[toml_example(skip)]
177//!     d: usize,
178//! }
179//! assert_eq!(Config::toml_example(),
180//! r#"# Config.a is an optional number
181//! a = 0
182//!
183//! ## Config.b is an optional string
184//! b = ""
185//!
186//! c = "third"
187//!
188//! "#)
189//! ```
190//!
191//! You can also use field less enums, but you have to annotate them with `#[toml_example(enum)]` or
192//! `#[toml_example(is_enum)]` if you mind the keyword highlight you likely get when writing
193//! "enum".<br>
194//! When annotating a field with `#[toml_example(default)]` it will use the
195//! [Debug](core::fmt::Debug) implementation.
196//! However for non-TOML data types like enums, this does not work as the value needs to be treated
197//! as a string in TOML. The `#[toml_example(enum)]` attribute just adds the needed quotes around
198//! the [Debug](core::fmt::Debug) implementation and can be omitted if a custom
199//! [Debug](core::fmt::Debug) already includes those.
200//! ```rust
201//! use toml_example::TomlExample;
202//! #[derive(TomlExample)]
203//! struct Config {
204//!     /// Config.priority is an enum
205//!     #[toml_example(default, enum)]
206//!     priority: Priority,
207//! }
208//! #[derive(Debug, Default)]
209//! enum Priority {
210//!     #[default]
211//!     Important,
212//!     Trivial,
213//! }
214//! assert_eq!(Config::toml_example(),
215//! r#"# Config.priority is an enum
216//! priority = "Important"
217//!
218//! "#)
219//! ```
220//!
221//! Rust comments can be excluded from the TOML example by adding a `#[toml_example(doc_skip_prefix
222//! = "prefix")]` attribute and then prefixing every comment line that should not be included in the
223//! TOML example with the specified prefix:
224//! ```rust
225//! # use toml_example::TomlExample;
226//! #[derive(TomlExample)]
227//! // We have to use double backslash here, else it will fuck up the AST
228//! #[toml_example(doc_skip_prefix = "\\")]
229//! struct Config {
230//!     /// \ This comment is not added to the TOML
231//!     /// This comment is added to the TOML
232//!     a: String,
233//!     #[toml_example(doc_skip_prefix = "dev-doc:")]
234//!     /// This is a TOML comment
235//!     /// dev-doc: We can add additional prefixes.
236//!     b: String,
237//! }
238//! assert_eq!(Config::toml_example(),
239//! r#"# This comment is added to the TOML
240//! a = ""
241//!
242//! ## This is a TOML comment
243//! b = ""
244//!
245//! "#)
246//! ```
247//!
248//! You can use `#[toml_example(help = "...")]` to override the doc string with a custom help
249//! text for the TOML example. This works on both struct fields and structs themselves.
250//! When `help` is set, the doc string is ignored and the help text is used as the TOML comment.
251//! ```rust
252//! use toml_example::TomlExample;
253//! #[derive(TomlExample)]
254//! struct Config {
255//!     /// Config.a should be a number
256//!     #[toml_example(help = "The port number to listen on")]
257//!     a: usize,
258//! }
259//! assert_eq!(Config::toml_example(),
260//! r#"# The port number to listen on
261//! a = 0
262//!
263//! "#)
264//! ```
265
266#[doc(hidden)]
267pub use toml_example_derive::TomlExample;
268pub mod traits;
269pub use traits::*;
270
271#[cfg(test)]
272mod tests {
273    use crate as toml_example;
274    use serde_derive::Deserialize;
275    use std::collections::HashMap;
276    use toml_example::TomlExample;
277
278    #[test]
279    fn basic() {
280        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
281        #[allow(dead_code)]
282        struct Config {
283            /// Config.a should be a number
284            a: usize,
285            /// Config.b should be a string
286            b: String,
287        }
288        assert_eq!(
289            Config::toml_example(),
290            r#"# Config.a should be a number
291a = 0
292
293# Config.b should be a string
294b = ""
295
296"#
297        );
298        assert_eq!(
299            toml::from_str::<Config>(&Config::toml_example()).unwrap(),
300            Config::default()
301        );
302        let mut tmp_file = std::env::temp_dir();
303        tmp_file.push("config.toml");
304        Config::to_toml_example(&tmp_file.as_path().to_str().unwrap()).unwrap();
305        assert_eq!(
306            std::fs::read_to_string(tmp_file).unwrap(),
307            r#"# Config.a should be a number
308a = 0
309
310# Config.b should be a string
311b = ""
312
313"#
314        );
315    }
316
317    #[test]
318    fn hinden_doc() {
319        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
320        #[allow(dead_code)]
321        struct Config {
322            /// Config.a should be a number
323            // This doc will be hiddend not in the example
324            a: usize,
325
326            // NOTE: This note is only shown in the code
327            /// Config.b should be a string
328            b: String,
329        }
330        assert_eq!(
331            Config::toml_example(),
332            r#"# Config.a should be a number
333a = 0
334
335# Config.b should be a string
336b = ""
337
338"#
339        );
340    }
341
342    #[test]
343    fn option() {
344        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
345        #[allow(dead_code)]
346        struct Config {
347            /// Config.a is an optional number
348            a: Option<usize>,
349            /// Config.b is an optional string
350            b: Option<String>,
351        }
352        assert_eq!(
353            Config::toml_example(),
354            r#"# Config.a is an optional number
355# a = 0
356
357# Config.b is an optional string
358# b = ""
359
360"#
361        );
362        assert_eq!(
363            toml::from_str::<Config>(&Config::toml_example()).unwrap(),
364            Config::default()
365        )
366    }
367
368    #[test]
369    fn vec() {
370        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
371        #[allow(dead_code)]
372        struct Config {
373            /// Config.a is a list of number
374            a: Vec<usize>,
375            /// Config.b is a list of string
376            b: Vec<String>,
377            /// Config.c
378            c: Vec<Option<usize>>,
379            /// Config.d
380            d: Option<Vec<usize>>,
381        }
382        assert_eq!(
383            Config::toml_example(),
384            r#"# Config.a is a list of number
385a = [ 0, ]
386
387# Config.b is a list of string
388b = [ "", ]
389
390# Config.c
391c = [ 0, ]
392
393# Config.d
394# d = [ 0, ]
395
396"#
397        );
398        assert!(toml::from_str::<Config>(&Config::toml_example()).is_ok())
399    }
400
401    #[test]
402    fn hashset_btreeset() {
403        use std::collections::{BTreeSet, HashSet};
404
405        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
406        #[allow(dead_code)]
407        struct Config {
408            /// Config.a is a set of string
409            a: HashSet<String>,
410            /// Config.b is a set of number
411            b: BTreeSet<usize>,
412            /// Config.c is optional
413            c: Option<HashSet<String>>,
414        }
415        assert_eq!(
416            Config::toml_example(),
417            r#"# Config.a is a set of string
418a = [ "", ]
419
420# Config.b is a set of number
421b = [ 0, ]
422
423# Config.c is optional
424# c = [ "", ]
425
426"#
427        );
428        assert!(toml::from_str::<Config>(&Config::toml_example()).is_ok())
429    }
430
431    #[test]
432    fn struct_doc() {
433        /// Config is to arrange something or change the controls on a computer or other device
434        /// so that it can be used in a particular way
435        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
436        #[allow(dead_code)]
437        struct Config {
438            /// Config.a should be a number
439            /// the number should be greater or equal zero
440            a: usize,
441        }
442        assert_eq!(
443            Config::toml_example(),
444            r#"# Config is to arrange something or change the controls on a computer or other device
445# so that it can be used in a particular way
446# Config.a should be a number
447# the number should be greater or equal zero
448a = 0
449
450"#
451        );
452        assert_eq!(
453            toml::from_str::<Config>(&Config::toml_example()).unwrap(),
454            Config::default()
455        )
456    }
457
458    #[test]
459    fn struct_doc_skip() {
460        #[derive(TomlExample)]
461        #[toml_example(doc_skip_prefix = "\\")]
462        #[allow(dead_code)]
463        struct Config {
464            /// This comment will be shown.
465            /// \ This comment is only relevant to the
466            /// \ developers and is hidden in user examples.
467            /// dev-doc: this was only specified for b
468            a: u8,
469            #[toml_example(doc_skip_prefix = "dev-doc:")]
470            /// \ This is a dev comment.
471            /// This is a toml comment.
472            /// \ Dev comment again.
473            /// dev-doc: No toml doc here
474            b: u8,
475        }
476        assert_eq!(
477            Config::toml_example(),
478            r#"# This comment will be shown.
479# dev-doc: this was only specified for b
480a = 0
481
482# This is a toml comment.
483b = 0
484
485"#
486        );
487    }
488
489    #[test]
490    fn serde_default() {
491        fn default_a() -> usize {
492            7
493        }
494        fn default_b() -> String {
495            "default".into()
496        }
497        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
498        #[allow(dead_code)]
499        struct Config {
500            /// Config.a should be a number
501            #[serde(default = "default_a")]
502            a: usize,
503            /// Config.b should be a string
504            #[serde(default = "default_b")]
505            b: String,
506            /// Config.c should be a number
507            #[serde(default)]
508            c: usize,
509            /// Config.d should be a string
510            #[serde(default)]
511            d: String,
512            #[serde(default)]
513            e: Option<usize>,
514        }
515        assert_eq!(
516            Config::toml_example(),
517            r#"# Config.a should be a number
518a = 7
519
520# Config.b should be a string
521b = "default"
522
523# Config.c should be a number
524c = 0
525
526# Config.d should be a string
527d = ""
528
529# e = 0
530
531"#
532        );
533    }
534
535    #[test]
536    fn toml_example_default() {
537        fn default_str() -> String {
538            "seven".into()
539        }
540        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
541        #[allow(dead_code)]
542        struct Config {
543            /// Config.a should be a number
544            #[toml_example(default = 7)]
545            a: usize,
546            /// Config.b should be a string
547            #[toml_example(default = "default")]
548            #[serde(default = "default_str")]
549            b: String,
550            #[serde(default = "default_str")]
551            #[toml_example(default = "default")]
552            c: String,
553            #[toml_example(default = [ "default", ])]
554            e: Vec<String>,
555            #[toml_example(
556                default = "super looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string"
557            )]
558            f: String,
559            #[toml_example(default = [ "super looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string",
560               "second",
561               "third",
562            ])]
563            g: Vec<String>,
564            /// Config.color should be a hex color code
565            #[toml_example(default = "#FAFAFA")]
566            color: String,
567        }
568        assert_eq!(
569            Config::toml_example(),
570            r##"# Config.a should be a number
571a = 7
572
573# Config.b should be a string
574b = "seven"
575
576c = "default"
577
578e = ["default",]
579
580f = "super looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string"
581
582g = ["super looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string",
583"second", "third",]
584
585# Config.color should be a hex color code
586color = "#FAFAFA"
587
588"##
589        );
590    }
591
592    #[test]
593    fn struct_serde_default() {
594        #[derive(TomlExample, Deserialize, PartialEq)]
595        #[serde(default)]
596        struct Foo {
597            bar: String,
598            #[serde(default)]
599            x: usize,
600        }
601        impl Default for Foo {
602            fn default() -> Self {
603                Foo {
604                    bar: String::from("hello world"),
605                    x: 12,
606                }
607            }
608        }
609        assert_eq!(
610            Foo::toml_example(),
611            r##"bar = "hello world"
612
613x = 0
614
615"##
616        );
617    }
618
619    #[test]
620    fn struct_serde_default_fn() {
621        #[derive(TomlExample, Deserialize, PartialEq)]
622        #[serde(default = "default")]
623        struct Foo {
624            bar: String,
625            #[toml_example(default = "field override")]
626            baz: String,
627        }
628        fn default() -> Foo {
629            Foo {
630                bar: String::from("hello world"),
631                baz: String::from("custom default"),
632            }
633        }
634        assert_eq!(
635            Foo::toml_example(),
636            r##"bar = "hello world"
637
638baz = "field override"
639
640"##
641        );
642    }
643
644    #[test]
645    fn struct_toml_example_default() {
646        #[derive(TomlExample, Deserialize, PartialEq)]
647        #[toml_example(default)]
648        struct Foo {
649            #[serde(default = "paru")]
650            yay: &'static str,
651            aur_is_useful: bool,
652        }
653        impl Default for Foo {
654            fn default() -> Self {
655                Foo {
656                    yay: "yay!",
657                    aur_is_useful: true,
658                }
659            }
660        }
661        fn paru() -> &'static str {
662            "no, paru!"
663        }
664        assert_eq!(
665            Foo::toml_example(),
666            r##"yay = "no, paru!"
667
668aur_is_useful = true
669
670"##
671        );
672    }
673
674    #[test]
675    fn no_nesting() {
676        /// Inner is a config live in Outer
677        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
678        #[allow(dead_code)]
679        struct Inner {
680            /// Inner.a should be a number
681            a: usize,
682        }
683        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
684        #[allow(dead_code)]
685        struct Outer {
686            /// Outer.inner is a complex struct
687            inner: Inner,
688        }
689        assert_eq!(
690            Outer::toml_example(),
691            r#"# Outer.inner is a complex struct
692inner = ""
693
694"#
695        );
696    }
697
698    #[test]
699    fn nesting() {
700        /// Inner is a config live in Outer
701        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
702        #[allow(dead_code)]
703        struct Inner {
704            /// Inner.a should be a number
705            a: usize,
706        }
707        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
708        #[allow(dead_code)]
709        struct Outer {
710            /// Outer.inner is a complex struct
711            #[toml_example(nesting)]
712            inner: Inner,
713        }
714        assert_eq!(
715            Outer::toml_example(),
716            r#"# Outer.inner is a complex struct
717# Inner is a config live in Outer
718[inner]
719# Inner.a should be a number
720a = 0
721
722"#
723        );
724        assert_eq!(
725            toml::from_str::<Outer>(&Outer::toml_example()).unwrap(),
726            Outer::default()
727        );
728    }
729
730    #[test]
731    fn nesting_by_section() {
732        /// Inner is a config live in Outer
733        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
734        #[allow(dead_code)]
735        struct Inner {
736            /// Inner.a should be a number
737            a: usize,
738        }
739        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
740        #[allow(dead_code)]
741        struct Outer {
742            /// Outer.inner is a complex struct
743            #[toml_example(nesting = section)]
744            inner: Inner,
745        }
746        assert_eq!(
747            Outer::toml_example(),
748            r#"# Outer.inner is a complex struct
749# Inner is a config live in Outer
750[inner]
751# Inner.a should be a number
752a = 0
753
754"#
755        );
756        assert_eq!(
757            toml::from_str::<Outer>(&Outer::toml_example()).unwrap(),
758            Outer::default()
759        );
760    }
761
762    #[test]
763    fn nesting_by_prefix() {
764        /// Inner is a config live in Outer
765        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
766        #[allow(dead_code)]
767        struct Inner {
768            /// Inner.a should be a number
769            a: usize,
770        }
771        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
772        #[allow(dead_code)]
773        struct Outer {
774            /// Outer.inner is a complex struct
775            #[toml_example(nesting = prefix)]
776            inner: Inner,
777        }
778        assert_eq!(
779            Outer::toml_example(),
780            r#"# Outer.inner is a complex struct
781# Inner is a config live in Outer
782# Inner.a should be a number
783inner.a = 0
784
785"#
786        );
787        assert_eq!(
788            toml::from_str::<Outer>(&Outer::toml_example()).unwrap(),
789            Outer::default()
790        );
791    }
792
793    #[test]
794    fn nesting_vector() {
795        /// Service with specific port
796        #[derive(TomlExample, Deserialize)]
797        #[allow(dead_code)]
798        struct Service {
799            /// port should be a number
800            port: usize,
801        }
802        #[derive(TomlExample, Deserialize)]
803        #[allow(dead_code)]
804        struct Node {
805            /// Services are running in the node
806            #[toml_example(nesting)]
807            services: Vec<Service>,
808        }
809        assert_eq!(
810            Node::toml_example(),
811            r#"# Services are running in the node
812# Service with specific port
813[[services]]
814# port should be a number
815port = 0
816
817"#
818        );
819        assert!(toml::from_str::<Node>(&Node::toml_example()).is_ok());
820    }
821
822    #[test]
823    fn nesting_hashmap() {
824        /// Service with specific port
825        #[derive(TomlExample, Deserialize)]
826        #[allow(dead_code)]
827        struct Service {
828            /// port should be a number
829            port: usize,
830        }
831        #[derive(TomlExample, Deserialize)]
832        #[allow(dead_code)]
833        struct Node {
834            /// Services are running in the node
835            #[toml_example(nesting)]
836            services: HashMap<String, Service>,
837        }
838        assert_eq!(
839            Node::toml_example(),
840            r#"# Services are running in the node
841# Service with specific port
842[services.example]
843# port should be a number
844port = 0
845
846"#
847        );
848        assert!(toml::from_str::<Node>(&Node::toml_example()).is_ok());
849    }
850
851    #[test]
852    fn optional_nesting() {
853        /// Inner is a config live in Outer
854        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
855        #[allow(dead_code)]
856        struct Inner {
857            /// Inner.a should be a number
858            a: usize,
859        }
860        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
861        #[allow(dead_code)]
862        struct Outer {
863            /// Outer.inner is a complex struct
864            #[toml_example(nesting)]
865            inner: Option<Inner>,
866        }
867        assert_eq!(
868            Outer::toml_example(),
869            r#"# Outer.inner is a complex struct
870# Inner is a config live in Outer
871# [inner]
872# Inner.a should be a number
873# a = 0
874
875"#
876        );
877        assert_eq!(
878            toml::from_str::<Outer>(&Outer::toml_example()).unwrap(),
879            Outer::default()
880        );
881    }
882
883    #[test]
884    fn optional_nesting_by_section() {
885        /// Inner is a config live in Outer
886        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
887        #[allow(dead_code)]
888        struct Inner {
889            /// Inner.a should be a number
890            a: usize,
891        }
892        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
893        #[allow(dead_code)]
894        struct Outer {
895            /// Outer.inner is a complex struct
896            #[toml_example(nesting = section)]
897            inner: Option<Inner>,
898        }
899        assert_eq!(
900            Outer::toml_example(),
901            r#"# Outer.inner is a complex struct
902# Inner is a config live in Outer
903# [inner]
904# Inner.a should be a number
905# a = 0
906
907"#
908        );
909        assert_eq!(
910            toml::from_str::<Outer>(&Outer::toml_example()).unwrap(),
911            Outer::default()
912        );
913    }
914
915    #[test]
916    fn optional_nesting_by_prefix() {
917        /// Inner is a config live in Outer
918        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
919        #[allow(dead_code)]
920        struct Inner {
921            /// Inner.a should be a number
922            a: usize,
923        }
924        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
925        #[allow(dead_code)]
926        struct Outer {
927            /// Outer.inner is a complex struct
928            #[toml_example(nesting = prefix)]
929            inner: Option<Inner>,
930        }
931        assert_eq!(
932            Outer::toml_example(),
933            r#"# Outer.inner is a complex struct
934# Inner is a config live in Outer
935# Inner.a should be a number
936# inner.a = 0
937
938"#
939        );
940        assert_eq!(
941            toml::from_str::<Outer>(&Outer::toml_example()).unwrap(),
942            Outer::default()
943        );
944    }
945
946    #[test]
947    fn optional_nesting_vector() {
948        /// Service with specific port
949        #[derive(TomlExample, Deserialize)]
950        #[allow(dead_code)]
951        struct Service {
952            /// port should be a number
953            port: usize,
954        }
955        #[derive(TomlExample, Deserialize)]
956        #[allow(dead_code)]
957        struct Node {
958            /// Services are running in the node
959            #[toml_example(nesting)]
960            services: Option<Vec<Service>>,
961        }
962        assert_eq!(
963            Node::toml_example(),
964            r#"# Services are running in the node
965# Service with specific port
966# [[services]]
967# port should be a number
968# port = 0
969
970"#
971        );
972        assert!(toml::from_str::<Node>(&Node::toml_example()).is_ok());
973    }
974
975    #[test]
976    fn optional_nesting_hashmap() {
977        /// Service with specific port
978        #[derive(TomlExample, Deserialize)]
979        #[allow(dead_code)]
980        struct Service {
981            /// port should be a number
982            port: usize,
983        }
984        #[derive(TomlExample, Deserialize)]
985        #[allow(dead_code)]
986        struct Node {
987            /// Services are running in the node
988            #[toml_example(nesting)]
989            services: Option<HashMap<String, Service>>,
990        }
991        assert_eq!(
992            Node::toml_example(),
993            r#"# Services are running in the node
994# Service with specific port
995# [services.example]
996# port should be a number
997# port = 0
998
999"#
1000        );
1001        assert!(toml::from_str::<Node>(&Node::toml_example()).is_ok());
1002    }
1003
1004    #[test]
1005    fn nesting_hashmap_with_default_name() {
1006        /// Service with specific port
1007        #[derive(TomlExample, Deserialize)]
1008        #[allow(dead_code)]
1009        struct Service {
1010            /// port should be a number
1011            #[toml_example(default = 80)]
1012            port: usize,
1013        }
1014        #[derive(TomlExample, Deserialize)]
1015        #[allow(dead_code)]
1016        struct Node {
1017            /// Services are running in the node
1018            #[toml_example(nesting)]
1019            #[toml_example(default = http)]
1020            services: HashMap<String, Service>,
1021        }
1022        assert_eq!(
1023            Node::toml_example(),
1024            r#"# Services are running in the node
1025# Service with specific port
1026[services.http]
1027# port should be a number
1028port = 80
1029
1030"#
1031        );
1032        assert!(toml::from_str::<Node>(&Node::toml_example()).is_ok());
1033    }
1034
1035    #[test]
1036    fn nesting_hashmap_with_dash_name() {
1037        /// Service with specific port
1038        #[derive(TomlExample, Deserialize)]
1039        #[allow(dead_code)]
1040        struct Service {
1041            /// port should be a number
1042            #[toml_example(default = 80)]
1043            port: usize,
1044        }
1045        #[derive(TomlExample, Deserialize)]
1046        #[allow(dead_code)]
1047        struct Node {
1048            /// Services are running in the node
1049            #[toml_example(nesting)]
1050            #[toml_example(default = http.01)]
1051            services: HashMap<String, Service>,
1052        }
1053        assert_eq!(
1054            Node::toml_example(),
1055            r#"# Services are running in the node
1056# Service with specific port
1057[services.http-01]
1058# port should be a number
1059port = 80
1060
1061"#
1062        );
1063        assert!(toml::from_str::<Node>(&Node::toml_example()).is_ok());
1064    }
1065
1066    #[test]
1067    fn recursive_nesting() {
1068        #[derive(TomlExample, Default, Debug, Deserialize, PartialEq)]
1069        struct Outer {
1070            #[toml_example(nesting)]
1071            _middle: Middle,
1072        }
1073        #[derive(TomlExample, Default, Debug, Deserialize, PartialEq)]
1074        struct Middle {
1075            #[toml_example(nesting)]
1076            _inner: Inner,
1077        }
1078        #[derive(TomlExample, Default, Debug, Deserialize, PartialEq)]
1079        struct Inner {
1080            _value: usize,
1081        }
1082        let example = Outer::toml_example();
1083        assert_eq!(toml::from_str::<Outer>(&example).unwrap(), Outer::default());
1084        assert_eq!(
1085            example,
1086            r#"[_middle]
1087[_middle._inner]
1088_value = 0
1089
1090"#
1091        );
1092    }
1093
1094    #[test]
1095    fn recursive_nesting_and_flatten() {
1096        #[derive(TomlExample, Default, Debug, Deserialize, PartialEq)]
1097        struct Outer {
1098            #[toml_example(nesting)]
1099            middle: Middle,
1100            #[toml_example(default = false)]
1101            /// Some toggle
1102            flag: bool,
1103        }
1104        #[derive(TomlExample, Default, Debug, Deserialize, PartialEq)]
1105        struct Middle {
1106            #[serde(flatten)]
1107            #[toml_example(nesting)]
1108            /// Values of [Inner] are flattened into [Middle]
1109            inner: Inner,
1110        }
1111        #[derive(TomlExample, Default, Debug, Deserialize, PartialEq)]
1112        struct Inner {
1113            #[toml_example(nesting)]
1114            /// [Extra] is flattened into [Middle]
1115            extra: Extra,
1116            /// `value` is defined below `extra`, but shown above
1117            value: usize,
1118        }
1119        #[derive(TomlExample, Debug, Deserialize, PartialEq)]
1120        #[toml_example(default)]
1121        struct Extra {
1122            name: String,
1123        }
1124        impl Default for Extra {
1125            fn default() -> Self {
1126                Self {
1127                    name: String::from("ferris"),
1128                }
1129            }
1130        }
1131        let example = Outer::toml_example();
1132        assert_eq!(toml::from_str::<Outer>(&example).unwrap(), Outer::default());
1133        assert_eq!(
1134            example,
1135            r#"# Some toggle
1136flag = false
1137
1138[middle]
1139# Values of [Inner] are flattened into [Middle]
1140# `value` is defined below `extra`, but shown above
1141value = 0
1142
1143# [Extra] is flattened into [Middle]
1144[middle.extra]
1145name = "ferris"
1146
1147"#
1148        );
1149    }
1150
1151    #[test]
1152    fn require() {
1153        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
1154        #[allow(dead_code)]
1155        struct Config {
1156            /// Config.a is an optional number
1157            #[toml_example(require)]
1158            a: Option<usize>,
1159            /// Config.b is an optional string
1160            #[toml_example(require)]
1161            b: Option<String>,
1162            #[toml_example(require)]
1163            #[toml_example(default = "third")]
1164            c: Option<String>,
1165        }
1166        assert_eq!(
1167            Config::toml_example(),
1168            r#"# Config.a is an optional number
1169a = 0
1170
1171# Config.b is an optional string
1172b = ""
1173
1174c = "third"
1175
1176"#
1177        );
1178    }
1179
1180    #[test]
1181    fn skip() {
1182        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
1183        #[allow(dead_code)]
1184        struct Config {
1185            /// Config.a is a number
1186            a: usize,
1187            #[toml_example(skip)]
1188            b: usize,
1189            #[serde(skip)]
1190            c: usize,
1191            #[serde(skip_deserializing)]
1192            d: usize,
1193        }
1194        assert_eq!(
1195            Config::toml_example(),
1196            r#"# Config.a is a number
1197a = 0
1198
1199"#
1200        );
1201    }
1202
1203    #[test]
1204    fn is_enum() {
1205        fn b() -> AB {
1206            AB::B
1207        }
1208        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
1209        #[allow(dead_code)]
1210        struct Config {
1211            /// Config.ab is an enum
1212            #[toml_example(enum, default)]
1213            ab: AB,
1214            /// Config.ab2 is an enum too
1215            #[toml_example(is_enum)]
1216            #[serde(default)]
1217            ab2: AB,
1218            /// Config.ab3 is an enum as well
1219            #[toml_example(is_enum)]
1220            #[serde(default = "b")]
1221            ab3: AB,
1222        }
1223        #[derive(Debug, Default, Deserialize, PartialEq)]
1224        enum AB {
1225            #[default]
1226            A,
1227            B,
1228        }
1229        assert_eq!(
1230            Config::toml_example(),
1231            r#"# Config.ab is an enum
1232ab = "A"
1233
1234# Config.ab2 is an enum too
1235ab2 = "A"
1236
1237# Config.ab3 is an enum as well
1238ab3 = "B"
1239
1240"#
1241        );
1242    }
1243
1244    #[test]
1245    fn flatten() {
1246        #[derive(TomlExample)]
1247        struct ItemWrapper {
1248            #[toml_example(flatten, nesting)]
1249            _item: Item,
1250        }
1251        #[derive(TomlExample)]
1252        struct Item {
1253            _value: String,
1254        }
1255        assert_eq!(ItemWrapper::toml_example(), Item::toml_example());
1256    }
1257
1258    #[test]
1259    fn flatten_order() {
1260        #[derive(TomlExample)]
1261        struct Outer {
1262            #[toml_example(nesting)]
1263            _nested: Item,
1264            #[toml_example(flatten, nesting)]
1265            _flattened: Item,
1266        }
1267        #[derive(TomlExample)]
1268        struct Item {
1269            _value: String,
1270        }
1271        assert_eq!(
1272            Outer::toml_example(),
1273            r#"_value = ""
1274
1275[_nested]
1276_value = ""
1277
1278"#
1279        );
1280    }
1281
1282    #[test]
1283    fn multi_attr_escaping() {
1284        #[derive(TomlExample, Deserialize, PartialEq)]
1285        struct ConfigWrapper {
1286            #[toml_example(default = ["hello", "{nice :)\""], require)]
1287            vec: Option<Vec<String>>,
1288
1289            #[toml_example(require, default = ["\"\\\n}])", "super (fancy\\! :-) )"])]
1290            list: Option<[String; 2]>,
1291        }
1292
1293        assert_eq!(
1294            ConfigWrapper::toml_example(),
1295            r#"vec = ["hello", "{nice :)\""]
1296
1297list = ["\"\\\n}])", "super (fancy\\! :-) )"]
1298
1299"#
1300        );
1301    }
1302
1303    #[test]
1304    fn r_sharp_field() {
1305        #[derive(TomlExample)]
1306        #[allow(dead_code)]
1307        struct Config {
1308            /// Config.type is a number
1309            r#type: usize,
1310        }
1311        assert_eq!(
1312            Config::toml_example(),
1313            r#"# Config.type is a number
1314type = 0
1315
1316"#
1317        );
1318    }
1319
1320    #[test]
1321    fn non_nesting_field_should_be_first() {
1322        #[derive(TomlExample)]
1323        #[allow(dead_code)]
1324        struct Foo {
1325            a: String,
1326        }
1327
1328        #[derive(TomlExample)]
1329        #[allow(dead_code)]
1330        struct Bar {
1331            #[toml_example(nesting)]
1332            foo: Foo,
1333            b: String,
1334        }
1335
1336        assert_eq!(
1337            Bar::toml_example(),
1338            r#"b = ""
1339
1340[foo]
1341a = ""
1342
1343"#
1344        );
1345    }
1346
1347    #[test]
1348    fn rename() {
1349        use serde::Serialize;
1350
1351        #[derive(Deserialize, Serialize, TomlExample)]
1352        struct Config {
1353            #[serde(rename = "bb")]
1354            b: usize,
1355        }
1356        assert_eq!(
1357            Config::toml_example(),
1358            r#"bb = 0
1359
1360"#
1361        );
1362    }
1363
1364    #[test]
1365    fn rename_all() {
1366        use serde::Serialize;
1367
1368        #[derive(Deserialize, Serialize, TomlExample)]
1369        #[serde(rename_all = "kebab-case")]
1370        struct Config {
1371            a_a: usize,
1372        }
1373        assert_eq!(
1374            Config::toml_example(),
1375            r#"a-a = 0
1376
1377"#
1378        );
1379    }
1380
1381    #[test]
1382    fn hashset_and_struct() {
1383        use std::collections::HashMap;
1384
1385        #[derive(TomlExample)]
1386        #[allow(dead_code)]
1387        struct Foo {
1388            a: String,
1389        }
1390
1391        #[derive(TomlExample)]
1392        #[allow(dead_code)]
1393        struct Bar {
1394            /// Default instances doc
1395            #[toml_example(nesting)]
1396            default: Foo,
1397
1398            /// Instances doc
1399            #[toml_example(nesting)]
1400            instance: HashMap<String, Foo>,
1401        }
1402
1403        assert_eq!(
1404            Bar::toml_example(),
1405            r#"# Default instances doc
1406[default]
1407a = ""
1408
1409# Instances doc
1410[instance.example]
1411a = ""
1412
1413"#
1414        );
1415    }
1416
1417    #[test]
1418    fn optional_long_vector_field() {
1419        #[derive(TomlExample)]
1420        #[allow(dead_code)]
1421        struct Foo {
1422            /// Option<Vec<String>>, with small default values
1423            #[toml_example(default = ["a", "b", "c"])]
1424            array_value_example: Option<Vec<String>>,
1425
1426            /// Option<Vec<String>>, with long default values
1427            #[toml_example(default = ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1428                "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", 
1429                "ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", 
1430                "ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
1431            ])]
1432            array_long_value_example: Option<Vec<String>>,
1433
1434            /// Option<Vec<String>>, with a long default value but no space after comma
1435            #[toml_example(default = ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"])]
1436            array_long_value_no_space_example: Option<Vec<String>>,
1437        }
1438        assert_eq!(
1439            Foo::toml_example(),
1440            r#"# Option<Vec<String>>, with small default values
1441# array_value_example = ["a", "b", "c"]
1442
1443# Option<Vec<String>>, with long default values
1444# array_long_value_example = ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1445# "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
1446# "ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
1447# "ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"]
1448
1449# Option<Vec<String>>, with a long default value but no space after comma
1450# array_long_value_no_space_example = ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"]
1451
1452"#
1453        );
1454    }
1455
1456    #[test]
1457    fn help() {
1458        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
1459        #[allow(dead_code)]
1460        #[toml_example(help = "This is the struct help text")]
1461        struct Config {
1462            /// Config.a should be a number
1463            #[toml_example(help = "This is the help text for a")]
1464            a: usize,
1465            /// Config.b should be a string
1466            #[toml_example(help = r#"This is the help text for b
1467Line two of help"#)]
1468            b: String,
1469            /// Config.c is an optional number
1470            #[toml_example(help = "This is the help text for c", default = 42)]
1471            c: Option<usize>,
1472        }
1473        assert_eq!(
1474            Config::toml_example(),
1475            r#"# This is the struct help text
1476# This is the help text for a
1477a = 0
1478
1479# This is the help text for b
1480# Line two of help
1481b = ""
1482
1483# This is the help text for c
1484# c = 42
1485
1486"#
1487        );
1488    }
1489
1490    #[test]
1491    fn help_with_nesting() {
1492        use std::collections::HashMap;
1493
1494        /// Service doc string
1495        #[derive(TomlExample)]
1496        #[allow(dead_code)]
1497        struct Service {
1498            /// port doc string
1499            #[toml_example(help = "Port number help text", default = 80)]
1500            port: usize,
1501        }
1502        #[derive(TomlExample)]
1503        #[allow(dead_code)]
1504        struct Node {
1505            /// Services doc string
1506            #[toml_example(help = "Services help text", default = http, nesting)]
1507            services: HashMap<String, Service>,
1508        }
1509        assert_eq!(
1510            Node::toml_example(),
1511            r#"# Services help text
1512# Service doc string
1513[services.http]
1514# Port number help text
1515port = 80
1516
1517"#
1518        );
1519    }
1520}