package ast_test import ( "reflect" "testing" "git.makaay.nl/mauricem/go-toml/ast" ) func Test_ConstructSlightlyComplexStructure(t *testing.T) { testAST(t, func() (error, *ast.Document) { p := ast.NewDocument() p.SetKeyValuePair(ast.NewKey("ding"), ast.NewValue(ast.TypeInteger, 10)) p.SetKeyValuePair(ast.NewKey("dong"), ast.NewValue(ast.TypeString, "not a song")) p.OpenTable(ast.NewKey("key1", "key2 a")) p.SetKeyValuePair(ast.NewKey("dooh"), ast.NewValue(ast.TypeBool, true)) p.SetKeyValuePair(ast.NewKey("dah"), ast.NewValue(ast.TypeBool, false)) p.OpenTable(ast.NewKey("key1", "key2 b")) p.SetKeyValuePair(ast.NewKey("dieh"), ast.NewValue(ast.TypeFloat, 1.111)) p.SetKeyValuePair(ast.NewKey("duh"), ast.NewValue(ast.TypeFloat, 1.18e-12)) arr := ast.NewArray() arr.Append(ast.NewValue(ast.TypeInteger, 1)) arr.Append(ast.NewValue(ast.TypeInteger, 2)) p.SetKeyValuePair(ast.NewKey("foo", "bar"), ast.NewValue(ast.TypeArray, arr)) p.OpenArrayOfTables(ast.NewKey("aaah", "table array")) p.SetKeyValuePair(ast.NewKey("a"), ast.NewValue(ast.TypeFloat, 1.234)) p.OpenArrayOfTables(ast.NewKey("aaah", "table array")) p.SetKeyValuePair(ast.NewKey("b"), ast.NewValue(ast.TypeFloat, 2.345)) p.SetKeyValuePair(ast.NewKey("c"), ast.NewValue(ast.TypeString, "bingo!")) p.OpenArrayOfTables(ast.NewKey("aaah", "table array")) return nil, p }, "", `{"aaah": {"table array": [{"a": 1.234}, {"b": 2.345, "c": "bingo!"}, {}]}, `+ `"ding": 10, "dong": "not a song", `+ `"key1": {"key2 a": {"dah": false, "dooh": true}, "key2 b": {"dieh": 1.111, "duh": 1.18e-12, "foo": {"bar": [1, 2]}}}}`) } // This document structure represents the actual structure of the example for nested // arrays of tables from the TOML 0.5.0 specficiation. func Test_ConstructNestedArraysOfTables(t *testing.T) { testAST(t, func() (error, *ast.Document) { p := ast.NewDocument() p.OpenArrayOfTables(ast.NewKey("fruit")) p.SetKeyValuePair(ast.NewKey("name"), ast.NewValue(ast.TypeString, "apple")) p.OpenTable(ast.NewKey("fruit", "physical")) p.SetKeyValuePair(ast.NewKey("color"), ast.NewValue(ast.TypeString, "red")) p.SetKeyValuePair(ast.NewKey("shape"), ast.NewValue(ast.TypeString, "round")) p.OpenArrayOfTables(ast.NewKey("fruit", "variety")) p.SetKeyValuePair(ast.NewKey("name"), ast.NewValue(ast.TypeString, "red delicious")) p.OpenArrayOfTables(ast.NewKey("fruit", "variety")) p.SetKeyValuePair(ast.NewKey("name"), ast.NewValue(ast.TypeString, "granny smith")) p.OpenArrayOfTables(ast.NewKey("fruit")) p.SetKeyValuePair(ast.NewKey("name"), ast.NewValue(ast.TypeString, "banana")) p.OpenArrayOfTables(ast.NewKey("fruit", "variety")) p.SetKeyValuePair(ast.NewKey("name"), ast.NewValue(ast.TypeString, "plantain")) return nil, p }, "", `{"fruit": [`+ `{"name": "apple", "physical": {"color": "red", "shape": "round"}, "variety": [{"name": "red delicious"}, {"name": "granny smith"}]}, `+ `{"name": "banana", "variety": [{"name": "plantain"}]}`+ `]}`) } // This is a case from the BurntSushi test set which my parser did not correctly // handle. From the specs, it was unclear to me that is was okay to handle things // in this way. The actual TOML document that would lead to this looks like: // // [a.b.c] // answer = 42 // // [a] // better = 43 func Test_ConstructExplicitTableAfterImplicitSubtable(t *testing.T) { testAST(t, func() (error, *ast.Document) { p := ast.NewDocument() p.OpenTable(ast.NewKey("a", "b", "c")) p.SetKeyValuePair(ast.NewKey("answer"), ast.NewValue(ast.TypeString, "42")) err := p.OpenTable(ast.NewKey("a")) p.SetKeyValuePair(ast.NewKey("better"), ast.NewValue(ast.TypeString, "43")) return err, p }, "", `{"a": {"b": {"c": {"answer": "42"}}, "better": "43"}}`) } func Test_EmptyKeyForCreatingTablePath_Panics(t *testing.T) { defer func() { r := recover() if r.(string) != "makeTablePath(): empty key provided; a key must have at least one key part" { t.Fatalf("Did not get the expected panic message") } }() p := ast.NewDocument() p.OpenTable(ast.NewKey()) } func Test_StoreValueInRooTypeTable(t *testing.T) { testAST(t, func() (error, *ast.Document) { p := ast.NewDocument() p.SetKeyValuePair(ast.NewKey("key1"), ast.NewValue(ast.TypeString, "value1")) return p.SetKeyValuePair(ast.NewKey("key2"), ast.NewValue(ast.TypeString, "value2")), p }, "", `{"key1": "value1", "key2": "value2"}`) } func Test_StoreValueWithMultipartKey_CreatesTableHierarchy(t *testing.T) { testAST(t, func() (error, *ast.Document) { p := ast.NewDocument() return p.SetKeyValuePair(ast.NewKey("key1", "key2", "key3"), ast.NewValue(ast.TypeString, "value")), p }, "", `{"key1": {"key2": {"key3": "value"}}}`) } func Test_StoreDuplicateKeyInRooTypeTable_ReturnsError(t *testing.T) { testAST(t, func() (error, *ast.Document) { p := ast.NewDocument() p.SetKeyValuePair(ast.NewKey("key"), ast.NewValue(ast.TypeString, "value")) return p.SetKeyValuePair(ast.NewKey("key"), ast.NewValue(ast.TypeInteger, 321)), p }, `invalid key/value pair: string value already exists at key [key]`, `{"key": "value"}`) } func Test_StoreValueWithMultipartKey_UnderSubtable_CreatesTableHierarchy(t *testing.T) { testAST(t, func() (error, *ast.Document) { p := ast.NewDocument() p.OpenTable(ast.NewKey("tablekey1", "tablekey2")) return p.SetKeyValuePair(ast.NewKey("valuekey1", "valuekey2", "valuekey3"), ast.NewValue(ast.TypeString, "value")), p }, "", `{"tablekey1": {"tablekey2": {"valuekey1": {"valuekey2": {"valuekey3": "value"}}}}}`) } func Test_StoreKeyPathWherePathContainsNonTableAlready_ReturnsError(t *testing.T) { testAST(t, func() (error, *ast.Document) { p := ast.NewDocument() p.OpenTable(ast.NewKey("key1")) p.SetKeyValuePair(ast.NewKey("key2"), ast.NewValue(ast.TypeInteger, 0)) return p.SetKeyValuePair(ast.NewKey("key2", "key3"), ast.NewValue(ast.TypeString, "value")), p }, `invalid key/value pair: integer value already exists at key [key1->key2]`, `{"key1": {"key2": 0}}`) } func Test_GivenExistingTableAtKey_CreatingTableAtSameKey_ReturnsError(t *testing.T) { testAST(t, func() (error, *ast.Document) { p := ast.NewDocument() p.OpenTable(ast.NewKey("key1", "key2")) return p.OpenTable(ast.NewKey("key1", "key2")), p }, `invalid table: table value already exists at key [key1->key2]`, `{"key1": {"key2": {}}}`) } func Test_GivenExistingValueAtKey_CreatingTableAtSameKey_ReturnsError(t *testing.T) { testAST(t, func() (error, *ast.Document) { p := ast.NewDocument() p.SetKeyValuePair(ast.NewKey("key"), ast.NewValue(ast.TypeString, "value")) return p.OpenTable(ast.NewKey("key")), p }, `invalid table: string value already exists at key [key]`, `{"key": "value"}`) } func Test_GivenExistingValueInKeyPath_CreatingTable_ReturnsError(t *testing.T) { testAST(t, func() (error, *ast.Document) { p := ast.NewDocument() p.SetKeyValuePair(ast.NewKey("key"), ast.NewValue(ast.TypeString, "value")) return p.OpenTable(ast.NewKey("key", "subkey")), p }, `invalid table: string value already exists at key [key]`, `{"key": "value"}`) } func Test_GivenExistingValueAtDeepKey_CreatingTableAtSameKey_ReturnsError(t *testing.T) { testAST(t, func() (error, *ast.Document) { p := ast.NewDocument() p.OpenTable(ast.NewKey("deep", "table")) p.SetKeyValuePair(ast.NewKey("key"), ast.NewValue(ast.TypeInteger, 0)) return p.OpenTable(ast.NewKey("deep", "table", "key")), p }, `invalid table: integer value already exists at key [deep->table->key]`, `{"deep": {"table": {"key": 0}}}`) } func Test_GivenExistingValueAtDeepKeyFromSubTable_CreatingTableAtSameKey_ReturnsError(t *testing.T) { testAST(t, func() (error, *ast.Document) { p := ast.NewDocument() p.OpenTable(ast.NewKey("deep", "table")) p.SetKeyValuePair(ast.NewKey("key1", "key2"), ast.NewValue(ast.TypeInteger, 0)) return p.SetKeyValuePair(ast.NewKey("key1", "key2"), ast.NewValue(ast.TypeBool, true)), p }, // This test mainly tests the formatting of [deep->table->key1->key2], being a concatenation // of the currently active table plus the multipart key for SetKeyValuePair(). `invalid key/value pair: integer value already exists at key [deep->table->key1->key2]`, `{"deep": {"table": {"key1": {"key2": 0}}}}`) } func Test_FormattingOfQuotedPathPartInError(t *testing.T) { testAST(t, func() (error, *ast.Document) { p := ast.NewDocument() p.OpenTable(ast.NewKey("must be quoted")) p.SetKeyValuePair(ast.NewKey("this one too"), ast.NewValue(ast.TypeInteger, 0)) return p.SetKeyValuePair(ast.NewKey("this one too"), ast.NewValue(ast.TypeInteger, 0)), p }, `invalid key/value pair: integer value already exists at key ["must be quoted"->"this one too"]`, `{"must be quoted": {"this one too": 0}}`) } func Test_GivenExistingValueAtKey_CreatingArrayOfTablesAtSameKey_ReturnsError(t *testing.T) { testAST(t, func() (error, *ast.Document) { p := ast.NewDocument() p.Root["key"] = ast.NewValue(ast.TypeString, "value") return p.OpenArrayOfTables(ast.NewKey("key")), p }, `invalid table array: string value already exists at key [key]`, `{"key": "value"}`) } func Test_GivenExistingValueInKeyPath_CreatingArrayOfTables_ReturnsError(t *testing.T) { testAST(t, func() (error, *ast.Document) { p := ast.NewDocument() p.Root["key"] = ast.NewValue(ast.TypeString, "value") return p.OpenArrayOfTables(ast.NewKey("key", "subkey")), p }, `invalid table array: string value already exists at key [key]`, `{"key": "value"}`) } func testAST(t *testing.T, code func() (error, *ast.Document), expectedError string, expectedData string) { err, p := code() if expectedError == "" { if err != nil { t.Fatalf("Unexpected error: %s", err) } } else { if err == nil { t.Fatalf("An error was expected, but no error was returned") } else if err.Error() != expectedError { t.Fatalf("Unexpected error:\nexpected: %s\nactual: %s\n", expectedError, err.Error()) } } if expectedData == "" { return } if expectedData != p.Root.String() { t.Fatalf("Unexpected data after parsing:\nexpected: %s\nactual: %s\n", expectedData, p.Root.String()) } } func Test_NewArray(t *testing.T) { a := ast.NewArray() if a.ItemType != "" { t.Fatalf("New array unexpectedly has a Type set to %q", a.ItemType) } if a.First != nil { t.Fatalf("New array unexpectedly has the First item set") } if a.Last != nil { t.Fatalf("New array unexpectedly has the Last item set") } if a.Length != 0 { t.Fatalf("New array unexpectedly has the Length set to %d", a.Length) } } func Test_AppendOneValueToArray(t *testing.T) { a := ast.NewArray() value := ast.NewValue(ast.TypeString, "Hi!") if err := a.Append(value); err != nil { t.Fatalf("Unexpected error while adding string value to array: %s", err) } if a.Length != 1 { t.Fatalf("Expected Array.Length 1, but got %d", a.Length) } if a.First != a.Last { t.Fatalf("Array.First and Array.Last do not point to the same ArrayItem") } if a.First.Value != value { t.Fatalf("Array.First.Value does not point to the appended string value") } if a.First.Next != nil { t.Fatalf("Array.First unexpectedly has Next set") } } func Test_AppendTwoValuesToArray(t *testing.T) { a := ast.NewArray() value1 := ast.NewValue(ast.TypeString, "Hi!") value2 := ast.NewValue(ast.TypeString, "Hello!") a.Append(value1) if err := a.Append(value2); err != nil { t.Fatalf("Unexpected error while adding second string value to Array: %s", err) } if a.Length != 2 { t.Fatalf("Array.Length unexpected not 2, but %d", a.Length) } if a.First == a.Last { t.Fatalf("Array.First and Array.Last unexpectedly point to the same ArrayItem") } if a.First.Next != a.Last { t.Fatalf("Array.First.Next unexpectedly does not point to Array.Last") } if a.Last.Value != value2 { t.Fatalf("Array.Last.Value does not point to the second appended string value") } } func Test_AppendMultipleValuesToArray(t *testing.T) { a := ast.NewArray() a.Append(ast.NewValue(ast.TypeInteger, 1)) a.Append(ast.NewValue(ast.TypeInteger, 2)) a.Append(ast.NewValue(ast.TypeInteger, 3)) a.Append(ast.NewValue(ast.TypeInteger, 4)) x := make([]int, 0, a.Length) for i := a.First; i != nil; i = i.Next { x = append(x, i.Value.Data[0].(int)) } if !reflect.DeepEqual(x, []int{1, 2, 3, 4}) { t.Fatalf("Array contents do not match Array [1, 2, 3, 4]") } } func Test_GivenValuesOfDifferentTypes_WhenAppendingThemToArray_AnErrorIsReturned(t *testing.T) { a := ast.NewArray() a.Append(ast.NewValue(ast.TypeString, "first one is string")) err := a.Append(ast.NewValue(ast.TypeInteger, 2)) // second one an integer if err == nil { t.Fatalf("Unexpectedly, no error was returned while adding an integer to an array of strings") } expected := "type mismatch in array of strings: found an item of type integer" if err.Error() != expected { t.Fatalf("Unexpected error from invalid Append():\nexpected: %q\nactual: %q", expected, err.Error()) } }