-
Notifications
You must be signed in to change notification settings - Fork 8.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[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 <jyellick@us.ibm.com>
- Loading branch information
Jason Yellick
committed
May 26, 2017
1 parent
7b5b661
commit 7c4fcbf
Showing
7 changed files
with
519 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
/* | ||
Copyright IBM Corp. 2017 All Rights Reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package protolator | ||
|
||
import ( | ||
"reflect" | ||
|
||
"github.com/golang/protobuf/proto" | ||
) | ||
|
||
func dynamicFrom(dynamicMsg func(underlying proto.Message) (proto.Message, error), value interface{}, destType reflect.Type) (reflect.Value, error) { | ||
tree := value.(map[string]interface{}) // Safe, already checked | ||
uMsg := reflect.New(destType.Elem()) | ||
nMsg, err := dynamicMsg(uMsg.Interface().(proto.Message)) // Safe, already checked | ||
if err != nil { | ||
return reflect.Value{}, err | ||
} | ||
if err := recursivelyPopulateMessageFromTree(tree, nMsg); err != nil { | ||
return reflect.Value{}, err | ||
} | ||
return uMsg, nil | ||
} | ||
|
||
func dynamicTo(dynamicMsg func(underlying proto.Message) (proto.Message, error), value reflect.Value) (interface{}, error) { | ||
nMsg, err := dynamicMsg(value.Interface().(proto.Message)) // Safe, already checked | ||
if err != nil { | ||
return nil, err | ||
} | ||
return recursivelyCreateTreeFromMessage(nMsg) | ||
} | ||
|
||
type dynamicFieldFactory struct{} | ||
|
||
func (dff dynamicFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool { | ||
dynamicProto, ok := msg.(DynamicFieldProto) | ||
if !ok { | ||
return false | ||
} | ||
|
||
return stringInSlice(fieldName, dynamicProto.DynamicFields()) | ||
} | ||
|
||
func (dff dynamicFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) { | ||
dynamicProto, _ := msg.(DynamicFieldProto) // Type checked in Handles | ||
|
||
return &plainField{ | ||
baseField: baseField{ | ||
msg: msg, | ||
name: fieldName, | ||
fType: mapStringInterfaceType, | ||
vType: fieldType, | ||
value: fieldValue, | ||
}, | ||
populateFrom: func(v interface{}, dT reflect.Type) (reflect.Value, error) { | ||
return dynamicFrom(func(underlying proto.Message) (proto.Message, error) { | ||
return dynamicProto.DynamicFieldProto(fieldName, underlying) | ||
}, v, dT) | ||
}, | ||
populateTo: func(v reflect.Value) (interface{}, error) { | ||
return dynamicTo(func(underlying proto.Message) (proto.Message, error) { | ||
return dynamicProto.DynamicFieldProto(fieldName, underlying) | ||
}, v) | ||
}, | ||
}, nil | ||
} | ||
|
||
type dynamicMapFieldFactory struct{} | ||
|
||
func (dmff dynamicMapFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool { | ||
dynamicProto, ok := msg.(DynamicMapFieldProto) | ||
if !ok { | ||
return false | ||
} | ||
|
||
return stringInSlice(fieldName, dynamicProto.DynamicMapFields()) | ||
} | ||
|
||
func (dmff dynamicMapFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) { | ||
dynamicProto := msg.(DynamicMapFieldProto) // Type checked by Handles | ||
|
||
return &mapField{ | ||
baseField: baseField{ | ||
msg: msg, | ||
name: fieldName, | ||
fType: mapStringInterfaceType, | ||
vType: fieldType, | ||
value: fieldValue, | ||
}, | ||
populateFrom: func(k string, v interface{}, dT reflect.Type) (reflect.Value, error) { | ||
return dynamicFrom(func(underlying proto.Message) (proto.Message, error) { | ||
return dynamicProto.DynamicMapFieldProto(fieldName, k, underlying) | ||
}, v, dT) | ||
}, | ||
populateTo: func(k string, v reflect.Value) (interface{}, error) { | ||
return dynamicTo(func(underlying proto.Message) (proto.Message, error) { | ||
return dynamicProto.DynamicMapFieldProto(fieldName, k, underlying) | ||
}, v) | ||
}, | ||
}, nil | ||
} | ||
|
||
type dynamicSliceFieldFactory struct{} | ||
|
||
func (dmff dynamicSliceFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool { | ||
dynamicProto, ok := msg.(DynamicSliceFieldProto) | ||
if !ok { | ||
return false | ||
} | ||
|
||
return stringInSlice(fieldName, dynamicProto.DynamicSliceFields()) | ||
} | ||
|
||
func (dmff dynamicSliceFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) { | ||
dynamicProto := msg.(DynamicSliceFieldProto) // Type checked by Handles | ||
|
||
return &sliceField{ | ||
baseField: baseField{ | ||
msg: msg, | ||
name: fieldName, | ||
fType: mapStringInterfaceType, | ||
vType: fieldType, | ||
value: fieldValue, | ||
}, | ||
populateFrom: func(i int, v interface{}, dT reflect.Type) (reflect.Value, error) { | ||
return dynamicFrom(func(underlying proto.Message) (proto.Message, error) { | ||
return dynamicProto.DynamicSliceFieldProto(fieldName, i, underlying) | ||
}, v, dT) | ||
}, | ||
populateTo: func(i int, v reflect.Value) (interface{}, error) { | ||
return dynamicTo(func(underlying proto.Message) (proto.Message, error) { | ||
return dynamicProto.DynamicSliceFieldProto(fieldName, i, underlying) | ||
}, v) | ||
}, | ||
}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
/* | ||
Copyright IBM Corp. 2017 All Rights Reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package protolator | ||
|
||
import ( | ||
"bytes" | ||
"testing" | ||
|
||
"github.com/hyperledger/fabric/common/tools/protolator/testprotos" | ||
"github.com/hyperledger/fabric/protos/utils" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestPlainDynamicMsg(t *testing.T) { | ||
fromPrefix := "from" | ||
toPrefix := "to" | ||
tppff := &testProtoPlainFieldFactory{ | ||
fromPrefix: fromPrefix, | ||
toPrefix: toPrefix, | ||
} | ||
|
||
fieldFactories = []protoFieldFactory{tppff, variablyOpaqueFieldFactory{}} | ||
|
||
pfValue := "foo" | ||
startMsg := &testprotos.DynamicMsg{ | ||
DynamicType: "SimpleMsg", | ||
PlainDynamicField: &testprotos.ContextlessMsg{ | ||
OpaqueField: utils.MarshalOrPanic(&testprotos.SimpleMsg{ | ||
PlainField: pfValue, | ||
}), | ||
}, | ||
} | ||
|
||
var buffer bytes.Buffer | ||
assert.NoError(t, DeepMarshalJSON(&buffer, startMsg)) | ||
newMsg := &testprotos.DynamicMsg{} | ||
assert.NoError(t, DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg)) | ||
assert.NotEqual(t, fromPrefix+toPrefix+extractSimpleMsgPlainField(startMsg.PlainDynamicField.OpaqueField), extractSimpleMsgPlainField(newMsg.PlainDynamicField.OpaqueField)) | ||
|
||
fieldFactories = []protoFieldFactory{tppff, variablyOpaqueFieldFactory{}, dynamicFieldFactory{}} | ||
|
||
buffer.Reset() | ||
assert.NoError(t, DeepMarshalJSON(&buffer, startMsg)) | ||
assert.NoError(t, DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg)) | ||
assert.Equal(t, fromPrefix+toPrefix+extractSimpleMsgPlainField(startMsg.PlainDynamicField.OpaqueField), extractSimpleMsgPlainField(newMsg.PlainDynamicField.OpaqueField)) | ||
} | ||
|
||
func TestMapDynamicMsg(t *testing.T) { | ||
fromPrefix := "from" | ||
toPrefix := "to" | ||
tppff := &testProtoPlainFieldFactory{ | ||
fromPrefix: fromPrefix, | ||
toPrefix: toPrefix, | ||
} | ||
|
||
fieldFactories = []protoFieldFactory{tppff, variablyOpaqueFieldFactory{}} | ||
|
||
pfValue := "foo" | ||
mapKey := "bar" | ||
startMsg := &testprotos.DynamicMsg{ | ||
DynamicType: "SimpleMsg", | ||
MapDynamicField: map[string]*testprotos.ContextlessMsg{ | ||
mapKey: &testprotos.ContextlessMsg{ | ||
OpaqueField: utils.MarshalOrPanic(&testprotos.SimpleMsg{ | ||
PlainField: pfValue, | ||
}), | ||
}, | ||
}, | ||
} | ||
|
||
var buffer bytes.Buffer | ||
assert.NoError(t, DeepMarshalJSON(&buffer, startMsg)) | ||
newMsg := &testprotos.DynamicMsg{} | ||
assert.NoError(t, DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg)) | ||
assert.NotEqual(t, fromPrefix+toPrefix+extractSimpleMsgPlainField(startMsg.MapDynamicField[mapKey].OpaqueField), extractSimpleMsgPlainField(newMsg.MapDynamicField[mapKey].OpaqueField)) | ||
|
||
fieldFactories = []protoFieldFactory{tppff, variablyOpaqueFieldFactory{}, dynamicMapFieldFactory{}} | ||
|
||
buffer.Reset() | ||
assert.NoError(t, DeepMarshalJSON(&buffer, startMsg)) | ||
assert.NoError(t, DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg)) | ||
assert.Equal(t, fromPrefix+toPrefix+extractSimpleMsgPlainField(startMsg.MapDynamicField[mapKey].OpaqueField), extractSimpleMsgPlainField(newMsg.MapDynamicField[mapKey].OpaqueField)) | ||
} | ||
|
||
func TestSliceDynamicMsg(t *testing.T) { | ||
fromPrefix := "from" | ||
toPrefix := "to" | ||
tppff := &testProtoPlainFieldFactory{ | ||
fromPrefix: fromPrefix, | ||
toPrefix: toPrefix, | ||
} | ||
|
||
fieldFactories = []protoFieldFactory{tppff, variablyOpaqueFieldFactory{}} | ||
|
||
pfValue := "foo" | ||
startMsg := &testprotos.DynamicMsg{ | ||
DynamicType: "SimpleMsg", | ||
SliceDynamicField: []*testprotos.ContextlessMsg{ | ||
&testprotos.ContextlessMsg{ | ||
OpaqueField: utils.MarshalOrPanic(&testprotos.SimpleMsg{ | ||
PlainField: pfValue, | ||
}), | ||
}, | ||
}, | ||
} | ||
|
||
var buffer bytes.Buffer | ||
assert.NoError(t, DeepMarshalJSON(&buffer, startMsg)) | ||
newMsg := &testprotos.DynamicMsg{} | ||
assert.NoError(t, DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg)) | ||
assert.NotEqual(t, fromPrefix+toPrefix+extractSimpleMsgPlainField(startMsg.SliceDynamicField[0].OpaqueField), extractSimpleMsgPlainField(newMsg.SliceDynamicField[0].OpaqueField)) | ||
|
||
fieldFactories = []protoFieldFactory{tppff, variablyOpaqueFieldFactory{}, dynamicSliceFieldFactory{}} | ||
|
||
buffer.Reset() | ||
assert.NoError(t, DeepMarshalJSON(&buffer, startMsg)) | ||
assert.NoError(t, DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newMsg)) | ||
assert.Equal(t, fromPrefix+toPrefix+extractSimpleMsgPlainField(startMsg.SliceDynamicField[0].OpaqueField), extractSimpleMsgPlainField(newMsg.SliceDynamicField[0].OpaqueField)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.