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 nestring 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(nesting)]
72//!     #[toml_example(default = http)]
73//!     services: HashMap<String, Service>,
74//! }
75//!
76//! assert_eq!(Node::toml_example(),
77//! r#"# Services are running in the node
78//! ## Service with specific port
79//! [services.http]
80//! ## port should be a number
81//! port = 80
82//!
83//! "#);
84//! ```
85//!
86//! If you want an optional field become a required field in example,
87//! place the `#[toml_example(require)]` on the field.
88//! If you want to skip some field you can use `#[toml_example(skip)]`,
89//! the `#[serde(skip)]`, `#[serde(skip_deserializing)]` also works.
90//! ```rust
91//! use toml_example::TomlExample;
92//! #[derive(TomlExample)]
93//! struct Config {
94//!     /// Config.a is an optional number
95//!     #[toml_example(require)]
96//!     a: Option<usize>,
97//!     /// Config.b is an optional string
98//!     #[toml_example(require)]
99//!     b: Option<String>,
100//!     #[toml_example(require)]
101//!     #[toml_example(default = "third")]
102//!     c: Option<String>,
103//!     #[toml_example(skip)]
104//!     d: usize,
105//! }
106//! assert_eq!(Config::toml_example(),
107//! r#"# Config.a is an optional number
108//! a = 0
109//!
110//! ## Config.b is an optional string
111//! b = ""
112//!
113//! c = "third"
114//!
115//! "#)
116//! ```
117
118#[doc(hidden)]
119pub use toml_example_derive::TomlExample;
120pub mod traits;
121pub use traits::*;
122
123#[cfg(test)]
124mod tests {
125    use crate as toml_example;
126    use serde_derive::Deserialize;
127    use std::collections::HashMap;
128    use toml_example::TomlExample;
129
130    #[test]
131    fn basic() {
132        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
133        #[allow(dead_code)]
134        struct Config {
135            /// Config.a should be a number
136            a: usize,
137            /// Config.b should be a string
138            b: String,
139        }
140        assert_eq!(
141            Config::toml_example(),
142            r#"# Config.a should be a number
143a = 0
144
145# Config.b should be a string
146b = ""
147
148"#
149        );
150        assert_eq!(
151            toml::from_str::<Config>(&Config::toml_example()).unwrap(),
152            Config::default()
153        );
154        let mut tmp_file = std::env::temp_dir();
155        tmp_file.push("config.toml");
156        Config::to_toml_example(&tmp_file.as_path().to_str().unwrap()).unwrap();
157        assert_eq!(
158            std::fs::read_to_string(tmp_file).unwrap(),
159            r#"# Config.a should be a number
160a = 0
161
162# Config.b should be a string
163b = ""
164
165"#
166        );
167    }
168
169    #[test]
170    fn option() {
171        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
172        #[allow(dead_code)]
173        struct Config {
174            /// Config.a is an optional number
175            a: Option<usize>,
176            /// Config.b is an optional string
177            b: Option<String>,
178        }
179        assert_eq!(
180            Config::toml_example(),
181            r#"# Config.a is an optional number
182# a = 0
183
184# Config.b is an optional string
185# b = ""
186
187"#
188        );
189        assert_eq!(
190            toml::from_str::<Config>(&Config::toml_example()).unwrap(),
191            Config::default()
192        )
193    }
194
195    #[test]
196    fn vec() {
197        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
198        #[allow(dead_code)]
199        struct Config {
200            /// Config.a is a list of number
201            a: Vec<usize>,
202            /// Config.b is a list of string
203            b: Vec<String>,
204            /// Config.c
205            c: Vec<Option<usize>>,
206            /// Config.d
207            d: Option<Vec<usize>>,
208        }
209        assert_eq!(
210            Config::toml_example(),
211            r#"# Config.a is a list of number
212a = [ 0, ]
213
214# Config.b is a list of string
215b = [ "", ]
216
217# Config.c
218c = [ 0, ]
219
220# Config.d
221# d = [ 0, ]
222
223"#
224        );
225        assert!(toml::from_str::<Config>(&Config::toml_example()).is_ok())
226    }
227
228    #[test]
229    fn struct_doc() {
230        /// Config is to arrange something or change the controls on a computer or other device
231        /// so that it can be used in a particular way
232        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
233        #[allow(dead_code)]
234        struct Config {
235            /// Config.a should be a number
236            /// the number should be greater or equal zero
237            a: usize,
238        }
239        assert_eq!(
240            Config::toml_example(),
241            r#"# Config is to arrange something or change the controls on a computer or other device
242# so that it can be used in a particular way
243# Config.a should be a number
244# the number should be greater or equal zero
245a = 0
246
247"#
248        );
249        assert_eq!(
250            toml::from_str::<Config>(&Config::toml_example()).unwrap(),
251            Config::default()
252        )
253    }
254
255    #[test]
256    fn serde_default() {
257        fn default_a() -> usize {
258            7
259        }
260        fn default_b() -> String {
261            "default".into()
262        }
263        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
264        #[allow(dead_code)]
265        struct Config {
266            /// Config.a should be a number
267            #[serde(default = "default_a")]
268            a: usize,
269            /// Config.b should be a string
270            #[serde(default = "default_b")]
271            b: String,
272            /// Config.c should be a number
273            #[serde(default)]
274            c: usize,
275            /// Config.d should be a string
276            #[serde(default)]
277            d: String,
278            #[serde(default)]
279            e: Option<usize>,
280        }
281        assert_eq!(
282            Config::toml_example(),
283            r#"# Config.a should be a number
284a = 7
285
286# Config.b should be a string
287b = "default"
288
289# Config.c should be a number
290c = 0
291
292# Config.d should be a string
293d = ""
294
295# e = 0
296
297"#
298        );
299    }
300
301    #[test]
302    fn toml_example_default() {
303        fn default_str() -> String {
304            "seven".into()
305        }
306        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
307        #[allow(dead_code)]
308        struct Config {
309            /// Config.a should be a number
310            #[toml_example(default = 7)]
311            a: usize,
312            /// Config.b should be a string
313            #[toml_example(default = "default")]
314            #[serde(default = "default_str")]
315            b: String,
316            #[serde(default = "default_str")]
317            #[toml_example(default = "default")]
318            c: String,
319            #[toml_example(default = [ "default", ])]
320            e: Vec<String>,
321            #[toml_example(
322                default = "super looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string"
323            )]
324            f: String,
325            #[toml_example(default = [ "super looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string",
326               "second",
327               "third",
328            ])]
329            g: Vec<String>,
330            /// Config.color should be a hex color code
331            #[toml_example(default = "#FAFAFA")]
332            color: String,
333        }
334        assert_eq!(
335            Config::toml_example(),
336            r##"# Config.a should be a number
337a = 7
338
339# Config.b should be a string
340b = "seven"
341
342c = "default"
343
344e = ["default",]
345
346f = "super looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string"
347
348g = ["super looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string",
349"second", "third",]
350
351# Config.color should be a hex color code
352color = "#FAFAFA"
353
354"##
355        );
356    }
357
358    #[test]
359    fn no_nesting() {
360        /// Inner is a config live in Outer
361        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
362        #[allow(dead_code)]
363        struct Inner {
364            /// Inner.a should be a number
365            a: usize,
366        }
367        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
368        #[allow(dead_code)]
369        struct Outer {
370            /// Outer.inner is a complex struct
371            inner: Inner,
372        }
373        assert_eq!(
374            Outer::toml_example(),
375            r#"# Outer.inner is a complex struct
376inner = ""
377
378"#
379        );
380    }
381
382    #[test]
383    fn nesting() {
384        /// Inner is a config live in Outer
385        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
386        #[allow(dead_code)]
387        struct Inner {
388            /// Inner.a should be a number
389            a: usize,
390        }
391        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
392        #[allow(dead_code)]
393        struct Outer {
394            /// Outer.inner is a complex struct
395            #[toml_example(nesting)]
396            inner: Inner,
397        }
398        assert_eq!(
399            Outer::toml_example(),
400            r#"# Outer.inner is a complex struct
401# Inner is a config live in Outer
402[inner]
403# Inner.a should be a number
404a = 0
405
406"#
407        );
408        assert_eq!(
409            toml::from_str::<Outer>(&Outer::toml_example()).unwrap(),
410            Outer::default()
411        );
412    }
413
414    #[test]
415    fn nesting_by_section() {
416        /// Inner is a config live in Outer
417        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
418        #[allow(dead_code)]
419        struct Inner {
420            /// Inner.a should be a number
421            a: usize,
422        }
423        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
424        #[allow(dead_code)]
425        struct Outer {
426            /// Outer.inner is a complex struct
427            #[toml_example(nesting = section)]
428            inner: Inner,
429        }
430        assert_eq!(
431            Outer::toml_example(),
432            r#"# Outer.inner is a complex struct
433# Inner is a config live in Outer
434[inner]
435# Inner.a should be a number
436a = 0
437
438"#
439        );
440        assert_eq!(
441            toml::from_str::<Outer>(&Outer::toml_example()).unwrap(),
442            Outer::default()
443        );
444    }
445
446    #[test]
447    fn nesting_by_prefix() {
448        /// Inner is a config live in Outer
449        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
450        #[allow(dead_code)]
451        struct Inner {
452            /// Inner.a should be a number
453            a: usize,
454        }
455        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
456        #[allow(dead_code)]
457        struct Outer {
458            /// Outer.inner is a complex struct
459            #[toml_example(nesting = prefix)]
460            inner: Inner,
461        }
462        assert_eq!(
463            Outer::toml_example(),
464            r#"# Outer.inner is a complex struct
465# Inner is a config live in Outer
466# Inner.a should be a number
467inner.a = 0
468
469"#
470        );
471        assert_eq!(
472            toml::from_str::<Outer>(&Outer::toml_example()).unwrap(),
473            Outer::default()
474        );
475    }
476
477    #[test]
478    fn nesting_vector() {
479        /// Service with specific port
480        #[derive(TomlExample, Deserialize)]
481        #[allow(dead_code)]
482        struct Service {
483            /// port should be a number
484            port: usize,
485        }
486        #[derive(TomlExample, Deserialize)]
487        #[allow(dead_code)]
488        struct Node {
489            /// Services are running in the node
490            #[toml_example(nesting)]
491            services: Vec<Service>,
492        }
493        assert_eq!(
494            Node::toml_example(),
495            r#"# Services are running in the node
496# Service with specific port
497[[services]]
498# port should be a number
499port = 0
500
501"#
502        );
503        assert!(toml::from_str::<Node>(&Node::toml_example()).is_ok());
504    }
505
506    #[test]
507    fn nesting_hashmap() {
508        /// Service with specific port
509        #[derive(TomlExample, Deserialize)]
510        #[allow(dead_code)]
511        struct Service {
512            /// port should be a number
513            port: usize,
514        }
515        #[derive(TomlExample, Deserialize)]
516        #[allow(dead_code)]
517        struct Node {
518            /// Services are running in the node
519            #[toml_example(nesting)]
520            services: HashMap<String, Service>,
521        }
522        assert_eq!(
523            Node::toml_example(),
524            r#"# Services are running in the node
525# Service with specific port
526[services.example]
527# port should be a number
528port = 0
529
530"#
531        );
532        assert!(toml::from_str::<Node>(&Node::toml_example()).is_ok());
533    }
534
535    #[test]
536    fn optional_nesting() {
537        /// Inner is a config live in Outer
538        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
539        #[allow(dead_code)]
540        struct Inner {
541            /// Inner.a should be a number
542            a: usize,
543        }
544        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
545        #[allow(dead_code)]
546        struct Outer {
547            /// Outer.inner is a complex struct
548            #[toml_example(nesting)]
549            inner: Option<Inner>,
550        }
551        assert_eq!(
552            Outer::toml_example(),
553            r#"# Outer.inner is a complex struct
554# Inner is a config live in Outer
555# [inner]
556# Inner.a should be a number
557# a = 0
558
559"#
560        );
561        assert_eq!(
562            toml::from_str::<Outer>(&Outer::toml_example()).unwrap(),
563            Outer::default()
564        );
565    }
566
567    #[test]
568    fn optional_nesting_by_section() {
569        /// Inner is a config live in Outer
570        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
571        #[allow(dead_code)]
572        struct Inner {
573            /// Inner.a should be a number
574            a: usize,
575        }
576        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
577        #[allow(dead_code)]
578        struct Outer {
579            /// Outer.inner is a complex struct
580            #[toml_example(nesting = section)]
581            inner: Option<Inner>,
582        }
583        assert_eq!(
584            Outer::toml_example(),
585            r#"# Outer.inner is a complex struct
586# Inner is a config live in Outer
587# [inner]
588# Inner.a should be a number
589# a = 0
590
591"#
592        );
593        assert_eq!(
594            toml::from_str::<Outer>(&Outer::toml_example()).unwrap(),
595            Outer::default()
596        );
597    }
598
599    #[test]
600    fn optional_nesting_by_prefix() {
601        /// Inner is a config live in Outer
602        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
603        #[allow(dead_code)]
604        struct Inner {
605            /// Inner.a should be a number
606            a: usize,
607        }
608        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
609        #[allow(dead_code)]
610        struct Outer {
611            /// Outer.inner is a complex struct
612            #[toml_example(nesting = prefix)]
613            inner: Option<Inner>,
614        }
615        assert_eq!(
616            Outer::toml_example(),
617            r#"# Outer.inner is a complex struct
618# Inner is a config live in Outer
619# Inner.a should be a number
620# inner.a = 0
621
622"#
623        );
624        assert_eq!(
625            toml::from_str::<Outer>(&Outer::toml_example()).unwrap(),
626            Outer::default()
627        );
628    }
629
630    #[test]
631    fn optional_nesting_vector() {
632        /// Service with specific port
633        #[derive(TomlExample, Deserialize)]
634        #[allow(dead_code)]
635        struct Service {
636            /// port should be a number
637            port: usize,
638        }
639        #[derive(TomlExample, Deserialize)]
640        #[allow(dead_code)]
641        struct Node {
642            /// Services are running in the node
643            #[toml_example(nesting)]
644            services: Option<Vec<Service>>,
645        }
646        assert_eq!(
647            Node::toml_example(),
648            r#"# Services are running in the node
649# Service with specific port
650# [[services]]
651# port should be a number
652# port = 0
653
654"#
655        );
656        assert!(toml::from_str::<Node>(&Node::toml_example()).is_ok());
657    }
658
659    #[test]
660    fn optional_nesting_hashmap() {
661        /// Service with specific port
662        #[derive(TomlExample, Deserialize)]
663        #[allow(dead_code)]
664        struct Service {
665            /// port should be a number
666            port: usize,
667        }
668        #[derive(TomlExample, Deserialize)]
669        #[allow(dead_code)]
670        struct Node {
671            /// Services are running in the node
672            #[toml_example(nesting)]
673            services: Option<HashMap<String, Service>>,
674        }
675        assert_eq!(
676            Node::toml_example(),
677            r#"# Services are running in the node
678# Service with specific port
679# [services.example]
680# port should be a number
681# port = 0
682
683"#
684        );
685        assert!(toml::from_str::<Node>(&Node::toml_example()).is_ok());
686    }
687
688    #[test]
689    fn nesting_hashmap_with_default_name() {
690        /// Service with specific port
691        #[derive(TomlExample, Deserialize)]
692        #[allow(dead_code)]
693        struct Service {
694            /// port should be a number
695            #[toml_example(default = 80)]
696            port: usize,
697        }
698        #[derive(TomlExample, Deserialize)]
699        #[allow(dead_code)]
700        struct Node {
701            /// Services are running in the node
702            #[toml_example(nesting)]
703            #[toml_example(default = http)]
704            services: HashMap<String, Service>,
705        }
706        assert_eq!(
707            Node::toml_example(),
708            r#"# Services are running in the node
709# Service with specific port
710[services.http]
711# port should be a number
712port = 80
713
714"#
715        );
716        assert!(toml::from_str::<Node>(&Node::toml_example()).is_ok());
717    }
718
719    #[test]
720    fn nesting_hashmap_with_dash_name() {
721        /// Service with specific port
722        #[derive(TomlExample, Deserialize)]
723        #[allow(dead_code)]
724        struct Service {
725            /// port should be a number
726            #[toml_example(default = 80)]
727            port: usize,
728        }
729        #[derive(TomlExample, Deserialize)]
730        #[allow(dead_code)]
731        struct Node {
732            /// Services are running in the node
733            #[toml_example(nesting)]
734            #[toml_example(default = http.01)]
735            services: HashMap<String, Service>,
736        }
737        assert_eq!(
738            Node::toml_example(),
739            r#"# Services are running in the node
740# Service with specific port
741[services.http-01]
742# port should be a number
743port = 80
744
745"#
746        );
747        assert!(toml::from_str::<Node>(&Node::toml_example()).is_ok());
748    }
749
750    #[test]
751    fn require() {
752        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
753        #[allow(dead_code)]
754        struct Config {
755            /// Config.a is an optional number
756            #[toml_example(require)]
757            a: Option<usize>,
758            /// Config.b is an optional string
759            #[toml_example(require)]
760            b: Option<String>,
761            #[toml_example(require)]
762            #[toml_example(default = "third")]
763            c: Option<String>,
764        }
765        assert_eq!(
766            Config::toml_example(),
767            r#"# Config.a is an optional number
768a = 0
769
770# Config.b is an optional string
771b = ""
772
773c = "third"
774
775"#
776        );
777    }
778
779    #[test]
780    fn skip() {
781        #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
782        #[allow(dead_code)]
783        struct Config {
784            /// Config.a is a number
785            a: usize,
786            #[toml_example(skip)]
787            b: usize,
788            #[serde(skip)]
789            c: usize,
790            #[serde(skip_deserializing)]
791            d: usize,
792        }
793        assert_eq!(
794            Config::toml_example(),
795            r#"# Config.a is a number
796a = 0
797
798"#
799        );
800    }
801
802    #[test]
803    fn r_sharp_field() {
804        #[derive(TomlExample)]
805        #[allow(dead_code)]
806        struct Config {
807            /// Config.type is a number
808            r#type: usize,
809        }
810        assert_eq!(
811            Config::toml_example(),
812            r#"# Config.type is a number
813type = 0
814
815"#
816        );
817    }
818
819    #[test]
820    fn non_nesting_field_should_be_first() {
821        #[derive(TomlExample)]
822        #[allow(dead_code)]
823        struct Foo {
824            a: String,
825        }
826
827        #[derive(TomlExample)]
828        #[allow(dead_code)]
829        struct Bar {
830            #[toml_example(nesting)]
831            foo: Foo,
832            b: String,
833        }
834
835        assert_eq!(
836            Bar::toml_example(),
837            r#"b = ""
838
839[foo]
840a = ""
841
842"#
843        );
844    }
845
846    #[test]
847    fn rename() {
848        use serde::Serialize;
849
850        #[derive(Deserialize, Serialize, TomlExample)]
851        struct Config {
852            #[serde(rename = "bb")]
853            b: usize,
854        }
855        assert_eq!(
856            Config::toml_example(),
857            r#"bb = 0
858
859"#
860        );
861    }
862
863    #[test]
864    fn rename_all() {
865        use serde::Serialize;
866
867        #[derive(Deserialize, Serialize, TomlExample)]
868        #[serde(rename_all = "kebab-case")]
869        struct Config {
870            a_a: usize,
871        }
872        assert_eq!(
873            Config::toml_example(),
874            r#"a-a = 0
875
876"#
877        );
878    }
879
880    #[test]
881    fn hashset_and_struct() {
882        use std::collections::HashMap;
883
884        #[derive(TomlExample)]
885        #[allow(dead_code)]
886        struct Foo {
887            a: String,
888        }
889
890        #[derive(TomlExample)]
891        #[allow(dead_code)]
892        struct Bar {
893            /// Default instances doc
894            #[toml_example(nesting)]
895            default: Foo,
896
897            /// Instances doc
898            #[toml_example(nesting)]
899            instance: HashMap<String, Foo>,
900        }
901
902        assert_eq!(
903            Bar::toml_example(),
904            r#"# Default instances doc
905[default]
906a = ""
907
908# Instances doc
909[instance.example]
910a = ""
911
912"#
913        );
914    }
915}