mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-03-08 19:14:05 +00:00
all: implement first-class WKT support
This CL introduces generation of specialized APIs directly into the generated packages for certain well-known types. This follows the pattern set forth by the other language implementations that have specialized generated support for certain well-known types. Overview of new API: package anypb func MarshalFrom(*Any, proto.Message, proto.MarshalOptions) error func UnmarshalTo(*Any, proto.Message, proto.UnmarshalOptions) error func UnmarshalNew(*Any, proto.UnmarshalOptions) (proto.Message, error) func (*Any) MessageIs(proto.Message) bool func (*Any) MessageName() protoreflect.FullName func (*Any) MarshalFrom(proto.Message) error func (*Any) UnmarshalTo(proto.Message) error func (*Any) UnmarshalNew() (proto.Message, error) package timestamppb func Now() *Timestamp func New(time.Time) *Timestamp func (*Timestamp) AsTime() time.Time func (*Timestamp) IsValid() bool func (*Timestamp) CheckValid() error package durationpb func New(time.Duration) *Duration func (*Duration) AsDuration() time.Duration func (*Duration) IsValid() bool func (*Duration) CheckValid() error package structpb func NewStruct(map[string]interface{}) (*Struct, error) func (*Struct) AsMap() map[string]interface{} func (*Struct) MarshalJSON() ([]byte, error) func (*Struct) UnmarshalJSON(b []byte) error func NewList([]interface{}) (*ListValue, error) func (*ListValue) AsSlice() []interface{} func (*ListValue) MarshalJSON() ([]byte, error) func (*ListValue) UnmarshalJSON(b []byte) error func NewValue(interface{}) (*Value, error) func NewNullValue() *Value func NewBoolValue(bool) *Value func NewNumberValue(float64) *Value func NewStringValue(string) *Value func NewStructValue(*Struct) *Value func NewListValue(*ListValue) *Value func (*Value) AsInterface() interface{} func (*Value) MarshalJSON() ([]byte, error) func (*Value) UnmarshalJSON(b []byte) error package fieldmaskpb func New(proto.Message, ...string) (*FieldMask, error) func Union(*FieldMask, *FieldMask, ...*FieldMask) *FieldMask func Intersect(*FieldMask, *FieldMask, ...*FieldMask) *FieldMask func (*FieldMask) IsValid(proto.Message) bool func (*FieldMask) Append(proto.Message, ...string) error func (*FieldMask) Normalize() package wrapperspb func Bool(bool) *BoolValue func Int32(int32) *Int32Value func Int64(int64) *Int64Value func UInt32(uint32) *UInt32Value func UInt64(uint64) *UInt64Value func Float(float32) *FloatValue func Double(float64) *DoubleValue func String(string) *StringValue func Bytes([]byte) *BytesValue This functionality expands upon and supersedes the older github.com/golang/protobuf/ptypes package, which provided helpers for Any, Timestamp, and Duration. Comparison with older ptypes package: * ptypes.AnyMessageName is replaced by anypb.Any.MessageName. The former returned an error for malformed type URLs, while the latter simply returns an empty string. * ptypes.Is is replaced by anypb.Any.MessageIs. * ptypes.Empty has no direct replacement as it is equivalent to: mt, err := protoregistry.GlobalTypes.FindMessageByURL(any.GetTypeUrl()) if err != nil { return nil, err } return mt.New().Interface(), nil Analysis of user code revealed that this function is seldom used. * ptypes.MarshalAny is replaced by anypb.Any.MarshalFrom. The former creates a new Any message and returns it, while the latter is a method that modifies the receiver. * ptypes.UnmarshalAny is replaced by anypb.Any.UnmarshalTo. * ptypes.DynamicAny is loosely replaced by anypb.Any.UnmarshalNew. The DynamicAny type is a custom proto.Message that is special to ptypes.UnmarshalAny where it would allocate a new message and store it into the DynamicAny instance. The UnmarshalNew method accomplishes the equivalent functionality in a more direct fashion. * ptypes.TimestampNow is replaced by timestamppb.Now. * ptypes.TimestampProto is replaced by timestamppb.New. The former returned an error if the timestamp was outside the 10000-year range recommended by timestamp.proto, while the latter always succeeded. To preserve the behavior of the former validation check, the replacement can additionally call the timestamppb.Timestamp.CheckValid method. * ptypes.Timestamp is replaced by timestamppb.Timestamp.AsTime. The former returned an error if the timestamp was outside the 10000-year range recommended by timestamp.proto, while the latter always succeeded. To preserve the behavior of the former validation check, the replacement can additionally call the timestamppb.Timestamp.CheckValid method. * ptypes.TimestampString has no direct replacement as it is equivalent to: ts.AsTime().Format(time.RFC3339Nano) * ptypes.DurationProto is replaced by durationpb.New. * ptypes.Duration is replaced by durationpb.Duration.AsDuration. The former returned an error if the duration would overflow when converting to a time.Duration, while the latter uses saturation arithmetic (similiar to the time package itself). Underflow resulted in time.Duration(math.MinInt64), while overflow resulted in time.Duration(math.MaxInt64). To preserve the behavior of former validation checks, the replacement can call the durationpb.Duration.CheckValid method and check whether the duration is fixed to one of the overflow values. Change-Id: Ia996b1037a1fcafced7c7e10e9408ef7fa22863a Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/225298 Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
parent
69839c78c3
commit
f49fd502d3
@ -36,9 +36,14 @@ var GenerateVersionMarkers = true
|
||||
|
||||
// Standard library dependencies.
|
||||
const (
|
||||
base64Package = protogen.GoImportPath("encoding/base64")
|
||||
mathPackage = protogen.GoImportPath("math")
|
||||
reflectPackage = protogen.GoImportPath("reflect")
|
||||
sortPackage = protogen.GoImportPath("sort")
|
||||
stringsPackage = protogen.GoImportPath("strings")
|
||||
syncPackage = protogen.GoImportPath("sync")
|
||||
timePackage = protogen.GoImportPath("time")
|
||||
utf8Package = protogen.GoImportPath("unicode/utf8")
|
||||
)
|
||||
|
||||
// Protobuf library dependencies.
|
||||
@ -47,11 +52,13 @@ const (
|
||||
// patched to support unique build environments that impose restrictions
|
||||
// on the dependencies of generated source code.
|
||||
var (
|
||||
protoPackage goImportPath = protogen.GoImportPath("google.golang.org/protobuf/proto")
|
||||
protoifacePackage goImportPath = protogen.GoImportPath("google.golang.org/protobuf/runtime/protoiface")
|
||||
protoimplPackage goImportPath = protogen.GoImportPath("google.golang.org/protobuf/runtime/protoimpl")
|
||||
protoreflectPackage goImportPath = protogen.GoImportPath("google.golang.org/protobuf/reflect/protoreflect")
|
||||
protoV1Package goImportPath = protogen.GoImportPath("github.com/golang/protobuf/proto")
|
||||
protoPackage goImportPath = protogen.GoImportPath("google.golang.org/protobuf/proto")
|
||||
protoifacePackage goImportPath = protogen.GoImportPath("google.golang.org/protobuf/runtime/protoiface")
|
||||
protoimplPackage goImportPath = protogen.GoImportPath("google.golang.org/protobuf/runtime/protoimpl")
|
||||
protojsonPackage goImportPath = protogen.GoImportPath("google.golang.org/protobuf/encoding/protojson")
|
||||
protoreflectPackage goImportPath = protogen.GoImportPath("google.golang.org/protobuf/reflect/protoreflect")
|
||||
protoregistryPackage goImportPath = protogen.GoImportPath("google.golang.org/protobuf/reflect/protoregistry")
|
||||
protoV1Package goImportPath = protogen.GoImportPath("github.com/golang/protobuf/proto")
|
||||
)
|
||||
|
||||
type goImportPath interface {
|
||||
@ -328,6 +335,7 @@ func genMessage(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo) {
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
genMessageKnownFunctions(g, f, m)
|
||||
genMessageDefaultDecls(g, f, m)
|
||||
genMessageMethods(g, f, m)
|
||||
genMessageOneofWrapperTypes(g, f, m)
|
||||
|
723
cmd/protoc-gen-go/internal_gengo/well_known_types.go
Normal file
723
cmd/protoc-gen-go/internal_gengo/well_known_types.go
Normal file
@ -0,0 +1,723 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package internal_gengo
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"google.golang.org/protobuf/compiler/protogen"
|
||||
"google.golang.org/protobuf/internal/genid"
|
||||
)
|
||||
|
||||
// Specialized support for well-known types are hard-coded into the generator
|
||||
// as opposed to being injected in adjacent .go sources in the generated package
|
||||
// in order to support specialized build systems like Bazel that always generate
|
||||
// dynamically from the source .proto files.
|
||||
|
||||
func genMessageKnownFunctions(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo) {
|
||||
switch m.Desc.FullName() {
|
||||
case genid.Any_message_fullname:
|
||||
g.P("// MarshalFrom marshals src into dst as the underlying message")
|
||||
g.P("// using the provided marshal options.")
|
||||
g.P("//")
|
||||
g.P("// If no options are specified, call dst.MarshalFrom instead.")
|
||||
g.P("func MarshalFrom(dst *Any, src ", protoPackage.Ident("Message"), ", opts ", protoPackage.Ident("MarshalOptions"), ") error {")
|
||||
g.P(" const urlPrefix = \"type.googleapis.com/\"")
|
||||
g.P(" b, err := opts.Marshal(src)")
|
||||
g.P(" if err != nil {")
|
||||
g.P(" return err")
|
||||
g.P(" }")
|
||||
g.P(" dst.TypeUrl = urlPrefix + string(src.ProtoReflect().Descriptor().FullName())")
|
||||
g.P(" dst.Value = b")
|
||||
g.P(" return nil")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// UnmarshalTo unmarshals the underlying message from src into dst")
|
||||
g.P("// using the provided unmarshal options.")
|
||||
g.P("// It reports an error if dst is not of the right message type.")
|
||||
g.P("//")
|
||||
g.P("// If no options are specified, call src.UnmarshalTo instead.")
|
||||
g.P("func UnmarshalTo(src *Any, dst ", protoPackage.Ident("Message"), ", opts ", protoPackage.Ident("UnmarshalOptions"), ") error {")
|
||||
g.P(" if !src.MessageIs(dst) {")
|
||||
g.P(" got := dst.ProtoReflect().Descriptor().FullName()")
|
||||
g.P(" want := src.MessageName()")
|
||||
g.P(" return ", protoimplPackage.Ident("X"), ".NewError(\"mismatching message types: got %q, want %q\", got, want)")
|
||||
g.P(" }")
|
||||
g.P(" return opts.Unmarshal(src.GetValue(), dst)")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// UnmarshalNew unmarshals the underlying message from src into dst,")
|
||||
g.P("// which is newly created message using a type resolved from the type URL.")
|
||||
g.P("// The message type is resolved according to opt.Resolver,")
|
||||
g.P("// which should implement protoregistry.MessageTypeResolver.")
|
||||
g.P("// It reports an error if the underlying message type could not be resolved.")
|
||||
g.P("//")
|
||||
g.P("// If no options are specified, call src.UnmarshalNew instead.")
|
||||
g.P("func UnmarshalNew(src *Any, opts ", protoPackage.Ident("UnmarshalOptions"), ") (dst ", protoPackage.Ident("Message"), ", err error) {")
|
||||
g.P(" if src.GetTypeUrl() == \"\" {")
|
||||
g.P(" return nil, ", protoimplPackage.Ident("X"), ".NewError(\"invalid empty type URL\")")
|
||||
g.P(" }")
|
||||
g.P(" if opts.Resolver == nil {")
|
||||
g.P(" opts.Resolver = ", protoregistryPackage.Ident("GlobalTypes"))
|
||||
g.P(" }")
|
||||
g.P(" r, ok := opts.Resolver.(", protoregistryPackage.Ident("MessageTypeResolver"), ")")
|
||||
g.P(" if !ok {")
|
||||
g.P(" return nil, ", protoregistryPackage.Ident("NotFound"))
|
||||
g.P(" }")
|
||||
g.P(" mt, err := r.FindMessageByURL(src.GetTypeUrl())")
|
||||
g.P(" if err != nil {")
|
||||
g.P(" if err == ", protoregistryPackage.Ident("NotFound"), " {")
|
||||
g.P(" return nil, err")
|
||||
g.P(" }")
|
||||
g.P(" return nil, ", protoimplPackage.Ident("X"), ".NewError(\"could not resolve %q: %v\", src.GetTypeUrl(), err)")
|
||||
g.P(" }")
|
||||
g.P(" dst = mt.New().Interface()")
|
||||
g.P(" return dst, opts.Unmarshal(src.GetValue(), dst)")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// MessageIs reports whether the underlying message is of the same type as m.")
|
||||
g.P("func (x *Any) MessageIs(m ", protoPackage.Ident("Message"), ") bool {")
|
||||
g.P(" if m == nil {")
|
||||
g.P(" return false")
|
||||
g.P(" }")
|
||||
g.P(" url := x.GetTypeUrl()")
|
||||
g.P(" name := string(m.ProtoReflect().Descriptor().FullName())")
|
||||
g.P(" if !", stringsPackage.Ident("HasSuffix"), "(url, name) {")
|
||||
g.P(" return false")
|
||||
g.P(" }")
|
||||
g.P(" return len(url) == len(name) || url[len(url)-len(name)-1] == '/'")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// MessageName reports the full name of the underlying message,")
|
||||
g.P("// returning an empty string if invalid.")
|
||||
g.P("func (x *Any) MessageName() ", protoreflectPackage.Ident("FullName"), " {")
|
||||
g.P(" url := x.GetTypeUrl()")
|
||||
g.P(" name := ", protoreflectPackage.Ident("FullName"), "(url)")
|
||||
g.P(" if i := ", stringsPackage.Ident("LastIndexByte"), "(url, '/'); i >= 0 {")
|
||||
g.P(" name = name[i+len(\"/\"):]")
|
||||
g.P(" }")
|
||||
g.P(" if !name.IsValid() {")
|
||||
g.P(" return \"\"")
|
||||
g.P(" }")
|
||||
g.P(" return name")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// MarshalFrom marshals m into x as the underlying message.")
|
||||
g.P("func (x *Any) MarshalFrom(m ", protoPackage.Ident("Message"), ") error {")
|
||||
g.P(" return MarshalFrom(x, m, ", protoPackage.Ident("MarshalOptions"), "{})")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// UnmarshalTo unmarshals the contents of the underlying message of x into m.")
|
||||
g.P("// It resets m before performing the unmarshal operation.")
|
||||
g.P("// It reports an error if m is not of the right message type.")
|
||||
g.P("func (x *Any) UnmarshalTo(m ", protoPackage.Ident("Message"), ") error {")
|
||||
g.P(" return UnmarshalTo(x, m, ", protoPackage.Ident("UnmarshalOptions"), "{})")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// UnmarshalNew unmarshals the contents of the underlying message of x into")
|
||||
g.P("// a newly allocated message of the specified type.")
|
||||
g.P("// It reports an error if the underlying message type could not be resolved.")
|
||||
g.P("func (x *Any) UnmarshalNew() (", protoPackage.Ident("Message"), ", error) {")
|
||||
g.P(" return UnmarshalNew(x, ", protoPackage.Ident("UnmarshalOptions"), "{})")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
case genid.Timestamp_message_fullname:
|
||||
g.P("// Now constructs a new Timestamp from the current time.")
|
||||
g.P("func Now() *Timestamp {")
|
||||
g.P(" return New(", timePackage.Ident("Now"), "())")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// New constructs a new Timestamp from the provided time.Time.")
|
||||
g.P("func New(t ", timePackage.Ident("Time"), ") *Timestamp {")
|
||||
g.P(" return &Timestamp{Seconds: int64(t.Unix()), Nanos: int32(t.Nanosecond())}")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// AsTime converts x to a time.Time.")
|
||||
g.P("func (x *Timestamp) AsTime() ", timePackage.Ident("Time"), " {")
|
||||
g.P(" return ", timePackage.Ident("Unix"), "(int64(x.GetSeconds()), int64(x.GetNanos())).UTC()")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// IsValid reports whether the timestamp is valid.")
|
||||
g.P("// It is equivalent to CheckValid == nil.")
|
||||
g.P("func (x *Timestamp) IsValid() bool {")
|
||||
g.P(" return x.check() == 0")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// CheckValid returns an error if the timestamp is invalid.")
|
||||
g.P("// In particular, it checks whether the value represents a date that is")
|
||||
g.P("// in the range of 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive.")
|
||||
g.P("func (x *Timestamp) CheckValid() error {")
|
||||
g.P(" switch x.check() {")
|
||||
g.P(" case invalidUnderflow:")
|
||||
g.P(" return ", protoimplPackage.Ident("X"), ".NewError(\"timestamp (%v) before 0001-01-01\", x)")
|
||||
g.P(" case invalidOverflow:")
|
||||
g.P(" return ", protoimplPackage.Ident("X"), ".NewError(\"timestamp (%v) after 9999-12-31\", x)")
|
||||
g.P(" case invalidNanos:")
|
||||
g.P(" return ", protoimplPackage.Ident("X"), ".NewError(\"timestamp (%v) has out-of-range nanos\", x)")
|
||||
g.P(" default:")
|
||||
g.P(" return nil")
|
||||
g.P(" }")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("const invalidUnderflow = 1")
|
||||
g.P("const invalidOverflow = 2")
|
||||
g.P("const invalidNanos = 3")
|
||||
g.P()
|
||||
|
||||
g.P("func (x *Timestamp) check() uint {")
|
||||
g.P(" const minTimestamp = -62135596800 // Seconds between 1970-01-01T00:00:00Z and 0001-01-01T00:00:00Z, inclusive")
|
||||
g.P(" const maxTimestamp = +253402300799 // Seconds between 1970-01-01T00:00:00Z and 9999-12-31T23:59:59Z, inclusive")
|
||||
g.P(" secs := x.GetSeconds()")
|
||||
g.P(" nanos := x.GetNanos()")
|
||||
g.P(" switch {")
|
||||
g.P(" case secs < minTimestamp:")
|
||||
g.P(" return invalidUnderflow")
|
||||
g.P(" case secs > maxTimestamp:")
|
||||
g.P(" return invalidOverflow")
|
||||
g.P(" case nanos < 0 || nanos >= 1e9:")
|
||||
g.P(" return invalidNanos")
|
||||
g.P(" default:")
|
||||
g.P(" return 0")
|
||||
g.P(" }")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
case genid.Duration_message_fullname:
|
||||
g.P("// New constructs a new Duration from the provided time.Duration.")
|
||||
g.P("func New(d ", timePackage.Ident("Duration"), ") *Duration {")
|
||||
g.P(" nanos := d.Nanoseconds()")
|
||||
g.P(" secs := nanos / 1e9")
|
||||
g.P(" nanos -= secs * 1e9")
|
||||
g.P(" return &Duration{Seconds: int64(secs), Nanos: int32(nanos)}")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// AsDuration converts x to a time.Duration,")
|
||||
g.P("// returning the closest duration value in the event of overflow.")
|
||||
g.P("func (x *Duration) AsDuration() ", timePackage.Ident("Duration"), " {")
|
||||
g.P(" secs := x.GetSeconds()")
|
||||
g.P(" nanos := x.GetNanos()")
|
||||
g.P(" d := ", timePackage.Ident("Duration"), "(secs) * ", timePackage.Ident("Second"))
|
||||
g.P(" overflow := d/", timePackage.Ident("Second"), " != ", timePackage.Ident("Duration"), "(secs)")
|
||||
g.P(" d += ", timePackage.Ident("Duration"), "(nanos) * ", timePackage.Ident("Nanosecond"))
|
||||
g.P(" overflow = overflow || (secs < 0 && nanos < 0 && d > 0)")
|
||||
g.P(" overflow = overflow || (secs > 0 && nanos > 0 && d < 0)")
|
||||
g.P(" if overflow {")
|
||||
g.P(" switch {")
|
||||
g.P(" case secs < 0:")
|
||||
g.P(" return ", timePackage.Ident("Duration"), "(", mathPackage.Ident("MinInt64"), ")")
|
||||
g.P(" case secs > 0:")
|
||||
g.P(" return ", timePackage.Ident("Duration"), "(", mathPackage.Ident("MaxInt64"), ")")
|
||||
g.P(" }")
|
||||
g.P(" }")
|
||||
g.P(" return d")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// IsValid reports whether the duration is valid.")
|
||||
g.P("// It is equivalent to CheckValid == nil.")
|
||||
g.P("func (x *Duration) IsValid() bool {")
|
||||
g.P(" return x.check() == 0")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// CheckValid returns an error if the duration is invalid.")
|
||||
g.P("// In particular, it checks whether the value is within the range of")
|
||||
g.P("// -10000 years to +10000 years inclusive.")
|
||||
g.P("func (x *Duration) CheckValid() error {")
|
||||
g.P(" switch x.check() {")
|
||||
g.P(" case invalidUnderflow:")
|
||||
g.P(" return ", protoimplPackage.Ident("X"), ".NewError(\"duration (%v) exceeds -10000 years\", x)")
|
||||
g.P(" case invalidOverflow:")
|
||||
g.P(" return ", protoimplPackage.Ident("X"), ".NewError(\"duration (%v) exceeds +10000 years\", x)")
|
||||
g.P(" case invalidNanosRange:")
|
||||
g.P(" return ", protoimplPackage.Ident("X"), ".NewError(\"duration (%v) has out-of-range nanos\", x)")
|
||||
g.P(" case invalidNanosSign:")
|
||||
g.P(" return ", protoimplPackage.Ident("X"), ".NewError(\"duration (%v) has seconds and nanos with different signs\", x)")
|
||||
g.P(" default:")
|
||||
g.P(" return nil")
|
||||
g.P(" }")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("const invalidUnderflow = 1")
|
||||
g.P("const invalidOverflow = 2")
|
||||
g.P("const invalidNanosRange = 3")
|
||||
g.P("const invalidNanosSign = 4")
|
||||
g.P()
|
||||
|
||||
g.P("func (x *Duration) check() uint {")
|
||||
g.P(" const absDuration = 315576000000 // 10000yr * 365.25day/yr * 24hr/day * 60min/hr * 60sec/min")
|
||||
g.P(" secs := x.GetSeconds()")
|
||||
g.P(" nanos := x.GetNanos()")
|
||||
g.P(" switch {")
|
||||
g.P(" case secs < -absDuration:")
|
||||
g.P(" return invalidUnderflow")
|
||||
g.P(" case secs > +absDuration:")
|
||||
g.P(" return invalidOverflow")
|
||||
g.P(" case nanos <= -1e9 || nanos >= +1e9:")
|
||||
g.P(" return invalidNanosRange")
|
||||
g.P(" case (secs > 0 && nanos < 0) || (secs < 0 && nanos > 0):")
|
||||
g.P(" return invalidNanosSign")
|
||||
g.P(" default:")
|
||||
g.P(" return 0")
|
||||
g.P(" }")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
case genid.Struct_message_fullname:
|
||||
g.P("// NewStruct constructs a Struct from a general-purpose Go map.")
|
||||
g.P("// The map keys must be valid UTF-8.")
|
||||
g.P("// The map values are converted using NewValue.")
|
||||
g.P("func NewStruct(v map[string]interface{}) (*Struct, error) {")
|
||||
g.P(" x := &Struct{Fields: make(map[string]*Value, len(v))}")
|
||||
g.P(" for k, v := range v {")
|
||||
g.P(" if !", utf8Package.Ident("ValidString"), "(k) {")
|
||||
g.P(" return nil, ", protoimplPackage.Ident("X"), ".NewError(\"invalid UTF-8 in string: %q\", k)")
|
||||
g.P(" }")
|
||||
g.P(" var err error")
|
||||
g.P(" x.Fields[k], err = NewValue(v)")
|
||||
g.P(" if err != nil {")
|
||||
g.P(" return nil, err")
|
||||
g.P(" }")
|
||||
g.P(" }")
|
||||
g.P(" return x, nil")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// AsMap converts x to a general-purpose Go map.")
|
||||
g.P("// The map values are converted by calling Value.AsInterface.")
|
||||
g.P("func (x *Struct) AsMap() map[string]interface{} {")
|
||||
g.P(" vs := make(map[string]interface{})")
|
||||
g.P(" for k, v := range x.GetFields() {")
|
||||
g.P(" vs[k] = v.AsInterface()")
|
||||
g.P(" }")
|
||||
g.P(" return vs")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("func (x *Struct) MarshalJSON() ([]byte, error) {")
|
||||
g.P(" return ", protojsonPackage.Ident("Marshal"), "(x)")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("func (x *Struct) UnmarshalJSON(b []byte) error {")
|
||||
g.P(" return ", protojsonPackage.Ident("Unmarshal"), "(b, x)")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
case genid.ListValue_message_fullname:
|
||||
g.P("// NewList constructs a ListValue from a general-purpose Go slice.")
|
||||
g.P("// The slice elements are converted using NewValue.")
|
||||
g.P("func NewList(v []interface{}) (*ListValue, error) {")
|
||||
g.P(" x := &ListValue{Values: make([]*Value, len(v))}")
|
||||
g.P(" for i, v := range v {")
|
||||
g.P(" var err error")
|
||||
g.P(" x.Values[i], err = NewValue(v)")
|
||||
g.P(" if err != nil {")
|
||||
g.P(" return nil, err")
|
||||
g.P(" }")
|
||||
g.P(" }")
|
||||
g.P(" return x, nil")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// AsSlice converts x to a general-purpose Go slice.")
|
||||
g.P("// The slice elements are converted by calling Value.AsInterface.")
|
||||
g.P("func (x *ListValue) AsSlice() []interface{} {")
|
||||
g.P(" vs := make([]interface{}, len(x.GetValues()))")
|
||||
g.P(" for i, v := range x.GetValues() {")
|
||||
g.P(" vs[i] = v.AsInterface()")
|
||||
g.P(" }")
|
||||
g.P(" return vs")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("func (x *ListValue) MarshalJSON() ([]byte, error) {")
|
||||
g.P(" return ", protojsonPackage.Ident("Marshal"), "(x)")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("func (x *ListValue) UnmarshalJSON(b []byte) error {")
|
||||
g.P(" return ", protojsonPackage.Ident("Unmarshal"), "(b, x)")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
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]interface{} │ stored as StructValue ║")
|
||||
g.P("// ║ []interface{} │ 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.")
|
||||
g.P("func NewValue(v interface{}) (*Value, error) {")
|
||||
g.P(" switch v := v.(type) {")
|
||||
g.P(" case nil:")
|
||||
g.P(" return NewNullValue(), nil")
|
||||
g.P(" case bool:")
|
||||
g.P(" return NewBoolValue(v), nil")
|
||||
g.P(" case int:")
|
||||
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 uint32:")
|
||||
g.P(" return NewNumberValue(float64(v)), nil")
|
||||
g.P(" case uint64:")
|
||||
g.P(" return NewNumberValue(float64(v)), nil")
|
||||
g.P(" case float32:")
|
||||
g.P(" return NewNumberValue(float64(v)), nil")
|
||||
g.P(" case float64:")
|
||||
g.P(" return NewNumberValue(float64(v)), 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)")
|
||||
g.P(" }")
|
||||
g.P(" return NewStringValue(v), nil")
|
||||
g.P(" case []byte:")
|
||||
g.P(" s := ", base64Package.Ident("StdEncoding"), ".EncodeToString(v)")
|
||||
g.P(" return NewStringValue(s), nil")
|
||||
g.P(" case map[string]interface{}:")
|
||||
g.P(" v2, err := NewStruct(v)")
|
||||
g.P(" if err != nil {")
|
||||
g.P(" return nil, err")
|
||||
g.P(" }")
|
||||
g.P(" return NewStructValue(v2), nil")
|
||||
g.P(" case []interface{}:")
|
||||
g.P(" v2, err := NewList(v)")
|
||||
g.P(" if err != nil {")
|
||||
g.P(" return nil, err")
|
||||
g.P(" }")
|
||||
g.P(" return NewListValue(v2), nil")
|
||||
g.P(" default:")
|
||||
g.P(" return nil, ", protoimplPackage.Ident("X"), ".NewError(\"invalid type: %T\", v)")
|
||||
g.P(" }")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// NewNullValue constructs a new null Value.")
|
||||
g.P("func NewNullValue() *Value {")
|
||||
g.P(" return &Value{Kind: &Value_NullValue{NullValue: NullValue_NULL_VALUE}}")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// NewBoolValue constructs a new boolean Value.")
|
||||
g.P("func NewBoolValue(v bool) *Value {")
|
||||
g.P(" return &Value{Kind: &Value_BoolValue{BoolValue: v}}")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// NewNumberValue constructs a new number Value.")
|
||||
g.P("func NewNumberValue(v float64) *Value {")
|
||||
g.P(" return &Value{Kind: &Value_NumberValue{NumberValue: v}}")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// NewStringValue constructs a new string Value.")
|
||||
g.P("func NewStringValue(v string) *Value {")
|
||||
g.P(" return &Value{Kind: &Value_StringValue{StringValue: v}}")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// NewStructValue constructs a new struct Value.")
|
||||
g.P("func NewStructValue(v *Struct) *Value {")
|
||||
g.P(" return &Value{Kind: &Value_StructValue{StructValue: v}}")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// NewListValue constructs a new list Value.")
|
||||
g.P("func NewListValue(v *ListValue) *Value {")
|
||||
g.P(" return &Value{Kind: &Value_ListValue{ListValue: v}}")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// AsInterface converts x to a general-purpose Go interface.")
|
||||
g.P("//")
|
||||
g.P("// Calling Value.MarshalJSON and \"encoding/json\".Marshal on this output produce")
|
||||
g.P("// semantically equivalent JSON (assuming no errors occur).")
|
||||
g.P("//")
|
||||
g.P("// Floating-point values (i.e., \"NaN\", \"Infinity\", and \"-Infinity\") are")
|
||||
g.P("// converted as strings to remain compatible with MarshalJSON.")
|
||||
g.P("func (x *Value) AsInterface() interface{} {")
|
||||
g.P(" switch v := x.GetKind().(type) {")
|
||||
g.P(" case *Value_NumberValue:")
|
||||
g.P(" if v != nil {")
|
||||
g.P(" switch {")
|
||||
g.P(" case ", mathPackage.Ident("IsNaN"), "(v.NumberValue):")
|
||||
g.P(" return \"NaN\"")
|
||||
g.P(" case ", mathPackage.Ident("IsInf"), "(v.NumberValue, +1):")
|
||||
g.P(" return \"Infinity\"")
|
||||
g.P(" case ", mathPackage.Ident("IsInf"), "(v.NumberValue, -1):")
|
||||
g.P(" return \"-Infinity\"")
|
||||
g.P(" default:")
|
||||
g.P(" return v.NumberValue")
|
||||
g.P(" }")
|
||||
g.P(" }")
|
||||
g.P(" case *Value_StringValue:")
|
||||
g.P(" if v != nil {")
|
||||
g.P(" return v.StringValue")
|
||||
g.P(" }")
|
||||
g.P(" case *Value_BoolValue:")
|
||||
g.P(" if v != nil {")
|
||||
g.P(" return v.BoolValue")
|
||||
g.P(" }")
|
||||
g.P(" case *Value_StructValue:")
|
||||
g.P(" if v != nil {")
|
||||
g.P(" return v.StructValue.AsMap()")
|
||||
g.P(" }")
|
||||
g.P(" case *Value_ListValue:")
|
||||
g.P(" if v != nil {")
|
||||
g.P(" return v.ListValue.AsSlice()")
|
||||
g.P(" }")
|
||||
g.P(" }")
|
||||
g.P(" return nil")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("func (x *Value) MarshalJSON() ([]byte, error) {")
|
||||
g.P(" return ", protojsonPackage.Ident("Marshal"), "(x)")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("func (x *Value) UnmarshalJSON(b []byte) error {")
|
||||
g.P(" return ", protojsonPackage.Ident("Unmarshal"), "(b, x)")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
case genid.FieldMask_message_fullname:
|
||||
g.P("// New constructs a field mask from a list of paths and verifies that")
|
||||
g.P("// each one is valid according to the specified message type.")
|
||||
g.P("func New(m ", protoPackage.Ident("Message"), ", paths ...string) (*FieldMask, error) {")
|
||||
g.P(" x := new(FieldMask)")
|
||||
g.P(" return x, x.Append(m, paths...)")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// Union returns the union of all the paths in the input field masks.")
|
||||
g.P("func Union(mx *FieldMask, my *FieldMask, ms ...*FieldMask) *FieldMask {")
|
||||
g.P(" var out []string")
|
||||
g.P(" out = append(out, mx.GetPaths()...)")
|
||||
g.P(" out = append(out, my.GetPaths()...)")
|
||||
g.P(" for _, m := range ms {")
|
||||
g.P(" out = append(out, m.GetPaths()...)")
|
||||
g.P(" }")
|
||||
g.P(" return &FieldMask{Paths: normalizePaths(out)}")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// Intersect returns the intersection of all the paths in the input field masks.")
|
||||
g.P("func Intersect(mx *FieldMask, my *FieldMask, ms ...*FieldMask) *FieldMask {")
|
||||
g.P(" var ss1, ss2 []string // reused buffers for performance")
|
||||
g.P(" intersect := func(out, in []string) []string {")
|
||||
g.P(" ss1 = normalizePaths(append(ss1[:0], in...))")
|
||||
g.P(" ss2 = normalizePaths(append(ss2[:0], out...))")
|
||||
g.P(" out = out[:0]")
|
||||
g.P(" for i1, i2 := 0, 0; i1 < len(ss1) && i2 < len(ss2); {")
|
||||
g.P(" switch s1, s2 := ss1[i1], ss2[i2]; {")
|
||||
g.P(" case hasPathPrefix(s1, s2):")
|
||||
g.P(" out = append(out, s1)")
|
||||
g.P(" i1++")
|
||||
g.P(" case hasPathPrefix(s2, s1):")
|
||||
g.P(" out = append(out, s2)")
|
||||
g.P(" i2++")
|
||||
g.P(" case lessPath(s1, s2):")
|
||||
g.P(" i1++")
|
||||
g.P(" case lessPath(s2, s1):")
|
||||
g.P(" i2++")
|
||||
g.P(" }")
|
||||
g.P(" }")
|
||||
g.P(" return out")
|
||||
g.P(" }")
|
||||
g.P()
|
||||
g.P(" out := Union(mx, my, ms...).GetPaths()")
|
||||
g.P(" out = intersect(out, mx.GetPaths())")
|
||||
g.P(" out = intersect(out, my.GetPaths())")
|
||||
g.P(" for _, m := range ms {")
|
||||
g.P(" out = intersect(out, m.GetPaths())")
|
||||
g.P(" }")
|
||||
g.P(" return &FieldMask{Paths: normalizePaths(out)}")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// IsValid reports whether all the paths are syntactically valid and")
|
||||
g.P("// refer to known fields in the specified message type.")
|
||||
g.P("func (x *FieldMask) IsValid(m ", protoPackage.Ident("Message"), ") bool {")
|
||||
g.P(" paths := x.GetPaths()")
|
||||
g.P(" return numValidPaths(m, paths) == len(paths)")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// Append appends a list of paths to the mask and verifies that each one")
|
||||
g.P("// is valid according to the specified message type.")
|
||||
g.P("// An invalid path is not appended and breaks insertion of subsequent paths.")
|
||||
g.P("func (x *FieldMask) Append(m ", protoPackage.Ident("Message"), ", paths ...string) error {")
|
||||
g.P(" numValid := numValidPaths(m, paths)")
|
||||
g.P(" x.Paths = append(x.Paths, paths[:numValid]...)")
|
||||
g.P(" paths = paths[numValid:]")
|
||||
g.P(" if len(paths) > 0 {")
|
||||
g.P(" name := m.ProtoReflect().Descriptor().FullName()")
|
||||
g.P(" return ", protoimplPackage.Ident("X"), ".NewError(\"invalid path %q for message %q\", paths[0], name)")
|
||||
g.P(" }")
|
||||
g.P(" return nil")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("func numValidPaths(m ", protoPackage.Ident("Message"), ", paths []string) int {")
|
||||
g.P(" md0 := m.ProtoReflect().Descriptor()")
|
||||
g.P(" for i, path := range paths {")
|
||||
g.P(" md := md0")
|
||||
g.P(" if !rangeFields(path, func(field string) bool {")
|
||||
g.P(" // Search the field within the message.")
|
||||
g.P(" if md == nil {")
|
||||
g.P(" return false // not within a message")
|
||||
g.P(" }")
|
||||
g.P(" fd := md.Fields().ByName(", protoreflectPackage.Ident("Name"), "(field))")
|
||||
g.P(" // The real field name of a group is the message name.")
|
||||
g.P(" if fd == nil {")
|
||||
g.P(" gd := md.Fields().ByName(", protoreflectPackage.Ident("Name"), "(", stringsPackage.Ident("ToLower"), "(field)))")
|
||||
g.P(" if gd != nil && gd.Kind() == ", protoreflectPackage.Ident("GroupKind"), " && string(gd.Message().Name()) == field {")
|
||||
g.P(" fd = gd")
|
||||
g.P(" }")
|
||||
g.P(" } else if fd.Kind() == ", protoreflectPackage.Ident("GroupKind"), " && string(fd.Message().Name()) != field {")
|
||||
g.P(" fd = nil")
|
||||
g.P(" }")
|
||||
g.P(" if fd == nil {")
|
||||
g.P(" return false // message has does not have this field")
|
||||
g.P(" }")
|
||||
g.P()
|
||||
g.P(" // Identify the next message to search within.")
|
||||
g.P(" md = fd.Message() // may be nil")
|
||||
g.P(" if fd.IsMap() {")
|
||||
g.P(" md = fd.MapValue().Message() // may be nil")
|
||||
g.P(" }")
|
||||
g.P(" return true")
|
||||
g.P(" }) {")
|
||||
g.P(" return i")
|
||||
g.P(" }")
|
||||
g.P(" }")
|
||||
g.P(" return len(paths)")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// Normalize converts the mask to its canonical form where all paths are sorted")
|
||||
g.P("// and redundant paths are removed.")
|
||||
g.P("func (x *FieldMask) Normalize() {")
|
||||
g.P(" x.Paths = normalizePaths(x.Paths)")
|
||||
g.P("}")
|
||||
g.P()
|
||||
g.P("func normalizePaths(paths []string) []string {")
|
||||
g.P(" ", sortPackage.Ident("Slice"), "(paths, func(i, j int) bool {")
|
||||
g.P(" return lessPath(paths[i], paths[j])")
|
||||
g.P(" })")
|
||||
g.P()
|
||||
g.P(" // Elide any path that is a prefix match on the previous.")
|
||||
g.P(" out := paths[:0]")
|
||||
g.P(" for _, path := range paths {")
|
||||
g.P(" if len(out) > 0 && hasPathPrefix(path, out[len(out)-1]) {")
|
||||
g.P(" continue")
|
||||
g.P(" }")
|
||||
g.P(" out = append(out, path)")
|
||||
g.P(" }")
|
||||
g.P(" return out")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// hasPathPrefix is like strings.HasPrefix, but further checks for either")
|
||||
g.P("// an exact matche or that the prefix is delimited by a dot.")
|
||||
g.P("func hasPathPrefix(path, prefix string) bool {")
|
||||
g.P(" return ", stringsPackage.Ident("HasPrefix"), "(path, prefix) && (len(path) == len(prefix) || path[len(prefix)] == '.')")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// lessPath is a lexicographical comparison where dot is specially treated")
|
||||
g.P("// as the smallest symbol.")
|
||||
g.P("func lessPath(x, y string) bool {")
|
||||
g.P(" for i := 0; i < len(x) && i < len(y); i++ {")
|
||||
g.P(" if x[i] != y[i] {")
|
||||
g.P(" return (x[i] - '.') < (y[i] - '.')")
|
||||
g.P(" }")
|
||||
g.P(" }")
|
||||
g.P(" return len(x) < len(y)")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
g.P("// rangeFields is like strings.Split(path, \".\"), but avoids allocations by")
|
||||
g.P("// iterating over each field in place and calling a iterator function.")
|
||||
g.P("func rangeFields(path string, f func(field string) bool) bool {")
|
||||
g.P(" for {")
|
||||
g.P(" var field string")
|
||||
g.P(" if i := ", stringsPackage.Ident("IndexByte"), "(path, '.'); i >= 0 {")
|
||||
g.P(" field, path = path[:i], path[i:]")
|
||||
g.P(" } else {")
|
||||
g.P(" field, path = path, \"\"")
|
||||
g.P(" }")
|
||||
g.P()
|
||||
g.P(" if !f(field) {")
|
||||
g.P(" return false")
|
||||
g.P(" }")
|
||||
g.P()
|
||||
g.P(" if len(path) == 0 {")
|
||||
g.P(" return true")
|
||||
g.P(" }")
|
||||
g.P(" path = ", stringsPackage.Ident("TrimPrefix"), "(path, \".\")")
|
||||
g.P(" }")
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
case genid.BoolValue_message_fullname,
|
||||
genid.Int32Value_message_fullname,
|
||||
genid.Int64Value_message_fullname,
|
||||
genid.UInt32Value_message_fullname,
|
||||
genid.UInt64Value_message_fullname,
|
||||
genid.FloatValue_message_fullname,
|
||||
genid.DoubleValue_message_fullname,
|
||||
genid.StringValue_message_fullname,
|
||||
genid.BytesValue_message_fullname:
|
||||
funcName := strings.TrimSuffix(m.GoIdent.GoName, "Value")
|
||||
typeName := strings.ToLower(funcName)
|
||||
switch typeName {
|
||||
case "float":
|
||||
typeName = "float32"
|
||||
case "double":
|
||||
typeName = "float64"
|
||||
case "bytes":
|
||||
typeName = "[]byte"
|
||||
}
|
||||
|
||||
g.P("// ", funcName, " stores v in a new ", m.GoIdent, " and returns a pointer to it.")
|
||||
g.P("func ", funcName, "(v ", typeName, ") *", m.GoIdent, " {")
|
||||
g.P(" return &", m.GoIdent, "{Value: v}")
|
||||
g.P("}")
|
||||
g.P()
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"google.golang.org/protobuf/encoding/prototext"
|
||||
"google.golang.org/protobuf/internal/errors"
|
||||
"google.golang.org/protobuf/proto"
|
||||
pref "google.golang.org/protobuf/reflect/protoreflect"
|
||||
piface "google.golang.org/protobuf/runtime/protoiface"
|
||||
@ -19,6 +20,12 @@ import (
|
||||
// functions that we do not want to appear in godoc.
|
||||
type Export struct{}
|
||||
|
||||
// NewError formats a string according to the format specifier and arguments and
|
||||
// returns an error that has a "proto" prefix.
|
||||
func (Export) NewError(f string, x ...interface{}) error {
|
||||
return errors.New(f, x...)
|
||||
}
|
||||
|
||||
// enum is any enum type generated by protoc-gen-go
|
||||
// and must be a named int32 type.
|
||||
type enum = interface{}
|
||||
|
@ -34,9 +34,12 @@
|
||||
package anypb
|
||||
|
||||
import (
|
||||
proto "google.golang.org/protobuf/proto"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoregistry "google.golang.org/protobuf/reflect/protoregistry"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
strings "strings"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
@ -158,6 +161,110 @@ type Any struct {
|
||||
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// MarshalFrom marshals src into dst as the underlying message
|
||||
// using the provided marshal options.
|
||||
//
|
||||
// If no options are specified, call dst.MarshalFrom instead.
|
||||
func MarshalFrom(dst *Any, src proto.Message, opts proto.MarshalOptions) error {
|
||||
const urlPrefix = "type.googleapis.com/"
|
||||
b, err := opts.Marshal(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dst.TypeUrl = urlPrefix + string(src.ProtoReflect().Descriptor().FullName())
|
||||
dst.Value = b
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalTo unmarshals the underlying message from src into dst
|
||||
// using the provided unmarshal options.
|
||||
// It reports an error if dst is not of the right message type.
|
||||
//
|
||||
// If no options are specified, call src.UnmarshalTo instead.
|
||||
func UnmarshalTo(src *Any, dst proto.Message, opts proto.UnmarshalOptions) error {
|
||||
if !src.MessageIs(dst) {
|
||||
got := dst.ProtoReflect().Descriptor().FullName()
|
||||
want := src.MessageName()
|
||||
return protoimpl.X.NewError("mismatching message types: got %q, want %q", got, want)
|
||||
}
|
||||
return opts.Unmarshal(src.GetValue(), dst)
|
||||
}
|
||||
|
||||
// UnmarshalNew unmarshals the underlying message from src into dst,
|
||||
// which is newly created message using a type resolved from the type URL.
|
||||
// The message type is resolved according to opt.Resolver,
|
||||
// which should implement protoregistry.MessageTypeResolver.
|
||||
// It reports an error if the underlying message type could not be resolved.
|
||||
//
|
||||
// If no options are specified, call src.UnmarshalNew instead.
|
||||
func UnmarshalNew(src *Any, opts proto.UnmarshalOptions) (dst proto.Message, err error) {
|
||||
if src.GetTypeUrl() == "" {
|
||||
return nil, protoimpl.X.NewError("invalid empty type URL")
|
||||
}
|
||||
if opts.Resolver == nil {
|
||||
opts.Resolver = protoregistry.GlobalTypes
|
||||
}
|
||||
r, ok := opts.Resolver.(protoregistry.MessageTypeResolver)
|
||||
if !ok {
|
||||
return nil, protoregistry.NotFound
|
||||
}
|
||||
mt, err := r.FindMessageByURL(src.GetTypeUrl())
|
||||
if err != nil {
|
||||
if err == protoregistry.NotFound {
|
||||
return nil, err
|
||||
}
|
||||
return nil, protoimpl.X.NewError("could not resolve %q: %v", src.GetTypeUrl(), err)
|
||||
}
|
||||
dst = mt.New().Interface()
|
||||
return dst, opts.Unmarshal(src.GetValue(), dst)
|
||||
}
|
||||
|
||||
// MessageIs reports whether the underlying message is of the same type as m.
|
||||
func (x *Any) MessageIs(m proto.Message) bool {
|
||||
if m == nil {
|
||||
return false
|
||||
}
|
||||
url := x.GetTypeUrl()
|
||||
name := string(m.ProtoReflect().Descriptor().FullName())
|
||||
if !strings.HasSuffix(url, name) {
|
||||
return false
|
||||
}
|
||||
return len(url) == len(name) || url[len(url)-len(name)-1] == '/'
|
||||
}
|
||||
|
||||
// MessageName reports the full name of the underlying message,
|
||||
// returning an empty string if invalid.
|
||||
func (x *Any) MessageName() protoreflect.FullName {
|
||||
url := x.GetTypeUrl()
|
||||
name := protoreflect.FullName(url)
|
||||
if i := strings.LastIndexByte(url, '/'); i >= 0 {
|
||||
name = name[i+len("/"):]
|
||||
}
|
||||
if !name.IsValid() {
|
||||
return ""
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// MarshalFrom marshals m into x as the underlying message.
|
||||
func (x *Any) MarshalFrom(m proto.Message) error {
|
||||
return MarshalFrom(x, m, proto.MarshalOptions{})
|
||||
}
|
||||
|
||||
// UnmarshalTo unmarshals the contents of the underlying message of x into m.
|
||||
// It resets m before performing the unmarshal operation.
|
||||
// It reports an error if m is not of the right message type.
|
||||
func (x *Any) UnmarshalTo(m proto.Message) error {
|
||||
return UnmarshalTo(x, m, proto.UnmarshalOptions{})
|
||||
}
|
||||
|
||||
// UnmarshalNew unmarshals the contents of the underlying message of x into
|
||||
// a newly allocated message of the specified type.
|
||||
// It reports an error if the underlying message type could not be resolved.
|
||||
func (x *Any) UnmarshalNew() (proto.Message, error) {
|
||||
return UnmarshalNew(x, proto.UnmarshalOptions{})
|
||||
}
|
||||
|
||||
func (x *Any) Reset() {
|
||||
*x = Any{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
|
184
types/known/anypb/any_test.go
Normal file
184
types/known/anypb/any_test.go
Normal file
@ -0,0 +1,184 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package anypb_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
|
||||
testpb "google.golang.org/protobuf/internal/testprotos/test"
|
||||
apb "google.golang.org/protobuf/types/known/anypb"
|
||||
epb "google.golang.org/protobuf/types/known/emptypb"
|
||||
wpb "google.golang.org/protobuf/types/known/wrapperspb"
|
||||
)
|
||||
|
||||
func mustMarshal(m proto.Message) []byte {
|
||||
b, err := proto.MarshalOptions{AllowPartial: true, Deterministic: true}.Marshal(m)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func TestMessage(t *testing.T) {
|
||||
tests := []struct {
|
||||
inAny *apb.Any
|
||||
inTarget proto.Message
|
||||
wantIs bool
|
||||
wantName protoreflect.FullName
|
||||
}{{
|
||||
inAny: nil,
|
||||
inTarget: nil,
|
||||
wantIs: false,
|
||||
wantName: "",
|
||||
}, {
|
||||
inAny: new(apb.Any),
|
||||
inTarget: nil,
|
||||
wantIs: false,
|
||||
wantName: "",
|
||||
}, {
|
||||
inAny: new(apb.Any),
|
||||
inTarget: (*testpb.TestAllTypes)(nil),
|
||||
wantIs: false,
|
||||
wantName: "",
|
||||
}, {
|
||||
inAny: &apb.Any{TypeUrl: "foo"},
|
||||
inTarget: (*testpb.TestAllTypes)(nil),
|
||||
wantIs: false,
|
||||
wantName: "foo",
|
||||
}, {
|
||||
inAny: &apb.Any{TypeUrl: "foo$"},
|
||||
inTarget: (*testpb.TestAllTypes)(nil),
|
||||
wantIs: false,
|
||||
wantName: "",
|
||||
}, {
|
||||
inAny: &apb.Any{TypeUrl: "/foo"},
|
||||
inTarget: (*testpb.TestAllTypes)(nil),
|
||||
wantIs: false,
|
||||
wantName: "foo",
|
||||
}, {
|
||||
inAny: &apb.Any{TypeUrl: "/bar/foo"},
|
||||
inTarget: (*testpb.TestAllTypes)(nil),
|
||||
wantIs: false,
|
||||
wantName: "foo",
|
||||
}, {
|
||||
inAny: &apb.Any{TypeUrl: "google.golang.org/bar/foo"},
|
||||
inTarget: (*testpb.TestAllTypes)(nil),
|
||||
wantIs: false,
|
||||
wantName: "foo",
|
||||
}, {
|
||||
inAny: &apb.Any{TypeUrl: "goproto.proto.test.TestAllTypes"},
|
||||
inTarget: (*testpb.TestAllTypes)(nil),
|
||||
wantIs: true,
|
||||
wantName: "goproto.proto.test.TestAllTypes",
|
||||
}, {
|
||||
inAny: &apb.Any{TypeUrl: "goproto.proto.test.TestAllTypes$"},
|
||||
inTarget: (*testpb.TestAllTypes)(nil),
|
||||
wantIs: false,
|
||||
wantName: "",
|
||||
}, {
|
||||
inAny: &apb.Any{TypeUrl: "/goproto.proto.test.TestAllTypes"},
|
||||
inTarget: (*testpb.TestAllTypes)(nil),
|
||||
wantIs: true,
|
||||
wantName: "goproto.proto.test.TestAllTypes",
|
||||
}, {
|
||||
inAny: &apb.Any{TypeUrl: "google.golang.org/foo/goproto.proto.test.TestAllTypes"},
|
||||
inTarget: (*testpb.TestAllTypes)(nil),
|
||||
wantIs: true,
|
||||
wantName: "goproto.proto.test.TestAllTypes",
|
||||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
gotIs := tt.inAny.MessageIs(tt.inTarget)
|
||||
if gotIs != tt.wantIs {
|
||||
t.Errorf("MessageIs(%v, %v) = %v, want %v", tt.inAny, tt.inTarget, gotIs, tt.wantIs)
|
||||
}
|
||||
gotName := tt.inAny.MessageName()
|
||||
if gotName != tt.wantName {
|
||||
t.Errorf("MessageName(%v) = %v, want %v", tt.inAny, gotName, tt.wantName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundtrip(t *testing.T) {
|
||||
tests := []struct {
|
||||
msg proto.Message
|
||||
any *apb.Any
|
||||
}{{
|
||||
msg: &testpb.TestAllTypes{},
|
||||
any: &apb.Any{
|
||||
TypeUrl: "type.googleapis.com/goproto.proto.test.TestAllTypes",
|
||||
},
|
||||
}, {
|
||||
msg: &testpb.TestAllTypes{
|
||||
OptionalString: proto.String("hello, world!"),
|
||||
},
|
||||
any: &apb.Any{
|
||||
TypeUrl: "type.googleapis.com/goproto.proto.test.TestAllTypes",
|
||||
Value: mustMarshal(&testpb.TestAllTypes{
|
||||
OptionalString: proto.String("hello, world!"),
|
||||
}),
|
||||
},
|
||||
}, {
|
||||
msg: &wpb.StringValue{Value: ""},
|
||||
any: &apb.Any{
|
||||
TypeUrl: "type.googleapis.com/google.protobuf.StringValue",
|
||||
},
|
||||
}, {
|
||||
msg: wpb.String("hello, world"),
|
||||
any: &apb.Any{
|
||||
TypeUrl: "type.googleapis.com/google.protobuf.StringValue",
|
||||
Value: mustMarshal(wpb.String("hello, world")),
|
||||
},
|
||||
}, {
|
||||
msg: &apb.Any{
|
||||
TypeUrl: "type.googleapis.com/google.protobuf.StringValue",
|
||||
Value: mustMarshal(wpb.String("hello, world")),
|
||||
},
|
||||
any: &apb.Any{
|
||||
TypeUrl: "type.googleapis.com/google.protobuf.Any",
|
||||
Value: mustMarshal(&apb.Any{
|
||||
TypeUrl: "type.googleapis.com/google.protobuf.StringValue",
|
||||
Value: mustMarshal(wpb.String("hello, world")),
|
||||
}),
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
// Unmarshal to the wrong message type.
|
||||
var empty epb.Empty
|
||||
if err := tt.any.UnmarshalTo(&empty); err == nil {
|
||||
t.Errorf("UnmarshalTo(empty) = nil, want non-nil")
|
||||
}
|
||||
|
||||
gotAny := new(apb.Any)
|
||||
if err := gotAny.MarshalFrom(tt.msg); err != nil {
|
||||
t.Errorf("MarshalFrom() error: %v", err)
|
||||
}
|
||||
if diff := cmp.Diff(tt.any, gotAny, protocmp.Transform()); diff != "" {
|
||||
t.Errorf("MarshalFrom() output mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
gotPB := tt.msg.ProtoReflect().New().Interface()
|
||||
if err := tt.any.UnmarshalTo(gotPB); err != nil {
|
||||
t.Errorf("UnmarshalTo() error: %v", err)
|
||||
}
|
||||
if diff := cmp.Diff(tt.msg, gotPB, protocmp.Transform()); diff != "" {
|
||||
t.Errorf("UnmarshalTo() output mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
gotPB, err := tt.any.UnmarshalNew()
|
||||
if err != nil {
|
||||
t.Errorf("UnmarshalNew() error: %v", err)
|
||||
}
|
||||
if diff := cmp.Diff(tt.msg, gotPB, protocmp.Transform()); diff != "" {
|
||||
t.Errorf("UnmarshalNew() output mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
}
|
@ -36,8 +36,10 @@ package durationpb
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
math "math"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
time "time"
|
||||
)
|
||||
|
||||
// A Duration represents a signed, fixed-length span of time represented
|
||||
@ -118,6 +120,82 @@ type Duration struct {
|
||||
Nanos int32 `protobuf:"varint,2,opt,name=nanos,proto3" json:"nanos,omitempty"`
|
||||
}
|
||||
|
||||
// New constructs a new Duration from the provided time.Duration.
|
||||
func New(d time.Duration) *Duration {
|
||||
nanos := d.Nanoseconds()
|
||||
secs := nanos / 1e9
|
||||
nanos -= secs * 1e9
|
||||
return &Duration{Seconds: int64(secs), Nanos: int32(nanos)}
|
||||
}
|
||||
|
||||
// AsDuration converts x to a time.Duration,
|
||||
// returning the closest duration value in the event of overflow.
|
||||
func (x *Duration) AsDuration() time.Duration {
|
||||
secs := x.GetSeconds()
|
||||
nanos := x.GetNanos()
|
||||
d := time.Duration(secs) * time.Second
|
||||
overflow := d/time.Second != time.Duration(secs)
|
||||
d += time.Duration(nanos) * time.Nanosecond
|
||||
overflow = overflow || (secs < 0 && nanos < 0 && d > 0)
|
||||
overflow = overflow || (secs > 0 && nanos > 0 && d < 0)
|
||||
if overflow {
|
||||
switch {
|
||||
case secs < 0:
|
||||
return time.Duration(math.MinInt64)
|
||||
case secs > 0:
|
||||
return time.Duration(math.MaxInt64)
|
||||
}
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// IsValid reports whether the duration is valid.
|
||||
// It is equivalent to CheckValid == nil.
|
||||
func (x *Duration) IsValid() bool {
|
||||
return x.check() == 0
|
||||
}
|
||||
|
||||
// CheckValid returns an error if the duration is invalid.
|
||||
// In particular, it checks whether the value is within the range of
|
||||
// -10000 years to +10000 years inclusive.
|
||||
func (x *Duration) CheckValid() error {
|
||||
switch x.check() {
|
||||
case invalidUnderflow:
|
||||
return protoimpl.X.NewError("duration (%v) exceeds -10000 years", x)
|
||||
case invalidOverflow:
|
||||
return protoimpl.X.NewError("duration (%v) exceeds +10000 years", x)
|
||||
case invalidNanosRange:
|
||||
return protoimpl.X.NewError("duration (%v) has out-of-range nanos", x)
|
||||
case invalidNanosSign:
|
||||
return protoimpl.X.NewError("duration (%v) has seconds and nanos with different signs", x)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
const invalidUnderflow = 1
|
||||
const invalidOverflow = 2
|
||||
const invalidNanosRange = 3
|
||||
const invalidNanosSign = 4
|
||||
|
||||
func (x *Duration) check() uint {
|
||||
const absDuration = 315576000000 // 10000yr * 365.25day/yr * 24hr/day * 60min/hr * 60sec/min
|
||||
secs := x.GetSeconds()
|
||||
nanos := x.GetNanos()
|
||||
switch {
|
||||
case secs < -absDuration:
|
||||
return invalidUnderflow
|
||||
case secs > +absDuration:
|
||||
return invalidOverflow
|
||||
case nanos <= -1e9 || nanos >= +1e9:
|
||||
return invalidNanosRange
|
||||
case (secs > 0 && nanos < 0) || (secs < 0 && nanos > 0):
|
||||
return invalidNanosSign
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Duration) Reset() {
|
||||
*x = Duration{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
|
100
types/known/durationpb/duration_test.go
Normal file
100
types/known/durationpb/duration_test.go
Normal file
@ -0,0 +1,100 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package durationpb_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"google.golang.org/protobuf/internal/detrand"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
|
||||
durpb "google.golang.org/protobuf/types/known/durationpb"
|
||||
)
|
||||
|
||||
func init() {
|
||||
detrand.Disable()
|
||||
}
|
||||
|
||||
const (
|
||||
minGoSeconds = math.MinInt64 / int64(1e9)
|
||||
maxGoSeconds = math.MaxInt64 / int64(1e9)
|
||||
absSeconds = 315576000000 // 10000yr * 365.25day/yr * 24hr/day * 60min/hr * 60sec/min
|
||||
)
|
||||
|
||||
func TestToDuration(t *testing.T) {
|
||||
tests := []struct {
|
||||
in time.Duration
|
||||
want *durpb.Duration
|
||||
}{
|
||||
{in: time.Duration(0), want: &durpb.Duration{Seconds: 0, Nanos: 0}},
|
||||
{in: -time.Second, want: &durpb.Duration{Seconds: -1, Nanos: 0}},
|
||||
{in: +time.Second, want: &durpb.Duration{Seconds: +1, Nanos: 0}},
|
||||
{in: -time.Second - time.Millisecond, want: &durpb.Duration{Seconds: -1, Nanos: -1e6}},
|
||||
{in: +time.Second + time.Millisecond, want: &durpb.Duration{Seconds: +1, Nanos: +1e6}},
|
||||
{in: time.Duration(math.MinInt64), want: &durpb.Duration{Seconds: minGoSeconds, Nanos: int32(math.MinInt64 - 1e9*minGoSeconds)}},
|
||||
{in: time.Duration(math.MaxInt64), want: &durpb.Duration{Seconds: maxGoSeconds, Nanos: int32(math.MaxInt64 - 1e9*maxGoSeconds)}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := durpb.New(tt.in)
|
||||
if diff := cmp.Diff(tt.want, got, protocmp.Transform()); diff != "" {
|
||||
t.Errorf("New(%v) mismatch (-want +got):\n%s", tt.in, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromDuration(t *testing.T) {
|
||||
tests := []struct {
|
||||
in *durpb.Duration
|
||||
wantDur time.Duration
|
||||
wantErr error
|
||||
}{
|
||||
{in: nil, wantDur: time.Duration(0)},
|
||||
{in: new(durpb.Duration), wantDur: time.Duration(0)},
|
||||
{in: &durpb.Duration{Seconds: -1, Nanos: 0}, wantDur: -time.Second},
|
||||
{in: &durpb.Duration{Seconds: +1, Nanos: 0}, wantDur: +time.Second},
|
||||
{in: &durpb.Duration{Seconds: 0, Nanos: -1}, wantDur: -time.Nanosecond},
|
||||
{in: &durpb.Duration{Seconds: 0, Nanos: +1}, wantDur: +time.Nanosecond},
|
||||
{in: &durpb.Duration{Seconds: -100, Nanos: 0}, wantDur: -100 * time.Second},
|
||||
{in: &durpb.Duration{Seconds: +100, Nanos: 0}, wantDur: +100 * time.Second},
|
||||
{in: &durpb.Duration{Seconds: -100, Nanos: -987}, wantDur: -100*time.Second - 987*time.Nanosecond},
|
||||
{in: &durpb.Duration{Seconds: +100, Nanos: +987}, wantDur: +100*time.Second + 987*time.Nanosecond},
|
||||
{in: &durpb.Duration{Seconds: minGoSeconds, Nanos: int32(math.MinInt64 - 1e9*minGoSeconds)}, wantDur: time.Duration(math.MinInt64)},
|
||||
{in: &durpb.Duration{Seconds: maxGoSeconds, Nanos: int32(math.MaxInt64 - 1e9*maxGoSeconds)}, wantDur: time.Duration(math.MaxInt64)},
|
||||
{in: &durpb.Duration{Seconds: minGoSeconds - 1, Nanos: int32(math.MinInt64 - 1e9*minGoSeconds)}, wantDur: time.Duration(math.MinInt64)},
|
||||
{in: &durpb.Duration{Seconds: maxGoSeconds + 1, Nanos: int32(math.MaxInt64 - 1e9*maxGoSeconds)}, wantDur: time.Duration(math.MaxInt64)},
|
||||
{in: &durpb.Duration{Seconds: minGoSeconds, Nanos: int32(math.MinInt64-1e9*minGoSeconds) - 1}, wantDur: time.Duration(math.MinInt64)},
|
||||
{in: &durpb.Duration{Seconds: maxGoSeconds, Nanos: int32(math.MaxInt64-1e9*maxGoSeconds) + 1}, wantDur: time.Duration(math.MaxInt64)},
|
||||
{in: &durpb.Duration{Seconds: -123, Nanos: +456}, wantDur: -123*time.Second + 456*time.Nanosecond, wantErr: textError("duration (seconds:-123 nanos:456) has seconds and nanos with different signs")},
|
||||
{in: &durpb.Duration{Seconds: +123, Nanos: -456}, wantDur: +123*time.Second - 456*time.Nanosecond, wantErr: textError("duration (seconds:123 nanos:-456) has seconds and nanos with different signs")},
|
||||
{in: &durpb.Duration{Seconds: math.MinInt64}, wantDur: time.Duration(math.MinInt64), wantErr: textError("duration (seconds:-9223372036854775808) exceeds -10000 years")},
|
||||
{in: &durpb.Duration{Seconds: math.MaxInt64}, wantDur: time.Duration(math.MaxInt64), wantErr: textError("duration (seconds:9223372036854775807) exceeds +10000 years")},
|
||||
{in: &durpb.Duration{Seconds: -absSeconds, Nanos: -(1e9 - 1)}, wantDur: time.Duration(math.MinInt64)},
|
||||
{in: &durpb.Duration{Seconds: +absSeconds, Nanos: +(1e9 - 1)}, wantDur: time.Duration(math.MaxInt64)},
|
||||
{in: &durpb.Duration{Seconds: -absSeconds - 1, Nanos: 0}, wantDur: time.Duration(math.MinInt64), wantErr: textError("duration (seconds:-315576000001) exceeds -10000 years")},
|
||||
{in: &durpb.Duration{Seconds: +absSeconds + 1, Nanos: 0}, wantDur: time.Duration(math.MaxInt64), wantErr: textError("duration (seconds:315576000001) exceeds +10000 years")},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
gotDur := tt.in.AsDuration()
|
||||
if diff := cmp.Diff(tt.wantDur, gotDur); diff != "" {
|
||||
t.Errorf("AsDuration(%v) mismatch (-want +got):\n%s", tt.in, diff)
|
||||
}
|
||||
gotErr := tt.in.CheckValid()
|
||||
if diff := cmp.Diff(tt.wantErr, gotErr, cmpopts.EquateErrors()); diff != "" {
|
||||
t.Errorf("CheckValid(%v) mismatch (-want +got):\n%s", tt.in, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type textError string
|
||||
|
||||
func (e textError) Error() string { return string(e) }
|
||||
func (e textError) Is(err error) bool { return err != nil && strings.Contains(err.Error(), e.Error()) }
|
@ -34,9 +34,12 @@
|
||||
package fieldmaskpb
|
||||
|
||||
import (
|
||||
proto "google.golang.org/protobuf/proto"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sort "sort"
|
||||
strings "strings"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
@ -248,6 +251,175 @@ type FieldMask struct {
|
||||
Paths []string `protobuf:"bytes,1,rep,name=paths,proto3" json:"paths,omitempty"`
|
||||
}
|
||||
|
||||
// New constructs a field mask from a list of paths and verifies that
|
||||
// each one is valid according to the specified message type.
|
||||
func New(m proto.Message, paths ...string) (*FieldMask, error) {
|
||||
x := new(FieldMask)
|
||||
return x, x.Append(m, paths...)
|
||||
}
|
||||
|
||||
// Union returns the union of all the paths in the input field masks.
|
||||
func Union(mx *FieldMask, my *FieldMask, ms ...*FieldMask) *FieldMask {
|
||||
var out []string
|
||||
out = append(out, mx.GetPaths()...)
|
||||
out = append(out, my.GetPaths()...)
|
||||
for _, m := range ms {
|
||||
out = append(out, m.GetPaths()...)
|
||||
}
|
||||
return &FieldMask{Paths: normalizePaths(out)}
|
||||
}
|
||||
|
||||
// Intersect returns the intersection of all the paths in the input field masks.
|
||||
func Intersect(mx *FieldMask, my *FieldMask, ms ...*FieldMask) *FieldMask {
|
||||
var ss1, ss2 []string // reused buffers for performance
|
||||
intersect := func(out, in []string) []string {
|
||||
ss1 = normalizePaths(append(ss1[:0], in...))
|
||||
ss2 = normalizePaths(append(ss2[:0], out...))
|
||||
out = out[:0]
|
||||
for i1, i2 := 0, 0; i1 < len(ss1) && i2 < len(ss2); {
|
||||
switch s1, s2 := ss1[i1], ss2[i2]; {
|
||||
case hasPathPrefix(s1, s2):
|
||||
out = append(out, s1)
|
||||
i1++
|
||||
case hasPathPrefix(s2, s1):
|
||||
out = append(out, s2)
|
||||
i2++
|
||||
case lessPath(s1, s2):
|
||||
i1++
|
||||
case lessPath(s2, s1):
|
||||
i2++
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
out := Union(mx, my, ms...).GetPaths()
|
||||
out = intersect(out, mx.GetPaths())
|
||||
out = intersect(out, my.GetPaths())
|
||||
for _, m := range ms {
|
||||
out = intersect(out, m.GetPaths())
|
||||
}
|
||||
return &FieldMask{Paths: normalizePaths(out)}
|
||||
}
|
||||
|
||||
// IsValid reports whether all the paths are syntactically valid and
|
||||
// refer to known fields in the specified message type.
|
||||
func (x *FieldMask) IsValid(m proto.Message) bool {
|
||||
paths := x.GetPaths()
|
||||
return numValidPaths(m, paths) == len(paths)
|
||||
}
|
||||
|
||||
// Append appends a list of paths to the mask and verifies that each one
|
||||
// is valid according to the specified message type.
|
||||
// An invalid path is not appended and breaks insertion of subsequent paths.
|
||||
func (x *FieldMask) Append(m proto.Message, paths ...string) error {
|
||||
numValid := numValidPaths(m, paths)
|
||||
x.Paths = append(x.Paths, paths[:numValid]...)
|
||||
paths = paths[numValid:]
|
||||
if len(paths) > 0 {
|
||||
name := m.ProtoReflect().Descriptor().FullName()
|
||||
return protoimpl.X.NewError("invalid path %q for message %q", paths[0], name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func numValidPaths(m proto.Message, paths []string) int {
|
||||
md0 := m.ProtoReflect().Descriptor()
|
||||
for i, path := range paths {
|
||||
md := md0
|
||||
if !rangeFields(path, func(field string) bool {
|
||||
// Search the field within the message.
|
||||
if md == nil {
|
||||
return false // not within a message
|
||||
}
|
||||
fd := md.Fields().ByName(protoreflect.Name(field))
|
||||
// The real field name of a group is the message name.
|
||||
if fd == nil {
|
||||
gd := md.Fields().ByName(protoreflect.Name(strings.ToLower(field)))
|
||||
if gd != nil && gd.Kind() == protoreflect.GroupKind && string(gd.Message().Name()) == field {
|
||||
fd = gd
|
||||
}
|
||||
} else if fd.Kind() == protoreflect.GroupKind && string(fd.Message().Name()) != field {
|
||||
fd = nil
|
||||
}
|
||||
if fd == nil {
|
||||
return false // message has does not have this field
|
||||
}
|
||||
|
||||
// Identify the next message to search within.
|
||||
md = fd.Message() // may be nil
|
||||
if fd.IsMap() {
|
||||
md = fd.MapValue().Message() // may be nil
|
||||
}
|
||||
return true
|
||||
}) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return len(paths)
|
||||
}
|
||||
|
||||
// Normalize converts the mask to its canonical form where all paths are sorted
|
||||
// and redundant paths are removed.
|
||||
func (x *FieldMask) Normalize() {
|
||||
x.Paths = normalizePaths(x.Paths)
|
||||
}
|
||||
|
||||
func normalizePaths(paths []string) []string {
|
||||
sort.Slice(paths, func(i, j int) bool {
|
||||
return lessPath(paths[i], paths[j])
|
||||
})
|
||||
|
||||
// Elide any path that is a prefix match on the previous.
|
||||
out := paths[:0]
|
||||
for _, path := range paths {
|
||||
if len(out) > 0 && hasPathPrefix(path, out[len(out)-1]) {
|
||||
continue
|
||||
}
|
||||
out = append(out, path)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// hasPathPrefix is like strings.HasPrefix, but further checks for either
|
||||
// an exact matche or that the prefix is delimited by a dot.
|
||||
func hasPathPrefix(path, prefix string) bool {
|
||||
return strings.HasPrefix(path, prefix) && (len(path) == len(prefix) || path[len(prefix)] == '.')
|
||||
}
|
||||
|
||||
// lessPath is a lexicographical comparison where dot is specially treated
|
||||
// as the smallest symbol.
|
||||
func lessPath(x, y string) bool {
|
||||
for i := 0; i < len(x) && i < len(y); i++ {
|
||||
if x[i] != y[i] {
|
||||
return (x[i] - '.') < (y[i] - '.')
|
||||
}
|
||||
}
|
||||
return len(x) < len(y)
|
||||
}
|
||||
|
||||
// rangeFields is like strings.Split(path, "."), but avoids allocations by
|
||||
// iterating over each field in place and calling a iterator function.
|
||||
func rangeFields(path string, f func(field string) bool) bool {
|
||||
for {
|
||||
var field string
|
||||
if i := strings.IndexByte(path, '.'); i >= 0 {
|
||||
field, path = path[:i], path[i:]
|
||||
} else {
|
||||
field, path = path, ""
|
||||
}
|
||||
|
||||
if !f(field) {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(path) == 0 {
|
||||
return true
|
||||
}
|
||||
path = strings.TrimPrefix(path, ".")
|
||||
}
|
||||
}
|
||||
|
||||
func (x *FieldMask) Reset() {
|
||||
*x = FieldMask{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
|
196
types/known/fieldmaskpb/field_mask_test.go
Normal file
196
types/known/fieldmaskpb/field_mask_test.go
Normal file
@ -0,0 +1,196 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fieldmaskpb_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
testpb "google.golang.org/protobuf/internal/testprotos/test"
|
||||
fmpb "google.golang.org/protobuf/types/known/fieldmaskpb"
|
||||
)
|
||||
|
||||
func TestAppend(t *testing.T) {
|
||||
tests := []struct {
|
||||
inMessage proto.Message
|
||||
inPaths []string
|
||||
wantPaths []string
|
||||
wantError error
|
||||
}{{
|
||||
inMessage: (*fmpb.FieldMask)(nil),
|
||||
inPaths: []string{},
|
||||
wantPaths: []string{},
|
||||
}, {
|
||||
inMessage: (*fmpb.FieldMask)(nil),
|
||||
inPaths: []string{"paths", "paths"},
|
||||
wantPaths: []string{"paths", "paths"},
|
||||
}, {
|
||||
inMessage: (*fmpb.FieldMask)(nil),
|
||||
inPaths: []string{"paths", "<INVALID>", "paths"},
|
||||
wantPaths: []string{"paths"},
|
||||
wantError: cmpopts.AnyError,
|
||||
}, {
|
||||
inMessage: (*testpb.TestAllTypes)(nil),
|
||||
inPaths: []string{"optional_int32", "OptionalGroup.optional_nested_message", "map_uint32_uint32", "map_string_nested_message.corecursive", "oneof_bool"},
|
||||
wantPaths: []string{"optional_int32", "OptionalGroup.optional_nested_message", "map_uint32_uint32", "map_string_nested_message.corecursive", "oneof_bool"},
|
||||
}, {
|
||||
inMessage: (*testpb.TestAllTypes)(nil),
|
||||
inPaths: []string{"optional_nested_message", "optional_nested_message.corecursive", "optional_nested_message.corecursive.optional_nested_message", "optional_nested_message.corecursive.optional_nested_message.corecursive"},
|
||||
wantPaths: []string{"optional_nested_message", "optional_nested_message.corecursive", "optional_nested_message.corecursive.optional_nested_message", "optional_nested_message.corecursive.optional_nested_message.corecursive"},
|
||||
}, {
|
||||
inMessage: (*testpb.TestAllTypes)(nil),
|
||||
inPaths: []string{"optional_int32", "optional_nested_message.corecursive.optional_int64", "optional_nested_message.corecursive.<INVALID>", "optional_int64"},
|
||||
wantPaths: []string{"optional_int32", "optional_nested_message.corecursive.optional_int64"},
|
||||
wantError: cmpopts.AnyError,
|
||||
}, {
|
||||
inMessage: (*testpb.TestAllTypes)(nil),
|
||||
inPaths: []string{"optional_int32", "optional_nested_message.corecursive.oneof_uint32", "optional_nested_message.oneof_field", "optional_int64"},
|
||||
wantPaths: []string{"optional_int32", "optional_nested_message.corecursive.oneof_uint32"},
|
||||
wantError: cmpopts.AnyError,
|
||||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
var mask fmpb.FieldMask
|
||||
gotError := mask.Append(tt.inMessage, tt.inPaths...)
|
||||
gotPaths := mask.GetPaths()
|
||||
if diff := cmp.Diff(tt.wantPaths, gotPaths, cmpopts.EquateEmpty()); diff != "" {
|
||||
t.Errorf("Append() paths mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(tt.wantError, gotError, cmpopts.EquateErrors()); diff != "" {
|
||||
t.Errorf("Append() error mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCombine(t *testing.T) {
|
||||
tests := []struct {
|
||||
in [][]string
|
||||
wantUnion []string
|
||||
wantIntersect []string
|
||||
}{{
|
||||
in: [][]string{
|
||||
{},
|
||||
{},
|
||||
},
|
||||
wantUnion: []string{},
|
||||
wantIntersect: []string{},
|
||||
}, {
|
||||
in: [][]string{
|
||||
{"a"},
|
||||
{},
|
||||
},
|
||||
wantUnion: []string{"a"},
|
||||
wantIntersect: []string{},
|
||||
}, {
|
||||
in: [][]string{
|
||||
{"a"},
|
||||
{"a"},
|
||||
},
|
||||
wantUnion: []string{"a"},
|
||||
wantIntersect: []string{"a"},
|
||||
}, {
|
||||
in: [][]string{
|
||||
{"a"},
|
||||
{"b"},
|
||||
{"c"},
|
||||
},
|
||||
wantUnion: []string{"a", "b", "c"},
|
||||
wantIntersect: []string{},
|
||||
}, {
|
||||
in: [][]string{
|
||||
{"a", "b"},
|
||||
{"b.b"},
|
||||
{"b"},
|
||||
{"b", "a.A"},
|
||||
{"b", "c", "c.a", "c.b"},
|
||||
},
|
||||
wantUnion: []string{"a", "b", "c"},
|
||||
wantIntersect: []string{"b.b"},
|
||||
}, {
|
||||
in: [][]string{
|
||||
{"a.b", "a.c.d"},
|
||||
{"a"},
|
||||
},
|
||||
wantUnion: []string{"a"},
|
||||
wantIntersect: []string{"a.b", "a.c.d"},
|
||||
}, {
|
||||
in: [][]string{
|
||||
{},
|
||||
{"a.b", "a.c", "d"},
|
||||
},
|
||||
wantUnion: []string{"a.b", "a.c", "d"},
|
||||
wantIntersect: []string{},
|
||||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
var masks []*fmpb.FieldMask
|
||||
for _, paths := range tt.in {
|
||||
masks = append(masks, &fmpb.FieldMask{Paths: paths})
|
||||
}
|
||||
|
||||
union := fmpb.Union(masks[0], masks[1], masks[2:]...)
|
||||
gotUnion := union.GetPaths()
|
||||
if diff := cmp.Diff(tt.wantUnion, gotUnion, cmpopts.EquateEmpty()); diff != "" {
|
||||
t.Errorf("Union() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
intersect := fmpb.Intersect(masks[0], masks[1], masks[2:]...)
|
||||
gotIntersect := intersect.GetPaths()
|
||||
if diff := cmp.Diff(tt.wantIntersect, gotIntersect, cmpopts.EquateEmpty()); diff != "" {
|
||||
t.Errorf("Intersect() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalize(t *testing.T) {
|
||||
tests := []struct {
|
||||
in []string
|
||||
want []string
|
||||
}{{
|
||||
in: []string{},
|
||||
want: []string{},
|
||||
}, {
|
||||
in: []string{"a"},
|
||||
want: []string{"a"},
|
||||
}, {
|
||||
in: []string{"foo", "foo.bar", "foo.baz"},
|
||||
want: []string{"foo"},
|
||||
}, {
|
||||
in: []string{"foo.bar", "foo.baz"},
|
||||
want: []string{"foo.bar", "foo.baz"},
|
||||
}, {
|
||||
in: []string{"", "a.", ".b", "a.b", ".", "", "a.", ".b", "a.b", "."},
|
||||
want: []string{"", "a.", "a.b"},
|
||||
}, {
|
||||
in: []string{"e.a", "e.b", "e.c", "e.d", "e.f", "e.g", "e.b.a", "e$c", "e.b.c"},
|
||||
want: []string{"e.a", "e.b", "e.c", "e.d", "e.f", "e.g", "e$c"},
|
||||
}, {
|
||||
in: []string{"a", "aa", "aaa", "a$", "AAA", "aA.a", "a.a", "a", "aa", "aaa", "a$", "AAA", "aA.a"},
|
||||
want: []string{"AAA", "a", "aA.a", "aa", "aaa", "a$"},
|
||||
}, {
|
||||
in: []string{"a.b", "aa.bb.cc", ".", "a$b", "aa", "a.", "a", "b.c.d", ".a", "", "a$", "a$", "a.b", "a", "a.bb", ""},
|
||||
want: []string{"", "a", "aa", "a$", "a$b", "b.c.d"},
|
||||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
mask := &fmpb.FieldMask{
|
||||
Paths: append([]string(nil), tt.in...),
|
||||
}
|
||||
mask.Normalize()
|
||||
got := mask.GetPaths()
|
||||
if diff := cmp.Diff(tt.want, got, cmpopts.EquateEmpty()); diff != "" {
|
||||
t.Errorf("Normalize() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -34,10 +34,14 @@
|
||||
package structpb
|
||||
|
||||
import (
|
||||
base64 "encoding/base64"
|
||||
protojson "google.golang.org/protobuf/encoding/protojson"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
math "math"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
utf8 "unicode/utf8"
|
||||
)
|
||||
|
||||
// `NullValue` is a singleton enumeration to represent the null value for the
|
||||
@ -105,6 +109,42 @@ type Struct struct {
|
||||
Fields map[string]*Value `protobuf:"bytes,1,rep,name=fields,proto3" json:"fields,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
}
|
||||
|
||||
// NewStruct constructs a Struct from a general-purpose Go map.
|
||||
// The map keys must be valid UTF-8.
|
||||
// The map values are converted using NewValue.
|
||||
func NewStruct(v map[string]interface{}) (*Struct, error) {
|
||||
x := &Struct{Fields: make(map[string]*Value, len(v))}
|
||||
for k, v := range v {
|
||||
if !utf8.ValidString(k) {
|
||||
return nil, protoimpl.X.NewError("invalid UTF-8 in string: %q", k)
|
||||
}
|
||||
var err error
|
||||
x.Fields[k], err = NewValue(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// AsMap converts x to a general-purpose Go map.
|
||||
// The map values are converted by calling Value.AsInterface.
|
||||
func (x *Struct) AsMap() map[string]interface{} {
|
||||
vs := make(map[string]interface{})
|
||||
for k, v := range x.GetFields() {
|
||||
vs[k] = v.AsInterface()
|
||||
}
|
||||
return vs
|
||||
}
|
||||
|
||||
func (x *Struct) MarshalJSON() ([]byte, error) {
|
||||
return protojson.Marshal(x)
|
||||
}
|
||||
|
||||
func (x *Struct) UnmarshalJSON(b []byte) error {
|
||||
return protojson.Unmarshal(b, x)
|
||||
}
|
||||
|
||||
func (x *Struct) Reset() {
|
||||
*x = Struct{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
@ -167,6 +207,151 @@ type Value struct {
|
||||
Kind isValue_Kind `protobuf_oneof:"kind"`
|
||||
}
|
||||
|
||||
// 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]interface{} │ stored as StructValue ║
|
||||
// ║ []interface{} │ stored as ListValue ║
|
||||
// ╚════════════════════════╧════════════════════════════════════════════╝
|
||||
//
|
||||
// When converting an int64 or uint64 to a NumberValue, numeric precision loss
|
||||
// is possible since they are stored as a float64.
|
||||
func NewValue(v interface{}) (*Value, error) {
|
||||
switch v := v.(type) {
|
||||
case nil:
|
||||
return NewNullValue(), nil
|
||||
case bool:
|
||||
return NewBoolValue(v), nil
|
||||
case int:
|
||||
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 uint32:
|
||||
return NewNumberValue(float64(v)), nil
|
||||
case uint64:
|
||||
return NewNumberValue(float64(v)), nil
|
||||
case float32:
|
||||
return NewNumberValue(float64(v)), nil
|
||||
case float64:
|
||||
return NewNumberValue(float64(v)), nil
|
||||
case string:
|
||||
if !utf8.ValidString(v) {
|
||||
return nil, protoimpl.X.NewError("invalid UTF-8 in string: %q", v)
|
||||
}
|
||||
return NewStringValue(v), nil
|
||||
case []byte:
|
||||
s := base64.StdEncoding.EncodeToString(v)
|
||||
return NewStringValue(s), nil
|
||||
case map[string]interface{}:
|
||||
v2, err := NewStruct(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewStructValue(v2), nil
|
||||
case []interface{}:
|
||||
v2, err := NewList(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewListValue(v2), nil
|
||||
default:
|
||||
return nil, protoimpl.X.NewError("invalid type: %T", v)
|
||||
}
|
||||
}
|
||||
|
||||
// NewNullValue constructs a new null Value.
|
||||
func NewNullValue() *Value {
|
||||
return &Value{Kind: &Value_NullValue{NullValue: NullValue_NULL_VALUE}}
|
||||
}
|
||||
|
||||
// NewBoolValue constructs a new boolean Value.
|
||||
func NewBoolValue(v bool) *Value {
|
||||
return &Value{Kind: &Value_BoolValue{BoolValue: v}}
|
||||
}
|
||||
|
||||
// NewNumberValue constructs a new number Value.
|
||||
func NewNumberValue(v float64) *Value {
|
||||
return &Value{Kind: &Value_NumberValue{NumberValue: v}}
|
||||
}
|
||||
|
||||
// NewStringValue constructs a new string Value.
|
||||
func NewStringValue(v string) *Value {
|
||||
return &Value{Kind: &Value_StringValue{StringValue: v}}
|
||||
}
|
||||
|
||||
// NewStructValue constructs a new struct Value.
|
||||
func NewStructValue(v *Struct) *Value {
|
||||
return &Value{Kind: &Value_StructValue{StructValue: v}}
|
||||
}
|
||||
|
||||
// NewListValue constructs a new list Value.
|
||||
func NewListValue(v *ListValue) *Value {
|
||||
return &Value{Kind: &Value_ListValue{ListValue: v}}
|
||||
}
|
||||
|
||||
// AsInterface converts x to a general-purpose Go interface.
|
||||
//
|
||||
// Calling Value.MarshalJSON and "encoding/json".Marshal on this output produce
|
||||
// semantically equivalent JSON (assuming no errors occur).
|
||||
//
|
||||
// Floating-point values (i.e., "NaN", "Infinity", and "-Infinity") are
|
||||
// converted as strings to remain compatible with MarshalJSON.
|
||||
func (x *Value) AsInterface() interface{} {
|
||||
switch v := x.GetKind().(type) {
|
||||
case *Value_NumberValue:
|
||||
if v != nil {
|
||||
switch {
|
||||
case math.IsNaN(v.NumberValue):
|
||||
return "NaN"
|
||||
case math.IsInf(v.NumberValue, +1):
|
||||
return "Infinity"
|
||||
case math.IsInf(v.NumberValue, -1):
|
||||
return "-Infinity"
|
||||
default:
|
||||
return v.NumberValue
|
||||
}
|
||||
}
|
||||
case *Value_StringValue:
|
||||
if v != nil {
|
||||
return v.StringValue
|
||||
}
|
||||
case *Value_BoolValue:
|
||||
if v != nil {
|
||||
return v.BoolValue
|
||||
}
|
||||
case *Value_StructValue:
|
||||
if v != nil {
|
||||
return v.StructValue.AsMap()
|
||||
}
|
||||
case *Value_ListValue:
|
||||
if v != nil {
|
||||
return v.ListValue.AsSlice()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Value) MarshalJSON() ([]byte, error) {
|
||||
return protojson.Marshal(x)
|
||||
}
|
||||
|
||||
func (x *Value) UnmarshalJSON(b []byte) error {
|
||||
return protojson.Unmarshal(b, x)
|
||||
}
|
||||
|
||||
func (x *Value) Reset() {
|
||||
*x = Value{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
@ -306,6 +491,38 @@ type ListValue struct {
|
||||
Values []*Value `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
|
||||
}
|
||||
|
||||
// NewList constructs a ListValue from a general-purpose Go slice.
|
||||
// The slice elements are converted using NewValue.
|
||||
func NewList(v []interface{}) (*ListValue, error) {
|
||||
x := &ListValue{Values: make([]*Value, len(v))}
|
||||
for i, v := range v {
|
||||
var err error
|
||||
x.Values[i], err = NewValue(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// AsSlice converts x to a general-purpose Go slice.
|
||||
// The slice elements are converted by calling Value.AsInterface.
|
||||
func (x *ListValue) AsSlice() []interface{} {
|
||||
vs := make([]interface{}, len(x.GetValues()))
|
||||
for i, v := range x.GetValues() {
|
||||
vs[i] = v.AsInterface()
|
||||
}
|
||||
return vs
|
||||
}
|
||||
|
||||
func (x *ListValue) MarshalJSON() ([]byte, error) {
|
||||
return protojson.Marshal(x)
|
||||
}
|
||||
|
||||
func (x *ListValue) UnmarshalJSON(b []byte) error {
|
||||
return protojson.Unmarshal(b, x)
|
||||
}
|
||||
|
||||
func (x *ListValue) Reset() {
|
||||
*x = ListValue{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
|
512
types/known/structpb/struct_test.go
Normal file
512
types/known/structpb/struct_test.go
Normal file
@ -0,0 +1,512 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package structpb_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
|
||||
spb "google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
var equateJSON = cmpopts.AcyclicTransformer("UnmarshalJSON", func(in []byte) (out interface{}) {
|
||||
if err := json.Unmarshal(in, &out); err != nil {
|
||||
return in
|
||||
}
|
||||
return out
|
||||
})
|
||||
|
||||
func TestToStruct(t *testing.T) {
|
||||
tests := []struct {
|
||||
in map[string]interface{}
|
||||
wantPB *spb.Struct
|
||||
wantErr error
|
||||
}{{
|
||||
in: nil,
|
||||
wantPB: new(spb.Struct),
|
||||
}, {
|
||||
in: make(map[string]interface{}),
|
||||
wantPB: new(spb.Struct),
|
||||
}, {
|
||||
in: map[string]interface{}{
|
||||
"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]interface{}{"k1": "v1", "k2": "v2"},
|
||||
"slice": []interface{}{"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=="),
|
||||
"map": spb.NewStructValue(&spb.Struct{Fields: map[string]*spb.Value{
|
||||
"k1": spb.NewStringValue("v1"),
|
||||
"k2": spb.NewStringValue("v2"),
|
||||
}}),
|
||||
"slice": spb.NewListValue(&spb.ListValue{Values: []*spb.Value{
|
||||
spb.NewStringValue("one"),
|
||||
spb.NewStringValue("two"),
|
||||
spb.NewStringValue("three"),
|
||||
}}),
|
||||
}},
|
||||
}, {
|
||||
in: map[string]interface{}{"\xde\xad\xbe\xef": "<invalid UTF-8>"},
|
||||
wantErr: cmpopts.AnyError,
|
||||
}, {
|
||||
in: map[string]interface{}{"<invalid UTF-8>": "\xde\xad\xbe\xef"},
|
||||
wantErr: cmpopts.AnyError,
|
||||
}, {
|
||||
in: map[string]interface{}{"key": protoreflect.Name("named string")},
|
||||
wantErr: cmpopts.AnyError,
|
||||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
gotPB, gotErr := spb.NewStruct(tt.in)
|
||||
if diff := cmp.Diff(tt.wantPB, gotPB, protocmp.Transform()); diff != "" {
|
||||
t.Errorf("NewStruct(%v) output mismatch (-want +got):\n%s", tt.in, diff)
|
||||
}
|
||||
if diff := cmp.Diff(tt.wantErr, gotErr, cmpopts.EquateErrors()); diff != "" {
|
||||
t.Errorf("NewStruct(%v) error mismatch (-want +got):\n%s", tt.in, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromStruct(t *testing.T) {
|
||||
tests := []struct {
|
||||
in *spb.Struct
|
||||
want map[string]interface{}
|
||||
}{{
|
||||
in: nil,
|
||||
want: make(map[string]interface{}),
|
||||
}, {
|
||||
in: new(spb.Struct),
|
||||
want: make(map[string]interface{}),
|
||||
}, {
|
||||
in: &spb.Struct{Fields: make(map[string]*spb.Value)},
|
||||
want: make(map[string]interface{}),
|
||||
}, {
|
||||
in: &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=="),
|
||||
"map": spb.NewStructValue(&spb.Struct{Fields: map[string]*spb.Value{
|
||||
"k1": spb.NewStringValue("v1"),
|
||||
"k2": spb.NewStringValue("v2"),
|
||||
}}),
|
||||
"slice": spb.NewListValue(&spb.ListValue{Values: []*spb.Value{
|
||||
spb.NewStringValue("one"),
|
||||
spb.NewStringValue("two"),
|
||||
spb.NewStringValue("three"),
|
||||
}}),
|
||||
}},
|
||||
want: map[string]interface{}{
|
||||
"nil": nil,
|
||||
"bool": bool(false),
|
||||
"int": float64(-123),
|
||||
"int32": float64(math.MinInt32),
|
||||
"int64": float64(math.MinInt64),
|
||||
"uint": float64(123),
|
||||
"uint32": float64(math.MaxInt32),
|
||||
"uint64": float64(math.MaxInt64),
|
||||
"float32": float64(float32(123.456)),
|
||||
"float64": float64(float64(123.456)),
|
||||
"string": string("hello, world!"),
|
||||
"bytes": string("3q2+7w=="),
|
||||
"map": map[string]interface{}{"k1": "v1", "k2": "v2"},
|
||||
"slice": []interface{}{"one", "two", "three"},
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := tt.in.AsMap()
|
||||
if diff := cmp.Diff(tt.want, got); diff != "" {
|
||||
t.Errorf("AsMap(%v) mismatch (-want +got):\n%s", tt.in, diff)
|
||||
}
|
||||
gotJSON, err := json.Marshal(got)
|
||||
if err != nil {
|
||||
t.Errorf("Marshal error: %v", err)
|
||||
}
|
||||
wantJSON, err := tt.in.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("Marshal error: %v", err)
|
||||
}
|
||||
if diff := cmp.Diff(wantJSON, gotJSON, equateJSON); diff != "" {
|
||||
t.Errorf("MarshalJSON(%v) mismatch (-want +got):\n%s", tt.in, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestToListValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
in []interface{}
|
||||
wantPB *spb.ListValue
|
||||
wantErr error
|
||||
}{{
|
||||
in: nil,
|
||||
wantPB: new(spb.ListValue),
|
||||
}, {
|
||||
in: make([]interface{}, 0),
|
||||
wantPB: new(spb.ListValue),
|
||||
}, {
|
||||
in: []interface{}{
|
||||
nil,
|
||||
bool(false),
|
||||
int(-123),
|
||||
int32(math.MinInt32),
|
||||
int64(math.MinInt64),
|
||||
uint(123),
|
||||
uint32(math.MaxInt32),
|
||||
uint64(math.MaxInt64),
|
||||
float32(123.456),
|
||||
float64(123.456),
|
||||
string("hello, world!"),
|
||||
[]byte("\xde\xad\xbe\xef"),
|
||||
map[string]interface{}{"k1": "v1", "k2": "v2"},
|
||||
[]interface{}{"one", "two", "three"},
|
||||
},
|
||||
wantPB: &spb.ListValue{Values: []*spb.Value{
|
||||
spb.NewNullValue(),
|
||||
spb.NewBoolValue(false),
|
||||
spb.NewNumberValue(float64(-123)),
|
||||
spb.NewNumberValue(float64(math.MinInt32)),
|
||||
spb.NewNumberValue(float64(math.MinInt64)),
|
||||
spb.NewNumberValue(float64(123)),
|
||||
spb.NewNumberValue(float64(math.MaxInt32)),
|
||||
spb.NewNumberValue(float64(math.MaxInt64)),
|
||||
spb.NewNumberValue(float64(float32(123.456))),
|
||||
spb.NewNumberValue(float64(float64(123.456))),
|
||||
spb.NewStringValue("hello, world!"),
|
||||
spb.NewStringValue("3q2+7w=="),
|
||||
spb.NewStructValue(&spb.Struct{Fields: map[string]*spb.Value{
|
||||
"k1": spb.NewStringValue("v1"),
|
||||
"k2": spb.NewStringValue("v2"),
|
||||
}}),
|
||||
spb.NewListValue(&spb.ListValue{Values: []*spb.Value{
|
||||
spb.NewStringValue("one"),
|
||||
spb.NewStringValue("two"),
|
||||
spb.NewStringValue("three"),
|
||||
}}),
|
||||
}},
|
||||
}, {
|
||||
in: []interface{}{"\xde\xad\xbe\xef"},
|
||||
wantErr: cmpopts.AnyError,
|
||||
}, {
|
||||
in: []interface{}{protoreflect.Name("named string")},
|
||||
wantErr: cmpopts.AnyError,
|
||||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
gotPB, gotErr := spb.NewList(tt.in)
|
||||
if diff := cmp.Diff(tt.wantPB, gotPB, protocmp.Transform()); diff != "" {
|
||||
t.Errorf("NewListValue(%v) output mismatch (-want +got):\n%s", tt.in, diff)
|
||||
}
|
||||
if diff := cmp.Diff(tt.wantErr, gotErr, cmpopts.EquateErrors()); diff != "" {
|
||||
t.Errorf("NewListValue(%v) error mismatch (-want +got):\n%s", tt.in, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromListValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
in *spb.ListValue
|
||||
want []interface{}
|
||||
}{{
|
||||
in: nil,
|
||||
want: make([]interface{}, 0),
|
||||
}, {
|
||||
in: new(spb.ListValue),
|
||||
want: make([]interface{}, 0),
|
||||
}, {
|
||||
in: &spb.ListValue{Values: make([]*spb.Value, 0)},
|
||||
want: make([]interface{}, 0),
|
||||
}, {
|
||||
in: &spb.ListValue{Values: []*spb.Value{
|
||||
spb.NewNullValue(),
|
||||
spb.NewBoolValue(false),
|
||||
spb.NewNumberValue(float64(-123)),
|
||||
spb.NewNumberValue(float64(math.MinInt32)),
|
||||
spb.NewNumberValue(float64(math.MinInt64)),
|
||||
spb.NewNumberValue(float64(123)),
|
||||
spb.NewNumberValue(float64(math.MaxInt32)),
|
||||
spb.NewNumberValue(float64(math.MaxInt64)),
|
||||
spb.NewNumberValue(float64(float32(123.456))),
|
||||
spb.NewNumberValue(float64(float64(123.456))),
|
||||
spb.NewStringValue("hello, world!"),
|
||||
spb.NewStringValue("3q2+7w=="),
|
||||
spb.NewStructValue(&spb.Struct{Fields: map[string]*spb.Value{
|
||||
"k1": spb.NewStringValue("v1"),
|
||||
"k2": spb.NewStringValue("v2"),
|
||||
}}),
|
||||
spb.NewListValue(&spb.ListValue{Values: []*spb.Value{
|
||||
spb.NewStringValue("one"),
|
||||
spb.NewStringValue("two"),
|
||||
spb.NewStringValue("three"),
|
||||
}}),
|
||||
}},
|
||||
want: []interface{}{
|
||||
nil,
|
||||
bool(false),
|
||||
float64(-123),
|
||||
float64(math.MinInt32),
|
||||
float64(math.MinInt64),
|
||||
float64(123),
|
||||
float64(math.MaxInt32),
|
||||
float64(math.MaxInt64),
|
||||
float64(float32(123.456)),
|
||||
float64(float64(123.456)),
|
||||
string("hello, world!"),
|
||||
string("3q2+7w=="),
|
||||
map[string]interface{}{"k1": "v1", "k2": "v2"},
|
||||
[]interface{}{"one", "two", "three"},
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := tt.in.AsSlice()
|
||||
if diff := cmp.Diff(tt.want, got); diff != "" {
|
||||
t.Errorf("AsSlice(%v) mismatch (-want +got):\n%s", tt.in, diff)
|
||||
}
|
||||
gotJSON, err := json.Marshal(got)
|
||||
if err != nil {
|
||||
t.Errorf("Marshal error: %v", err)
|
||||
}
|
||||
wantJSON, err := tt.in.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("Marshal error: %v", err)
|
||||
}
|
||||
if diff := cmp.Diff(wantJSON, gotJSON, equateJSON); diff != "" {
|
||||
t.Errorf("MarshalJSON(%v) mismatch (-want +got):\n%s", tt.in, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestToValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
in interface{}
|
||||
wantPB *spb.Value
|
||||
wantErr error
|
||||
}{{
|
||||
in: nil,
|
||||
wantPB: spb.NewNullValue(),
|
||||
}, {
|
||||
in: bool(false),
|
||||
wantPB: spb.NewBoolValue(false),
|
||||
}, {
|
||||
in: int(-123),
|
||||
wantPB: spb.NewNumberValue(float64(-123)),
|
||||
}, {
|
||||
in: int32(math.MinInt32),
|
||||
wantPB: spb.NewNumberValue(float64(math.MinInt32)),
|
||||
}, {
|
||||
in: int64(math.MinInt64),
|
||||
wantPB: spb.NewNumberValue(float64(math.MinInt64)),
|
||||
}, {
|
||||
in: uint(123),
|
||||
wantPB: spb.NewNumberValue(float64(123)),
|
||||
}, {
|
||||
in: uint32(math.MaxInt32),
|
||||
wantPB: spb.NewNumberValue(float64(math.MaxInt32)),
|
||||
}, {
|
||||
in: uint64(math.MaxInt64),
|
||||
wantPB: spb.NewNumberValue(float64(math.MaxInt64)),
|
||||
}, {
|
||||
in: float32(123.456),
|
||||
wantPB: spb.NewNumberValue(float64(float32(123.456))),
|
||||
}, {
|
||||
in: float64(123.456),
|
||||
wantPB: spb.NewNumberValue(float64(float64(123.456))),
|
||||
}, {
|
||||
in: string("hello, world!"),
|
||||
wantPB: spb.NewStringValue("hello, world!"),
|
||||
}, {
|
||||
in: []byte("\xde\xad\xbe\xef"),
|
||||
wantPB: spb.NewStringValue("3q2+7w=="),
|
||||
}, {
|
||||
in: map[string]interface{}(nil),
|
||||
wantPB: spb.NewStructValue(nil),
|
||||
}, {
|
||||
in: make(map[string]interface{}),
|
||||
wantPB: spb.NewStructValue(nil),
|
||||
}, {
|
||||
in: map[string]interface{}{"k1": "v1", "k2": "v2"},
|
||||
wantPB: spb.NewStructValue(&spb.Struct{Fields: map[string]*spb.Value{
|
||||
"k1": spb.NewStringValue("v1"),
|
||||
"k2": spb.NewStringValue("v2"),
|
||||
}}),
|
||||
}, {
|
||||
in: []interface{}(nil),
|
||||
wantPB: spb.NewListValue(nil),
|
||||
}, {
|
||||
in: make([]interface{}, 0),
|
||||
wantPB: spb.NewListValue(nil),
|
||||
}, {
|
||||
in: []interface{}{"one", "two", "three"},
|
||||
wantPB: spb.NewListValue(&spb.ListValue{Values: []*spb.Value{
|
||||
spb.NewStringValue("one"),
|
||||
spb.NewStringValue("two"),
|
||||
spb.NewStringValue("three"),
|
||||
}}),
|
||||
}, {
|
||||
in: "\xde\xad\xbe\xef",
|
||||
wantErr: cmpopts.AnyError,
|
||||
}, {
|
||||
in: protoreflect.Name("named string"),
|
||||
wantErr: cmpopts.AnyError,
|
||||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
gotPB, gotErr := spb.NewValue(tt.in)
|
||||
if diff := cmp.Diff(tt.wantPB, gotPB, protocmp.Transform()); diff != "" {
|
||||
t.Errorf("NewValue(%v) output mismatch (-want +got):\n%s", tt.in, diff)
|
||||
}
|
||||
if diff := cmp.Diff(tt.wantErr, gotErr, cmpopts.EquateErrors()); diff != "" {
|
||||
t.Errorf("NewValue(%v) error mismatch (-want +got):\n%s", tt.in, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
in *spb.Value
|
||||
want interface{}
|
||||
}{{
|
||||
in: nil,
|
||||
want: nil,
|
||||
}, {
|
||||
in: new(spb.Value),
|
||||
want: nil,
|
||||
}, {
|
||||
in: &spb.Value{Kind: (*spb.Value_NullValue)(nil)},
|
||||
want: nil,
|
||||
}, {
|
||||
in: spb.NewNullValue(),
|
||||
want: nil,
|
||||
}, {
|
||||
in: &spb.Value{Kind: &spb.Value_NullValue{NullValue: math.MinInt32}},
|
||||
want: nil,
|
||||
}, {
|
||||
in: &spb.Value{Kind: (*spb.Value_BoolValue)(nil)},
|
||||
want: nil,
|
||||
}, {
|
||||
in: spb.NewBoolValue(false),
|
||||
want: bool(false),
|
||||
}, {
|
||||
in: &spb.Value{Kind: (*spb.Value_NumberValue)(nil)},
|
||||
want: nil,
|
||||
}, {
|
||||
in: spb.NewNumberValue(float64(math.MinInt32)),
|
||||
want: float64(math.MinInt32),
|
||||
}, {
|
||||
in: spb.NewNumberValue(float64(math.MinInt64)),
|
||||
want: float64(math.MinInt64),
|
||||
}, {
|
||||
in: spb.NewNumberValue(float64(123)),
|
||||
want: float64(123),
|
||||
}, {
|
||||
in: spb.NewNumberValue(float64(math.MaxInt32)),
|
||||
want: float64(math.MaxInt32),
|
||||
}, {
|
||||
in: spb.NewNumberValue(float64(math.MaxInt64)),
|
||||
want: float64(math.MaxInt64),
|
||||
}, {
|
||||
in: spb.NewNumberValue(float64(float32(123.456))),
|
||||
want: float64(float32(123.456)),
|
||||
}, {
|
||||
in: spb.NewNumberValue(float64(float64(123.456))),
|
||||
want: float64(float64(123.456)),
|
||||
}, {
|
||||
in: spb.NewNumberValue(math.NaN()),
|
||||
want: string("NaN"),
|
||||
}, {
|
||||
in: spb.NewNumberValue(math.Inf(-1)),
|
||||
want: string("-Infinity"),
|
||||
}, {
|
||||
in: spb.NewNumberValue(math.Inf(+1)),
|
||||
want: string("Infinity"),
|
||||
}, {
|
||||
in: &spb.Value{Kind: (*spb.Value_StringValue)(nil)},
|
||||
want: nil,
|
||||
}, {
|
||||
in: spb.NewStringValue("hello, world!"),
|
||||
want: string("hello, world!"),
|
||||
}, {
|
||||
in: spb.NewStringValue("3q2+7w=="),
|
||||
want: string("3q2+7w=="),
|
||||
}, {
|
||||
in: &spb.Value{Kind: (*spb.Value_StructValue)(nil)},
|
||||
want: nil,
|
||||
}, {
|
||||
in: &spb.Value{Kind: &spb.Value_StructValue{}},
|
||||
want: make(map[string]interface{}),
|
||||
}, {
|
||||
in: spb.NewListValue(&spb.ListValue{Values: []*spb.Value{
|
||||
spb.NewStringValue("one"),
|
||||
spb.NewStringValue("two"),
|
||||
spb.NewStringValue("three"),
|
||||
}}),
|
||||
want: []interface{}{"one", "two", "three"},
|
||||
}, {
|
||||
in: &spb.Value{Kind: (*spb.Value_ListValue)(nil)},
|
||||
want: nil,
|
||||
}, {
|
||||
in: &spb.Value{Kind: &spb.Value_ListValue{}},
|
||||
want: make([]interface{}, 0),
|
||||
}, {
|
||||
in: spb.NewListValue(&spb.ListValue{Values: []*spb.Value{
|
||||
spb.NewStringValue("one"),
|
||||
spb.NewStringValue("two"),
|
||||
spb.NewStringValue("three"),
|
||||
}}),
|
||||
want: []interface{}{"one", "two", "three"},
|
||||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := tt.in.AsInterface()
|
||||
if diff := cmp.Diff(tt.want, got); diff != "" {
|
||||
t.Errorf("AsInterface(%v) mismatch (-want +got):\n%s", tt.in, diff)
|
||||
}
|
||||
gotJSON, gotErr := json.Marshal(got)
|
||||
if gotErr != nil {
|
||||
t.Errorf("Marshal error: %v", gotErr)
|
||||
}
|
||||
wantJSON, wantErr := tt.in.MarshalJSON()
|
||||
if diff := cmp.Diff(wantJSON, gotJSON, equateJSON); diff != "" && wantErr == nil {
|
||||
t.Errorf("MarshalJSON(%v) mismatch (-want +got):\n%s", tt.in, diff)
|
||||
}
|
||||
}
|
||||
}
|
@ -38,6 +38,7 @@ import (
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
time "time"
|
||||
)
|
||||
|
||||
// A Timestamp represents a point in time independent of any time zone or local
|
||||
@ -140,6 +141,64 @@ type Timestamp struct {
|
||||
Nanos int32 `protobuf:"varint,2,opt,name=nanos,proto3" json:"nanos,omitempty"`
|
||||
}
|
||||
|
||||
// Now constructs a new Timestamp from the current time.
|
||||
func Now() *Timestamp {
|
||||
return New(time.Now())
|
||||
}
|
||||
|
||||
// New constructs a new Timestamp from the provided time.Time.
|
||||
func New(t time.Time) *Timestamp {
|
||||
return &Timestamp{Seconds: int64(t.Unix()), Nanos: int32(t.Nanosecond())}
|
||||
}
|
||||
|
||||
// AsTime converts x to a time.Time.
|
||||
func (x *Timestamp) AsTime() time.Time {
|
||||
return time.Unix(int64(x.GetSeconds()), int64(x.GetNanos())).UTC()
|
||||
}
|
||||
|
||||
// IsValid reports whether the timestamp is valid.
|
||||
// It is equivalent to CheckValid == nil.
|
||||
func (x *Timestamp) IsValid() bool {
|
||||
return x.check() == 0
|
||||
}
|
||||
|
||||
// CheckValid returns an error if the timestamp is invalid.
|
||||
// In particular, it checks whether the value represents a date that is
|
||||
// in the range of 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive.
|
||||
func (x *Timestamp) CheckValid() error {
|
||||
switch x.check() {
|
||||
case invalidUnderflow:
|
||||
return protoimpl.X.NewError("timestamp (%v) before 0001-01-01", x)
|
||||
case invalidOverflow:
|
||||
return protoimpl.X.NewError("timestamp (%v) after 9999-12-31", x)
|
||||
case invalidNanos:
|
||||
return protoimpl.X.NewError("timestamp (%v) has out-of-range nanos", x)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
const invalidUnderflow = 1
|
||||
const invalidOverflow = 2
|
||||
const invalidNanos = 3
|
||||
|
||||
func (x *Timestamp) check() uint {
|
||||
const minTimestamp = -62135596800 // Seconds between 1970-01-01T00:00:00Z and 0001-01-01T00:00:00Z, inclusive
|
||||
const maxTimestamp = +253402300799 // Seconds between 1970-01-01T00:00:00Z and 9999-12-31T23:59:59Z, inclusive
|
||||
secs := x.GetSeconds()
|
||||
nanos := x.GetNanos()
|
||||
switch {
|
||||
case secs < minTimestamp:
|
||||
return invalidUnderflow
|
||||
case secs > maxTimestamp:
|
||||
return invalidOverflow
|
||||
case nanos < 0 || nanos >= 1e9:
|
||||
return invalidNanos
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Timestamp) Reset() {
|
||||
*x = Timestamp{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
|
104
types/known/timestamppb/timestamp_test.go
Normal file
104
types/known/timestamppb/timestamp_test.go
Normal file
@ -0,0 +1,104 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package timestamppb_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"google.golang.org/protobuf/internal/detrand"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
|
||||
tspb "google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func init() {
|
||||
detrand.Disable()
|
||||
}
|
||||
|
||||
const (
|
||||
minTimestamp = -62135596800 // Seconds between 1970-01-01T00:00:00Z and 0001-01-01T00:00:00Z, inclusive
|
||||
maxTimestamp = +253402300799 // Seconds between 1970-01-01T00:00:00Z and 9999-12-31T23:59:59Z, inclusive
|
||||
)
|
||||
|
||||
func TestToTimestamp(t *testing.T) {
|
||||
tests := []struct {
|
||||
in time.Time
|
||||
want *tspb.Timestamp
|
||||
}{
|
||||
{in: time.Time{}, want: &tspb.Timestamp{Seconds: -62135596800, Nanos: 0}},
|
||||
{in: time.Unix(0, 0), want: &tspb.Timestamp{Seconds: 0, Nanos: 0}},
|
||||
{in: time.Unix(math.MinInt64, 0), want: &tspb.Timestamp{Seconds: math.MinInt64, Nanos: 0}},
|
||||
{in: time.Unix(math.MaxInt64, 1e9-1), want: &tspb.Timestamp{Seconds: math.MaxInt64, Nanos: 1e9 - 1}},
|
||||
{in: time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC), want: &tspb.Timestamp{Seconds: minTimestamp, Nanos: 0}},
|
||||
{in: time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC).Add(-time.Nanosecond), want: &tspb.Timestamp{Seconds: minTimestamp - 1, Nanos: 1e9 - 1}},
|
||||
{in: time.Date(9999, 12, 31, 23, 59, 59, 1e9-1, time.UTC), want: &tspb.Timestamp{Seconds: maxTimestamp, Nanos: 1e9 - 1}},
|
||||
{in: time.Date(9999, 12, 31, 23, 59, 59, 1e9-1, time.UTC).Add(+time.Nanosecond), want: &tspb.Timestamp{Seconds: maxTimestamp + 1}},
|
||||
{in: time.Date(1961, 1, 26, 0, 0, 0, 0, time.UTC), want: &tspb.Timestamp{Seconds: -281836800, Nanos: 0}},
|
||||
{in: time.Date(2011, 1, 26, 0, 0, 0, 0, time.UTC), want: &tspb.Timestamp{Seconds: 1296000000, Nanos: 0}},
|
||||
{in: time.Date(2011, 1, 26, 3, 25, 45, 940483, time.UTC), want: &tspb.Timestamp{Seconds: 1296012345, Nanos: 940483}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := tspb.New(tt.in)
|
||||
if diff := cmp.Diff(tt.want, got, protocmp.Transform()); diff != "" {
|
||||
t.Errorf("New(%v) mismatch (-want +got):\n%s", tt.in, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromTimestamp(t *testing.T) {
|
||||
tests := []struct {
|
||||
in *tspb.Timestamp
|
||||
wantTime time.Time
|
||||
wantErr error
|
||||
}{
|
||||
{in: nil, wantTime: time.Unix(0, 0)},
|
||||
{in: new(tspb.Timestamp), wantTime: time.Unix(0, 0)},
|
||||
{in: &tspb.Timestamp{Seconds: -62135596800, Nanos: 0}, wantTime: time.Time{}},
|
||||
{in: &tspb.Timestamp{Seconds: -1, Nanos: -1}, wantTime: time.Unix(-1, -1), wantErr: textError("timestamp (seconds:-1 nanos:-1) has out-of-range nanos")},
|
||||
{in: &tspb.Timestamp{Seconds: -1, Nanos: 0}, wantTime: time.Unix(-1, 0)},
|
||||
{in: &tspb.Timestamp{Seconds: -1, Nanos: +1}, wantTime: time.Unix(-1, +1)},
|
||||
{in: &tspb.Timestamp{Seconds: 0, Nanos: -1}, wantTime: time.Unix(0, -1), wantErr: textError("timestamp (nanos:-1) has out-of-range nanos")},
|
||||
{in: &tspb.Timestamp{Seconds: 0, Nanos: 0}, wantTime: time.Unix(0, 0)},
|
||||
{in: &tspb.Timestamp{Seconds: 0, Nanos: +1}, wantTime: time.Unix(0, +1)},
|
||||
{in: &tspb.Timestamp{Seconds: +1, Nanos: -1}, wantTime: time.Unix(+1, -1), wantErr: textError("timestamp (seconds:1 nanos:-1) has out-of-range nanos")},
|
||||
{in: &tspb.Timestamp{Seconds: +1, Nanos: 0}, wantTime: time.Unix(+1, 0)},
|
||||
{in: &tspb.Timestamp{Seconds: +1, Nanos: +1}, wantTime: time.Unix(+1, +1)},
|
||||
{in: &tspb.Timestamp{Seconds: -9876543210, Nanos: -1098765432}, wantTime: time.Unix(-9876543210, -1098765432), wantErr: textError("timestamp (seconds:-9876543210 nanos:-1098765432) has out-of-range nanos")},
|
||||
{in: &tspb.Timestamp{Seconds: +9876543210, Nanos: -1098765432}, wantTime: time.Unix(+9876543210, -1098765432), wantErr: textError("timestamp (seconds:9876543210 nanos:-1098765432) has out-of-range nanos")},
|
||||
{in: &tspb.Timestamp{Seconds: -9876543210, Nanos: +1098765432}, wantTime: time.Unix(-9876543210, +1098765432), wantErr: textError("timestamp (seconds:-9876543210 nanos:1098765432) has out-of-range nanos")},
|
||||
{in: &tspb.Timestamp{Seconds: +9876543210, Nanos: +1098765432}, wantTime: time.Unix(+9876543210, +1098765432), wantErr: textError("timestamp (seconds:9876543210 nanos:1098765432) has out-of-range nanos")},
|
||||
{in: &tspb.Timestamp{Seconds: math.MinInt64, Nanos: 0}, wantTime: time.Unix(math.MinInt64, 0), wantErr: textError("timestamp (seconds:-9223372036854775808) before 0001-01-01")},
|
||||
{in: &tspb.Timestamp{Seconds: math.MaxInt64, Nanos: 1e9 - 1}, wantTime: time.Unix(math.MaxInt64, 1e9-1), wantErr: textError("timestamp (seconds:9223372036854775807 nanos:999999999) after 9999-12-31")},
|
||||
{in: &tspb.Timestamp{Seconds: minTimestamp, Nanos: 0}, wantTime: time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC)},
|
||||
{in: &tspb.Timestamp{Seconds: minTimestamp - 1, Nanos: 1e9 - 1}, wantTime: time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC).Add(-time.Nanosecond), wantErr: textError("timestamp (seconds:-62135596801 nanos:999999999) before 0001-01-01")},
|
||||
{in: &tspb.Timestamp{Seconds: maxTimestamp, Nanos: 1e9 - 1}, wantTime: time.Date(9999, 12, 31, 23, 59, 59, 1e9-1, time.UTC)},
|
||||
{in: &tspb.Timestamp{Seconds: maxTimestamp + 1}, wantTime: time.Date(9999, 12, 31, 23, 59, 59, 1e9-1, time.UTC).Add(+time.Nanosecond), wantErr: textError("timestamp (seconds:253402300800) after 9999-12-31")},
|
||||
{in: &tspb.Timestamp{Seconds: -281836800, Nanos: 0}, wantTime: time.Date(1961, 1, 26, 0, 0, 0, 0, time.UTC)},
|
||||
{in: &tspb.Timestamp{Seconds: 1296000000, Nanos: 0}, wantTime: time.Date(2011, 1, 26, 0, 0, 0, 0, time.UTC)},
|
||||
{in: &tspb.Timestamp{Seconds: 1296012345, Nanos: 940483}, wantTime: time.Date(2011, 1, 26, 3, 25, 45, 940483, time.UTC)},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
gotTime := tt.in.AsTime()
|
||||
if diff := cmp.Diff(tt.wantTime, gotTime); diff != "" {
|
||||
t.Errorf("AsTime(%v) mismatch (-want +got):\n%s", tt.in, diff)
|
||||
}
|
||||
gotErr := tt.in.CheckValid()
|
||||
if diff := cmp.Diff(tt.wantErr, gotErr, cmpopts.EquateErrors()); diff != "" {
|
||||
t.Errorf("CheckValid(%v) mismatch (-want +got):\n%s", tt.in, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type textError string
|
||||
|
||||
func (e textError) Error() string { return string(e) }
|
||||
func (e textError) Is(err error) bool { return err != nil && strings.Contains(err.Error(), e.Error()) }
|
@ -62,6 +62,11 @@ type DoubleValue struct {
|
||||
Value float64 `protobuf:"fixed64,1,opt,name=value,proto3" json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// Double stores v in a new DoubleValue and returns a pointer to it.
|
||||
func Double(v float64) *DoubleValue {
|
||||
return &DoubleValue{Value: v}
|
||||
}
|
||||
|
||||
func (x *DoubleValue) Reset() {
|
||||
*x = DoubleValue{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
@ -113,6 +118,11 @@ type FloatValue struct {
|
||||
Value float32 `protobuf:"fixed32,1,opt,name=value,proto3" json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// Float stores v in a new FloatValue and returns a pointer to it.
|
||||
func Float(v float32) *FloatValue {
|
||||
return &FloatValue{Value: v}
|
||||
}
|
||||
|
||||
func (x *FloatValue) Reset() {
|
||||
*x = FloatValue{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
@ -164,6 +174,11 @@ type Int64Value struct {
|
||||
Value int64 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// Int64 stores v in a new Int64Value and returns a pointer to it.
|
||||
func Int64(v int64) *Int64Value {
|
||||
return &Int64Value{Value: v}
|
||||
}
|
||||
|
||||
func (x *Int64Value) Reset() {
|
||||
*x = Int64Value{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
@ -215,6 +230,11 @@ type UInt64Value struct {
|
||||
Value uint64 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// UInt64 stores v in a new UInt64Value and returns a pointer to it.
|
||||
func UInt64(v uint64) *UInt64Value {
|
||||
return &UInt64Value{Value: v}
|
||||
}
|
||||
|
||||
func (x *UInt64Value) Reset() {
|
||||
*x = UInt64Value{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
@ -266,6 +286,11 @@ type Int32Value struct {
|
||||
Value int32 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// Int32 stores v in a new Int32Value and returns a pointer to it.
|
||||
func Int32(v int32) *Int32Value {
|
||||
return &Int32Value{Value: v}
|
||||
}
|
||||
|
||||
func (x *Int32Value) Reset() {
|
||||
*x = Int32Value{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
@ -317,6 +342,11 @@ type UInt32Value struct {
|
||||
Value uint32 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// UInt32 stores v in a new UInt32Value and returns a pointer to it.
|
||||
func UInt32(v uint32) *UInt32Value {
|
||||
return &UInt32Value{Value: v}
|
||||
}
|
||||
|
||||
func (x *UInt32Value) Reset() {
|
||||
*x = UInt32Value{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
@ -368,6 +398,11 @@ type BoolValue struct {
|
||||
Value bool `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// Bool stores v in a new BoolValue and returns a pointer to it.
|
||||
func Bool(v bool) *BoolValue {
|
||||
return &BoolValue{Value: v}
|
||||
}
|
||||
|
||||
func (x *BoolValue) Reset() {
|
||||
*x = BoolValue{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
@ -419,6 +454,11 @@ type StringValue struct {
|
||||
Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// String stores v in a new StringValue and returns a pointer to it.
|
||||
func String(v string) *StringValue {
|
||||
return &StringValue{Value: v}
|
||||
}
|
||||
|
||||
func (x *StringValue) Reset() {
|
||||
*x = StringValue{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
@ -470,6 +510,11 @@ type BytesValue struct {
|
||||
Value []byte `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// Bytes stores v in a new BytesValue and returns a pointer to it.
|
||||
func Bytes(v []byte) *BytesValue {
|
||||
return &BytesValue{Value: v}
|
||||
}
|
||||
|
||||
func (x *BytesValue) Reset() {
|
||||
*x = BytesValue{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
|
Loading…
x
Reference in New Issue
Block a user