Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ require (
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
github.com/temporalio/cli/cliext v0.0.0-20260602200703-8bb57b77ad55
go.temporal.io/api v1.62.13
go.temporal.io/api v1.63.6-0.20260811224032-89570b10e9e2
go.temporal.io/cloud-sdk v0.16.0
go.temporal.io/sdk v1.44.1
go.temporal.io/sdk/contrib/envconfig v1.0.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfC
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.temporal.io/api v1.62.13 h1:xMa8Nt5oAMX+LvlCJA44wjTCc1H09i2rG9poB1/xvH4=
go.temporal.io/api v1.62.13/go.mod h1:0k75tRljEuELWGeXjEZZO7zYqBln4+1FrG6+IMOMy7Q=
go.temporal.io/api v1.63.6-0.20260811224032-89570b10e9e2 h1:VeNTxlOHETym01zX6kSXHIVw4nc2RrX/KVgepQ/dr/8=
go.temporal.io/api v1.63.6-0.20260811224032-89570b10e9e2/go.mod h1:SrlW2JMwVlDP4nRWSNznUFqnSHd+YeMDS1BkYo63HCQ=
go.temporal.io/cloud-sdk v0.16.0 h1:8EgFMyc3M1XV2+OIOOuKeyhUI8Kjy7YCCbmGYlRw1No=
go.temporal.io/cloud-sdk v0.16.0/go.mod h1:W2O9t9tvo3Q/LhGgYdj8JijWbN5C84os+cz/BadIHYI=
go.temporal.io/sdk v1.44.1 h1:Mt2OZLZpqkzDIdg9YyQzO0Rb/HqCDnnqHlIAGAJ5gqM=
Expand Down
2 changes: 1 addition & 1 deletion temporalcloudcli/commands.namespace.capacity.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func (c *CloudNamespaceCapacityGetCommand) run(cctx *CommandContext, _ []string)
if err != nil {
return err
}
return cctx.Printer.PrintResource(res.CapacityInfo, printer.PrintResourceOptions{})
return cctx.Printer.PrintResource(res.CapacityInfo, printer.PrintResourceOptions{EmitDefaultValues: true})
}

func (c *CloudNamespaceCapacityUpdateCommand) run(cctx *CommandContext, _ []string) error {
Expand Down
2 changes: 2 additions & 0 deletions temporalcloudcli/commands.namespace.capacity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ func TestCapacityGet(t *testing.T) {
JSONOutput: true,
ExpectedError: tt.expectedErr,
ExpectedOutputJson: tt.expectedJsonOutput,
// The get command prints with EmitDefaultValues, so zero-valued fields appear in the output.
ExpectedOutputJsonEmitDefaults: true,
})
})
}
Expand Down
23 changes: 13 additions & 10 deletions temporalcloudcli/commands.testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,16 @@ type (
}

TestCommandOptions struct {
Args []string
CloudClientExpectations func(cloudClient *cloudmock.MockCloudServiceClient)
AsyncPollerOptions TestAsyncPollerOptions
PromptOptions TestPromptOptions
EditorOptions TestEditorOptions
JSONOutput bool
ExpectedError string
ExpectedOutput string
ExpectedOutputJson any
Args []string
CloudClientExpectations func(cloudClient *cloudmock.MockCloudServiceClient)
AsyncPollerOptions TestAsyncPollerOptions
PromptOptions TestPromptOptions
EditorOptions TestEditorOptions
JSONOutput bool
ExpectedError string
ExpectedOutput string
ExpectedOutputJson any
ExpectedOutputJsonEmitDefaults bool
}
)

Expand Down Expand Up @@ -199,7 +200,9 @@ func TestCommand(t *testing.T, command CommandIfc, opts TestCommandOptions) {
var js []byte
var err error
if protoMessage, ok := opts.ExpectedOutputJson.(proto.Message); ok {
js, err = protojson.Marshal(protoMessage)
js, err = protojson.MarshalOptions{
EmitDefaultValues: opts.ExpectedOutputJsonEmitDefaults,
}.Marshal(protoMessage)
} else {
js, err = json.Marshal(opts.ExpectedOutputJson)
}
Expand Down
92 changes: 64 additions & 28 deletions temporalcloudcli/internal/printer/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import (

"github.com/fatih/color"
"github.com/kylelemons/godebug/diff"
"github.com/olekukonko/tablewriter/tw"
"github.com/olekukonko/tablewriter/pkg/twwidth"
"github.com/olekukonko/tablewriter/tw"
"go.temporal.io/api/common/v1"
"go.temporal.io/api/temporalproto"
"google.golang.org/protobuf/proto"
Expand Down Expand Up @@ -126,6 +126,9 @@ type StructuredOptions struct {
OverrideJSONPayloadShorthand *bool
// Indent this many additional times when printing non-JSON
NonJSONExtraIndent int
// EmitDefaultValues when set to true, fields holding their default value (e.g. 0, false, "") are printed instead of omitted.
// Unset pointer fields remain omitted.
EmitDefaultValues bool
}

type Align tw.Align
Expand Down Expand Up @@ -155,7 +158,7 @@ func (p *Printer) PrintStructured(v any, options StructuredOptions) error {

// Get data
cols := options.toPredefinedCols()
cols, rows, err := p.tableData(cols, v)
cols, rows, err := p.tableData(cols, v, options.EmitDefaultValues)
if err != nil {
return err
}
Expand Down Expand Up @@ -203,7 +206,7 @@ func (p *Printer) PrintStructuredTableIter(
if v == nil || err != nil {
return err
}
row, err := p.tableRowData(cols, v)
row, err := p.tableRowData(cols, v, options.EmitDefaultValues)
if err != nil {
return err
}
Expand Down Expand Up @@ -248,7 +251,7 @@ func (p *Printer) printJSON(v any, options StructuredOptions) error {
if options.OverrideJSONPayloadShorthand != nil {
shorthandPayloads = *options.OverrideJSONPayloadShorthand
}
if b, err := p.jsonVal(v, p.JSONIndent, shorthandPayloads); err != nil {
if b, err := p.jsonVal(v, p.JSONIndent, shorthandPayloads, options.EmitDefaultValues); err != nil {
return err
} else if _, err := p.Output.Write(b); err != nil {
return err
Expand All @@ -263,10 +266,10 @@ func (p *Printer) printJSON(v any, options StructuredOptions) error {
return nil
}

func (p *Printer) jsonVal(v any, indent string, shorthandPayloads bool) ([]byte, error) {
func (p *Printer) jsonVal(v any, indent string, shorthandPayloads, emitDefaultValues bool) ([]byte, error) {
// Use proto JSON if a proto message
if protoMessage, ok := v.(proto.Message); ok {
opts := temporalproto.CustomJSONMarshalOptions{Indent: indent}
opts := temporalproto.CustomJSONMarshalOptions{Indent: indent, EmitDefaultValues: emitDefaultValues}
if shorthandPayloads {
opts.Metadata = map[string]any{common.EnablePayloadShorthandMetadataKey: true}
}
Expand Down Expand Up @@ -443,7 +446,7 @@ func (p *Printer) applyConverters(v any) (string, bool) {
return "", false
}

func (p *Printer) textVal(v any) string {
func (p *Printer) textVal(v any, emitDefaultValues bool) string {
// Check converters first
if ifv, ok := p.applyConverters(v); ok {
return ifv
Expand All @@ -460,7 +463,7 @@ func (p *Printer) textVal(v any) string {
}
return p.FormatTime(ref.Interface().(time.Time))
} else if (ref.Kind() == reflect.Struct && ref.CanInterface()) || ref.Type().Implements(jsonMarshalerType) {
b, err := p.jsonVal(v, "", true)
b, err := p.jsonVal(v, "", true, emitDefaultValues)
if err != nil {
return fmt.Sprintf("<failed converting to string: %v>", err)
}
Expand All @@ -477,7 +480,7 @@ func (p *Printer) textVal(v any) string {
if i > 0 {
sb.WriteString(", ")
}
sb.WriteString(p.textVal(ref.Index(i).Interface()))
sb.WriteString(p.textVal(ref.Index(i).Interface(), emitDefaultValues))
}
sb.WriteString("]")
return sb.String()
Expand All @@ -489,7 +492,11 @@ func (p *Printer) textVal(v any) string {
return fmt.Sprintf("%v", v)
}

func (p *Printer) tableData(predefinedCols []*col, v any) (cols []*col, rows []map[string]colVal, err error) {
func (p *Printer) tableData(
predefinedCols []*col,
v any,
emitDefaultValues bool,
) (cols []*col, rows []map[string]colVal, err error) {
singleItemType := reflect.TypeOf(v)
if singleItemType.Kind() == reflect.Slice {
singleItemType = singleItemType.Elem()
Expand Down Expand Up @@ -520,15 +527,15 @@ func (p *Printer) tableData(predefinedCols []*col, v any) (cols []*col, rows []m
row := make(map[string]colVal, len(cols))
for _, col := range cols {
colVal := colVal{val: colValGetter(col, itemVal)}
colVal.text = p.textVal(colVal.val)
colVal.text = p.textVal(colVal.val, emitDefaultValues)
row[col.name] = colVal
}
rows[i] = row
}
return
}

func (p *Printer) tableRowData(cols []*col, v any) (map[string]colVal, error) {
func (p *Printer) tableRowData(cols []*col, v any, emitDefaultValues bool) (map[string]colVal, error) {
colValGetter, err := colValGetterForType(reflect.TypeOf(v))
if err != nil {
return nil, err
Expand All @@ -537,7 +544,7 @@ func (p *Printer) tableRowData(cols []*col, v any) (map[string]colVal, error) {
itemVal := reflect.ValueOf(v)
for _, col := range cols {
colVal := colVal{val: colValGetter(col, itemVal)}
colVal.text = p.textVal(colVal.val)
colVal.text = p.textVal(colVal.val, emitDefaultValues)
row[col.name] = colVal
}
return row, nil
Expand Down Expand Up @@ -668,11 +675,11 @@ func (p *Printer) PrintDiff(a, b any, options DiffOptions) error {
// Each value is marshaled individually so proto messages are handled correctly,
// then embedded as RawMessage to preserve field order in the outer object.
if p.JSON {
beforeJSON, err := p.jsonVal(a, "", p.JSONPayloadShorthand)
beforeJSON, err := p.jsonVal(a, "", p.JSONPayloadShorthand, false)
if err != nil {
return fmt.Errorf("unable to convert before value for diff: %w", err)
}
afterJSON, err := p.jsonVal(b, "", p.JSONPayloadShorthand)
afterJSON, err := p.jsonVal(b, "", p.JSONPayloadShorthand, false)
if err != nil {
return fmt.Errorf("unable to convert after value for diff: %w", err)
}
Expand All @@ -683,11 +690,11 @@ func (p *Printer) PrintDiff(a, b any, options DiffOptions) error {
}

var atext, btext []byte
atext, err := p.jsonVal(a, " ", true)
atext, err := p.jsonVal(a, " ", true, false)
if err != nil {
return fmt.Errorf("unable to convert a to text for diff: %w", err)
}
btext, err = p.jsonVal(b, " ", true)
btext, err = p.jsonVal(b, " ", true, false)
if err != nil {
return fmt.Errorf("unable to convert b to text for diff: %w", err)
}
Expand Down Expand Up @@ -721,11 +728,14 @@ type PrintResourceOptions struct {
Fields []string
// SpecFields is a list of fields to print from the "Spec" sub-object, if empty all fields are printed. This is ignored for JSON output.
SpecFields []string
// EmitDefaultValues prints fields holding their default value (e.g. 0, false, "") instead of omitting them.
// Unset pointer fields (proto messages and presence-sensing scalars) remain omitted in both output modes.
EmitDefaultValues bool
}

func (p *Printer) PrintResponseWithAsyncOperation(resource any, options PrintResourceOptions) error {
if p.JSON {
return p.PrintStructured(resource, StructuredOptions{})
return p.PrintStructured(resource, StructuredOptions{EmitDefaultValues: options.EmitDefaultValues})
}
resourceVal := reflect.ValueOf(resource)
if resourceVal.Kind() == reflect.Pointer {
Expand All @@ -736,11 +746,17 @@ func (p *Printer) PrintResponseWithAsyncOperation(resource any, options PrintRes
}

// print all top-level fields except "Spec"
cols, row := p.parseFields(resourceVal, options.Fields, []string{"AsyncOperation"}, 1)
cols, row := p.parseFields(resourceVal, options.Fields, []string{"AsyncOperation"}, 1, options.EmitDefaultValues)
p.printCard(cols, row)

// now print "Spec" fields if present
specCols, specRow := p.parseFields(resourceVal.FieldByName("AsyncOperation"), options.SpecFields, nil, 2)
specCols, specRow := p.parseFields(
resourceVal.FieldByName("AsyncOperation"),
options.SpecFields,
nil,
2,
options.EmitDefaultValues,
)
if len(specCols) > 0 {
p.writeStr(NonJSONIndent)
p.writeStr("AsyncOperation:\n")
Expand All @@ -752,7 +768,7 @@ func (p *Printer) PrintResponseWithAsyncOperation(resource any, options PrintRes
func (p *Printer) PrintResource(resource any, options PrintResourceOptions) error {
// For JSON we can just print the whole thing, ignoring the field options
if p.JSON {
return p.PrintStructured(resource, StructuredOptions{})
return p.PrintStructured(resource, StructuredOptions{EmitDefaultValues: options.EmitDefaultValues})
}

// For text we want to print "metadata" fields at the top level, and then "spec" fields below that with an indent. We can achieve this by printing two separate cards.
Expand All @@ -765,11 +781,17 @@ func (p *Printer) PrintResource(resource any, options PrintResourceOptions) erro
}

// print all top-level fields except "Spec"
cols, row := p.parseFields(resourceVal, options.Fields, []string{"Spec"}, 1)
cols, row := p.parseFields(resourceVal, options.Fields, []string{"Spec"}, 1, options.EmitDefaultValues)
p.printCard(cols, row)

// now print "Spec" fields if present
specCols, specRow := p.parseFields(resourceVal.FieldByName("Spec"), options.SpecFields, nil, 2)
specCols, specRow := p.parseFields(
resourceVal.FieldByName("Spec"),
options.SpecFields,
nil,
2,
options.EmitDefaultValues,
)
if len(specCols) > 0 {
p.writeStr(NonJSONIndent)
p.writeStr("Spec:\n")
Expand All @@ -783,6 +805,7 @@ func (p *Printer) parseFields(
allowList []string,
excludeList []string,
indent int,
emitDefaultValues bool,
) (cols []*col, row map[string]colVal) {
if !v.IsValid() {
return
Expand All @@ -806,11 +829,18 @@ func (p *Printer) parseFields(
if slices.Contains(excludeList, field.Name) {
continue
}
if isZero(v.Field(i).Interface()) {
if emitDefaultValues {
if v.Field(i).Kind() == reflect.Pointer && v.Field(i).IsNil() {
continue
}
} else if isZero(v.Field(i).Interface()) {
continue
}
cols = append(cols, &col{name: field.Name, indentAmount: indent})
row[field.Name] = colVal{val: v.Field(i).Interface(), text: p.textVal(v.Field(i).Interface())}
row[field.Name] = colVal{
val: v.Field(i).Interface(),
text: p.textVal(v.Field(i).Interface(), emitDefaultValues),
}
}
return
}
Expand Down Expand Up @@ -863,7 +893,7 @@ func (p *Printer) PrintResourceList(
tableOptions TableOptions,
) error {
if p.JSON {
return p.PrintStructured(resourceListResp, StructuredOptions{})
return p.PrintStructured(resourceListResp, StructuredOptions{EmitDefaultValues: options.EmitDefaultValues})
}

v := reflect.ValueOf(resourceListResp)
Expand Down Expand Up @@ -918,8 +948,14 @@ func (p *Printer) PrintResourceList(
if resourceVal.Kind() == reflect.Pointer {
resourceVal = resourceVal.Elem()
}
_, row := p.parseFields(resourceVal, options.Fields, []string{"Spec"}, 1)
_, specRow := p.parseFields(resourceVal.FieldByName("Spec"), options.SpecFields, nil, 1)
_, row := p.parseFields(resourceVal, options.Fields, []string{"Spec"}, 1, options.EmitDefaultValues)
_, specRow := p.parseFields(
resourceVal.FieldByName("Spec"),
options.SpecFields,
nil,
1,
options.EmitDefaultValues,
)
maps.Copy(row, specRow)
rows = append(rows, row)
}
Expand Down
Loading
Loading