mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-05 18:44:58 +03:00
caddyfile: Change JSON format to use arrays, not objects
Since a directive can appear on multiple lines, the object syntax wasn't working well. This also fixes several other serialization bugs.
This commit is contained in:
parent
13557eb5ef
commit
c31e86db02
2 changed files with 100 additions and 50 deletions
|
@ -5,6 +5,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -23,18 +24,29 @@ func ToJSON(caddyfile []byte) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, sb := range serverBlocks {
|
for _, sb := range serverBlocks {
|
||||||
block := ServerBlock{Body: make(map[string]interface{})}
|
block := ServerBlock{Body: [][]interface{}{}}
|
||||||
|
|
||||||
|
// Fill up host list
|
||||||
for _, host := range sb.HostList() {
|
for _, host := range sb.HostList() {
|
||||||
block.Hosts = append(block.Hosts, strings.TrimSuffix(host, ":"))
|
block.Hosts = append(block.Hosts, strings.TrimSuffix(host, ":"))
|
||||||
}
|
}
|
||||||
|
|
||||||
for dir, tokens := range sb.Tokens {
|
// Extract directives deterministically by sorting them
|
||||||
disp := parse.NewDispenserTokens(filename, tokens)
|
var directives = make([]string, len(sb.Tokens))
|
||||||
disp.Next() // the first token is the directive; skip it
|
for dir := range sb.Tokens {
|
||||||
block.Body[dir] = constructLine(disp)
|
directives = append(directives, dir)
|
||||||
|
}
|
||||||
|
sort.Strings(directives)
|
||||||
|
|
||||||
|
// Convert each directive's tokens into our JSON structure
|
||||||
|
for _, dir := range directives {
|
||||||
|
disp := parse.NewDispenserTokens(filename, sb.Tokens[dir])
|
||||||
|
for disp.Next() {
|
||||||
|
block.Body = append(block.Body, constructLine(&disp))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tack this block onto the end of the list
|
||||||
j = append(j, block)
|
j = append(j, block)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,17 +62,18 @@ func ToJSON(caddyfile []byte) ([]byte, error) {
|
||||||
// but only one line at a time, to be used at the top-level of
|
// but only one line at a time, to be used at the top-level of
|
||||||
// a server block only (where the first token on each line is a
|
// a server block only (where the first token on each line is a
|
||||||
// directive) - not to be used at any other nesting level.
|
// directive) - not to be used at any other nesting level.
|
||||||
func constructLine(d parse.Dispenser) interface{} {
|
// goes to end of line
|
||||||
|
func constructLine(d *parse.Dispenser) []interface{} {
|
||||||
var args []interface{}
|
var args []interface{}
|
||||||
|
|
||||||
all := d.RemainingArgs()
|
args = append(args, d.Val())
|
||||||
for _, arg := range all {
|
|
||||||
args = append(args, arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
d.Next()
|
for d.NextArg() {
|
||||||
if d.Val() == "{" {
|
if d.Val() == "{" {
|
||||||
args = append(args, constructBlock(d))
|
args = append(args, constructBlock(d))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
args = append(args, d.Val())
|
||||||
}
|
}
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
@ -68,26 +81,15 @@ func constructLine(d parse.Dispenser) interface{} {
|
||||||
|
|
||||||
// constructBlock recursively processes tokens into a
|
// constructBlock recursively processes tokens into a
|
||||||
// JSON-encodable structure.
|
// JSON-encodable structure.
|
||||||
func constructBlock(d parse.Dispenser) interface{} {
|
// goes to end of block
|
||||||
block := make(map[string]interface{})
|
func constructBlock(d *parse.Dispenser) [][]interface{} {
|
||||||
|
block := [][]interface{}{}
|
||||||
|
|
||||||
for d.Next() {
|
for d.Next() {
|
||||||
if d.Val() == "}" {
|
if d.Val() == "}" {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
block = append(block, constructLine(d))
|
||||||
dir := d.Val()
|
|
||||||
all := d.RemainingArgs()
|
|
||||||
|
|
||||||
var args []interface{}
|
|
||||||
for _, arg := range all {
|
|
||||||
args = append(args, arg)
|
|
||||||
}
|
|
||||||
if d.Val() == "{" {
|
|
||||||
args = append(args, constructBlock(d))
|
|
||||||
}
|
|
||||||
|
|
||||||
block[dir] = args
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return block
|
return block
|
||||||
|
@ -103,7 +105,10 @@ func FromJSON(jsonBytes []byte) ([]byte, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, sb := range j {
|
for sbPos, sb := range j {
|
||||||
|
if sbPos > 0 {
|
||||||
|
result += "\n\n"
|
||||||
|
}
|
||||||
for i, host := range sb.Hosts {
|
for i, host := range sb.Hosts {
|
||||||
if hostname, port, err := net.SplitHostPort(host); err == nil {
|
if hostname, port, err := net.SplitHostPort(host); err == nil {
|
||||||
if port == "http" || port == "https" {
|
if port == "http" || port == "https" {
|
||||||
|
@ -129,26 +134,36 @@ func jsonToText(scope interface{}, depth int) string {
|
||||||
switch val := scope.(type) {
|
switch val := scope.(type) {
|
||||||
case string:
|
case string:
|
||||||
if strings.ContainsAny(val, "\" \n\t\r") {
|
if strings.ContainsAny(val, "\" \n\t\r") {
|
||||||
result += ` "` + strings.Replace(val, "\"", "\\\"", -1) + `"`
|
result += `"` + strings.Replace(val, "\"", "\\\"", -1) + `"`
|
||||||
} else {
|
} else {
|
||||||
result += " " + val
|
result += val
|
||||||
}
|
}
|
||||||
case int:
|
case int:
|
||||||
result += " " + strconv.Itoa(val)
|
result += strconv.Itoa(val)
|
||||||
case float64:
|
case float64:
|
||||||
result += " " + fmt.Sprintf("%v", val)
|
result += fmt.Sprintf("%v", val)
|
||||||
case bool:
|
case bool:
|
||||||
result += " " + fmt.Sprintf("%t", val)
|
result += fmt.Sprintf("%t", val)
|
||||||
case map[string]interface{}:
|
case [][]interface{}:
|
||||||
result += " {\n"
|
result += " {\n"
|
||||||
for param, args := range val {
|
for _, arg := range val {
|
||||||
result += strings.Repeat("\t", depth) + param
|
result += strings.Repeat("\t", depth) + jsonToText(arg, depth+1) + "\n"
|
||||||
result += jsonToText(args, depth+1) + "\n"
|
|
||||||
}
|
}
|
||||||
result += strings.Repeat("\t", depth-1) + "}"
|
result += strings.Repeat("\t", depth-1) + "}"
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
for _, v := range val {
|
for i, v := range val {
|
||||||
|
if block, ok := v.([]interface{}); ok {
|
||||||
|
result += "{\n"
|
||||||
|
for _, arg := range block {
|
||||||
|
result += strings.Repeat("\t", depth) + jsonToText(arg, depth+1) + "\n"
|
||||||
|
}
|
||||||
|
result += strings.Repeat("\t", depth-1) + "}"
|
||||||
|
continue
|
||||||
|
}
|
||||||
result += jsonToText(v, depth)
|
result += jsonToText(v, depth)
|
||||||
|
if i < len(val)-1 {
|
||||||
|
result += " "
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,5 +176,5 @@ type Caddyfile []ServerBlock
|
||||||
// ServerBlock represents a server block.
|
// ServerBlock represents a server block.
|
||||||
type ServerBlock struct {
|
type ServerBlock struct {
|
||||||
Hosts []string `json:"hosts"`
|
Hosts []string `json:"hosts"`
|
||||||
Body map[string]interface{} `json:"body"`
|
Body [][]interface{} `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ var tests = []struct {
|
||||||
caddyfile: `foo {
|
caddyfile: `foo {
|
||||||
root /bar
|
root /bar
|
||||||
}`,
|
}`,
|
||||||
json: `[{"hosts":["foo"],"body":{"root":["/bar"]}}]`,
|
json: `[{"hosts":["foo"],"body":[["root","/bar"]]}]`,
|
||||||
},
|
},
|
||||||
{ // 1
|
{ // 1
|
||||||
caddyfile: `host1, host2 {
|
caddyfile: `host1, host2 {
|
||||||
|
@ -17,52 +17,87 @@ var tests = []struct {
|
||||||
def
|
def
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
json: `[{"hosts":["host1","host2"],"body":{"dir":[{"def":null}]}}]`,
|
json: `[{"hosts":["host1","host2"],"body":[["dir",[["def"]]]]}]`,
|
||||||
},
|
},
|
||||||
{ // 2
|
{ // 2
|
||||||
caddyfile: `host1, host2 {
|
caddyfile: `host1, host2 {
|
||||||
dir abc {
|
dir abc {
|
||||||
def ghi
|
def ghi
|
||||||
|
jkl
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
json: `[{"hosts":["host1","host2"],"body":{"dir":["abc",{"def":["ghi"]}]}}]`,
|
json: `[{"hosts":["host1","host2"],"body":[["dir","abc",[["def","ghi"],["jkl"]]]]}]`,
|
||||||
},
|
},
|
||||||
{ // 3
|
{ // 3
|
||||||
caddyfile: `host1:1234, host2:5678 {
|
caddyfile: `host1:1234, host2:5678 {
|
||||||
dir abc {
|
dir abc {
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
json: `[{"hosts":["host1:1234","host2:5678"],"body":{"dir":["abc",{}]}}]`,
|
json: `[{"hosts":["host1:1234","host2:5678"],"body":[["dir","abc",[]]]}]`,
|
||||||
},
|
},
|
||||||
{ // 4
|
{ // 4
|
||||||
caddyfile: `host {
|
caddyfile: `host {
|
||||||
foo "bar baz"
|
foo "bar baz"
|
||||||
}`,
|
}`,
|
||||||
json: `[{"hosts":["host"],"body":{"foo":["bar baz"]}}]`,
|
json: `[{"hosts":["host"],"body":[["foo","bar baz"]]}]`,
|
||||||
},
|
},
|
||||||
{ // 5
|
{ // 5
|
||||||
caddyfile: `host, host:80 {
|
caddyfile: `host, host:80 {
|
||||||
foo "bar \"baz\""
|
foo "bar \"baz\""
|
||||||
}`,
|
}`,
|
||||||
json: `[{"hosts":["host","host:80"],"body":{"foo":["bar \"baz\""]}}]`,
|
json: `[{"hosts":["host","host:80"],"body":[["foo","bar \"baz\""]]}]`,
|
||||||
},
|
},
|
||||||
{ // 6
|
{ // 6
|
||||||
caddyfile: `host {
|
caddyfile: `host {
|
||||||
foo "bar
|
foo "bar
|
||||||
baz"
|
baz"
|
||||||
}`,
|
}`,
|
||||||
json: `[{"hosts":["host"],"body":{"foo":["bar\nbaz"]}}]`,
|
json: `[{"hosts":["host"],"body":[["foo","bar\nbaz"]]}]`,
|
||||||
},
|
},
|
||||||
{ // 7
|
{ // 7
|
||||||
caddyfile: `host {
|
caddyfile: `host {
|
||||||
dir 123 4.56 true
|
dir 123 4.56 true
|
||||||
}`,
|
}`,
|
||||||
json: `[{"hosts":["host"],"body":{"dir":["123","4.56","true"]}}]`, // NOTE: I guess we assume numbers and booleans should be encoded as strings...?
|
json: `[{"hosts":["host"],"body":[["dir","123","4.56","true"]]}]`, // NOTE: I guess we assume numbers and booleans should be encoded as strings...?
|
||||||
},
|
},
|
||||||
{ // 8
|
{ // 8
|
||||||
caddyfile: `http://host, https://host {
|
caddyfile: `http://host, https://host {
|
||||||
}`,
|
}`,
|
||||||
json: `[{"hosts":["host:http","host:https"],"body":{}}]`, // hosts in JSON are always host:port format (if port is specified), for consistency
|
json: `[{"hosts":["host:http","host:https"],"body":[]}]`, // hosts in JSON are always host:port format (if port is specified), for consistency
|
||||||
|
},
|
||||||
|
{ // 9
|
||||||
|
caddyfile: `host {
|
||||||
|
dir1 a b
|
||||||
|
dir2 c d
|
||||||
|
}`,
|
||||||
|
json: `[{"hosts":["host"],"body":[["dir1","a","b"],["dir2","c","d"]]}]`,
|
||||||
|
},
|
||||||
|
{ // 10
|
||||||
|
caddyfile: `host {
|
||||||
|
dir a b
|
||||||
|
dir c d
|
||||||
|
}`,
|
||||||
|
json: `[{"hosts":["host"],"body":[["dir","a","b"],["dir","c","d"]]}]`,
|
||||||
|
},
|
||||||
|
{ // 11
|
||||||
|
caddyfile: `host {
|
||||||
|
dir1 a b
|
||||||
|
dir2 {
|
||||||
|
c
|
||||||
|
d
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
json: `[{"hosts":["host"],"body":[["dir1","a","b"],["dir2",[["c"],["d"]]]]}]`,
|
||||||
|
},
|
||||||
|
{ // 12
|
||||||
|
caddyfile: `host1 {
|
||||||
|
dir1
|
||||||
|
}
|
||||||
|
|
||||||
|
host2 {
|
||||||
|
dir2
|
||||||
|
}`,
|
||||||
|
json: `[{"hosts":["host1"],"body":[["dir1"]]},{"hosts":["host2"],"body":[["dir2"]]}]`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue