Skip to content

Commit d120e2f

Browse files
committed
Add Any helper funcs to the ptypes package.
1 parent 2fea9e1 commit d120e2f

File tree

2 files changed

+249
-0
lines changed

2 files changed

+249
-0
lines changed

ptypes/any.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Go support for Protocol Buffers - Google's data interchange format
2+
//
3+
// Copyright 2016 The Go Authors. All rights reserved.
4+
// https://github.com/golang/protobuf
5+
//
6+
// Redistribution and use in source and binary forms, with or without
7+
// modification, are permitted provided that the following conditions are
8+
// met:
9+
//
10+
// * Redistributions of source code must retain the above copyright
11+
// notice, this list of conditions and the following disclaimer.
12+
// * Redistributions in binary form must reproduce the above
13+
// copyright notice, this list of conditions and the following disclaimer
14+
// in the documentation and/or other materials provided with the
15+
// distribution.
16+
// * Neither the name of Google Inc. nor the names of its
17+
// contributors may be used to endorse or promote products derived from
18+
// this software without specific prior written permission.
19+
//
20+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
32+
package ptypes
33+
34+
// This file implements functions to marshal proto.Message to/from
35+
// google.protobuf.Any message.
36+
37+
import (
38+
"fmt"
39+
"reflect"
40+
"strings"
41+
42+
"github.com/golang/protobuf/proto"
43+
"github.com/golang/protobuf/ptypes/any"
44+
)
45+
46+
const googleApis = "type.googleapis.com/"
47+
48+
// AnyMessageName returns the name of the message contained in a google.protobuf.Any message.
49+
//
50+
// Note that regular type assertions should be done using the Is
51+
// function. AnyMessageName is provided for less common use cases like filtering a
52+
// sequence of Any messages based on a set of allowed message type names.
53+
func AnyMessageName(any *any.Any) (string, error) {
54+
slash := strings.LastIndex(any.TypeUrl, "/")
55+
if slash < 0 {
56+
return "", fmt.Errorf("message type url %q is invalid", any.TypeUrl)
57+
}
58+
return any.TypeUrl[slash+1:], nil
59+
}
60+
61+
// MarshalAny takes the protocol buffer and encodes it into google.protobuf.Any.
62+
func MarshalAny(pb proto.Message) (*any.Any, error) {
63+
value, err := proto.Marshal(pb)
64+
if err != nil {
65+
return nil, err
66+
}
67+
return &any.Any{TypeUrl: googleApis + proto.MessageName(pb), Value: value}, nil
68+
}
69+
70+
// DynamicAny is a value that can be passed to UnmarshalAny to automatically
71+
// allocate a proto.Message for the type specified in a google.protobuf.Any
72+
// message. The allocated message is stored in the embedded proto.Message.
73+
//
74+
// Example:
75+
//
76+
// var x ptypes.DynamicAny
77+
// if err := ptypes.UnmarshalAny(a, &x); err != nil { ... }
78+
// fmt.Printf("unmarshaled message: %v", x.Message)
79+
type DynamicAny struct {
80+
proto.Message
81+
}
82+
83+
// Empty returns a new proto.Message of the type specified in a
84+
// google.protobuf.Any message. It returns an error if corresponding message
85+
// type isn't linked in.
86+
func Empty(any *any.Any) (proto.Message, error) {
87+
aname, err := AnyMessageName(any)
88+
if err != nil {
89+
return nil, err
90+
}
91+
92+
t := proto.MessageType(aname)
93+
if t == nil {
94+
return nil, fmt.Errorf("any: message type %q isn't linked in", aname)
95+
}
96+
return reflect.New(t.Elem()).Interface().(proto.Message), nil
97+
}
98+
99+
// UnmarshalAny parses the protocol buffer representation in a google.protobuf.Any
100+
// message and places the decoded result in pb. It returns an error if type of
101+
// contents of Any message does not match type of pb message.
102+
//
103+
// pb can be a proto.Message, or a *DynamicAny.
104+
func UnmarshalAny(any *any.Any, pb proto.Message) error {
105+
if d, ok := pb.(*DynamicAny); ok {
106+
if d.Message == nil {
107+
var err error
108+
d.Message, err = Empty(any)
109+
if err != nil {
110+
return err
111+
}
112+
}
113+
return UnmarshalAny(any, d.Message)
114+
}
115+
116+
aname, err := AnyMessageName(any)
117+
if err != nil {
118+
return err
119+
}
120+
121+
mname := proto.MessageName(pb)
122+
if aname != mname {
123+
return fmt.Errorf("mismatched message type: got %q want %q", aname, mname)
124+
}
125+
return proto.Unmarshal(any.Value, pb)
126+
}
127+
128+
// Is returns true if any value contains a given message type.
129+
func Is(any *any.Any, pb proto.Message) bool {
130+
aname, err := AnyMessageName(any)
131+
if err != nil {
132+
return false
133+
}
134+
135+
return aname == proto.MessageName(pb)
136+
}

ptypes/any_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Go support for Protocol Buffers - Google's data interchange format
2+
//
3+
// Copyright 2016 The Go Authors. All rights reserved.
4+
// https://github.com/golang/protobuf
5+
//
6+
// Redistribution and use in source and binary forms, with or without
7+
// modification, are permitted provided that the following conditions are
8+
// met:
9+
//
10+
// * Redistributions of source code must retain the above copyright
11+
// notice, this list of conditions and the following disclaimer.
12+
// * Redistributions in binary form must reproduce the above
13+
// copyright notice, this list of conditions and the following disclaimer
14+
// in the documentation and/or other materials provided with the
15+
// distribution.
16+
// * Neither the name of Google Inc. nor the names of its
17+
// contributors may be used to endorse or promote products derived from
18+
// this software without specific prior written permission.
19+
//
20+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
32+
package ptypes
33+
34+
import (
35+
"testing"
36+
37+
"github.com/golang/protobuf/proto"
38+
pb "github.com/golang/protobuf/protoc-gen-go/descriptor"
39+
"github.com/golang/protobuf/ptypes/any"
40+
)
41+
42+
func TestMarshalUnmarshal(t *testing.T) {
43+
orig := &any.Any{Value: []byte("test")}
44+
45+
packed, err := MarshalAny(orig)
46+
if err != nil {
47+
t.Errorf("MarshalAny(%+v): got: _, %v exp: _, nil", orig, err)
48+
}
49+
50+
unpacked := &any.Any{}
51+
err = UnmarshalAny(packed, unpacked)
52+
if err != nil || !proto.Equal(unpacked, orig) {
53+
t.Errorf("got: %v, %+v; want nil, %+v", err, unpacked, orig)
54+
}
55+
}
56+
57+
func TestIs(t *testing.T) {
58+
a, err := MarshalAny(&pb.FileDescriptorProto{})
59+
if err != nil {
60+
t.Fatal(err)
61+
}
62+
if Is(a, &pb.DescriptorProto{}) {
63+
t.Error("FileDescriptorProto is not a DescriptorProto, but Is says it is")
64+
}
65+
if !Is(a, &pb.FileDescriptorProto{}) {
66+
t.Error("FileDescriptorProto is indeed a FileDescriptorProto, but Is says it is not")
67+
}
68+
}
69+
70+
func TestIsDifferentUrlPrefixes(t *testing.T) {
71+
m := &pb.FileDescriptorProto{}
72+
a := &any.Any{TypeUrl: "foo/bar/" + proto.MessageName(m)}
73+
if !Is(a, m) {
74+
t.Errorf("message with type url %q didn't satisfy Is for type %q", a.TypeUrl, proto.MessageName(m))
75+
}
76+
}
77+
78+
func TestUnmarshalDynamic(t *testing.T) {
79+
want := &pb.FileDescriptorProto{Name: proto.String("foo")}
80+
a, err := MarshalAny(want)
81+
if err != nil {
82+
t.Fatal(err)
83+
}
84+
var got DynamicAny
85+
if err := UnmarshalAny(a, &got); err != nil {
86+
t.Fatal(err)
87+
}
88+
if !proto.Equal(got.Message, want) {
89+
t.Errorf("invalid result from UnmarshalAny, got %q want %q", got.Message, want)
90+
}
91+
}
92+
93+
func TestEmpty(t *testing.T) {
94+
want := &pb.FileDescriptorProto{}
95+
a, err := MarshalAny(want)
96+
if err != nil {
97+
t.Fatal(err)
98+
}
99+
got, err := Empty(a)
100+
if err != nil {
101+
t.Fatal(err)
102+
}
103+
if !proto.Equal(got, want) {
104+
t.Errorf("unequal empty message, got %q, want %q", got, want)
105+
}
106+
107+
// that's a valid type_url for a message which shouldn't be linked into this
108+
// test binary. We want an error.
109+
a.TypeUrl = "type.googleapis.com/google.protobuf.FieldMask"
110+
if _, err := Empty(a); err == nil {
111+
t.Errorf("got no error for an attempt to create a message of type %q, which shouldn't be linked in", a.TypeUrl)
112+
}
113+
}

0 commit comments

Comments
 (0)