mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 03:38:42 -07:00
Fix JSON compatibility in conf format (#4418)
Fixes a side effect from #4394 which was breaking support for JSON configs.
This commit is contained in:
@@ -78,6 +78,7 @@ const (
|
|||||||
topOptTerm = '}'
|
topOptTerm = '}'
|
||||||
blockStart = '('
|
blockStart = '('
|
||||||
blockEnd = ')'
|
blockEnd = ')'
|
||||||
|
mapEndString = string(mapEnd)
|
||||||
)
|
)
|
||||||
|
|
||||||
type stateFn func(lx *lexer) stateFn
|
type stateFn func(lx *lexer) stateFn
|
||||||
|
|||||||
@@ -1471,6 +1471,8 @@ func TestJSONCompat(t *testing.T) {
|
|||||||
expected: []item{
|
expected: []item{
|
||||||
{itemKey, "http_port", 3, 28},
|
{itemKey, "http_port", 3, 28},
|
||||||
{itemInteger, "8223", 3, 40},
|
{itemInteger, "8223", 3, 40},
|
||||||
|
{itemKey, "}", 4, 25},
|
||||||
|
{itemEOF, "", 0, 0},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1486,6 +1488,8 @@ func TestJSONCompat(t *testing.T) {
|
|||||||
{itemInteger, "8223", 3, 40},
|
{itemInteger, "8223", 3, 40},
|
||||||
{itemKey, "port", 4, 28},
|
{itemKey, "port", 4, 28},
|
||||||
{itemInteger, "4223", 4, 35},
|
{itemInteger, "4223", 4, 35},
|
||||||
|
{itemKey, "}", 5, 25},
|
||||||
|
{itemEOF, "", 0, 0},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1510,6 +1514,8 @@ func TestJSONCompat(t *testing.T) {
|
|||||||
{itemBool, "true", 6, 36},
|
{itemBool, "true", 6, 36},
|
||||||
{itemKey, "max_control_line", 7, 28},
|
{itemKey, "max_control_line", 7, 28},
|
||||||
{itemInteger, "1024", 7, 47},
|
{itemInteger, "1024", 7, 47},
|
||||||
|
{itemKey, "}", 8, 25},
|
||||||
|
{itemEOF, "", 0, 0},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1521,6 +1527,7 @@ func TestJSONCompat(t *testing.T) {
|
|||||||
{itemInteger, "8224", 1, 14},
|
{itemInteger, "8224", 1, 14},
|
||||||
{itemKey, "port", 1, 20},
|
{itemKey, "port", 1, 20},
|
||||||
{itemInteger, "4224", 1, 27},
|
{itemInteger, "4224", 1, 27},
|
||||||
|
{itemEOF, "", 0, 0},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1533,6 +1540,8 @@ func TestJSONCompat(t *testing.T) {
|
|||||||
{itemInteger, "8225", 1, 14},
|
{itemInteger, "8225", 1, 14},
|
||||||
{itemKey, "port", 1, 20},
|
{itemKey, "port", 1, 20},
|
||||||
{itemInteger, "4225", 1, 27},
|
{itemInteger, "4225", 1, 27},
|
||||||
|
{itemKey, "}", 2, 25},
|
||||||
|
{itemEOF, "", 0, 0},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1557,6 +1566,8 @@ func TestJSONCompat(t *testing.T) {
|
|||||||
{itemString, "nats://127.0.0.1:4224", 1, 140},
|
{itemString, "nats://127.0.0.1:4224", 1, 140},
|
||||||
{itemArrayEnd, "", 1, 163},
|
{itemArrayEnd, "", 1, 163},
|
||||||
{itemMapEnd, "", 1, 164},
|
{itemMapEnd, "", 1, 164},
|
||||||
|
{itemKey, "}", 14, 25},
|
||||||
|
{itemEOF, "", 0, 0},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1594,6 +1605,35 @@ func TestJSONCompat(t *testing.T) {
|
|||||||
{itemString, "nats://127.0.0.1:4224", 11, 32},
|
{itemString, "nats://127.0.0.1:4224", 11, 32},
|
||||||
{itemArrayEnd, "", 12, 30},
|
{itemArrayEnd, "", 12, 30},
|
||||||
{itemMapEnd, "", 13, 28},
|
{itemMapEnd, "", 13, 28},
|
||||||
|
{itemKey, "}", 14, 25},
|
||||||
|
{itemEOF, "", 0, 0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should support JSON with blocks",
|
||||||
|
input: `{
|
||||||
|
"jetstream": {
|
||||||
|
"store_dir": "/tmp/nats"
|
||||||
|
"max_mem": 1000000,
|
||||||
|
},
|
||||||
|
"port": 4222,
|
||||||
|
"server_name": "nats1"
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expected: []item{
|
||||||
|
{itemKey, "jetstream", 2, 28},
|
||||||
|
{itemMapStart, "", 2, 41},
|
||||||
|
{itemKey, "store_dir", 3, 30},
|
||||||
|
{itemString, "/tmp/nats", 3, 43},
|
||||||
|
{itemKey, "max_mem", 4, 30},
|
||||||
|
{itemInteger, "1000000", 4, 40},
|
||||||
|
{itemMapEnd, "", 5, 28},
|
||||||
|
{itemKey, "port", 6, 28},
|
||||||
|
{itemInteger, "4222", 6, 35},
|
||||||
|
{itemKey, "server_name", 7, 28},
|
||||||
|
{itemString, "nats1", 7, 43},
|
||||||
|
{itemKey, "}", 8, 25},
|
||||||
|
{itemEOF, "", 0, 0},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
|
|||||||
@@ -137,16 +137,18 @@ func parse(data, fp string, pedantic bool) (p *parser, err error) {
|
|||||||
}
|
}
|
||||||
p.pushContext(p.mapping)
|
p.pushContext(p.mapping)
|
||||||
|
|
||||||
var prevItem itemType
|
var prevItem item
|
||||||
for {
|
for {
|
||||||
it := p.next()
|
it := p.next()
|
||||||
if it.typ == itemEOF {
|
if it.typ == itemEOF {
|
||||||
if prevItem == itemKey {
|
// Here we allow the final character to be a bracket '}'
|
||||||
|
// in order to support JSON like configurations.
|
||||||
|
if prevItem.typ == itemKey && prevItem.val != mapEndString {
|
||||||
return nil, fmt.Errorf("config is invalid (%s:%d:%d)", fp, it.line, it.pos)
|
return nil, fmt.Errorf("config is invalid (%s:%d:%d)", fp, it.line, it.pos)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
prevItem = it.typ
|
prevItem = it
|
||||||
if err := p.processItem(it, fp); err != nil {
|
if err := p.processItem(it, fp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -442,15 +442,6 @@ func TestParseWithNoValuesAreInvalid(t *testing.T) {
|
|||||||
`,
|
`,
|
||||||
"config is invalid (:3:25)",
|
"config is invalid (:3:25)",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
// trailing brackets accidentally can become keys, these are also invalid.
|
|
||||||
"trailing brackets after config",
|
|
||||||
`
|
|
||||||
accounts { users = [{}]}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
"config is invalid (:4:25)",
|
|
||||||
},
|
|
||||||
} {
|
} {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
if _, err := parse(test.conf, "", true); err == nil {
|
if _, err := parse(test.conf, "", true); err == nil {
|
||||||
@@ -494,6 +485,48 @@ func TestParseWithNoValuesEmptyConfigsAreValid(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseWithTrailingBracketsAreValid(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
conf string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"empty conf",
|
||||||
|
"{}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"just comments with no values",
|
||||||
|
`
|
||||||
|
{
|
||||||
|
# comments in the body
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// trailing brackets accidentally can become keys,
|
||||||
|
// this is valid since needed to support JSON like configs..
|
||||||
|
"trailing brackets after config",
|
||||||
|
`
|
||||||
|
accounts { users = [{}]}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"wrapped in brackets",
|
||||||
|
`{
|
||||||
|
accounts { users = [{}]}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
if _, err := parse(test.conf, "", true); err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseWithNoValuesIncludes(t *testing.T) {
|
func TestParseWithNoValuesIncludes(t *testing.T) {
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
input string
|
input string
|
||||||
@@ -564,3 +597,146 @@ func TestParseWithNoValuesIncludes(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJSONParseCompat(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
includes map[string]string
|
||||||
|
expected map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"JSON with nested blocks",
|
||||||
|
`
|
||||||
|
{
|
||||||
|
"http_port": 8227,
|
||||||
|
"port": 4227,
|
||||||
|
"write_deadline": "1h",
|
||||||
|
"cluster": {
|
||||||
|
"port": 6222,
|
||||||
|
"routes": [
|
||||||
|
"nats://127.0.0.1:4222",
|
||||||
|
"nats://127.0.0.1:4223",
|
||||||
|
"nats://127.0.0.1:4224"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
nil,
|
||||||
|
map[string]interface{}{
|
||||||
|
"http_port": int64(8227),
|
||||||
|
"port": int64(4227),
|
||||||
|
"write_deadline": "1h",
|
||||||
|
"cluster": map[string]interface{}{
|
||||||
|
"port": int64(6222),
|
||||||
|
"routes": []interface{}{
|
||||||
|
"nats://127.0.0.1:4222",
|
||||||
|
"nats://127.0.0.1:4223",
|
||||||
|
"nats://127.0.0.1:4224",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"JSON with nested blocks",
|
||||||
|
`{
|
||||||
|
"jetstream": {
|
||||||
|
"store_dir": "/tmp/nats"
|
||||||
|
"max_mem": 1000000,
|
||||||
|
},
|
||||||
|
"port": 4222,
|
||||||
|
"server_name": "nats1"
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
nil,
|
||||||
|
map[string]interface{}{
|
||||||
|
"jetstream": map[string]interface{}{
|
||||||
|
"store_dir": "/tmp/nats",
|
||||||
|
"max_mem": int64(1_000_000),
|
||||||
|
},
|
||||||
|
"port": int64(4222),
|
||||||
|
"server_name": "nats1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"JSON empty object in one line",
|
||||||
|
`{}`,
|
||||||
|
nil,
|
||||||
|
map[string]interface{}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"JSON empty object with line breaks",
|
||||||
|
`
|
||||||
|
{
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
nil,
|
||||||
|
map[string]interface{}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"JSON includes",
|
||||||
|
`
|
||||||
|
accounts {
|
||||||
|
foo { include 'foo.json' }
|
||||||
|
bar { include 'bar.json' }
|
||||||
|
quux { include 'quux.json' }
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
map[string]string{
|
||||||
|
"foo.json": `{ "users": [ {"user": "foo"} ] }`,
|
||||||
|
"bar.json": `{
|
||||||
|
"users": [ {"user": "bar"} ]
|
||||||
|
}`,
|
||||||
|
"quux.json": `{}`,
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"accounts": map[string]interface{}{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"users": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"user": "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"bar": map[string]interface{}{
|
||||||
|
"users": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"user": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"quux": map[string]interface{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
sdir := t.TempDir()
|
||||||
|
f, err := os.CreateTemp(sdir, "nats.conf-")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(f.Name(), []byte(test.input), 066); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if test.includes != nil {
|
||||||
|
for includeFile, contents := range test.includes {
|
||||||
|
inf, err := os.Create(filepath.Join(sdir, includeFile))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(inf.Name(), []byte(contents), 066); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m, err := ParseFile(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(m, test.expected) {
|
||||||
|
t.Fatalf("Not Equal:\nReceived: '%+v'\nExpected: '%+v'\n", m, test.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user