diff --git a/.changelog/3800.feature.2.md b/.changelog/3800.feature.2.md new file mode 100644 index 00000000000..47e21c4c891 --- /dev/null +++ b/.changelog/3800.feature.2.md @@ -0,0 +1,4 @@ +go/governance: Implement `PrettyPrint` interface for various types + +Implement `PrettyPrinter` for `ProposalContent`, `UpgradeProposal`, +`CancelUpgradeProposal` and `ProposalVote` types. diff --git a/go/governance/api/api.go b/go/governance/api/api.go index 76078efc58c..793992124a0 100644 --- a/go/governance/api/api.go +++ b/go/governance/api/api.go @@ -4,10 +4,12 @@ package api import ( "context" "fmt" + "io" beacon "github.com/oasisprotocol/oasis-core/go/beacon/api" "github.com/oasisprotocol/oasis-core/go/common/crypto/hash" "github.com/oasisprotocol/oasis-core/go/common/errors" + "github.com/oasisprotocol/oasis-core/go/common/prettyprint" "github.com/oasisprotocol/oasis-core/go/common/pubsub" "github.com/oasisprotocol/oasis-core/go/common/quantity" "github.com/oasisprotocol/oasis-core/go/consensus/api/transaction" @@ -18,6 +20,10 @@ import ( // ModuleName is a unique module name for the governance backend. const ModuleName = "governance" +// ProposalContentInvalidText is the textual representation of an invalid +// ProposalContent. +const ProposalContentInvalidText = "(invalid)" + var ( // ErrInvalidArgument is the error returned on malformed argument(s). ErrInvalidArgument = errors.New(ModuleName, 1, "governance: invalid argument") @@ -44,6 +50,11 @@ var ( MethodSubmitProposal, MethodCastVote, } + + _ prettyprint.PrettyPrinter = (*ProposalContent)(nil) + _ prettyprint.PrettyPrinter = (*UpgradeProposal)(nil) + _ prettyprint.PrettyPrinter = (*CancelUpgradeProposal)(nil) + _ prettyprint.PrettyPrinter = (*ProposalVote)(nil) ) // ProposalContent is a consensus layer governance proposal content. @@ -82,17 +93,62 @@ func (p *ProposalContent) Equals(other *ProposalContent) bool { } } +// PrettyPrint writes a pretty-printed representation of ProposalContent to the +// given writer. +func (p ProposalContent) PrettyPrint(ctx context.Context, prefix string, w io.Writer) { + switch { + case p.Upgrade != nil && p.CancelUpgrade == nil: + fmt.Fprintf(w, "%sUpgrade:\n", prefix) + p.Upgrade.PrettyPrint(ctx, prefix+" ", w) + case p.CancelUpgrade != nil && p.Upgrade == nil: + fmt.Fprintf(w, "%sCancel Upgrade:\n", prefix) + p.CancelUpgrade.PrettyPrint(ctx, prefix+" ", w) + default: + fmt.Fprintf(w, "%s%s\n", prefix, ProposalContentInvalidText) + } +} + +// PrettyType returns a representation of ProposalContent that can be used for +// pretty printing. +func (p ProposalContent) PrettyType() (interface{}, error) { + return p, nil +} + // UpgradeProposal is an upgrade proposal. type UpgradeProposal struct { upgrade.Descriptor } +// PrettyPrint writes a pretty-printed representation of UpgradeProposal to the +// given writer. +func (u UpgradeProposal) PrettyPrint(ctx context.Context, prefix string, w io.Writer) { + u.Descriptor.PrettyPrint(ctx, prefix, w) +} + +// PrettyType returns a representation of UpgradeProposal that can be used for +// pretty printing. +func (u UpgradeProposal) PrettyType() (interface{}, error) { + return u, nil +} + // CancelUpgradeProposal is an upgrade cancellation proposal. type CancelUpgradeProposal struct { // ProposalID is the identifier of the pending upgrade proposal. ProposalID uint64 `json:"proposal_id"` } +// PrettyPrint writes a pretty-printed representation of CancelUpgradeProposal +// to the given writer. +func (cu CancelUpgradeProposal) PrettyPrint(ctx context.Context, prefix string, w io.Writer) { + fmt.Fprintf(w, "%sProposal ID: %d\n", prefix, cu.ProposalID) +} + +// PrettyType returns a representation of CancelUpgradeProposal that can be used +// for pretty printing. +func (cu CancelUpgradeProposal) PrettyType() (interface{}, error) { + return cu, nil +} + // ProposalVote is a vote for a proposal. type ProposalVote struct { // ID is the unique identifier of a proposal. @@ -101,6 +157,19 @@ type ProposalVote struct { Vote Vote `json:"vote"` } +// PrettyPrint writes a pretty-printed representation of ProposalVote to the +// given writer. +func (pv ProposalVote) PrettyPrint(ctx context.Context, prefix string, w io.Writer) { + fmt.Fprintf(w, "%sProposal ID: %d\n", prefix, pv.ID) + fmt.Fprintf(w, "%sVote: %s\n", prefix, pv.Vote) +} + +// PrettyType returns a representation of ProposalVote that can be used for +// pretty printing. +func (pv ProposalVote) PrettyType() (interface{}, error) { + return pv, nil +} + // Backend is a governance implementation. type Backend interface { // ActiveProposals returns a list of all proposals that have not yet closed. diff --git a/go/governance/api/api_test.go b/go/governance/api/api_test.go index 638564e58be..5e86aad87fc 100644 --- a/go/governance/api/api_test.go +++ b/go/governance/api/api_test.go @@ -1,6 +1,8 @@ package api import ( + "bytes" + "context" "testing" "github.com/stretchr/testify/require" @@ -139,3 +141,44 @@ func TestProposalContentEquals(t *testing.T) { require.Equal(t, tc.equals, tc.p1.Equals(tc.p2), tc.msg) } } + +func TestProposalContentPrettyPrint(t *testing.T) { + for _, tc := range []struct { + expRegex string + p *ProposalContent + }{ + { + expRegex: "^Upgrade:", + p: &ProposalContent{ + Upgrade: &UpgradeProposal{ + Descriptor: api.Descriptor{Handler: "test"}, + }, + }, + }, + { + expRegex: "^Cancel Upgrade:", + p: &ProposalContent{ + CancelUpgrade: &CancelUpgradeProposal{ProposalID: 42}, + }, + }, + { + expRegex: ProposalContentInvalidText, + p: &ProposalContent{}, + }, + { + expRegex: ProposalContentInvalidText, + p: &ProposalContent{ + Upgrade: &UpgradeProposal{ + Descriptor: api.Descriptor{Handler: "test"}, + }, + CancelUpgrade: &CancelUpgradeProposal{ProposalID: 42}, + }, + }, + } { + var actualPrettyPrint bytes.Buffer + tc.p.PrettyPrint(context.Background(), "", &actualPrettyPrint) + require.Regexp(t, tc.expRegex, actualPrettyPrint.String(), + "pretty printing proposal content didn't return the expected result", + ) + } +}