Skip to content

Commit 923e70d

Browse files
author
Jason Yellick
committed
[FAB-2448] Add standard values proto initializer
https://jira.hyperledger.org/browse/FAB-2448 The proposer framework specifies a handler interface which requires Allocate to create the backing resources necessary to handle unmarshaling config and to map the config keys to the correct proto structure. This provides nice flexibility for arbitrary configuration, but, in reality, all config is instantiated in almost the same way. This CR adds a standard values proto intializer which will, given a struct whose fields are proto messages, will build the config map based on the field names, and allocate the memory necessary to unmarshal into the struct. This should help remove significant amounts of code as the assorted config handlers are moved to the Proposer framework. Change-Id: Ic54d116e8bbfa61dd6839068d6195026f215477f Signed-off-by: Jason Yellick <[email protected]>
1 parent bc2923b commit 923e70d

File tree

2 files changed

+203
-0
lines changed

2 files changed

+203
-0
lines changed
+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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 config
18+
19+
import (
20+
"fmt"
21+
"reflect"
22+
23+
"github.com/golang/protobuf/proto"
24+
)
25+
26+
type standardValues struct {
27+
lookup map[string]proto.Message
28+
}
29+
30+
// NewStandardValues accepts a structure which must contain only protobuf message
31+
// types. The structure may embed other (non-pointer) structures which satisfy
32+
// the same condition. NewStandard values will instantiate memory for all the proto
33+
// messages and build a lookup map from structure field name to proto message instance
34+
// This is a useful way to easily implement the Values interface
35+
func NewStandardValues(protosStructs ...interface{}) (*standardValues, error) {
36+
sv := &standardValues{
37+
lookup: make(map[string]proto.Message),
38+
}
39+
40+
for _, protosStruct := range protosStructs {
41+
if err := sv.initializeProtosStruct(reflect.ValueOf(protosStruct)); err != nil {
42+
return nil, err
43+
}
44+
}
45+
46+
return sv, nil
47+
}
48+
49+
func (sv *standardValues) ProtoMsg(key string) (proto.Message, bool) {
50+
msg, ok := sv.lookup[key]
51+
return msg, ok
52+
}
53+
54+
func (sv *standardValues) initializeProtosStruct(objValue reflect.Value) error {
55+
objType := objValue.Type()
56+
if objType.Kind() != reflect.Ptr {
57+
return fmt.Errorf("Non pointer type")
58+
}
59+
if objType.Elem().Kind() != reflect.Struct {
60+
return fmt.Errorf("Non struct type")
61+
}
62+
63+
numFields := objValue.Elem().NumField()
64+
for i := 0; i < numFields; i++ {
65+
structField := objType.Elem().Field(i)
66+
fmt.Printf("Processing field: %s\n", structField.Name)
67+
switch structField.Type.Kind() {
68+
case reflect.Ptr:
69+
fieldPtr := objValue.Elem().Field(i)
70+
if !fieldPtr.CanSet() {
71+
return fmt.Errorf("Cannot set structure field %s (unexported?)", structField)
72+
}
73+
fieldPtr.Set(reflect.New(structField.Type.Elem()))
74+
default:
75+
return fmt.Errorf("Bad type supplied: %s", structField.Type.Kind())
76+
}
77+
78+
proto, ok := objValue.Elem().Field(i).Interface().(proto.Message)
79+
if !ok {
80+
return fmt.Errorf("Field type %T does not implement proto.Message", objValue.Elem().Field(i))
81+
}
82+
83+
_, ok = sv.lookup[structField.Name]
84+
if ok {
85+
return fmt.Errorf("Ambiguous field name specified, multiple occurances of %s", structField.Name)
86+
}
87+
88+
sv.lookup[structField.Name] = proto
89+
}
90+
91+
return nil
92+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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 config
18+
19+
import (
20+
"testing"
21+
22+
cb "github.com/hyperledger/fabric/protos/common"
23+
24+
"github.com/stretchr/testify/assert"
25+
)
26+
27+
type foo struct {
28+
Msg1 *cb.Envelope
29+
Msg2 *cb.Payload
30+
}
31+
32+
type bar struct {
33+
Msg3 *cb.Header
34+
}
35+
36+
type conflict struct {
37+
Msg1 *cb.Header
38+
}
39+
40+
type nonProtos struct {
41+
Msg *cb.Envelope
42+
Wrong string
43+
}
44+
45+
type unexported struct {
46+
msg *cb.Envelope
47+
}
48+
49+
func TestSingle(t *testing.T) {
50+
fooVal := &foo{}
51+
sv, err := NewStandardValues(fooVal)
52+
assert.NoError(t, err, "Valid non-nested structure provided")
53+
assert.NotNil(t, fooVal.Msg1, "Should have initialized Msg1")
54+
assert.NotNil(t, fooVal.Msg2, "Should have initialized Msg2")
55+
56+
msg1, ok := sv.ProtoMsg("Msg1")
57+
assert.True(t, ok, "Should have found map entry")
58+
assert.Equal(t, msg1, fooVal.Msg1, "Should be same entry")
59+
60+
msg2, ok := sv.ProtoMsg("Msg2")
61+
assert.True(t, ok, "Should have found map entry")
62+
assert.Equal(t, msg2, fooVal.Msg2, "Should be same entry")
63+
}
64+
65+
func TestPair(t *testing.T) {
66+
fooVal := &foo{}
67+
barVal := &bar{}
68+
sv, err := NewStandardValues(fooVal, barVal)
69+
assert.NoError(t, err, "Valid non-nested structure provided")
70+
assert.NotNil(t, fooVal.Msg1, "Should have initialized Msg1")
71+
assert.NotNil(t, fooVal.Msg2, "Should have initialized Msg2")
72+
assert.NotNil(t, barVal.Msg3, "Should have initialized Msg3")
73+
74+
msg1, ok := sv.ProtoMsg("Msg1")
75+
assert.True(t, ok, "Should have found map entry")
76+
assert.Equal(t, msg1, fooVal.Msg1, "Should be same entry")
77+
78+
msg2, ok := sv.ProtoMsg("Msg2")
79+
assert.True(t, ok, "Should have found map entry")
80+
assert.Equal(t, msg2, fooVal.Msg2, "Should be same entry")
81+
82+
msg3, ok := sv.ProtoMsg("Msg3")
83+
assert.True(t, ok, "Should have found map entry")
84+
assert.Equal(t, msg3, barVal.Msg3, "Should be same entry")
85+
}
86+
87+
func TestNestingConflict(t *testing.T) {
88+
_, err := NewStandardValues(&foo{}, &conflict{})
89+
assert.Error(t, err, "Conflicting keys provided")
90+
}
91+
92+
func TestNonProtosStruct(t *testing.T) {
93+
_, err := NewStandardValues(&nonProtos{})
94+
assert.Error(t, err, "Structure with non-struct non-proto fields provided")
95+
}
96+
97+
func TestUnexportedField(t *testing.T) {
98+
_, err := NewStandardValues(&unexported{})
99+
assert.Error(t, err, "Structure with unexported fields")
100+
}
101+
102+
func TestNonPointerParam(t *testing.T) {
103+
_, err := NewStandardValues(foo{})
104+
assert.Error(t, err, "Parameter must be pointer")
105+
}
106+
107+
func TestPointerToNonStruct(t *testing.T) {
108+
nonStruct := "foo"
109+
_, err := NewStandardValues(&nonStruct)
110+
assert.Error(t, err, "Pointer must be to a struct")
111+
}

0 commit comments

Comments
 (0)