Skip to content

Commit 7c4fcbf

Browse files
author
Jason Yellick
committed
[FAB-4104] Proto translator dynamic field comp
The proto translation framework introduced in FAB-4100 needs to be applied to messages with dynamic fields. Dynamic fields are fields whose message may need to be unmarshaled differently depending on context. For instance, different ConfigValue messages may resolve the same field to different types, depending on the context in which the ConfigValue is evaluated. This CR adds a dynamic proto msg handler to the proto translation framework. Change-Id: I4bf809721f9af37778aeb3df3d706bfda1ed4e7b Signed-off-by: Jason Yellick <[email protected]>
1 parent 7b5b661 commit 7c4fcbf

File tree

7 files changed

+519
-31
lines changed

7 files changed

+519
-31
lines changed

common/tools/protolator/api.go

+49-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import (
2727
// opaque byte fields are represented as their expanded proto contents) and back once again
2828
// to standard proto messages.
2929
//
30-
// There are currently two different types of interfaces available for protos to implement:
30+
// There are currently three different types of interfaces available for protos to implement:
3131
//
3232
// 1. StaticallyOpaque*FieldProto: These interfaces should be implemented by protos which have
3333
// opaque byte fields whose marshaled type is known at compile time. This is mostly true
@@ -40,6 +40,13 @@ import (
4040
// For example, the Payload.data field depends upon the Payload.Header.ChannelHeader.type field,
4141
// which is along a statically marshaled path.
4242
//
43+
// 3. Dynamic*FieldProto: These interfaces are for messages which contain other messages whose
44+
// attributes cannot be determined at compile time. For example, a ConfigValue message may evaluate
45+
// the map field values["MSP"] successfully in an organization context, but not at all in a channel
46+
// context. Because go is not a dynamic language, this dynamic behavior must be simulated by
47+
// wrapping the underlying proto message in another type which can be configured at runtime with
48+
// different contextual behavior. (See tests for examples)
49+
//
4350
///////////////////////////////////////////////////////////////////////////////////////////////////
4451

4552
// StaticallyOpaqueFieldProto should be implemented by protos which have bytes fields which
@@ -107,3 +114,44 @@ type VariablyOpaqueSliceFieldProto interface {
107114
// type for the field name.
108115
VariablyOpaqueSliceFieldProto(name string, index int) (proto.Message, error)
109116
}
117+
118+
// DynamicFieldProto should be implemented by protos which have nested fields whose attributes
119+
// (such as their opaque types) cannot be determined until runtime
120+
type DynamicFieldProto interface {
121+
// DynamicFields returns the field names which are dynamic
122+
DynamicFields() []string
123+
124+
// DynamicFieldProto returns a newly allocated dynamic message, decorating an underlying
125+
// proto message with the runtime determined function
126+
DynamicFieldProto(name string, underlying proto.Message) (proto.Message, error)
127+
}
128+
129+
// DynamicMapFieldProto should be implemented by protos which have maps to messages whose attributes
130+
// (such as their opaque types) cannot be determined until runtime
131+
type DynamicMapFieldProto interface {
132+
// DynamicFields returns the field names which are dynamic
133+
DynamicMapFields() []string
134+
135+
// DynamicMapFieldProto returns a newly allocated dynamic message, decorating an underlying
136+
// proto message with the runtime determined function
137+
DynamicMapFieldProto(name string, key string, underlying proto.Message) (proto.Message, error)
138+
}
139+
140+
// DynamicSliceFieldProto should be implemented by protos which have slices of messages whose attributes
141+
// (such as their opaque types) cannot be determined until runtime
142+
type DynamicSliceFieldProto interface {
143+
// DynamicFields returns the field names which are dynamic
144+
DynamicSliceFields() []string
145+
146+
// DynamicSliceFieldProto returns a newly allocated dynamic message, decorating an underlying
147+
// proto message with the runtime determined function
148+
DynamicSliceFieldProto(name string, index int, underlying proto.Message) (proto.Message, error)
149+
}
150+
151+
// DecoreatedProto should be implemented by the dynamic wrappers applied by the Dynamic*FieldProto interfaces
152+
// This is necessary for the proto system to unmarshal, because it discovers proto message type by reflection
153+
// (Rather than by interface definition as it probably should ( https://github.com/golang/protobuf/issues/291 )
154+
type DecoratedProto interface {
155+
// Underlying returns the underlying proto message which is being dynamically decorated
156+
Underlying() proto.Message
157+
}

common/tools/protolator/dynamic.go

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
Copyright IBM Corp. 2017 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package protolator
18+
19+
import (
20+
"reflect"
21+
22+
"github.com/golang/protobuf/proto"
23+
)
24+
25+
func dynamicFrom(dynamicMsg func(underlying proto.Message) (proto.Message, error), value interface{}, destType reflect.Type) (reflect.Value, error) {
26+
tree := value.(map[string]interface{}) // Safe, already checked
27+
uMsg := reflect.New(destType.Elem())
28+
nMsg, err := dynamicMsg(uMsg.Interface().(proto.Message)) // Safe, already checked
29+
if err != nil {
30+
return reflect.Value{}, err
31+
}
32+
if err := recursivelyPopulateMessageFromTree(tree, nMsg); err != nil {
33+
return reflect.Value{}, err
34+
}
35+
return uMsg, nil
36+
}
37+
38+
func dynamicTo(dynamicMsg func(underlying proto.Message) (proto.Message, error), value reflect.Value) (interface{}, error) {
39+
nMsg, err := dynamicMsg(value.Interface().(proto.Message)) // Safe, already checked
40+
if err != nil {
41+
return nil, err
42+
}
43+
return recursivelyCreateTreeFromMessage(nMsg)
44+
}
45+
46+
type dynamicFieldFactory struct{}
47+
48+
func (dff dynamicFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool {
49+
dynamicProto, ok := msg.(DynamicFieldProto)
50+
if !ok {
51+
return false
52+
}
53+
54+
return stringInSlice(fieldName, dynamicProto.DynamicFields())
55+
}
56+
57+
func (dff dynamicFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) {
58+
dynamicProto, _ := msg.(DynamicFieldProto) // Type checked in Handles
59+
60+
return &plainField{
61+
baseField: baseField{
62+
msg: msg,
63+
name: fieldName,
64+
fType: mapStringInterfaceType,
65+
vType: fieldType,
66+
value: fieldValue,
67+
},
68+
populateFrom: func(v interface{}, dT reflect.Type) (reflect.Value, error) {
69+
return dynamicFrom(func(underlying proto.Message) (proto.Message, error) {
70+
return dynamicProto.DynamicFieldProto(fieldName, underlying)
71+
}, v, dT)
72+
},
73+
populateTo: func(v reflect.Value) (interface{}, error) {
74+
return dynamicTo(func(underlying proto.Message) (proto.Message, error) {
75+
return dynamicProto.DynamicFieldProto(fieldName, underlying)
76+
}, v)
77+
},
78+
}, nil
79+
}
80+
81+
type dynamicMapFieldFactory struct{}
82+
83+
func (dmff dynamicMapFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool {
84+
dynamicProto, ok := msg.(DynamicMapFieldProto)
85+
if !ok {
86+
return false
87+
}
88+
89+
return stringInSlice(fieldName, dynamicProto.DynamicMapFields())
90+
}
91+
92+
func (dmff dynamicMapFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) {
93+
dynamicProto := msg.(DynamicMapFieldProto) // Type checked by Handles
94+
95+
return &mapField{
96+
baseField: baseField{
97+
msg: msg,
98+
name: fieldName,
99+
fType: mapStringInterfaceType,
100+
vType: fieldType,
101+
value: fieldValue,
102+
},
103+
populateFrom: func(k string, v interface{}, dT reflect.Type) (reflect.Value, error) {
104+
return dynamicFrom(func(underlying proto.Message) (proto.Message, error) {
105+
return dynamicProto.DynamicMapFieldProto(fieldName, k, underlying)
106+
}, v, dT)
107+
},
108+
populateTo: func(k string, v reflect.Value) (interface{}, error) {
109+
return dynamicTo(func(underlying proto.Message) (proto.Message, error) {
110+
return dynamicProto.DynamicMapFieldProto(fieldName, k, underlying)
111+
}, v)
112+
},
113+
}, nil
114+
}
115+
116+
type dynamicSliceFieldFactory struct{}
117+
118+
func (dmff dynamicSliceFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool {
119+
dynamicProto, ok := msg.(DynamicSliceFieldProto)
120+
if !ok {
121+
return false
122+
}
123+
124+
return stringInSlice(fieldName, dynamicProto.DynamicSliceFields())
125+
}
126+
127+
func (dmff dynamicSliceFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) {
128+
dynamicProto := msg.(DynamicSliceFieldProto) // Type checked by Handles
129+
130+
return &sliceField{
131+
baseField: baseField{
132+
msg: msg,
133+
name: fieldName,
134+
fType: mapStringInterfaceType,
135+
vType: fieldType,
136+
value: fieldValue,
137+
},
138+
populateFrom: func(i int, v interface{}, dT reflect.Type) (reflect.Value, error) {
139+
return dynamicFrom(func(underlying proto.Message) (proto.Message, error) {
140+
return dynamicProto.DynamicSliceFieldProto(fieldName, i, underlying)
141+
}, v, dT)
142+
},
143+
populateTo: func(i int, v reflect.Value) (interface{}, error) {
144+
return dynamicTo(func(underlying proto.Message) (proto.Message, error) {
145+
return dynamicProto.DynamicSliceFieldProto(fieldName, i, underlying)
146+
}, v)
147+
},
148+
}, nil
149+
}
+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
Copyright IBM Corp. 2017 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package protolator
18+
19+
import (
20+
"bytes"
21+
"testing"
22+
23+
"github.com/hyperledger/fabric/common/tools/protolator/testprotos"
24+
"github.com/hyperledger/fabric/protos/utils"
25+
26+
"github.com/stretchr/testify/assert"
27+
)
28+
29+
func TestPlainDynamicMsg(t *testing.T) {
30+
fromPrefix := "from"
31+
toPrefix := "to"
32+
tppff := &testProtoPlainFieldFactory{
33+
fromPrefix: fromPrefix,
34+
toPrefix: toPrefix,
35+
}
36+
37+
fieldFactories = []protoFieldFactory{tppff, variablyOpaqueFieldFactory{}}
38+
39+
pfValue := "foo"
40+
startMsg := &testprotos.DynamicMsg{
41+
DynamicType: "SimpleMsg",
42+
PlainDynamicField: &testprotos.ContextlessMsg{
43+
OpaqueField: utils.MarshalOrPanic(&testprotos.SimpleMsg{
44+
PlainField: pfValue,
45+
}),
46+
},
47+
}
48+
49+
var buffer bytes.Buffer
50+
assert.NoError(t, DeepMarshalJSON(&buffer, startMsg))
51+
newMsg := &testprotos.DynamicMsg{}
52+
assert.NoError(t, DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg))
53+
assert.NotEqual(t, fromPrefix+toPrefix+extractSimpleMsgPlainField(startMsg.PlainDynamicField.OpaqueField), extractSimpleMsgPlainField(newMsg.PlainDynamicField.OpaqueField))
54+
55+
fieldFactories = []protoFieldFactory{tppff, variablyOpaqueFieldFactory{}, dynamicFieldFactory{}}
56+
57+
buffer.Reset()
58+
assert.NoError(t, DeepMarshalJSON(&buffer, startMsg))
59+
assert.NoError(t, DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg))
60+
assert.Equal(t, fromPrefix+toPrefix+extractSimpleMsgPlainField(startMsg.PlainDynamicField.OpaqueField), extractSimpleMsgPlainField(newMsg.PlainDynamicField.OpaqueField))
61+
}
62+
63+
func TestMapDynamicMsg(t *testing.T) {
64+
fromPrefix := "from"
65+
toPrefix := "to"
66+
tppff := &testProtoPlainFieldFactory{
67+
fromPrefix: fromPrefix,
68+
toPrefix: toPrefix,
69+
}
70+
71+
fieldFactories = []protoFieldFactory{tppff, variablyOpaqueFieldFactory{}}
72+
73+
pfValue := "foo"
74+
mapKey := "bar"
75+
startMsg := &testprotos.DynamicMsg{
76+
DynamicType: "SimpleMsg",
77+
MapDynamicField: map[string]*testprotos.ContextlessMsg{
78+
mapKey: &testprotos.ContextlessMsg{
79+
OpaqueField: utils.MarshalOrPanic(&testprotos.SimpleMsg{
80+
PlainField: pfValue,
81+
}),
82+
},
83+
},
84+
}
85+
86+
var buffer bytes.Buffer
87+
assert.NoError(t, DeepMarshalJSON(&buffer, startMsg))
88+
newMsg := &testprotos.DynamicMsg{}
89+
assert.NoError(t, DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg))
90+
assert.NotEqual(t, fromPrefix+toPrefix+extractSimpleMsgPlainField(startMsg.MapDynamicField[mapKey].OpaqueField), extractSimpleMsgPlainField(newMsg.MapDynamicField[mapKey].OpaqueField))
91+
92+
fieldFactories = []protoFieldFactory{tppff, variablyOpaqueFieldFactory{}, dynamicMapFieldFactory{}}
93+
94+
buffer.Reset()
95+
assert.NoError(t, DeepMarshalJSON(&buffer, startMsg))
96+
assert.NoError(t, DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg))
97+
assert.Equal(t, fromPrefix+toPrefix+extractSimpleMsgPlainField(startMsg.MapDynamicField[mapKey].OpaqueField), extractSimpleMsgPlainField(newMsg.MapDynamicField[mapKey].OpaqueField))
98+
}
99+
100+
func TestSliceDynamicMsg(t *testing.T) {
101+
fromPrefix := "from"
102+
toPrefix := "to"
103+
tppff := &testProtoPlainFieldFactory{
104+
fromPrefix: fromPrefix,
105+
toPrefix: toPrefix,
106+
}
107+
108+
fieldFactories = []protoFieldFactory{tppff, variablyOpaqueFieldFactory{}}
109+
110+
pfValue := "foo"
111+
startMsg := &testprotos.DynamicMsg{
112+
DynamicType: "SimpleMsg",
113+
SliceDynamicField: []*testprotos.ContextlessMsg{
114+
&testprotos.ContextlessMsg{
115+
OpaqueField: utils.MarshalOrPanic(&testprotos.SimpleMsg{
116+
PlainField: pfValue,
117+
}),
118+
},
119+
},
120+
}
121+
122+
var buffer bytes.Buffer
123+
assert.NoError(t, DeepMarshalJSON(&buffer, startMsg))
124+
newMsg := &testprotos.DynamicMsg{}
125+
assert.NoError(t, DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg))
126+
assert.NotEqual(t, fromPrefix+toPrefix+extractSimpleMsgPlainField(startMsg.SliceDynamicField[0].OpaqueField), extractSimpleMsgPlainField(newMsg.SliceDynamicField[0].OpaqueField))
127+
128+
fieldFactories = []protoFieldFactory{tppff, variablyOpaqueFieldFactory{}, dynamicSliceFieldFactory{}}
129+
130+
buffer.Reset()
131+
assert.NoError(t, DeepMarshalJSON(&buffer, startMsg))
132+
assert.NoError(t, DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg))
133+
assert.Equal(t, fromPrefix+toPrefix+extractSimpleMsgPlainField(startMsg.SliceDynamicField[0].OpaqueField), extractSimpleMsgPlainField(newMsg.SliceDynamicField[0].OpaqueField))
134+
}

common/tools/protolator/json.go

+11
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,9 @@ func jsonToMap(marshaled []byte) (map[string]interface{}, error) {
251251
// Factories listed lower, may depend on factories listed higher being
252252
// evaluated first.
253253
var fieldFactories = []protoFieldFactory{
254+
dynamicSliceFieldFactory{},
255+
dynamicMapFieldFactory{},
256+
dynamicFieldFactory{},
254257
variablyOpaqueSliceFieldFactory{},
255258
variablyOpaqueMapFieldFactory{},
256259
variablyOpaqueFieldFactory{},
@@ -326,6 +329,10 @@ func recursivelyCreateTreeFromMessage(msg proto.Message) (tree map[string]interf
326329
}()
327330

328331
uMsg := msg
332+
decorated, ok := msg.(DecoratedProto)
333+
if ok {
334+
uMsg = decorated.Underlying()
335+
}
329336

330337
fields, err := protoFields(msg, uMsg)
331338
if err != nil {
@@ -381,6 +388,10 @@ func recursivelyPopulateMessageFromTree(tree map[string]interface{}, msg proto.M
381388
}()
382389

383390
uMsg := msg
391+
decorated, ok := msg.(DecoratedProto)
392+
if ok {
393+
uMsg = decorated.Underlying()
394+
}
384395

385396
fields, err := protoFields(msg, uMsg)
386397
if err != nil {

0 commit comments

Comments
 (0)