Skip to content

Commit 4cf7d7e

Browse files
authored
cannon: Make jsonutil error when loading json payload with extra data (ethereum-optimism#10542)
1 parent 53c00e9 commit 4cf7d7e

File tree

2 files changed

+113
-1
lines changed

2 files changed

+113
-1
lines changed

op-service/jsonutil/json.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,14 @@ func LoadJSON[X any](inputPath string) (*X, error) {
2121
}
2222
defer f.Close()
2323
var state X
24-
if err := json.NewDecoder(f).Decode(&state); err != nil {
24+
decoder := json.NewDecoder(f)
25+
if err := decoder.Decode(&state); err != nil {
2526
return nil, fmt.Errorf("failed to decode file %q: %w", inputPath, err)
2627
}
28+
// We are only expecting 1 JSON object - confirm there is no trailing data
29+
if _, err := decoder.Token(); err != io.EOF {
30+
return nil, fmt.Errorf("unexpected trailing data in file %q", inputPath)
31+
}
2732
return &state, nil
2833
}
2934

op-service/jsonutil/json_test.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package jsonutil
22

33
import (
44
"encoding/json"
5+
"fmt"
56
"os"
67
"path/filepath"
78
"testing"
@@ -47,7 +48,113 @@ func TestRoundTripJSONWithGzip(t *testing.T) {
4748
require.EqualValues(t, data, result)
4849
}
4950

51+
func TestLoadJSONWithExtraDataAppended(t *testing.T) {
52+
data := &jsonTestData{A: "yay", B: 3}
53+
54+
cases := []struct {
55+
name string
56+
extraData func() ([]byte, error)
57+
}{
58+
{
59+
name: "duplicate json object",
60+
extraData: func() ([]byte, error) {
61+
return json.Marshal(data)
62+
},
63+
},
64+
{
65+
name: "duplicate comma-separated json object",
66+
extraData: func() ([]byte, error) {
67+
data, err := json.Marshal(data)
68+
if err != nil {
69+
return nil, err
70+
}
71+
return append([]byte(","), data...), nil
72+
},
73+
},
74+
{
75+
name: "additional characters",
76+
extraData: func() ([]byte, error) {
77+
return []byte("some text"), nil
78+
},
79+
},
80+
}
81+
82+
for _, tc := range cases {
83+
t.Run(tc.name, func(t *testing.T) {
84+
dir := t.TempDir()
85+
file := filepath.Join(dir, "test.json")
86+
extraData, err := tc.extraData()
87+
require.NoError(t, err)
88+
89+
// Write primary json payload + extra data to the file
90+
err = WriteJSON(file, data, 0o755)
91+
require.NoError(t, err)
92+
err = appendDataToFile(file, extraData)
93+
require.NoError(t, err)
94+
95+
var result *jsonTestData
96+
result, err = LoadJSON[jsonTestData](file)
97+
require.ErrorContains(t, err, fmt.Sprintf("unexpected trailing data in file %q", file))
98+
require.Nil(t, result)
99+
})
100+
}
101+
}
102+
103+
func TestLoadJSONWithTrailingWhitespace(t *testing.T) {
104+
cases := []struct {
105+
name string
106+
extraData []byte
107+
}{
108+
{
109+
name: "space",
110+
extraData: []byte(" "),
111+
},
112+
{
113+
name: "tab",
114+
extraData: []byte("\t"),
115+
},
116+
{
117+
name: "new line",
118+
extraData: []byte("\n"),
119+
},
120+
{
121+
name: "multiple chars",
122+
extraData: []byte(" \t\n"),
123+
},
124+
}
125+
126+
for _, tc := range cases {
127+
t.Run(tc.name, func(t *testing.T) {
128+
dir := t.TempDir()
129+
file := filepath.Join(dir, "test.json")
130+
data := &jsonTestData{A: "yay", B: 3}
131+
132+
// Write primary json payload + extra data to the file
133+
err := WriteJSON(file, data, 0o755)
134+
require.NoError(t, err)
135+
err = appendDataToFile(file, tc.extraData)
136+
require.NoError(t, err)
137+
138+
var result *jsonTestData
139+
result, err = LoadJSON[jsonTestData](file)
140+
require.NoError(t, err)
141+
require.EqualValues(t, data, result)
142+
})
143+
}
144+
}
145+
50146
type jsonTestData struct {
51147
A string `json:"a"`
52148
B int `json:"b"`
53149
}
150+
151+
func appendDataToFile(outputPath string, data []byte) error {
152+
file, err := os.OpenFile(outputPath, os.O_APPEND|os.O_WRONLY, 0644)
153+
if err != nil {
154+
return err
155+
}
156+
defer file.Close()
157+
158+
_, err = file.Write(data)
159+
return err
160+
}

0 commit comments

Comments
 (0)