1package toml
2
3import (
4	"fmt"
5	"log"
6	"reflect"
7	"testing"
8	"time"
9)
10
11func init() {
12	log.SetFlags(0)
13}
14
15func TestDecodeSimple(t *testing.T) {
16	var testSimple = `
17age = 250
18andrew = "gallant"
19kait = "brady"
20now = 1987-07-05T05:45:00Z
21yesOrNo = true
22pi = 3.14
23colors = [
24	["red", "green", "blue"],
25	["cyan", "magenta", "yellow", "black"],
26]
27
28[My.Cats]
29plato = "cat 1"
30cauchy = "cat 2"
31`
32
33	type cats struct {
34		Plato  string
35		Cauchy string
36	}
37	type simple struct {
38		Age     int
39		Colors  [][]string
40		Pi      float64
41		YesOrNo bool
42		Now     time.Time
43		Andrew  string
44		Kait    string
45		My      map[string]cats
46	}
47
48	var val simple
49	_, err := Decode(testSimple, &val)
50	if err != nil {
51		t.Fatal(err)
52	}
53
54	now, err := time.Parse("2006-01-02T15:04:05", "1987-07-05T05:45:00")
55	if err != nil {
56		panic(err)
57	}
58	var answer = simple{
59		Age:     250,
60		Andrew:  "gallant",
61		Kait:    "brady",
62		Now:     now,
63		YesOrNo: true,
64		Pi:      3.14,
65		Colors: [][]string{
66			{"red", "green", "blue"},
67			{"cyan", "magenta", "yellow", "black"},
68		},
69		My: map[string]cats{
70			"Cats": {Plato: "cat 1", Cauchy: "cat 2"},
71		},
72	}
73	if !reflect.DeepEqual(val, answer) {
74		t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n",
75			answer, val)
76	}
77}
78
79func TestDecodeEmbedded(t *testing.T) {
80	type Dog struct{ Name string }
81	type Age int
82
83	tests := map[string]struct {
84		input       string
85		decodeInto  interface{}
86		wantDecoded interface{}
87	}{
88		"embedded struct": {
89			input:       `Name = "milton"`,
90			decodeInto:  &struct{ Dog }{},
91			wantDecoded: &struct{ Dog }{Dog{"milton"}},
92		},
93		"embedded non-nil pointer to struct": {
94			input:       `Name = "milton"`,
95			decodeInto:  &struct{ *Dog }{},
96			wantDecoded: &struct{ *Dog }{&Dog{"milton"}},
97		},
98		"embedded nil pointer to struct": {
99			input:       ``,
100			decodeInto:  &struct{ *Dog }{},
101			wantDecoded: &struct{ *Dog }{nil},
102		},
103		"embedded int": {
104			input:       `Age = -5`,
105			decodeInto:  &struct{ Age }{},
106			wantDecoded: &struct{ Age }{-5},
107		},
108	}
109
110	for label, test := range tests {
111		_, err := Decode(test.input, test.decodeInto)
112		if err != nil {
113			t.Fatal(err)
114		}
115		if !reflect.DeepEqual(test.wantDecoded, test.decodeInto) {
116			t.Errorf("%s: want decoded == %+v, got %+v",
117				label, test.wantDecoded, test.decodeInto)
118		}
119	}
120}
121
122func TestDecodeIgnoredFields(t *testing.T) {
123	type simple struct {
124		Number int `toml:"-"`
125	}
126	const input = `
127Number = 123
128- = 234
129`
130	var s simple
131	if _, err := Decode(input, &s); err != nil {
132		t.Fatal(err)
133	}
134	if s.Number != 0 {
135		t.Errorf("got: %d; want 0", s.Number)
136	}
137}
138
139func TestTableArrays(t *testing.T) {
140	var tomlTableArrays = `
141[[albums]]
142name = "Born to Run"
143
144  [[albums.songs]]
145  name = "Jungleland"
146
147  [[albums.songs]]
148  name = "Meeting Across the River"
149
150[[albums]]
151name = "Born in the USA"
152
153  [[albums.songs]]
154  name = "Glory Days"
155
156  [[albums.songs]]
157  name = "Dancing in the Dark"
158`
159
160	type Song struct {
161		Name string
162	}
163
164	type Album struct {
165		Name  string
166		Songs []Song
167	}
168
169	type Music struct {
170		Albums []Album
171	}
172
173	expected := Music{[]Album{
174		{"Born to Run", []Song{{"Jungleland"}, {"Meeting Across the River"}}},
175		{"Born in the USA", []Song{{"Glory Days"}, {"Dancing in the Dark"}}},
176	}}
177	var got Music
178	if _, err := Decode(tomlTableArrays, &got); err != nil {
179		t.Fatal(err)
180	}
181	if !reflect.DeepEqual(expected, got) {
182		t.Fatalf("\n%#v\n!=\n%#v\n", expected, got)
183	}
184}
185
186// Case insensitive matching tests.
187// A bit more comprehensive than needed given the current implementation,
188// but implementations change.
189// Probably still missing demonstrations of some ugly corner cases regarding
190// case insensitive matching and multiple fields.
191func TestCase(t *testing.T) {
192	var caseToml = `
193tOpString = "string"
194tOpInt = 1
195tOpFloat = 1.1
196tOpBool = true
197tOpdate = 2006-01-02T15:04:05Z
198tOparray = [ "array" ]
199Match = "i should be in Match only"
200MatcH = "i should be in MatcH only"
201once = "just once"
202[nEst.eD]
203nEstedString = "another string"
204`
205
206	type InsensitiveEd struct {
207		NestedString string
208	}
209
210	type InsensitiveNest struct {
211		Ed InsensitiveEd
212	}
213
214	type Insensitive struct {
215		TopString string
216		TopInt    int
217		TopFloat  float64
218		TopBool   bool
219		TopDate   time.Time
220		TopArray  []string
221		Match     string
222		MatcH     string
223		Once      string
224		OncE      string
225		Nest      InsensitiveNest
226	}
227
228	tme, err := time.Parse(time.RFC3339, time.RFC3339[:len(time.RFC3339)-5])
229	if err != nil {
230		panic(err)
231	}
232	expected := Insensitive{
233		TopString: "string",
234		TopInt:    1,
235		TopFloat:  1.1,
236		TopBool:   true,
237		TopDate:   tme,
238		TopArray:  []string{"array"},
239		MatcH:     "i should be in MatcH only",
240		Match:     "i should be in Match only",
241		Once:      "just once",
242		OncE:      "",
243		Nest: InsensitiveNest{
244			Ed: InsensitiveEd{NestedString: "another string"},
245		},
246	}
247	var got Insensitive
248	if _, err := Decode(caseToml, &got); err != nil {
249		t.Fatal(err)
250	}
251	if !reflect.DeepEqual(expected, got) {
252		t.Fatalf("\n%#v\n!=\n%#v\n", expected, got)
253	}
254}
255
256func TestPointers(t *testing.T) {
257	type Object struct {
258		Type        string
259		Description string
260	}
261
262	type Dict struct {
263		NamedObject map[string]*Object
264		BaseObject  *Object
265		Strptr      *string
266		Strptrs     []*string
267	}
268	s1, s2, s3 := "blah", "abc", "def"
269	expected := &Dict{
270		Strptr:  &s1,
271		Strptrs: []*string{&s2, &s3},
272		NamedObject: map[string]*Object{
273			"foo": {"FOO", "fooooo!!!"},
274			"bar": {"BAR", "ba-ba-ba-ba-barrrr!!!"},
275		},
276		BaseObject: &Object{"BASE", "da base"},
277	}
278
279	ex1 := `
280Strptr = "blah"
281Strptrs = ["abc", "def"]
282
283[NamedObject.foo]
284Type = "FOO"
285Description = "fooooo!!!"
286
287[NamedObject.bar]
288Type = "BAR"
289Description = "ba-ba-ba-ba-barrrr!!!"
290
291[BaseObject]
292Type = "BASE"
293Description = "da base"
294`
295	dict := new(Dict)
296	_, err := Decode(ex1, dict)
297	if err != nil {
298		t.Errorf("Decode error: %v", err)
299	}
300	if !reflect.DeepEqual(expected, dict) {
301		t.Fatalf("\n%#v\n!=\n%#v\n", expected, dict)
302	}
303}
304
305func TestDecodeBadTimestamp(t *testing.T) {
306	var x struct {
307		T time.Time
308	}
309	for _, s := range []string{
310		"T = 123", "T = 2006-01-50T00:00:00Z", "T = 2006-01-30T00:00:00",
311	} {
312		if _, err := Decode(s, &x); err == nil {
313			t.Errorf("Expected invalid DateTime error for %q", s)
314		}
315	}
316}
317
318func TestDecodeMultilineStrings(t *testing.T) {
319	var x struct {
320		S string
321	}
322	const s0 = `s = """
323a b \n c
324d e f
325"""`
326	if _, err := Decode(s0, &x); err != nil {
327		t.Fatal(err)
328	}
329	if want := "a b \n c\nd e f\n"; x.S != want {
330		t.Errorf("got: %q; want: %q", x.S, want)
331	}
332	const s1 = `s = """a b c\
333"""`
334	if _, err := Decode(s1, &x); err != nil {
335		t.Fatal(err)
336	}
337	if want := "a b c"; x.S != want {
338		t.Errorf("got: %q; want: %q", x.S, want)
339	}
340}
341
342type sphere struct {
343	Center [3]float64
344	Radius float64
345}
346
347func TestDecodeSimpleArray(t *testing.T) {
348	var s1 sphere
349	if _, err := Decode(`center = [0.0, 1.5, 0.0]`, &s1); err != nil {
350		t.Fatal(err)
351	}
352}
353
354func TestDecodeArrayWrongSize(t *testing.T) {
355	var s1 sphere
356	if _, err := Decode(`center = [0.1, 2.3]`, &s1); err == nil {
357		t.Fatal("Expected array type mismatch error")
358	}
359}
360
361func TestDecodeLargeIntoSmallInt(t *testing.T) {
362	type table struct {
363		Value int8
364	}
365	var tab table
366	if _, err := Decode(`value = 500`, &tab); err == nil {
367		t.Fatal("Expected integer out-of-bounds error.")
368	}
369}
370
371func TestDecodeSizedInts(t *testing.T) {
372	type table struct {
373		U8  uint8
374		U16 uint16
375		U32 uint32
376		U64 uint64
377		U   uint
378		I8  int8
379		I16 int16
380		I32 int32
381		I64 int64
382		I   int
383	}
384	answer := table{1, 1, 1, 1, 1, -1, -1, -1, -1, -1}
385	toml := `
386	u8 = 1
387	u16 = 1
388	u32 = 1
389	u64 = 1
390	u = 1
391	i8 = -1
392	i16 = -1
393	i32 = -1
394	i64 = -1
395	i = -1
396	`
397	var tab table
398	if _, err := Decode(toml, &tab); err != nil {
399		t.Fatal(err.Error())
400	}
401	if answer != tab {
402		t.Fatalf("Expected %#v but got %#v", answer, tab)
403	}
404}
405
406func TestUnmarshaler(t *testing.T) {
407
408	var tomlBlob = `
409[dishes.hamboogie]
410name = "Hamboogie with fries"
411price = 10.99
412
413[[dishes.hamboogie.ingredients]]
414name = "Bread Bun"
415
416[[dishes.hamboogie.ingredients]]
417name = "Lettuce"
418
419[[dishes.hamboogie.ingredients]]
420name = "Real Beef Patty"
421
422[[dishes.hamboogie.ingredients]]
423name = "Tomato"
424
425[dishes.eggsalad]
426name = "Egg Salad with rice"
427price = 3.99
428
429[[dishes.eggsalad.ingredients]]
430name = "Egg"
431
432[[dishes.eggsalad.ingredients]]
433name = "Mayo"
434
435[[dishes.eggsalad.ingredients]]
436name = "Rice"
437`
438	m := &menu{}
439	if _, err := Decode(tomlBlob, m); err != nil {
440		log.Fatal(err)
441	}
442
443	if len(m.Dishes) != 2 {
444		t.Log("two dishes should be loaded with UnmarshalTOML()")
445		t.Errorf("expected %d but got %d", 2, len(m.Dishes))
446	}
447
448	eggSalad := m.Dishes["eggsalad"]
449	if _, ok := interface{}(eggSalad).(dish); !ok {
450		t.Errorf("expected a dish")
451	}
452
453	if eggSalad.Name != "Egg Salad with rice" {
454		t.Errorf("expected the dish to be named 'Egg Salad with rice'")
455	}
456
457	if len(eggSalad.Ingredients) != 3 {
458		t.Log("dish should be loaded with UnmarshalTOML()")
459		t.Errorf("expected %d but got %d", 3, len(eggSalad.Ingredients))
460	}
461
462	found := false
463	for _, i := range eggSalad.Ingredients {
464		if i.Name == "Rice" {
465			found = true
466			break
467		}
468	}
469	if !found {
470		t.Error("Rice was not loaded in UnmarshalTOML()")
471	}
472
473	// test on a value - must be passed as *
474	o := menu{}
475	if _, err := Decode(tomlBlob, &o); err != nil {
476		log.Fatal(err)
477	}
478
479}
480
481type menu struct {
482	Dishes map[string]dish
483}
484
485func (m *menu) UnmarshalTOML(p interface{}) error {
486	m.Dishes = make(map[string]dish)
487	data, _ := p.(map[string]interface{})
488	dishes := data["dishes"].(map[string]interface{})
489	for n, v := range dishes {
490		if d, ok := v.(map[string]interface{}); ok {
491			nd := dish{}
492			nd.UnmarshalTOML(d)
493			m.Dishes[n] = nd
494		} else {
495			return fmt.Errorf("not a dish")
496		}
497	}
498	return nil
499}
500
501type dish struct {
502	Name        string
503	Price       float32
504	Ingredients []ingredient
505}
506
507func (d *dish) UnmarshalTOML(p interface{}) error {
508	data, _ := p.(map[string]interface{})
509	d.Name, _ = data["name"].(string)
510	d.Price, _ = data["price"].(float32)
511	ingredients, _ := data["ingredients"].([]map[string]interface{})
512	for _, e := range ingredients {
513		n, _ := interface{}(e).(map[string]interface{})
514		name, _ := n["name"].(string)
515		i := ingredient{name}
516		d.Ingredients = append(d.Ingredients, i)
517	}
518	return nil
519}
520
521type ingredient struct {
522	Name string
523}
524
525func TestDecodeSlices(t *testing.T) {
526	s := struct{ Test []string }{Test: []string{}}
527	if _, err := Decode(`Test = ["test"]`, &s); err != nil {
528		t.Errorf("Error decoding into empty slice: %s", err)
529	}
530	s.Test = []string{"a", "b", "c"}
531	if _, err := Decode(`Test = ["test"]`, &s); err != nil {
532		t.Errorf("Error decoding into oversized slice: %s", err)
533	}
534	if want := []string{"test"}; !reflect.DeepEqual(s.Test, want) {
535		t.Errorf("Got %v; want %v", s.Test, want)
536	}
537}
538
539func ExampleMetaData_PrimitiveDecode() {
540	var md MetaData
541	var err error
542
543	var tomlBlob = `
544ranking = ["Springsteen", "J Geils"]
545
546[bands.Springsteen]
547started = 1973
548albums = ["Greetings", "WIESS", "Born to Run", "Darkness"]
549
550[bands."J Geils"]
551started = 1970
552albums = ["The J. Geils Band", "Full House", "Blow Your Face Out"]
553`
554
555	type band struct {
556		Started int
557		Albums  []string
558	}
559	type classics struct {
560		Ranking []string
561		Bands   map[string]Primitive
562	}
563
564	// Do the initial decode. Reflection is delayed on Primitive values.
565	var music classics
566	if md, err = Decode(tomlBlob, &music); err != nil {
567		log.Fatal(err)
568	}
569
570	// MetaData still includes information on Primitive values.
571	fmt.Printf("Is `bands.Springsteen` defined? %v\n",
572		md.IsDefined("bands", "Springsteen"))
573
574	// Decode primitive data into Go values.
575	for _, artist := range music.Ranking {
576		// A band is a primitive value, so we need to decode it to get a
577		// real `band` value.
578		primValue := music.Bands[artist]
579
580		var aBand band
581		if err = md.PrimitiveDecode(primValue, &aBand); err != nil {
582			log.Fatal(err)
583		}
584		fmt.Printf("%s started in %d.\n", artist, aBand.Started)
585	}
586	// Check to see if there were any fields left undecoded.
587	// Note that this won't be empty before decoding the Primitive value!
588	fmt.Printf("Undecoded: %q\n", md.Undecoded())
589
590	// Output:
591	// Is `bands.Springsteen` defined? true
592	// Springsteen started in 1973.
593	// J Geils started in 1970.
594	// Undecoded: []
595}
596
597func ExampleDecode() {
598	var tomlBlob = `
599# Some comments.
600[alpha]
601ip = "10.0.0.1"
602
603	[alpha.config]
604	Ports = [ 8001, 8002 ]
605	Location = "Toronto"
606	Created = 1987-07-05T05:45:00Z
607
608[beta]
609ip = "10.0.0.2"
610
611	[beta.config]
612	Ports = [ 9001, 9002 ]
613	Location = "New Jersey"
614	Created = 1887-01-05T05:55:00Z
615`
616
617	type serverConfig struct {
618		Ports    []int
619		Location string
620		Created  time.Time
621	}
622
623	type server struct {
624		IP     string       `toml:"ip,omitempty"`
625		Config serverConfig `toml:"config"`
626	}
627
628	type servers map[string]server
629
630	var config servers
631	if _, err := Decode(tomlBlob, &config); err != nil {
632		log.Fatal(err)
633	}
634
635	for _, name := range []string{"alpha", "beta"} {
636		s := config[name]
637		fmt.Printf("Server: %s (ip: %s) in %s created on %s\n",
638			name, s.IP, s.Config.Location,
639			s.Config.Created.Format("2006-01-02"))
640		fmt.Printf("Ports: %v\n", s.Config.Ports)
641	}
642
643	// Output:
644	// Server: alpha (ip: 10.0.0.1) in Toronto created on 1987-07-05
645	// Ports: [8001 8002]
646	// Server: beta (ip: 10.0.0.2) in New Jersey created on 1887-01-05
647	// Ports: [9001 9002]
648}
649
650type duration struct {
651	time.Duration
652}
653
654func (d *duration) UnmarshalText(text []byte) error {
655	var err error
656	d.Duration, err = time.ParseDuration(string(text))
657	return err
658}
659
660// Example Unmarshaler shows how to decode TOML strings into your own
661// custom data type.
662func Example_unmarshaler() {
663	blob := `
664[[song]]
665name = "Thunder Road"
666duration = "4m49s"
667
668[[song]]
669name = "Stairway to Heaven"
670duration = "8m03s"
671`
672	type song struct {
673		Name     string
674		Duration duration
675	}
676	type songs struct {
677		Song []song
678	}
679	var favorites songs
680	if _, err := Decode(blob, &favorites); err != nil {
681		log.Fatal(err)
682	}
683
684	// Code to implement the TextUnmarshaler interface for `duration`:
685	//
686	// type duration struct {
687	// 	time.Duration
688	// }
689	//
690	// func (d *duration) UnmarshalText(text []byte) error {
691	// 	var err error
692	// 	d.Duration, err = time.ParseDuration(string(text))
693	// 	return err
694	// }
695
696	for _, s := range favorites.Song {
697		fmt.Printf("%s (%s)\n", s.Name, s.Duration)
698	}
699	// Output:
700	// Thunder Road (4m49s)
701	// Stairway to Heaven (8m3s)
702}
703
704// Example StrictDecoding shows how to detect whether there are keys in the
705// TOML document that weren't decoded into the value given. This is useful
706// for returning an error to the user if they've included extraneous fields
707// in their configuration.
708func Example_strictDecoding() {
709	var blob = `
710key1 = "value1"
711key2 = "value2"
712key3 = "value3"
713`
714	type config struct {
715		Key1 string
716		Key3 string
717	}
718
719	var conf config
720	md, err := Decode(blob, &conf)
721	if err != nil {
722		log.Fatal(err)
723	}
724	fmt.Printf("Undecoded keys: %q\n", md.Undecoded())
725	// Output:
726	// Undecoded keys: ["key2"]
727}
728
729// Example UnmarshalTOML shows how to implement a struct type that knows how to
730// unmarshal itself. The struct must take full responsibility for mapping the
731// values passed into the struct. The method may be used with interfaces in a
732// struct in cases where the actual type is not known until the data is
733// examined.
734func Example_unmarshalTOML() {
735
736	var blob = `
737[[parts]]
738type = "valve"
739id = "valve-1"
740size = 1.2
741rating = 4
742
743[[parts]]
744type = "valve"
745id = "valve-2"
746size = 2.1
747rating = 5
748
749[[parts]]
750type = "pipe"
751id = "pipe-1"
752length = 2.1
753diameter = 12
754
755[[parts]]
756type = "cable"
757id = "cable-1"
758length = 12
759rating = 3.1
760`
761	o := &order{}
762	err := Unmarshal([]byte(blob), o)
763	if err != nil {
764		log.Fatal(err)
765	}
766
767	fmt.Println(len(o.parts))
768
769	for _, part := range o.parts {
770		fmt.Println(part.Name())
771	}
772
773	// Code to implement UmarshalJSON.
774
775	// type order struct {
776	// 	// NOTE `order.parts` is a private slice of type `part` which is an
777	// 	// interface and may only be loaded from toml using the
778	// 	// UnmarshalTOML() method of the Umarshaler interface.
779	// 	parts parts
780	// }
781
782	// func (o *order) UnmarshalTOML(data interface{}) error {
783
784	// 	// NOTE the example below contains detailed type casting to show how
785	// 	// the 'data' is retrieved. In operational use, a type cast wrapper
786	// 	// may be prefered e.g.
787	// 	//
788	// 	// func AsMap(v interface{}) (map[string]interface{}, error) {
789	// 	// 		return v.(map[string]interface{})
790	// 	// }
791	// 	//
792	// 	// resulting in:
793	// 	// d, _ := AsMap(data)
794	// 	//
795
796	// 	d, _ := data.(map[string]interface{})
797	// 	parts, _ := d["parts"].([]map[string]interface{})
798
799	// 	for _, p := range parts {
800
801	// 		typ, _ := p["type"].(string)
802	// 		id, _ := p["id"].(string)
803
804	// 		// detect the type of part and handle each case
805	// 		switch p["type"] {
806	// 		case "valve":
807
808	// 			size := float32(p["size"].(float64))
809	// 			rating := int(p["rating"].(int64))
810
811	// 			valve := &valve{
812	// 				Type:   typ,
813	// 				ID:     id,
814	// 				Size:   size,
815	// 				Rating: rating,
816	// 			}
817
818	// 			o.parts = append(o.parts, valve)
819
820	// 		case "pipe":
821
822	// 			length := float32(p["length"].(float64))
823	// 			diameter := int(p["diameter"].(int64))
824
825	// 			pipe := &pipe{
826	// 				Type:     typ,
827	// 				ID:       id,
828	// 				Length:   length,
829	// 				Diameter: diameter,
830	// 			}
831
832	// 			o.parts = append(o.parts, pipe)
833
834	// 		case "cable":
835
836	// 			length := int(p["length"].(int64))
837	// 			rating := float32(p["rating"].(float64))
838
839	// 			cable := &cable{
840	// 				Type:   typ,
841	// 				ID:     id,
842	// 				Length: length,
843	// 				Rating: rating,
844	// 			}
845
846	// 			o.parts = append(o.parts, cable)
847
848	// 		}
849	// 	}
850
851	// 	return nil
852	// }
853
854	// type parts []part
855
856	// type part interface {
857	// 	Name() string
858	// }
859
860	// type valve struct {
861	// 	Type   string
862	// 	ID     string
863	// 	Size   float32
864	// 	Rating int
865	// }
866
867	// func (v *valve) Name() string {
868	// 	return fmt.Sprintf("VALVE: %s", v.ID)
869	// }
870
871	// type pipe struct {
872	// 	Type     string
873	// 	ID       string
874	// 	Length   float32
875	// 	Diameter int
876	// }
877
878	// func (p *pipe) Name() string {
879	// 	return fmt.Sprintf("PIPE: %s", p.ID)
880	// }
881
882	// type cable struct {
883	// 	Type   string
884	// 	ID     string
885	// 	Length int
886	// 	Rating float32
887	// }
888
889	// func (c *cable) Name() string {
890	// 	return fmt.Sprintf("CABLE: %s", c.ID)
891	// }
892
893	// Output:
894	// 4
895	// VALVE: valve-1
896	// VALVE: valve-2
897	// PIPE: pipe-1
898	// CABLE: cable-1
899
900}
901
902type order struct {
903	// NOTE `order.parts` is a private slice of type `part` which is an
904	// interface and may only be loaded from toml using the UnmarshalTOML()
905	// method of the Umarshaler interface.
906	parts parts
907}
908
909func (o *order) UnmarshalTOML(data interface{}) error {
910
911	// NOTE the example below contains detailed type casting to show how
912	// the 'data' is retrieved. In operational use, a type cast wrapper
913	// may be prefered e.g.
914	//
915	// func AsMap(v interface{}) (map[string]interface{}, error) {
916	// 		return v.(map[string]interface{})
917	// }
918	//
919	// resulting in:
920	// d, _ := AsMap(data)
921	//
922
923	d, _ := data.(map[string]interface{})
924	parts, _ := d["parts"].([]map[string]interface{})
925
926	for _, p := range parts {
927
928		typ, _ := p["type"].(string)
929		id, _ := p["id"].(string)
930
931		// detect the type of part and handle each case
932		switch p["type"] {
933		case "valve":
934
935			size := float32(p["size"].(float64))
936			rating := int(p["rating"].(int64))
937
938			valve := &valve{
939				Type:   typ,
940				ID:     id,
941				Size:   size,
942				Rating: rating,
943			}
944
945			o.parts = append(o.parts, valve)
946
947		case "pipe":
948
949			length := float32(p["length"].(float64))
950			diameter := int(p["diameter"].(int64))
951
952			pipe := &pipe{
953				Type:     typ,
954				ID:       id,
955				Length:   length,
956				Diameter: diameter,
957			}
958
959			o.parts = append(o.parts, pipe)
960
961		case "cable":
962
963			length := int(p["length"].(int64))
964			rating := float32(p["rating"].(float64))
965
966			cable := &cable{
967				Type:   typ,
968				ID:     id,
969				Length: length,
970				Rating: rating,
971			}
972
973			o.parts = append(o.parts, cable)
974
975		}
976	}
977
978	return nil
979}
980
981type parts []part
982
983type part interface {
984	Name() string
985}
986
987type valve struct {
988	Type   string
989	ID     string
990	Size   float32
991	Rating int
992}
993
994func (v *valve) Name() string {
995	return fmt.Sprintf("VALVE: %s", v.ID)
996}
997
998type pipe struct {
999	Type     string
1000	ID       string
1001	Length   float32
1002	Diameter int
1003}
1004
1005func (p *pipe) Name() string {
1006	return fmt.Sprintf("PIPE: %s", p.ID)
1007}
1008
1009type cable struct {
1010	Type   string
1011	ID     string
1012	Length int
1013	Rating float32
1014}
1015
1016func (c *cable) Name() string {
1017	return fmt.Sprintf("CABLE: %s", c.ID)
1018}
1019