decode_test.go 18 KB

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