Skip to content

Commit

Permalink
Support []byte parameters and return type (#161)
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
bestbeforetoday authored Dec 23, 2024
1 parent 20772f8 commit f5d39cc
Show file tree
Hide file tree
Showing 18 changed files with 147 additions and 85 deletions.
62 changes: 33 additions & 29 deletions contractapi/contract_chaincode.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,40 +156,17 @@ 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))
}

if fn == "" {
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)
Expand All @@ -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)
Expand All @@ -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 {
Expand All @@ -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()

Expand Down Expand Up @@ -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)
}
49 changes: 47 additions & 2 deletions contractapi/contract_chaincode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@ 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"
"github.com/hyperledger/fabric-contract-api-go/v2/metadata"
"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"
)

// ================================
Expand All @@ -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 {
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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())

Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
2 changes: 1 addition & 1 deletion integrationtest/chaincode/advancedtypes/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down
4 changes: 2 additions & 2 deletions integrationtest/chaincode/advancedtypes/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
2 changes: 1 addition & 1 deletion integrationtest/chaincode/private/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down
4 changes: 2 additions & 2 deletions integrationtest/chaincode/private/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
2 changes: 1 addition & 1 deletion integrationtest/chaincode/simple/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down
4 changes: 2 additions & 2 deletions integrationtest/chaincode/simple/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
2 changes: 1 addition & 1 deletion integrationtest/chaincode/transactionhooks/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down
4 changes: 2 additions & 2 deletions integrationtest/chaincode/transactionhooks/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
22 changes: 11 additions & 11 deletions internal/contract_function.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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")
Expand All @@ -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
}
4 changes: 4 additions & 0 deletions internal/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
Loading

0 comments on commit f5d39cc

Please sign in to comment.