Skip to content

Commit

Permalink
[1760]: Refactor the MetadataAddress.Format method to handle more cas…
Browse files Browse the repository at this point in the history
…ses in better ways. Specifically, %v will now output the bech32 string so that the other .String() methods have the bech32 there instead of the hex bytes.
  • Loading branch information
SpicyLemon committed May 7, 2024
1 parent 7d40bf5 commit 06aaf93
Show file tree
Hide file tree
Showing 9 changed files with 688 additions and 361 deletions.
7 changes: 7 additions & 0 deletions proto/provenance/metadata/v1/scope.proto
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ Indexes (special kvstore records for efficient cross reference/queries)

// Scope defines a root reference for a collection of records owned by one or more parties.
message Scope {
option (gogoproto.goproto_stringer) = false;
option (gogoproto.stringer) = true;

// Unique ID for this scope. Implements sdk.Address interface for use where addresses are required in Cosmos
bytes scope_id = 1 [(gogoproto.nullable) = false, (gogoproto.customtype) = "MetadataAddress"];
// the scope specification that contains the specifications for data elements allowed within this scope
Expand All @@ -91,6 +94,9 @@ message Scope {
//
// NOTE: When there are no more Records within a Scope that reference a Session, the Session is removed.
message Session {
option (gogoproto.goproto_stringer) = false;
option (gogoproto.stringer) = true;

bytes session_id = 1 [(gogoproto.nullable) = false, (gogoproto.customtype) = "MetadataAddress"];
// unique id of the contract specification that was used to create this session.
bytes specification_id = 2 [(gogoproto.nullable) = false, (gogoproto.customtype) = "MetadataAddress"];
Expand All @@ -107,6 +113,7 @@ message Session {
// A record (of fact) is attached to a session or each consideration output from a contract
message Record {
option (gogoproto.goproto_stringer) = false;
option (gogoproto.stringer) = true;

// name/identifier for this record. Value must be unique within the scope. Also known as a Fact name
string name = 1;
Expand Down
12 changes: 12 additions & 0 deletions proto/provenance/metadata/v1/specification.proto
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ option java_multiple_files = true;

// ScopeSpecification defines the required parties, resources, conditions, and consideration outputs for a contract
message ScopeSpecification {
option (gogoproto.goproto_stringer) = false;
option (gogoproto.stringer) = true;

// unique identifier for this specification on chain
bytes specification_id = 1 [(gogoproto.nullable) = false, (gogoproto.customtype) = "MetadataAddress"];
// General information about this scope specification.
Expand All @@ -49,6 +52,9 @@ message ScopeSpecification {

// ContractSpecification defines the required parties, resources, conditions, and consideration outputs for a contract
message ContractSpecification {
option (gogoproto.goproto_stringer) = false;
option (gogoproto.stringer) = true;

// unique identifier for this specification on chain
bytes specification_id = 1 [(gogoproto.nullable) = false, (gogoproto.customtype) = "MetadataAddress"];
// Description information for this contract specification
Expand All @@ -71,6 +77,9 @@ message ContractSpecification {

// RecordSpecification defines the specification for a Record including allowed/required inputs/outputs
message RecordSpecification {
option (gogoproto.goproto_stringer) = false;
option (gogoproto.stringer) = true;

// unique identifier for this specification on chain
bytes specification_id = 1 [(gogoproto.nullable) = false, (gogoproto.customtype) = "MetadataAddress"];
// Name of Record that will be created when this specification is used
Expand All @@ -88,6 +97,9 @@ message RecordSpecification {
// InputSpecification defines a name, type_name, and source reference (either on or off chain) to define an input
// parameter
message InputSpecification {
option (gogoproto.goproto_stringer) = false;
option (gogoproto.stringer) = true;

// name for this input
string name = 1;
// a type_name (typically a proto name or class_name)
Expand Down
27 changes: 21 additions & 6 deletions x/metadata/types/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -626,15 +626,30 @@ func (ma MetadataAddress) ContractSpecRecordSpecIteratorPrefix() ([]byte, error)
return append(RecordSpecificationKeyPrefix, ma[1:17]...), nil
}

// Format implements fmt.Format interface
// Format implements fmt.Formatter interface for a MetadataAddress.
func (ma MetadataAddress) Format(s fmt.State, verb rune) {
var out string
switch verb {
case 's':
s.Write([]byte(ma.String()))
case 'p':
s.Write([]byte(fmt.Sprintf("%p", ma)))
case 's', 'q':
out = fmt.Sprintf(fmt.FormatString(s, verb), ma.String())
case 'v':
if s.Flag('#') {
out = fmt.Sprintf(fmt.FormatString(s, verb), []byte(ma))
out = "MetadataAddress" + strings.TrimPrefix(out, "[]byte")
} else {
// The auto-generated gogoproto.stringer methods use "%v" for the MetadataAddress fields.
// So here, we return the bech32 for "%v" so that those other strings look right.
out = fmt.Sprintf(fmt.FormatString(s, verb), ma.String())
}
case 'p', 'T':
out = fmt.Sprintf(fmt.FormatString(s, verb), ma)
default:
s.Write([]byte(fmt.Sprintf("%X", []byte(ma))))
out = fmt.Sprintf(fmt.FormatString(s, verb), []byte(ma))
}

_, err := s.Write([]byte(out))
if err != nil {
panic(err)
}
}

Expand Down
180 changes: 115 additions & 65 deletions x/metadata/types/address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1717,80 +1717,130 @@ func (s *AddressTestSuite) TestContractSpecAddressConverters() {
}
}

type mockState struct {
err string
}

var _ fmt.State = (*mockState)(nil)

func (s mockState) Write(b []byte) (n int, err error) {
return 0, errors.New(s.err)
}

func (s mockState) Width() (int, bool) {
return 0, false
}

func (s mockState) Precision() (int, bool) {
return 0, false
}

func (s mockState) Flag(c int) bool {
return false
}

func (s *AddressTestSuite) TestFormat() {
scopeID := ScopeMetadataAddress(s.scopeUUID)
emptyID := MetadataAddress{}
someUUIDStr := "97263339-CFAA-41D9-809E-82CD78C84F02"
someUUID, err := uuid.Parse(someUUIDStr)
s.Require().NoError(err, "uuid.Parse(%q)", someUUIDStr)

type namedMetadataAddress struct {
name string
id MetadataAddress
}

scopeID := namedMetadataAddress{name: "scope", id: ScopeMetadataAddress(someUUID)}
contractSpecID := namedMetadataAddress{name: "contract spec", id: ContractSpecMetadataAddress(someUUID)}
emptyID := namedMetadataAddress{name: "empty", id: MetadataAddress{}}
nilID := namedMetadataAddress{name: "nil", id: nil}
invalidID := namedMetadataAddress{name: "invalid", id: MetadataAddress("do not create MetadataAddresses this way")}

tests := []struct {
name string
id MetadataAddress
format string
expected string
id namedMetadataAddress
fmt string
exp string
}{
{
"format using %s",
scopeID,
"%s",
s.scopeBech32,
},
{
// %p is for the address (in memory). Can't hard-code it.
"format using %p",
scopeID,
"%p",
fmt.Sprintf("%p", scopeID),
},
{
"format using %d - should use default %X",
scopeID,
"%d",
"008D80B25AC0894446956E5D08CFE3E1A5",
},
{
"format using %v - should use default %X",
scopeID,
"%v",
"008D80B25AC0894446956E5D08CFE3E1A5",
},
{
"format empty using %s",
emptyID,
"%s",
"",
},
{
// %p is for the address (in memory). Can't hard-code it.
"format using %p",
emptyID,
"%p",
fmt.Sprintf("%p", emptyID),
},
{
"format empty using %d - should use default %X",
emptyID,
"%d",
"",
},
{
"format empty using %v - should use default %X",
emptyID,
"%v",
"",
},
{
"format %s is equal to .String() which fails on bad addresses",
MetadataAddress("do not create MetadataAddresses this way"),
"%s",
"%!s(PANIC=Format method: invalid metadata address type: 100)",
},
{id: scopeID, fmt: "%s", exp: "scope1qztjvveee74yrkvqn6pv67xgfupqyumx55"},
{id: scopeID, fmt: "%20s", exp: "scope1qztjvveee74yrkvqn6pv67xgfupqyumx55"},
{id: scopeID, fmt: "%-20s", exp: "scope1qztjvveee74yrkvqn6pv67xgfupqyumx55"},
{id: scopeID, fmt: "%50s", exp: " scope1qztjvveee74yrkvqn6pv67xgfupqyumx55"},
{id: scopeID, fmt: "%-50s", exp: "scope1qztjvveee74yrkvqn6pv67xgfupqyumx55 "},
{id: scopeID, fmt: "%q", exp: `"scope1qztjvveee74yrkvqn6pv67xgfupqyumx55"`},
{id: scopeID, fmt: "%20q", exp: `"scope1qztjvveee74yrkvqn6pv67xgfupqyumx55"`},
{id: scopeID, fmt: "%-20q", exp: `"scope1qztjvveee74yrkvqn6pv67xgfupqyumx55"`},
{id: scopeID, fmt: "%50q", exp: ` "scope1qztjvveee74yrkvqn6pv67xgfupqyumx55"`},
{id: scopeID, fmt: "%-50q", exp: `"scope1qztjvveee74yrkvqn6pv67xgfupqyumx55" `},
{id: scopeID, fmt: "%v", exp: "scope1qztjvveee74yrkvqn6pv67xgfupqyumx55"},
{id: scopeID, fmt: "%#v", exp: "MetadataAddress{0x0, 0x97, 0x26, 0x33, 0x39, 0xcf, 0xaa, 0x41, 0xd9, 0x80, 0x9e, 0x82, 0xcd, 0x78, 0xc8, 0x4f, 0x2}"},
{id: scopeID, fmt: "%p", exp: fmt.Sprintf("%p", []byte(scopeID.id))}, // e.g. 0x14000d95818
{id: scopeID, fmt: "%#p", exp: fmt.Sprintf("%#p", []byte(scopeID.id))}, // e.g. 14000d95818
{id: scopeID, fmt: "%T", exp: "types.MetadataAddress"},
{id: scopeID, fmt: "%d", exp: "[0 151 38 51 57 207 170 65 217 128 158 130 205 120 200 79 2]"},
{id: scopeID, fmt: "%x", exp: "0097263339cfaa41d9809e82cd78c84f02"},
{id: scopeID, fmt: "%#x", exp: "0x0097263339cfaa41d9809e82cd78c84f02"},
{id: scopeID, fmt: "%X", exp: "0097263339CFAA41D9809E82CD78C84F02"},
{id: scopeID, fmt: "%#X", exp: "0X0097263339CFAA41D9809E82CD78C84F02"},
{id: contractSpecID, fmt: "%s", exp: "contractspec1qwtjvveee74yrkvqn6pv67xgfupqghravh"},
{id: contractSpecID, fmt: "%20s", exp: "contractspec1qwtjvveee74yrkvqn6pv67xgfupqghravh"},
{id: contractSpecID, fmt: "%-20s", exp: "contractspec1qwtjvveee74yrkvqn6pv67xgfupqghravh"},
{id: contractSpecID, fmt: "%50s", exp: " contractspec1qwtjvveee74yrkvqn6pv67xgfupqghravh"},
{id: contractSpecID, fmt: "%-50s", exp: "contractspec1qwtjvveee74yrkvqn6pv67xgfupqghravh "},
{id: contractSpecID, fmt: "%q", exp: `"contractspec1qwtjvveee74yrkvqn6pv67xgfupqghravh"`},
{id: contractSpecID, fmt: "%20q", exp: `"contractspec1qwtjvveee74yrkvqn6pv67xgfupqghravh"`},
{id: contractSpecID, fmt: "%-20q", exp: `"contractspec1qwtjvveee74yrkvqn6pv67xgfupqghravh"`},
{id: contractSpecID, fmt: "%50q", exp: ` "contractspec1qwtjvveee74yrkvqn6pv67xgfupqghravh"`},
{id: contractSpecID, fmt: "%-50q", exp: `"contractspec1qwtjvveee74yrkvqn6pv67xgfupqghravh" `},
{id: contractSpecID, fmt: "%v", exp: "contractspec1qwtjvveee74yrkvqn6pv67xgfupqghravh"},
{id: contractSpecID, fmt: "%#v", exp: "MetadataAddress{0x3, 0x97, 0x26, 0x33, 0x39, 0xcf, 0xaa, 0x41, 0xd9, 0x80, 0x9e, 0x82, 0xcd, 0x78, 0xc8, 0x4f, 0x2}"},
{id: contractSpecID, fmt: "%p", exp: fmt.Sprintf("%p", []byte(contractSpecID.id))}, // e.g. 0x14000d95818
{id: contractSpecID, fmt: "%#p", exp: fmt.Sprintf("%#p", []byte(contractSpecID.id))}, // e.g. 14000d95818
{id: contractSpecID, fmt: "%T", exp: "types.MetadataAddress"},
{id: contractSpecID, fmt: "%d", exp: "[3 151 38 51 57 207 170 65 217 128 158 130 205 120 200 79 2]"},
{id: contractSpecID, fmt: "%x", exp: "0397263339cfaa41d9809e82cd78c84f02"},
{id: contractSpecID, fmt: "%#x", exp: "0x0397263339cfaa41d9809e82cd78c84f02"},
{id: contractSpecID, fmt: "%X", exp: "0397263339CFAA41D9809E82CD78C84F02"},
{id: contractSpecID, fmt: "%#X", exp: "0X0397263339CFAA41D9809E82CD78C84F02"},
{id: emptyID, fmt: "%s", exp: ""},
{id: emptyID, fmt: "%q", exp: `""`},
{id: emptyID, fmt: "%v", exp: ""},
{id: emptyID, fmt: "%#v", exp: "MetadataAddress{}"},
{id: emptyID, fmt: "%T", exp: "types.MetadataAddress"},
{id: emptyID, fmt: "%x", exp: ""},
{id: nilID, fmt: "%s", exp: ""},
{id: nilID, fmt: "%q", exp: `""`},
{id: nilID, fmt: "%v", exp: ""},
{id: nilID, fmt: "%#v", exp: "MetadataAddress(nil)"},
{id: nilID, fmt: "%T", exp: "types.MetadataAddress"},
{id: nilID, fmt: "%x", exp: ""},
{id: invalidID, fmt: "%s", exp: "%!s(PANIC=Format method: invalid metadata address type: 100)"},
{id: invalidID, fmt: "%q", exp: "%!q(PANIC=Format method: invalid metadata address type: 100)"},
{id: invalidID, fmt: "%v", exp: "%!v(PANIC=Format method: invalid metadata address type: 100)"},
{id: invalidID, fmt: "%#v", exp: "MetadataAddress{0x64, 0x6f, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x77, 0x61, 0x79}"},
{id: invalidID, fmt: "%T", exp: "types.MetadataAddress"},
{id: invalidID, fmt: "%x", exp: "646f206e6f7420637265617465204d65746164617461416464726573736573207468697320776179"},
}

for _, test := range tests {
s.T().Run(test.name, func(t *testing.T) {
actual := fmt.Sprintf(test.format, test.id)
assert.Equal(t, test.expected, actual, test.name)
s.Run(test.id.name+" "+test.fmt, func() {
var actual string
testFunc := func() {
actual = fmt.Sprintf(test.fmt, test.id.id)
}
s.Require().NotPanics(testFunc, "Sprintf(%q, ...)", test.fmt)
s.Assert().Equal(test.exp, actual)
})
}

s.Run("write error", func() {
expPanic := "injected write error"
state := &mockState{err: expPanic}
verb := 's'
addr := ScopeMetadataAddress(s.scopeUUID)
testFunc := func() {
addr.Format(state, verb)
}
s.Require().PanicsWithError(expPanic, testFunc, "Format")
})
}

func (s *AddressTestSuite) TestGenerateExamples() {
Expand Down
12 changes: 0 additions & 12 deletions x/metadata/types/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package types
import (
"errors"
"fmt"
"strings"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -296,17 +295,6 @@ func (r Record) ValidateBasic() error {
return nil
}

// String implements stringer interface
func (r Record) String() string {
out := fmt.Sprintf("%s (%s) Results [", r.Name, r.SessionId)
for _, o := range r.Outputs {
out += fmt.Sprintf("%s - %s, ", o.Status, o.Hash)
}
out = strings.TrimRight(out, ", ")
out += fmt.Sprintf("] (%s/%s)", r.Process.Name, r.Process.Method)
return out
}

// GetRecordAddress returns the address for this record, or an empty MetadataAddress if it cannot be constructed.
func (r Record) GetRecordAddress() MetadataAddress {
addr, err := r.SessionId.AsRecordAddress(r.Name)
Expand Down
Loading

0 comments on commit 06aaf93

Please sign in to comment.