Skip to content
Open
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
13 changes: 12 additions & 1 deletion agent/format/jsonformat/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
package jsonformat

import (
"bytes"
"encoding/json"
"fmt"
"io"

Comment on lines 5 to 10

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — added the io import and a strict trailing-data check (dec.Token() must return io.EOF). Note: validate() can't also use UseNumber — json.Number is a string type the jsonschema validator rejects as a non-integer, which broke every test; left as a comment in the PR.

"github.com/google/jsonschema-go/jsonschema"
)
Expand Down Expand Up @@ -69,9 +71,18 @@ func (f *Format) Normalize(v any) error {
func applySchema(data json.RawMessage, resolved *jsonschema.Resolved) (json.RawMessage, error) {
var v any
if len(data) > 0 {
if err := json.Unmarshal(data, &v); err != nil {
// Decode with UseNumber so integers beyond 2^53 are not silently
// truncated by being decoded into float64 and re-marshalled.
dec := json.NewDecoder(bytes.NewReader(data))
dec.UseNumber()
if err := dec.Decode(&v); err != nil {
Comment on lines +74 to +78
return nil, fmt.Errorf("unmarshaling arguments: %w", err)
}
// Unlike json.Unmarshal, json.Decoder tolerates trailing data after the
// first value; reject it so validation is not looser than before.
if _, err := dec.Token(); err != io.EOF {
return nil, fmt.Errorf("unmarshaling arguments: unexpected trailing data after JSON value")
}
}
if err := resolved.ApplyDefaults(&v); err != nil {
return nil, fmt.Errorf("applying schema defaults: %w", err)
Expand Down
27 changes: 27 additions & 0 deletions agent/format/jsonformat/encoding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,30 @@ func TestNormalizeEmptyStruct(t *testing.T) {
t.Fatalf("Normalize: %v", err)
}
}

// Integer arguments beyond 2^53 must survive Unmarshal: decoding through an
// interface{} as float64 would silently truncate them.
func TestFormat_Unmarshal_PreservesLargeIntegerPrecision(t *testing.T) {
type output struct {
N int64 `json:"n"`
}
format := requireFormat(t, jsonformat.MustFor[output]())
var out output
if err := format.Unmarshal([]byte(`{"n":9007199254740993}`), &out); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if out.N != 9007199254740993 {
t.Errorf("N = %d, want 9007199254740993 (large-integer precision lost)", out.N)
}
}

func TestFormat_Unmarshal_RejectsTrailingData(t *testing.T) {
type output struct {
N int `json:"n"`
}
format := requireFormat(t, jsonformat.MustFor[output]())
var out output
if err := format.Unmarshal([]byte(`{"n":1} {"n":2}`), &out); err == nil {
t.Fatal("expected an error for trailing data after the JSON value, got nil")
}
}