Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 125 additions & 7 deletions pkg/printer/formatter.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package printer

import (
"encoding/json"
"fmt"
"net/http"
"sort"
"strconv"
"strings"
"time"

Expand All @@ -28,7 +28,7 @@ func (f Formatter) RequestLine(line result.RequestLine) string {
}

func (f Formatter) Json(j map[string]any) string {
return formatJson(j)
return formatJson(j, f.colored)
}

func formatHeaders(headers http.Header, colorized bool) string {
Expand Down Expand Up @@ -113,10 +113,128 @@ func formatRequestLine(line result.RequestLine, colorized bool) string {
return fmt.Sprintf(fstring, Colorize(line.Method, methodColor), line.Url, line.Protocol)
}

func formatJson(j map[string]any) string {
if prettyJson, err := json.MarshalIndent(j, "", " "); err != nil {
return fmt.Sprintf("unable to parse json: %v\n", err)
} else {
return string(prettyJson)
func formatJson(j map[string]any, colorized bool) string {
var f func(inner map[string]any, level int) string

var formatValue func(value any, level int) string

f = func(inner map[string]any, level int) string {
sb := strings.Builder{}
sb.WriteString("{")

keys := []string{}
for k := range inner {
keys = append(keys, k)
}

sort.Strings(keys)

padding := strings.Repeat(" ", level+1)

for i, k := range keys {
sb.WriteString("\n")

value := inner[k]
isLast := i == len(keys)-1

sb.WriteString(padding)
sb.WriteString(formatJsonKey(k, colorized))

sb.WriteString(formatValue(value, level))

if !isLast {
sb.WriteString(",")
} else {
sb.WriteString("\n")
}
}

padding = strings.Repeat(" ", level)
if sb.Len() != 1 {
sb.WriteString(padding)
}

sb.WriteString("}")

return sb.String()
}

formatValue = func(value any, level int) string {
switch v := value.(type) {
case string:
return formatJsonStringValue(v, colorized)
case int, float64:
return formatJsonNumberValue(v, colorized)
case bool:
return formatJsonBoolValue(v, colorized)
case nil:
return formatJsonNilValue(colorized)
case map[string]any:
return f(v, level+1)
case []any:
sb := strings.Builder{}
sb.WriteString("[")

for i, vv := range v {
sb.WriteString(formatValue(vv, level))

if i != len(v)-1 {
sb.WriteString(", ")
}
}

sb.WriteString("]")

return sb.String()
}

return ""
}

return f(j, 0)
}

func formatJsonKey(s string, colorized bool) string {
fstring := `%s: `
value := strconv.Quote(s)

if !colorized {
return fmt.Sprintf(fstring, value)
}

return fmt.Sprintf(fstring, Yellow(value))
}

func formatJsonStringValue(s string, colorized bool) string {
if !colorized {
return strconv.Quote(s)
}

return Green(strconv.Quote(s))
}

func formatJsonNumberValue(n any, colorized bool) string {
s := fmt.Sprintf("%v", n)
if !colorized {
return s
}

return Cyan(s)
}

func formatJsonBoolValue(b bool, colorized bool) string {
s := fmt.Sprintf("%v", b)
if !colorized {
return s
}

return Magenta(s)
}

func formatJsonNilValue(colorized bool) string {
if !colorized {
return "null"
}

return Red("null")
}
77 changes: 65 additions & 12 deletions pkg/printer/formatter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,17 +174,19 @@ func Test_formatRequestLine(t *testing.T) {

func Test_formatJson(t *testing.T) {
tests := []struct {
name string
json map[string]any
want string
name string
json map[string]any
useColor bool
want string
}{
{
name: "simple",
json: map[string]any{
"name": "Alice",
"age": 30,
},
want: "{\n \"age\": 30,\n \"name\": \"Alice\"\n}",
useColor: false,
want: "{\n \"age\": 30,\n \"name\": \"Alice\"\n}",
},
{
name: "nested",
Expand All @@ -194,25 +196,76 @@ func Test_formatJson(t *testing.T) {
"name": "Bob",
},
},
want: "{\n \"user\": {\n \"id\": 1,\n \"name\": \"Bob\"\n }\n}",
useColor: false,
want: "{\n \"user\": {\n \"id\": 1,\n \"name\": \"Bob\"\n }\n}",
},
{
name: "empty",
json: map[string]any{},
useColor: false,
want: "{}",
},
{
name: "colored int",
json: map[string]any{
"int": 1,
},
useColor: true,
want: fmt.Sprintf("{\n %v: %v\n}", Yellow(`"int"`), Cyan("1")),
},
{
name: "colored string",
json: map[string]any{
"string": "Hello, World",
},
useColor: true,
want: fmt.Sprintf("{\n %v: %v\n}", Yellow(`"string"`), Green(`"Hello, World"`)),
},
{
name: "empty",
json: map[string]any{},
want: "{}",
name: "colored boolean",
json: map[string]any{
"boolean": true,
},
useColor: true,
want: fmt.Sprintf("{\n %v: %v\n}", Yellow(`"boolean"`), Magenta("true")),
},
{
name: "unmarshalable value (channel)",
name: "colored float",
json: map[string]any{
"invalid": make(chan int),
"float": 1.23,
},
want: "unable to parse json: json: unsupported type: chan int\n",
useColor: true,
want: fmt.Sprintf("{\n %v: %v\n}", Yellow(`"float"`), Cyan("1.23")),
},
{
name: "colored nil",
json: map[string]any{
"nil": nil,
},
useColor: true,
want: fmt.Sprintf("{\n %v: %v\n}", Yellow(`"nil"`), Red("null")),
},
{
name: "colored list",
json: map[string]any{
"list": []any{1, "string", false, nil, map[string]any{"one": 1}},
},
useColor: true,
want: fmt.Sprintf("{\n %v: [%v, %v, %v, %v, {\n %v: %v\n }]\n}",
Yellow(`"list"`),
Cyan("1"),
Green(`"string"`),
Magenta("false"),
Red("null"),
Yellow(`"one"`),
Cyan("1"),
),
},
Comment thread
eynopv marked this conversation as resolved.
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
formatter := Formatter{}
formatter := Formatter{colored: tt.useColor}
got := formatter.Json(tt.json)
assert.Equal(t, tt.want, got)
})
Expand Down