diff --git a/cmd/protoc-gen-go/internal_gengo/main.go b/cmd/protoc-gen-go/internal_gengo/main.go index af3b35e20..5402dfd3b 100644 --- a/cmd/protoc-gen-go/internal_gengo/main.go +++ b/cmd/protoc-gen-go/internal_gengo/main.go @@ -41,6 +41,7 @@ var GenerateVersionMarkers = true // Standard library dependencies. const ( base64Package = protogen.GoImportPath("encoding/base64") + jsonPackage = protogen.GoImportPath("encoding/json") mathPackage = protogen.GoImportPath("math") reflectPackage = protogen.GoImportPath("reflect") sortPackage = protogen.GoImportPath("sort") diff --git a/cmd/protoc-gen-go/internal_gengo/well_known_types.go b/cmd/protoc-gen-go/internal_gengo/well_known_types.go index 9a7b59030..2ae6b0396 100644 --- a/cmd/protoc-gen-go/internal_gengo/well_known_types.go +++ b/cmd/protoc-gen-go/internal_gengo/well_known_types.go @@ -713,19 +713,20 @@ func genMessageKnownFunctions(g *protogen.GeneratedFile, f *fileInfo, m *message case genid.Value_message_fullname: g.P("// NewValue constructs a Value from a general-purpose Go interface.") g.P("//") - g.P("// ╔════════════════════════╤════════════════════════════════════════════╗") - g.P("// ║ Go type │ Conversion ║") - g.P("// ╠════════════════════════╪════════════════════════════════════════════╣") - g.P("// ║ nil │ stored as NullValue ║") - g.P("// ║ bool │ stored as BoolValue ║") - g.P("// ║ int, int32, int64 │ stored as NumberValue ║") - g.P("// ║ uint, uint32, uint64 │ stored as NumberValue ║") - g.P("// ║ float32, float64 │ stored as NumberValue ║") - g.P("// ║ string │ stored as StringValue; must be valid UTF-8 ║") - g.P("// ║ []byte │ stored as StringValue; base64-encoded ║") - g.P("// ║ map[string]any │ stored as StructValue ║") - g.P("// ║ []any │ stored as ListValue ║") - g.P("// ╚════════════════════════╧════════════════════════════════════════════╝") + g.P("// ╔═══════════════════════════════════════╤════════════════════════════════════════════╗") + g.P("// ║ Go type │ Conversion ║") + g.P("// ╠═══════════════════════════════════════╪════════════════════════════════════════════╣") + g.P("// ║ nil │ stored as NullValue ║") + g.P("// ║ bool │ stored as BoolValue ║") + g.P("// ║ int, int8, int16, int32, int64 │ stored as NumberValue ║") + g.P("// ║ uint, uint8, uint16, uint32, uint64 │ stored as NumberValue ║") + g.P("// ║ float32, float64 │ stored as NumberValue ║") + g.P("// ║ json.Number │ stored as NumberValue ║") + g.P("// ║ string │ stored as StringValue; must be valid UTF-8 ║") + g.P("// ║ []byte │ stored as StringValue; base64-encoded ║") + g.P("// ║ map[string]any │ stored as StructValue ║") + g.P("// ║ []any │ stored as ListValue ║") + g.P("// ╚═══════════════════════════════════════╧════════════════════════════════════════════╝") g.P("//") g.P("// When converting an int64 or uint64 to a NumberValue, numeric precision loss") g.P("// is possible since they are stored as a float64.") @@ -737,12 +738,20 @@ func genMessageKnownFunctions(g *protogen.GeneratedFile, f *fileInfo, m *message g.P(" return NewBoolValue(v), nil") g.P(" case int:") g.P(" return NewNumberValue(float64(v)), nil") + g.P(" case int8:") + g.P(" return NewNumberValue(float64(v)), nil") + g.P(" case int16:") + g.P(" return NewNumberValue(float64(v)), nil") g.P(" case int32:") g.P(" return NewNumberValue(float64(v)), nil") g.P(" case int64:") g.P(" return NewNumberValue(float64(v)), nil") g.P(" case uint:") g.P(" return NewNumberValue(float64(v)), nil") + g.P(" case uint8:") + g.P(" return NewNumberValue(float64(v)), nil") + g.P(" case uint16:") + g.P(" return NewNumberValue(float64(v)), nil") g.P(" case uint32:") g.P(" return NewNumberValue(float64(v)), nil") g.P(" case uint64:") @@ -751,6 +760,12 @@ func genMessageKnownFunctions(g *protogen.GeneratedFile, f *fileInfo, m *message g.P(" return NewNumberValue(float64(v)), nil") g.P(" case float64:") g.P(" return NewNumberValue(float64(v)), nil") + g.P(" case ", jsonPackage.Ident("Number"), ":") + g.P(" n, err := v.Float64()") + g.P(" if err != nil {") + g.P(" return nil, ", protoimplPackage.Ident("X"), ".NewError(\"invalid number format %q, expected a float64: %v\", v, err)") + g.P(" }") + g.P(" return NewNumberValue(n), nil") g.P(" case string:") g.P(" if !", utf8Package.Ident("ValidString"), "(v) {") g.P(" return nil, ", protoimplPackage.Ident("X"), ".NewError(\"invalid UTF-8 in string: %q\", v)") diff --git a/types/known/structpb/struct.pb.go b/types/known/structpb/struct.pb.go index d45361cbc..71986d72a 100644 --- a/types/known/structpb/struct.pb.go +++ b/types/known/structpb/struct.pb.go @@ -120,6 +120,7 @@ package structpb import ( base64 "encoding/base64" + json "encoding/json" protojson "google.golang.org/protobuf/encoding/protojson" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" @@ -296,19 +297,20 @@ type Value struct { // NewValue constructs a Value from a general-purpose Go interface. // -// ╔════════════════════════╤════════════════════════════════════════════╗ -// ║ Go type │ Conversion ║ -// ╠════════════════════════╪════════════════════════════════════════════╣ -// ║ nil │ stored as NullValue ║ -// ║ bool │ stored as BoolValue ║ -// ║ int, int32, int64 │ stored as NumberValue ║ -// ║ uint, uint32, uint64 │ stored as NumberValue ║ -// ║ float32, float64 │ stored as NumberValue ║ -// ║ string │ stored as StringValue; must be valid UTF-8 ║ -// ║ []byte │ stored as StringValue; base64-encoded ║ -// ║ map[string]any │ stored as StructValue ║ -// ║ []any │ stored as ListValue ║ -// ╚════════════════════════╧════════════════════════════════════════════╝ +// ╔═══════════════════════════════════════╤════════════════════════════════════════════╗ +// ║ Go type │ Conversion ║ +// ╠═══════════════════════════════════════╪════════════════════════════════════════════╣ +// ║ nil │ stored as NullValue ║ +// ║ bool │ stored as BoolValue ║ +// ║ int, int8, int16, int32, int64 │ stored as NumberValue ║ +// ║ uint, uint8, uint16, uint32, uint64 │ stored as NumberValue ║ +// ║ float32, float64 │ stored as NumberValue ║ +// ║ json.Number │ stored as NumberValue ║ +// ║ string │ stored as StringValue; must be valid UTF-8 ║ +// ║ []byte │ stored as StringValue; base64-encoded ║ +// ║ map[string]any │ stored as StructValue ║ +// ║ []any │ stored as ListValue ║ +// ╚═══════════════════════════════════════╧════════════════════════════════════════════╝ // // When converting an int64 or uint64 to a NumberValue, numeric precision loss // is possible since they are stored as a float64. @@ -320,12 +322,20 @@ func NewValue(v any) (*Value, error) { return NewBoolValue(v), nil case int: return NewNumberValue(float64(v)), nil + case int8: + return NewNumberValue(float64(v)), nil + case int16: + return NewNumberValue(float64(v)), nil case int32: return NewNumberValue(float64(v)), nil case int64: return NewNumberValue(float64(v)), nil case uint: return NewNumberValue(float64(v)), nil + case uint8: + return NewNumberValue(float64(v)), nil + case uint16: + return NewNumberValue(float64(v)), nil case uint32: return NewNumberValue(float64(v)), nil case uint64: @@ -334,6 +344,12 @@ func NewValue(v any) (*Value, error) { return NewNumberValue(float64(v)), nil case float64: return NewNumberValue(float64(v)), nil + case json.Number: + n, err := v.Float64() + if err != nil { + return nil, protoimpl.X.NewError("invalid number format %q, expected a float64: %v", v, err) + } + return NewNumberValue(n), nil case string: if !utf8.ValidString(v) { return nil, protoimpl.X.NewError("invalid UTF-8 in string: %q", v) diff --git a/types/known/structpb/struct_test.go b/types/known/structpb/struct_test.go index beb9c077f..39d357a03 100644 --- a/types/known/structpb/struct_test.go +++ b/types/known/structpb/struct_test.go @@ -37,34 +37,44 @@ func TestToStruct(t *testing.T) { wantPB: new(spb.Struct), }, { in: map[string]any{ - "nil": nil, - "bool": bool(false), - "int": int(-123), - "int32": int32(math.MinInt32), - "int64": int64(math.MinInt64), - "uint": uint(123), - "uint32": uint32(math.MaxInt32), - "uint64": uint64(math.MaxInt64), - "float32": float32(123.456), - "float64": float64(123.456), - "string": string("hello, world!"), - "bytes": []byte("\xde\xad\xbe\xef"), - "map": map[string]any{"k1": "v1", "k2": "v2"}, - "slice": []any{"one", "two", "three"}, + "nil": nil, + "bool": bool(false), + "int": int(-123), + "int8": int8(math.MinInt8), + "int16": int16(math.MinInt16), + "int32": int32(math.MinInt32), + "int64": int64(math.MinInt64), + "uint": uint(123), + "uint8": uint8(math.MaxInt8), + "uint16": uint16(math.MaxInt16), + "uint32": uint32(math.MaxInt32), + "uint64": uint64(math.MaxInt64), + "float32": float32(123.456), + "float64": float64(123.456), + "jsonNumber": json.Number("123.456"), + "string": string("hello, world!"), + "bytes": []byte("\xde\xad\xbe\xef"), + "map": map[string]any{"k1": "v1", "k2": "v2"}, + "slice": []any{"one", "two", "three"}, }, wantPB: &spb.Struct{Fields: map[string]*spb.Value{ - "nil": spb.NewNullValue(), - "bool": spb.NewBoolValue(false), - "int": spb.NewNumberValue(float64(-123)), - "int32": spb.NewNumberValue(float64(math.MinInt32)), - "int64": spb.NewNumberValue(float64(math.MinInt64)), - "uint": spb.NewNumberValue(float64(123)), - "uint32": spb.NewNumberValue(float64(math.MaxInt32)), - "uint64": spb.NewNumberValue(float64(math.MaxInt64)), - "float32": spb.NewNumberValue(float64(float32(123.456))), - "float64": spb.NewNumberValue(float64(float64(123.456))), - "string": spb.NewStringValue("hello, world!"), - "bytes": spb.NewStringValue("3q2+7w=="), + "nil": spb.NewNullValue(), + "bool": spb.NewBoolValue(false), + "int": spb.NewNumberValue(float64(-123)), + "int8": spb.NewNumberValue(float64(math.MinInt8)), + "int16": spb.NewNumberValue(float64(math.MinInt16)), + "int32": spb.NewNumberValue(float64(math.MinInt32)), + "int64": spb.NewNumberValue(float64(math.MinInt64)), + "uint": spb.NewNumberValue(float64(123)), + "uint8": spb.NewNumberValue(float64(math.MaxInt8)), + "uint16": spb.NewNumberValue(float64(math.MaxInt16)), + "uint32": spb.NewNumberValue(float64(math.MaxInt32)), + "uint64": spb.NewNumberValue(float64(math.MaxInt64)), + "float32": spb.NewNumberValue(float64(float32(123.456))), + "float64": spb.NewNumberValue(float64(float64(123.456))), + "jsonNumber": spb.NewNumberValue(float64(123.456)), + "string": spb.NewStringValue("hello, world!"), + "bytes": spb.NewStringValue("3q2+7w=="), "map": spb.NewStructValue(&spb.Struct{Fields: map[string]*spb.Value{ "k1": spb.NewStringValue("v1"), "k2": spb.NewStringValue("v2"), @@ -115,9 +125,13 @@ func TestFromStruct(t *testing.T) { "nil": spb.NewNullValue(), "bool": spb.NewBoolValue(false), "int": spb.NewNumberValue(float64(-123)), + "int8": spb.NewNumberValue(float64(math.MinInt8)), + "int16": spb.NewNumberValue(float64(math.MinInt16)), "int32": spb.NewNumberValue(float64(math.MinInt32)), "int64": spb.NewNumberValue(float64(math.MinInt64)), "uint": spb.NewNumberValue(float64(123)), + "uint8": spb.NewNumberValue(float64(math.MaxInt8)), + "uint16": spb.NewNumberValue(float64(math.MaxInt16)), "uint32": spb.NewNumberValue(float64(math.MaxInt32)), "uint64": spb.NewNumberValue(float64(math.MaxInt64)), "float32": spb.NewNumberValue(float64(float32(123.456))), @@ -138,9 +152,13 @@ func TestFromStruct(t *testing.T) { "nil": nil, "bool": bool(false), "int": float64(-123), + "int8": float64(math.MinInt8), + "int16": float64(math.MinInt16), "int32": float64(math.MinInt32), "int64": float64(math.MinInt64), "uint": float64(123), + "uint8": float64(math.MaxInt8), + "uint16": float64(math.MaxInt16), "uint32": float64(math.MaxInt32), "uint64": float64(math.MaxInt64), "float32": float64(float32(123.456)), @@ -187,13 +205,18 @@ func TestToListValue(t *testing.T) { nil, bool(false), int(-123), + int8(math.MinInt8), + int16(math.MinInt16), int32(math.MinInt32), int64(math.MinInt64), uint(123), + uint8(math.MaxInt8), + uint16(math.MaxInt16), uint32(math.MaxInt32), uint64(math.MaxInt64), float32(123.456), float64(123.456), + json.Number("123.456"), string("hello, world!"), []byte("\xde\xad\xbe\xef"), map[string]any{"k1": "v1", "k2": "v2"}, @@ -203,13 +226,18 @@ func TestToListValue(t *testing.T) { spb.NewNullValue(), spb.NewBoolValue(false), spb.NewNumberValue(float64(-123)), + spb.NewNumberValue(float64(math.MinInt8)), + spb.NewNumberValue(float64(math.MinInt16)), spb.NewNumberValue(float64(math.MinInt32)), spb.NewNumberValue(float64(math.MinInt64)), spb.NewNumberValue(float64(123)), + spb.NewNumberValue(float64(math.MaxInt8)), + spb.NewNumberValue(float64(math.MaxInt16)), spb.NewNumberValue(float64(math.MaxInt32)), spb.NewNumberValue(float64(math.MaxInt64)), spb.NewNumberValue(float64(float32(123.456))), spb.NewNumberValue(float64(float64(123.456))), + spb.NewNumberValue(float64(123.456)), spb.NewStringValue("hello, world!"), spb.NewStringValue("3q2+7w=="), spb.NewStructValue(&spb.Struct{Fields: map[string]*spb.Value{ @@ -259,9 +287,13 @@ func TestFromListValue(t *testing.T) { spb.NewNullValue(), spb.NewBoolValue(false), spb.NewNumberValue(float64(-123)), + spb.NewNumberValue(float64(math.MinInt8)), + spb.NewNumberValue(float64(math.MinInt16)), spb.NewNumberValue(float64(math.MinInt32)), spb.NewNumberValue(float64(math.MinInt64)), spb.NewNumberValue(float64(123)), + spb.NewNumberValue(float64(math.MaxInt8)), + spb.NewNumberValue(float64(math.MaxInt16)), spb.NewNumberValue(float64(math.MaxInt32)), spb.NewNumberValue(float64(math.MaxInt64)), spb.NewNumberValue(float64(float32(123.456))), @@ -282,9 +314,13 @@ func TestFromListValue(t *testing.T) { nil, bool(false), float64(-123), + float64(math.MinInt8), + float64(math.MinInt16), float64(math.MinInt32), float64(math.MinInt64), float64(123), + float64(math.MaxInt8), + float64(math.MaxInt16), float64(math.MaxInt32), float64(math.MaxInt64), float64(float32(123.456)), @@ -329,6 +365,12 @@ func TestToValue(t *testing.T) { }, { in: int(-123), wantPB: spb.NewNumberValue(float64(-123)), + }, { + in: int8(math.MinInt8), + wantPB: spb.NewNumberValue(float64(math.MinInt8)), + }, { + in: int16(math.MinInt16), + wantPB: spb.NewNumberValue(float64(math.MinInt16)), }, { in: int32(math.MinInt32), wantPB: spb.NewNumberValue(float64(math.MinInt32)), @@ -338,6 +380,12 @@ func TestToValue(t *testing.T) { }, { in: uint(123), wantPB: spb.NewNumberValue(float64(123)), + }, { + in: uint8(math.MaxInt8), + wantPB: spb.NewNumberValue(float64(math.MaxInt8)), + }, { + in: uint16(math.MaxInt16), + wantPB: spb.NewNumberValue(float64(math.MaxInt16)), }, { in: uint32(math.MaxInt32), wantPB: spb.NewNumberValue(float64(math.MaxInt32)), @@ -350,6 +398,9 @@ func TestToValue(t *testing.T) { }, { in: float64(123.456), wantPB: spb.NewNumberValue(float64(float64(123.456))), + }, { + in: json.Number("123.456"), + wantPB: spb.NewNumberValue(float64(123.456)), }, { in: string("hello, world!"), wantPB: spb.NewStringValue("hello, world!"), @@ -428,6 +479,12 @@ func TestFromValue(t *testing.T) { }, { in: &spb.Value{Kind: (*spb.Value_NumberValue)(nil)}, want: nil, + }, { + in: spb.NewNumberValue(float64(math.MinInt8)), + want: float64(math.MinInt8), + }, { + in: spb.NewNumberValue(float64(math.MinInt16)), + want: float64(math.MinInt16), }, { in: spb.NewNumberValue(float64(math.MinInt32)), want: float64(math.MinInt32), @@ -437,6 +494,12 @@ func TestFromValue(t *testing.T) { }, { in: spb.NewNumberValue(float64(123)), want: float64(123), + }, { + in: spb.NewNumberValue(float64(math.MaxInt8)), + want: float64(math.MaxInt8), + }, { + in: spb.NewNumberValue(float64(math.MaxInt16)), + want: float64(math.MaxInt16), }, { in: spb.NewNumberValue(float64(math.MaxInt32)), want: float64(math.MaxInt32),