-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmessage_codec.go
107 lines (97 loc) · 3.79 KB
/
message_codec.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package protobsoncodec
import (
"reflect"
"strings"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.vallahaye.net/protobson/protobsonoptions"
"google.golang.org/protobuf/proto"
)
// Message type.
var TypeMessage = reflect.TypeOf((*proto.Message)(nil)).Elem()
// MessageCodec is the Codec used for proto.Message values.
type MessageCodec struct {
*bsoncodec.StructCodec
}
// EncodeValue is the ValueEncoderFunc for proto.Message.
func (c *MessageCodec) EncodeValue(ec bsoncodec.EncodeContext, vw bsonrw.ValueWriter, v reflect.Value) error {
if !v.IsValid() || (!v.Type().Implements(TypeMessage) && !reflect.PtrTo(v.Type()).Implements(TypeMessage)) {
return bsoncodec.ValueEncoderError{
Name: "MessageCodec.EncodeValue",
Types: []reflect.Type{TypeMessage},
Received: v,
}
}
return c.StructCodec.EncodeValue(ec, vw, v.Elem())
}
// DecodeValue is the ValueDecoderFunc for proto.Message.
func (c *MessageCodec) DecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, v reflect.Value) error {
if !v.CanSet() || (!v.Type().Implements(TypeMessage) && !reflect.PtrTo(v.Type()).Implements(TypeMessage)) {
return bsoncodec.ValueDecoderError{
Name: "MessageCodec.DecodeValue",
Types: []reflect.Type{TypeMessage},
Received: v,
}
}
return c.StructCodec.DecodeValue(dc, vr, v.Elem())
}
// NewMessageCodec returns a MessageCodec with options opts.
func NewMessageCodec(opts ...*protobsonoptions.MessageCodecOptions) *MessageCodec {
mergedOpts := protobsonoptions.MergeMessageCodecOptions(opts...)
parser := JSONPBFallbackStructTagParser
if mergedOpts.UseProtoNames != nil && *mergedOpts.UseProtoNames {
parser = ProtoNamesFallbackStructTagParser
}
structCodec, _ := bsoncodec.NewStructCodec(parser, mergedOpts.StructCodecOptions)
return &MessageCodec{structCodec}
}
// JSONPBFallbackStructTagParser is the StructTagParser used by the MessageCodec by default.
// It has the same behavior as bsoncodec.DefaultStructTagParser but will also fallback to
// parsing the protobuf tag on a field where the bson tag isn't available. In this case, the
// key will be taken from the json property, or from the name property if there is none.
//
// An example:
//
// type T struct {
// Name string `protobuf:"bytes,1,opt,name=name,proto3"` // Key is "name"
// FooBar string `protobuf:"bytes,2,opt,name=foo_bar,json=fooBar,proto3"` // Key is "fooBar"
// BarFoo string `protobuf:"bytes,3,opt,name=bar_foo,json=barFoo,proto3" bson:"barfoo"` // Key is "barfoo"
// }
var JSONPBFallbackStructTagParser bsoncodec.StructTagParserFunc = func(sf reflect.StructField) (bsoncodec.StructTags, error) {
if _, ok := sf.Tag.Lookup("bson"); ok {
return bsoncodec.DefaultStructTagParser(sf)
}
tag, ok := sf.Tag.Lookup("protobuf")
if !ok {
return bsoncodec.DefaultStructTagParser(sf)
}
return parseTags(tag, false)
}
// ProtoNamesFallbackStructTagParser has the same behavior as JSONPBFallbackStructTagParser
// except it forces the use of the name property as the key when parsing protobuf tags.
var ProtoNamesFallbackStructTagParser bsoncodec.StructTagParserFunc = func(sf reflect.StructField) (bsoncodec.StructTags, error) {
if _, ok := sf.Tag.Lookup("bson"); ok {
return bsoncodec.DefaultStructTagParser(sf)
}
tag, ok := sf.Tag.Lookup("protobuf")
if !ok {
return bsoncodec.DefaultStructTagParser(sf)
}
return parseTags(tag, true)
}
func parseTags(tag string, useProtoNames bool) (bsoncodec.StructTags, error) {
rawProps := strings.Split(tag, ",")
props := make(map[string]string, len(rawProps))
for _, rawProp := range rawProps {
k, v, _ := strings.Cut(rawProp, "=")
props[k] = v
}
var st bsoncodec.StructTags
jsonName, hasJSONName := props["json"]
if !useProtoNames && hasJSONName {
st.Name = jsonName
} else {
st.Name = props["name"]
}
return st, nil
}