diff --git a/common/configtx/config.go b/common/configtx/config.go index 7fd4292bbbc..ac29dc314d3 100644 --- a/common/configtx/config.go +++ b/common/configtx/config.go @@ -17,6 +17,7 @@ limitations under the License. package configtx import ( + "bytes" "fmt" "github.com/hyperledger/fabric/common/config" @@ -24,6 +25,7 @@ import ( "github.com/hyperledger/fabric/common/policies" cb "github.com/hyperledger/fabric/protos/common" + "github.com/golang/protobuf/jsonpb" "github.com/golang/protobuf/proto" ) @@ -37,6 +39,77 @@ type configResult struct { deserializedValues map[string]proto.Message } +func (cr *configResult) JSON() string { + var buffer bytes.Buffer + buffer.WriteString("{") + cr.bufferJSON(&buffer) + buffer.WriteString("}") + return buffer.String() + +} + +// bufferJSON takes a buffer and writes a JSON representation of the configResult into the buffer +// Note that we use some mildly ad-hoc JSON encoding because the proto documentation explicitly +// mentions that the encoding/json package does not correctly marshal proto objects, and we +// do not have a proto object (nor can one be defined) which presents the mixed-map style of +// keys mapping to different types of the config +func (cr *configResult) bufferJSON(buffer *bytes.Buffer) { + jpb := &jsonpb.Marshaler{ + EmitDefaults: true, + Indent: " ", + } + + // "GroupName": { + buffer.WriteString("\"") + buffer.WriteString(cr.groupName) + buffer.WriteString("\": {") + + // "Values": { + buffer.WriteString("\"Values\": {") + count := 0 + for key, value := range cr.group.Values { + // "Key": { + buffer.WriteString("\"") + buffer.WriteString(key) + buffer.WriteString("\": {") + // "Version": "X", + buffer.WriteString("\"Version\":\"") + buffer.WriteString(fmt.Sprintf("%d", value.Version)) + buffer.WriteString("\",") + // "ModPolicy": "foo", + buffer.WriteString("\"ModPolicy\":\"") + buffer.WriteString(value.ModPolicy) + buffer.WriteString("\",") + // "Value": protoAsJSON + buffer.WriteString("\"Value\":") + jpb.Marshal(buffer, cr.deserializedValues[key]) + // }, + buffer.WriteString("}") + count++ + if count < len(cr.group.Values) { + buffer.WriteString(",") + } + } + // }, + buffer.WriteString("},") + + // "Groups": { + count = 0 + buffer.WriteString("\"Groups\": {") + for _, subResult := range cr.subResults { + subResult.bufferJSON(buffer) + count++ + if count < len(cr.subResults) { + buffer.WriteString(",") + } + } + // } + buffer.WriteString("}") + + // } + buffer.WriteString("}") +} + func (cr *configResult) preCommit() error { for _, subResult := range cr.subResults { err := subResult.preCommit() diff --git a/common/configtx/config_test.go b/common/configtx/config_test.go new file mode 100644 index 00000000000..73074e466ef --- /dev/null +++ b/common/configtx/config_test.go @@ -0,0 +1,79 @@ +/* +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 configtx + +import ( + "bytes" + "encoding/json" + "strings" + "testing" + + cb "github.com/hyperledger/fabric/protos/common" + ab "github.com/hyperledger/fabric/protos/orderer" + + "github.com/golang/protobuf/proto" + "github.com/stretchr/testify/assert" +) + +func TestJSON(t *testing.T) { + cr := &configResult{ + groupName: "rootGroup", + group: &cb.ConfigGroup{ + Values: map[string]*cb.ConfigValue{ + "outer": &cb.ConfigValue{Version: 1, ModPolicy: "mod1"}, + }, + }, + subResults: []*configResult{ + &configResult{ + groupName: "innerGroup1", + group: &cb.ConfigGroup{ + Values: map[string]*cb.ConfigValue{ + "inner1": &cb.ConfigValue{ModPolicy: "mod3"}, + }, + }, + deserializedValues: map[string]proto.Message{ + "inner1": &ab.ConsensusType{Type: "inner1"}, + }, + }, + &configResult{ + groupName: "innerGroup2", + group: &cb.ConfigGroup{ + Values: map[string]*cb.ConfigValue{ + "inner2": &cb.ConfigValue{ModPolicy: "mod3"}, + }, + }, + deserializedValues: map[string]proto.Message{ + "inner2": &ab.ConsensusType{Type: "inner2"}, + }, + }, + }, + deserializedValues: map[string]proto.Message{ + "outer": &ab.ConsensusType{Type: "outer"}, + }, + } + + buffer := &bytes.Buffer{} + assert.NoError(t, json.Indent(buffer, []byte(cr.JSON()), "", ""), "JSON should parse nicely") + + expected := "{\"rootGroup\":{\"Values\":{\"outer\":{\"Version\":\"1\",\"ModPolicy\":\"mod1\",\"Value\":{\"type\":\"outer\"}}},\"Groups\":{\"innerGroup1\":{\"Values\":{\"inner1\":{\"Version\":\"0\",\"ModPolicy\":\"mod3\",\"Value\":{\"type\":\"inner1\"}}},\"Groups\":{}},\"innerGroup2\":{\"Values\":{\"inner2\":{\"Version\":\"0\",\"ModPolicy\":\"mod3\",\"Value\":{\"type\":\"inner2\"}}},\"Groups\":{}}}}}" + + // Remove all newlines and spaces from the JSON + compactedJSON := strings.Replace(strings.Replace(buffer.String(), "\n", "", -1), " ", "", -1) + + assert.Equal(t, expected, compactedJSON) + +}