From f5d39ccb98d4dca00b7ff2d14d6571dc9bc91c67 Mon Sep 17 00:00:00 2001 From: "Mark S. Lewis" Date: Mon, 23 Dec 2024 17:26:36 +0000 Subject: [PATCH] Support []byte parameters and return type (#161) Pass []byte directly instead of treating as a JSON array. The correct OpenAPI type for []byte is type "string" with format "byte". Signed-off-by: Mark S. Lewis --- contractapi/contract_chaincode.go | 62 ++++++++++--------- contractapi/contract_chaincode_test.go | 49 ++++++++++++++- go.mod | 3 +- go.sum | 4 +- .../chaincode/advancedtypes/go.mod | 2 +- .../chaincode/advancedtypes/go.sum | 4 +- integrationtest/chaincode/private/go.mod | 2 +- integrationtest/chaincode/private/go.sum | 4 +- integrationtest/chaincode/simple/go.mod | 2 +- integrationtest/chaincode/simple/go.sum | 4 +- .../chaincode/transactionhooks/go.mod | 2 +- .../chaincode/transactionhooks/go.sum | 4 +- internal/contract_function.go | 22 +++---- .../simplecontract/simplecontract.go | 12 ++-- internal/types/types.go | 4 ++ metadata/schema.go | 43 +++++++------ metadata/schema_test.go | 2 +- serializer/json_transaction_serializer.go | 7 ++- 18 files changed, 147 insertions(+), 85 deletions(-) diff --git a/contractapi/contract_chaincode.go b/contractapi/contract_chaincode.go index e392d74..8a2a448 100644 --- a/contractapi/contract_chaincode.go +++ b/contractapi/contract_chaincode.go @@ -156,22 +156,10 @@ func (cc *ContractChaincode) Init(stub shim.ChaincodeStubInterface) *peer.Respon // If no contract name is passed then the default contract is used. func (cc *ContractChaincode) Invoke(stub shim.ChaincodeStubInterface) *peer.Response { - nsFcn, params := stub.GetFunctionAndParameters() + ns, fn, params := cc.getNamespaceFunctionAndParams(stub) - li := strings.LastIndex(nsFcn, ":") - - var ns string - var fn string - - if li == -1 { - ns = cc.DefaultContract - fn = nsFcn - } else { - ns = nsFcn[:li] - fn = nsFcn[li+1:] - } - - if _, ok := cc.contracts[ns]; !ok { + nsContract, ok := cc.contracts[ns] + if !ok { return shim.Error(fmt.Sprintf("Contract not found with name %s", ns)) } @@ -179,17 +167,6 @@ func (cc *ContractChaincode) Invoke(stub shim.ChaincodeStubInterface) *peer.Resp return shim.Error("Blank function name passed") } - originalFn := fn - - fnRune := []rune(fn) - - if unicode.IsLower(fnRune[0]) { - fnRune[0] = unicode.ToUpper(fnRune[0]) - fn = string(fnRune) - } - - nsContract := cc.contracts[ns] - ctx := reflect.New(nsContract.transactionContextHandler) ctxIface := ctx.Interface().(SettableTransactionContextInterface) ctxIface.SetStub(stub) @@ -213,10 +190,10 @@ func (cc *ContractChaincode) Invoke(stub shim.ChaincodeStubInterface) *peer.Resp serializer := cc.TransactionSerializer - if _, ok := nsContract.functions[fn]; !ok { + if contractFn, ok := nsContract.functions[toFirstRuneUpperCase(fn)]; !ok { unknownTransaction := nsContract.unknownTransaction if unknownTransaction == nil { - return shim.Error(fmt.Sprintf("Function %s not found in contract %s", originalFn, ns)) + return shim.Error(fmt.Sprintf("Function %s not found in contract %s", fn, ns)) } successReturn, successIFace, errorReturn = unknownTransaction.Call(ctx, nil, serializer) @@ -230,7 +207,7 @@ func (cc *ContractChaincode) Invoke(stub shim.ChaincodeStubInterface) *peer.Resp } } - successReturn, successIFace, errorReturn = nsContract.functions[fn].Call(ctx, transactionSchema, &cc.metadata.Components, serializer, params...) + successReturn, successIFace, errorReturn = contractFn.Call(ctx, transactionSchema, &cc.metadata.Components, serializer, params...) } if errorReturn != nil { @@ -250,6 +227,18 @@ func (cc *ContractChaincode) Invoke(stub shim.ChaincodeStubInterface) *peer.Resp return shim.Success([]byte(successReturn)) } +func (cc *ContractChaincode) getNamespaceFunctionAndParams(stub shim.ChaincodeStubInterface) (string, string, []string) { + nsFn, params := stub.GetFunctionAndParameters() + + nsIndex := strings.LastIndex(nsFn, ":") + + if nsIndex == -1 { + return cc.DefaultContract, nsFn, params + } + + return nsFn[:nsIndex], nsFn[nsIndex+1:], params +} + func (cc *ContractChaincode) addContract(contract ContractInterface, excludeFuncs []string) error { ns := contract.GetName() @@ -507,3 +496,18 @@ func getBoolEnv(key string, defaultVal bool) bool { } return value } + +func toFirstRuneUpperCase(text string) string { + if len(text) == 0 { + return text + } + + runes := []rune(text) + + if unicode.IsUpper(runes[0]) { + return text + } + + runes[0] = unicode.ToUpper(runes[0]) + return string(runes) +} diff --git a/contractapi/contract_chaincode_test.go b/contractapi/contract_chaincode_test.go index 1315087..50ab82f 100644 --- a/contractapi/contract_chaincode_test.go +++ b/contractapi/contract_chaincode_test.go @@ -11,6 +11,7 @@ import ( "strings" "testing" + "github.com/google/go-cmp/cmp" "github.com/hyperledger/fabric-chaincode-go/v2/shim" "github.com/hyperledger/fabric-contract-api-go/v2/internal" "github.com/hyperledger/fabric-contract-api-go/v2/internal/utils" @@ -18,7 +19,9 @@ import ( "github.com/hyperledger/fabric-contract-api-go/v2/serializer" "github.com/hyperledger/fabric-protos-go-apiv2/peer" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" ) // ================================ @@ -37,8 +40,36 @@ const ( // AssertProtoEqual ensures an expected protobuf message matches an actual message func AssertProtoEqual(t *testing.T, expected proto.Message, actual proto.Message) { - t.Helper() - require.True(t, proto.Equal(expected, actual), "Expected %v, got %v", expected, actual) + if diff := cmp.Diff(expected, actual, protocmp.Transform()); diff != "" { + require.FailNow(t, fmt.Sprintf( + "Not equal:\nexpected: %s\nactual : %s\n\nDiff:\n- Expected\n+ Actual\n\n%s", + formatProto(expected), + formatProto(actual), + diff, + )) + } +} + +func formatProto(message proto.Message) string { + if message == nil { + return fmt.Sprintf("%T", message) + } + + marshal := prototext.MarshalOptions{ + Multiline: true, + Indent: "\t", + AllowPartial: true, + } + formatted := strings.TrimSpace(marshal.Format(message)) + return fmt.Sprintf("%s{\n%s\n}", protoMessageType(message), indent(formatted)) +} + +func protoMessageType(message proto.Message) string { + return string(message.ProtoReflect().Descriptor().Name()) +} + +func indent(text string) string { + return "\t" + strings.ReplaceAll(text, "\n", "\n\t") } type simpleStruct struct { @@ -104,6 +135,14 @@ func (gc *goodContract) ReturnsError() error { func (gc *goodContract) ReturnsNothing() {} +func (gc *goodContract) ReturnsBytes() []byte { + return []byte("Some bytes") +} + +func (gc *goodContract) AcceptsBytes(arg []byte) []byte { + return arg +} + func (gc *goodContract) CheckContextStub(ctx *TransactionContext) (string, error) { if ctx.GetStub().GetTxID() != standardTxID { return "", fmt.Errorf("You used a non standard txID [%s]", ctx.GetStub().GetTxID()) @@ -267,6 +306,12 @@ func testCallingContractFunctions(t *testing.T, callType CallType) { // should return success when function returns no error callContractFunctionAndCheckSuccess(t, cc, []string{"goodContract:ReturnsString"}, callType, gc.ReturnsString()) + // should return success when function returns slice of byte + callContractFunctionAndCheckSuccess(t, cc, []string{"goodContract:ReturnsBytes"}, callType, string(gc.ReturnsBytes())) + + // should return success when function accepts slice of byte + callContractFunctionAndCheckSuccess(t, cc, []string{"goodContract:AcceptsBytes", "bytes message"}, callType, string("bytes message")) + // Should return error when function returns error callContractFunctionAndCheckError(t, cc, []string{"goodContract:ReturnsError"}, callType, gc.ReturnsError().Error()) diff --git a/go.mod b/go.mod index 0ecc9ea..7d6b06d 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,12 @@ go 1.21.0 require ( github.com/cucumber/godog v0.15.0 github.com/go-openapi/spec v0.21.0 + github.com/google/go-cmp v0.6.0 github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0 github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 github.com/stretchr/testify v1.10.0 github.com/xeipuuv/gojsonschema v1.2.0 - google.golang.org/protobuf v1.35.2 + google.golang.org/protobuf v1.36.1 ) require ( diff --git a/go.sum b/go.sum index bdd57d7..9f5edf7 100644 --- a/go.sum +++ b/go.sum @@ -88,8 +88,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/integrationtest/chaincode/advancedtypes/go.mod b/integrationtest/chaincode/advancedtypes/go.mod index 58a37ea..f10d084 100644 --- a/integrationtest/chaincode/advancedtypes/go.mod +++ b/integrationtest/chaincode/advancedtypes/go.mod @@ -21,7 +21,7 @@ require ( golang.org/x/text v0.17.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/grpc v1.67.0 // indirect - google.golang.org/protobuf v1.35.2 // indirect + google.golang.org/protobuf v1.36.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/integrationtest/chaincode/advancedtypes/go.sum b/integrationtest/chaincode/advancedtypes/go.sum index f32aaff..77ab6d1 100644 --- a/integrationtest/chaincode/advancedtypes/go.sum +++ b/integrationtest/chaincode/advancedtypes/go.sum @@ -50,8 +50,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/integrationtest/chaincode/private/go.mod b/integrationtest/chaincode/private/go.mod index dcecab3..db73fa6 100644 --- a/integrationtest/chaincode/private/go.mod +++ b/integrationtest/chaincode/private/go.mod @@ -21,7 +21,7 @@ require ( golang.org/x/text v0.17.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/grpc v1.67.0 // indirect - google.golang.org/protobuf v1.35.2 // indirect + google.golang.org/protobuf v1.36.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/integrationtest/chaincode/private/go.sum b/integrationtest/chaincode/private/go.sum index f32aaff..77ab6d1 100644 --- a/integrationtest/chaincode/private/go.sum +++ b/integrationtest/chaincode/private/go.sum @@ -50,8 +50,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/integrationtest/chaincode/simple/go.mod b/integrationtest/chaincode/simple/go.mod index 6f2bbb7..1455df5 100644 --- a/integrationtest/chaincode/simple/go.mod +++ b/integrationtest/chaincode/simple/go.mod @@ -21,7 +21,7 @@ require ( golang.org/x/text v0.17.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/grpc v1.67.0 // indirect - google.golang.org/protobuf v1.35.2 // indirect + google.golang.org/protobuf v1.36.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/integrationtest/chaincode/simple/go.sum b/integrationtest/chaincode/simple/go.sum index f32aaff..77ab6d1 100644 --- a/integrationtest/chaincode/simple/go.sum +++ b/integrationtest/chaincode/simple/go.sum @@ -50,8 +50,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/integrationtest/chaincode/transactionhooks/go.mod b/integrationtest/chaincode/transactionhooks/go.mod index ac9d5eb..b4d4cc9 100644 --- a/integrationtest/chaincode/transactionhooks/go.mod +++ b/integrationtest/chaincode/transactionhooks/go.mod @@ -21,7 +21,7 @@ require ( golang.org/x/text v0.17.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/grpc v1.67.0 // indirect - google.golang.org/protobuf v1.35.2 // indirect + google.golang.org/protobuf v1.36.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/integrationtest/chaincode/transactionhooks/go.sum b/integrationtest/chaincode/transactionhooks/go.sum index f32aaff..77ab6d1 100644 --- a/integrationtest/chaincode/transactionhooks/go.sum +++ b/integrationtest/chaincode/transactionhooks/go.sum @@ -50,8 +50,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/internal/contract_function.go b/internal/contract_function.go index fe83d15..90b3b24 100644 --- a/internal/contract_function.go +++ b/internal/contract_function.go @@ -140,7 +140,10 @@ func (cf *ContractFunction) formatArgs(ctx reflect.Value, supplementaryMetadata } c := make(chan formatArgResult) - go cf.formatArg(params[i], fieldType, paramMetadata, components, serializer, c) + go func(i int) { + defer close(c) + c <- cf.formatArg(params[i], fieldType, paramMetadata, components, serializer) + }(i) channels = append(channels, c) } @@ -158,23 +161,20 @@ func (cf *ContractFunction) formatArgs(ctx reflect.Value, supplementaryMetadata return values, nil } -func (cf *ContractFunction) formatArg(param string, fieldType reflect.Type, parameterMetadata *metadata.ParameterMetadata, components *metadata.ComponentMetadata, serializer serializer.TransactionSerializer, c chan formatArgResult) { - defer close(c) - +func (cf *ContractFunction) formatArg(param string, fieldType reflect.Type, parameterMetadata *metadata.ParameterMetadata, components *metadata.ComponentMetadata, serializer serializer.TransactionSerializer) formatArgResult { converted, err := serializer.FromString(param, fieldType, parameterMetadata, components) - paramName := "" + var paramName string if parameterMetadata != nil { paramName = " " + parameterMetadata.Name } - res := new(formatArgResult) - res.paramName = paramName - res.converted = converted - res.err = err - - c <- *res + return formatArgResult{ + paramName: paramName, + converted: converted, + err: err, + } } func (cf *ContractFunction) handleResponse(response []reflect.Value, returnsMetadata *metadata.ReturnMetadata, components *metadata.ComponentMetadata, serializer serializer.TransactionSerializer) (string, interface{}, error) { diff --git a/internal/functionaltests/contracts/simplecontract/simplecontract.go b/internal/functionaltests/contracts/simplecontract/simplecontract.go index f702241..3a2ff14 100644 --- a/internal/functionaltests/contracts/simplecontract/simplecontract.go +++ b/internal/functionaltests/contracts/simplecontract/simplecontract.go @@ -37,7 +37,7 @@ func (sc *SimpleContract) Create(ctx contractapi.TransactionContextInterface, ke } // Update - Updates a key with given ID in the world state -func (sc *SimpleContract) Update(ctx contractapi.TransactionContextInterface, key string, value string) error { +func (sc *SimpleContract) Update(ctx contractapi.TransactionContextInterface, key string, value []byte) error { existing, err := ctx.GetStub().GetState(key) if err != nil { @@ -48,7 +48,7 @@ func (sc *SimpleContract) Update(ctx contractapi.TransactionContextInterface, ke return fmt.Errorf("cannot update key. Key with id %s does not exist", key) } - err = ctx.GetStub().PutState(key, []byte(value)) + err = ctx.GetStub().PutState(key, value) if err != nil { return errors.New("unable to interact with world state") @@ -58,16 +58,16 @@ func (sc *SimpleContract) Update(ctx contractapi.TransactionContextInterface, ke } // Read - Returns value of a key with given ID from world state as string -func (sc *SimpleContract) Read(ctx contractapi.TransactionContextInterface, key string) (string, error) { +func (sc *SimpleContract) Read(ctx contractapi.TransactionContextInterface, key string) ([]byte, error) { existing, err := ctx.GetStub().GetState(key) if err != nil { - return "", errors.New("unable to interact with world state") + return nil, errors.New("unable to interact with world state") } if existing == nil { - return "", fmt.Errorf("cannot read key. Key with id %s does not exist", key) + return nil, fmt.Errorf("cannot read key. Key with id %s does not exist", key) } - return string(existing), nil + return existing, nil } diff --git a/internal/types/types.go b/internal/types/types.go index df90e5d..fc52199 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -355,6 +355,10 @@ var BasicTypes = map[reflect.Kind]basicType{ reflect.Interface: new(interfaceType), } +func IsBytes(t reflect.Type) bool { + return (t.Kind() == reflect.Array || t.Kind() == reflect.Slice) && t.Elem().Kind() == reflect.Uint8 +} + // ErrorType reflect type for errors var ErrorType = reflect.TypeOf((*error)(nil)).Elem() diff --git a/metadata/schema.go b/metadata/schema.go index 63e147b..cc8a8d3 100644 --- a/metadata/schema.go +++ b/metadata/schema.go @@ -25,32 +25,35 @@ func GetSchema(field reflect.Type, components *ComponentMetadata) (*spec.Schema, } func getSchema(field reflect.Type, components *ComponentMetadata, nested bool) (*spec.Schema, error) { - var schema *spec.Schema - var err error + if types.IsBytes(field) { + return spec.StrFmtProperty("byte"), nil + } - if bt, ok := types.BasicTypes[field.Kind()]; !ok { - if field == types.TimeType { - schema = spec.DateTimeProperty() - } else if field.Kind() == reflect.Array { - schema, err = buildArraySchema(reflect.New(field).Elem(), components, nested) - } else if field.Kind() == reflect.Slice { - schema, err = buildSliceSchema(reflect.MakeSlice(field, 1, 1), components, nested) - } else if field.Kind() == reflect.Map { - schema, err = buildMapSchema(reflect.MakeMap(field), components, nested) - } else if field.Kind() == reflect.Struct || (field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct) { - schema, err = buildStructSchema(field, components, nested) - } else { - return nil, fmt.Errorf("%s was not a valid type", field.String()) - } - } else { + if bt, ok := types.BasicTypes[field.Kind()]; ok { return bt.GetSchema(), nil } - if err != nil { - return nil, err + if field == types.TimeType { + return spec.DateTimeProperty(), nil + } + + if field.Kind() == reflect.Array { + return buildArraySchema(reflect.New(field).Elem(), components, nested) + } + + if field.Kind() == reflect.Slice { + return buildSliceSchema(reflect.MakeSlice(field, 1, 1), components, nested) + } + + if field.Kind() == reflect.Map { + return buildMapSchema(reflect.MakeMap(field), components, nested) + } + + if field.Kind() == reflect.Struct || (field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct) { + return buildStructSchema(field, components, nested) } - return schema, nil + return nil, fmt.Errorf("%s was not a valid type", field.String()) } func buildArraySchema(array reflect.Value, components *ComponentMetadata, nested bool) (*spec.Schema, error) { diff --git a/metadata/schema_test.go b/metadata/schema_test.go index 094bb0c..b8148e3 100644 --- a/metadata/schema_test.go +++ b/metadata/schema_test.go @@ -325,7 +325,7 @@ func TestGetSchema(t *testing.T) { int32ArraySchema := spec.ArrayProperty(types.BasicTypes[reflect.Int32].GetSchema()) int64ArraySchema := spec.ArrayProperty(types.BasicTypes[reflect.Int64].GetSchema()) uintArraySchema := spec.ArrayProperty(types.BasicTypes[reflect.Uint].GetSchema()) - uint8ArraySchema := spec.ArrayProperty(types.BasicTypes[reflect.Uint8].GetSchema()) + uint8ArraySchema := spec.StrFmtProperty("byte") uint16ArraySchema := spec.ArrayProperty(types.BasicTypes[reflect.Uint16].GetSchema()) uint32ArraySchema := spec.ArrayProperty(types.BasicTypes[reflect.Uint32].GetSchema()) uint64ArraySchema := spec.ArrayProperty(types.BasicTypes[reflect.Uint64].GetSchema()) diff --git a/serializer/json_transaction_serializer.go b/serializer/json_transaction_serializer.go index 2fe9a62..b46c2bd 100644 --- a/serializer/json_transaction_serializer.go +++ b/serializer/json_transaction_serializer.go @@ -63,6 +63,8 @@ func (js *JSONSerializer) ToString(result reflect.Value, resultType reflect.Type if !isNillableType(result.Kind()) || !result.IsNil() { if resultType == types.TimeType { str = result.Interface().(time.Time).Format(time.RFC3339) + } else if types.IsBytes(resultType) { + str = fmt.Sprintf("%s", result.Interface()) } else if isMarshallingType(resultType) || resultType.Kind() == reflect.Interface && isMarshallingType(result.Type()) { bytes, _ := json.Marshal(result.Interface()) str = string(bytes) @@ -102,6 +104,8 @@ func convertArg(fieldType reflect.Type, paramValue string) (reflect.Value, error var t time.Time t, err = time.Parse(time.RFC3339, paramValue) converted = reflect.ValueOf(t) + } else if types.IsBytes(fieldType) { + converted = reflect.ValueOf([]byte(paramValue)) } else if fieldType.Kind() == reflect.Array || fieldType.Kind() == reflect.Slice || fieldType.Kind() == reflect.Map || fieldType.Kind() == reflect.Struct || (fieldType.Kind() == reflect.Ptr && fieldType.Elem().Kind() == reflect.Struct) { converted, err = createArraySliceMapOrStruct(paramValue, fieldType) } else { @@ -147,5 +151,6 @@ func isNillableType(kind reflect.Kind) bool { } func isMarshallingType(typ reflect.Type) bool { - return typ.Kind() == reflect.Array || typ.Kind() == reflect.Slice || typ.Kind() == reflect.Map || typ.Kind() == reflect.Struct || (typ.Kind() == reflect.Ptr && isMarshallingType(typ.Elem())) + return !types.IsBytes(typ) && + (typ.Kind() == reflect.Array || typ.Kind() == reflect.Slice || typ.Kind() == reflect.Map || typ.Kind() == reflect.Struct || (typ.Kind() == reflect.Ptr && isMarshallingType(typ.Elem()))) }