Skip to content

Commit c0cae55

Browse files
Improve YAML marshaling (#121)
1 parent 81e1ac7 commit c0cae55

12 files changed

Lines changed: 121 additions & 88 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require (
77
github.com/charmbracelet/bubbletea v1.3.10
88
github.com/cli/safeexec v1.0.1
99
github.com/fatih/color v1.18.0
10+
github.com/go-viper/mapstructure/v2 v2.4.0
1011
github.com/google/jsonschema-go v0.2.3
1112
github.com/jackc/pgx/v5 v5.7.5
1213
github.com/modelcontextprotocol/go-sdk v0.5.0
@@ -80,7 +81,6 @@ require (
8081
github.com/go-openapi/swag/stringutils v0.24.0 // indirect
8182
github.com/go-openapi/swag/typeutils v0.24.0 // indirect
8283
github.com/go-openapi/swag/yamlutils v0.24.0 // indirect
83-
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
8484
github.com/gobwas/glob v0.2.3 // indirect
8585
github.com/godbus/dbus/v5 v5.1.0 // indirect
8686
github.com/gofrs/flock v0.12.1 // indirect

internal/tiger/cmd/auth.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ func outputAuthInfo(cmd *cobra.Command, authInfo api.AuthInfo, format string) er
247247
case "json":
248248
return util.SerializeToJSON(outputWriter, authInfo)
249249
case "yaml":
250-
return util.SerializeToYAML(outputWriter, authInfo, true)
250+
return util.SerializeToYAML(outputWriter, authInfo)
251251
default: // table format (default)
252252
return outputAuthInfoTable(authInfo, outputWriter)
253253
}

internal/tiger/cmd/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func buildConfigShowCmd() *cobra.Command {
6868
case "json":
6969
return util.SerializeToJSON(output, cfgOut)
7070
case "yaml":
71-
return util.SerializeToYAML(output, cfgOut, false)
71+
return util.SerializeToYAML(output, cfgOut)
7272
default:
7373
return outputTable(output, cfgOut)
7474
}

internal/tiger/cmd/config_test.go

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,8 @@ func TestConfigShow_JSONOutput(t *testing.T) {
121121
// Create config file with JSON output format
122122
configContent := `api_url: https://json.api.com/v1
123123
output: json
124-
analytics: true
125-
password_storage: none
126-
version_check_interval: 1h
124+
analytics: false
125+
password_storage: keyring
127126
version_check_last_time: ` + now.Format(time.RFC3339) + "\n"
128127

129128
configFile := config.GetConfigFile(tmpDir)
@@ -152,12 +151,12 @@ version_check_last_time: ` + now.Format(time.RFC3339) + "\n"
152151
"service_id": "",
153152
"color": true,
154153
"output": "json",
155-
"analytics": true,
156-
"password_storage": "none",
154+
"analytics": false,
155+
"password_storage": "keyring",
157156
"debug": false,
158157
"config_dir": tmpDir,
159158
"releases_url": "https://cli.tigerdata.com",
160-
"version_check_interval": float64(3600000000000), // JSON unmarshals time.Duration as nanoseconds (1 hour = 3600000000000ns)
159+
"version_check_interval": "24h0m0s",
161160
"version_check_last_time": now.Format(time.RFC3339),
162161
}
163162

@@ -216,22 +215,13 @@ version_check_last_time: ` + now.Format(time.RFC3339) + "\n"
216215
"debug": false,
217216
"config_dir": tmpDir,
218217
"releases_url": "https://cli.tigerdata.com",
219-
"version_check_interval": "24h0m0s", // YAML serializes time.Duration as string
220-
"version_check_last_time": now,
218+
"version_check_interval": "24h0m0s",
219+
"version_check_last_time": now.Format(time.RFC3339),
221220
}
222221

223222
for key, expectedValue := range expectedValues {
224-
switch expectedValue.(type) {
225-
case time.Time:
226-
// YAML unmarshals time.Time as time.Time type, so we need to compare differently
227-
if expectedValue.(time.Time).Format(time.RFC3339) != result[key].(time.Time).Format(time.RFC3339) {
228-
t.Errorf("foo Expected %s '%v', got %v", key, expectedValue, result[key])
229-
}
230-
default:
231-
// Other types can be compared directly
232-
if result[key] != expectedValue {
233-
t.Errorf("Expected %s '%v', got %v", key, expectedValue, result[key])
234-
}
223+
if result[key] != expectedValue {
224+
t.Errorf("Expected %s '%v', got %v", key, expectedValue, result[key])
235225
}
236226
}
237227

internal/tiger/cmd/db.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -530,10 +530,10 @@ func getPasswordForRole(passwordFlag string) (string, error) {
530530

531531
// CreateRoleResult represents the output of a create role operation
532532
type CreateRoleResult struct {
533-
RoleName string `json:"role_name" yaml:"role_name"`
534-
ReadOnly bool `json:"read_only,omitempty" yaml:"read_only,omitempty"`
535-
StatementTimeout string `json:"statement_timeout,omitempty" yaml:"statement_timeout,omitempty"`
536-
FromRoles []string `json:"from_roles,omitempty" yaml:"from_roles,omitempty"`
533+
RoleName string `json:"role_name"`
534+
ReadOnly bool `json:"read_only,omitempty"`
535+
StatementTimeout string `json:"statement_timeout,omitempty"`
536+
FromRoles []string `json:"from_roles,omitempty"`
537537
}
538538

539539
// outputCreateRoleResult formats and outputs the create role result
@@ -557,7 +557,7 @@ func outputCreateRoleResult(cmd *cobra.Command, roleName string, readOnly bool,
557557
case "json":
558558
return util.SerializeToJSON(outputWriter, result)
559559
case "yaml":
560-
return util.SerializeToYAML(outputWriter, result, false)
560+
return util.SerializeToYAML(outputWriter, result)
561561
default: // table format
562562
fmt.Fprintf(outputWriter, "✓ Role '%s' created successfully\n", roleName)
563563
if readOnly {

internal/tiger/cmd/mcp.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ Examples:
267267
case "json":
268268
return util.SerializeToJSON(output, capabilities)
269269
case "yaml":
270-
return util.SerializeToYAML(output, capabilities, true)
270+
return util.SerializeToYAML(output, capabilities)
271271
default:
272272
return outputCapabilitiesTable(output, capabilities)
273273
}

internal/tiger/cmd/service.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -549,8 +549,8 @@ Examples:
549549
type OutputService struct {
550550
api.Service
551551
common.ConnectionDetails
552-
ConnectionString string `json:"connection_string,omitempty" yaml:"connection_string,omitempty"`
553-
ConsoleURL string `json:"console_url,omitempty" yaml:"console_url,omitempty"`
552+
ConnectionString string `json:"connection_string,omitempty"`
553+
ConsoleURL string `json:"console_url,omitempty"`
554554
}
555555

556556
// outputService formats and outputs a single service based on the specified format
@@ -566,7 +566,7 @@ func outputService(cmd *cobra.Command, service api.Service, format string, withP
566566
case "json":
567567
return util.SerializeToJSON(outputWriter, outputSvc)
568568
case "yaml":
569-
return util.SerializeToYAML(outputWriter, outputSvc, true)
569+
return util.SerializeToYAML(outputWriter, outputSvc)
570570
case "env":
571571
return outputServiceEnv(outputSvc, outputWriter)
572572
default: // table format (default)
@@ -583,7 +583,7 @@ func outputServices(cmd *cobra.Command, services []api.Service, format string) e
583583
case "json":
584584
return util.SerializeToJSON(outputWriter, outputServices)
585585
case "yaml":
586-
return util.SerializeToYAML(outputWriter, outputServices, true)
586+
return util.SerializeToYAML(outputWriter, outputServices)
587587
case "env":
588588
return fmt.Errorf("environment variable output is not supported for multiple services")
589589
default: // table format (default)

internal/tiger/cmd/version.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ import (
1414
)
1515

1616
type VersionOutput struct {
17-
Version string `json:"version" yaml:"version"`
18-
BuildTime string `json:"build_time" yaml:"build_time"`
19-
GitCommit string `json:"git_commit" yaml:"git_commit"`
20-
GoVersion string `json:"go_version" yaml:"go_version"`
21-
Platform string `json:"platform" yaml:"platform"`
22-
LatestVersion string `json:"latest_version,omitempty" yaml:"latest_version,omitempty"`
23-
UpdateAvailable *bool `json:"update_available,omitempty" yaml:"update_available,omitempty"`
17+
Version string `json:"version"`
18+
BuildTime string `json:"build_time"`
19+
GitCommit string `json:"git_commit"`
20+
GoVersion string `json:"go_version"`
21+
Platform string `json:"platform"`
22+
LatestVersion string `json:"latest_version,omitempty"`
23+
UpdateAvailable *bool `json:"update_available,omitempty"`
2424
}
2525

2626
func buildVersionCmd() *cobra.Command {
@@ -64,7 +64,7 @@ func buildVersionCmd() *cobra.Command {
6464
return err
6565
}
6666
case "yaml":
67-
if err := util.SerializeToYAML(output, versionOutput, true); err != nil {
67+
if err := util.SerializeToYAML(output, versionOutput); err != nil {
6868
return err
6969
}
7070
case "bare":

internal/tiger/common/connection.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ type ConnectionDetailsOptions struct {
2424
}
2525

2626
type ConnectionDetails struct {
27-
Role string `json:"role,omitempty" yaml:"role,omitempty"`
28-
Password string `json:"password,omitempty" yaml:"password,omitempty"`
29-
Host string `json:"host,omitempty" yaml:"host,omitempty"`
30-
Port int `json:"port,omitempty" yaml:"port,omitempty"`
31-
Database string `json:"database,omitempty" yaml:"database,omitempty"`
32-
IsPooler bool `json:"is_pooler,omitempty" yaml:"is_pooler,omitempty"`
27+
Role string `json:"role,omitempty"`
28+
Password string `json:"password,omitempty"`
29+
Host string `json:"host,omitempty"`
30+
Port int `json:"port,omitempty"`
31+
Database string `json:"database,omitempty"`
32+
IsPooler bool `json:"is_pooler,omitempty"`
3333
}
3434

3535
func GetConnectionDetails(service api.Service, opts ConnectionDetailsOptions) (*ConnectionDetails, error) {

internal/tiger/config/config.go

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,47 +11,48 @@ import (
1111
"strconv"
1212
"time"
1313

14+
"github.com/go-viper/mapstructure/v2"
1415
"github.com/spf13/pflag"
1516
"github.com/spf13/viper"
1617

1718
"github.com/timescale/tiger-cli/internal/tiger/util"
1819
)
1920

2021
type Config struct {
21-
APIURL string `mapstructure:"api_url" yaml:"api_url"`
22-
Analytics bool `mapstructure:"analytics" yaml:"analytics"`
23-
Color bool `mapstructure:"color" yaml:"color"`
24-
ConfigDir string `mapstructure:"config_dir" yaml:"-"`
25-
ConsoleURL string `mapstructure:"console_url" yaml:"console_url"`
26-
Debug bool `mapstructure:"debug" yaml:"debug"`
27-
DocsMCP bool `mapstructure:"docs_mcp" yaml:"docs_mcp"`
28-
DocsMCPURL string `mapstructure:"docs_mcp_url" yaml:"docs_mcp_url"`
29-
GatewayURL string `mapstructure:"gateway_url" yaml:"gateway_url"`
30-
Output string `mapstructure:"output" yaml:"output"`
31-
PasswordStorage string `mapstructure:"password_storage" yaml:"password_storage"`
32-
ReleasesURL string `mapstructure:"releases_url" yaml:"releases_url"`
33-
ServiceID string `mapstructure:"service_id" yaml:"service_id"`
34-
VersionCheckInterval time.Duration `mapstructure:"version_check_interval" yaml:"version_check_interval"`
35-
VersionCheckLastTime time.Time `mapstructure:"version_check_last_time" yaml:"version_check_last_time"`
36-
viper *viper.Viper `mapstructure:"-" yaml:"-"`
22+
APIURL string `mapstructure:"api_url"`
23+
Analytics bool `mapstructure:"analytics"`
24+
Color bool `mapstructure:"color"`
25+
ConfigDir string `mapstructure:"config_dir"`
26+
ConsoleURL string `mapstructure:"console_url"`
27+
Debug bool `mapstructure:"debug"`
28+
DocsMCP bool `mapstructure:"docs_mcp"`
29+
DocsMCPURL string `mapstructure:"docs_mcp_url"`
30+
GatewayURL string `mapstructure:"gateway_url"`
31+
Output string `mapstructure:"output"`
32+
PasswordStorage string `mapstructure:"password_storage"`
33+
ReleasesURL string `mapstructure:"releases_url"`
34+
ServiceID string `mapstructure:"service_id"`
35+
VersionCheckInterval time.Duration `mapstructure:"version_check_interval"`
36+
VersionCheckLastTime time.Time `mapstructure:"version_check_last_time"`
37+
viper *viper.Viper `mapstructure:"-"`
3738
}
3839

3940
type ConfigOutput struct {
40-
APIURL *string `mapstructure:"api_url" json:"api_url,omitempty" yaml:"api_url,omitempty"`
41-
Analytics *bool `mapstructure:"analytics" json:"analytics,omitempty" yaml:"analytics,omitempty"`
42-
Color *bool `mapstructure:"color" json:"color,omitempty" yaml:"color,omitempty"`
43-
ConfigDir *string `mapstructure:"config_dir" json:"config_dir,omitempty" yaml:"config_dir,omitempty"`
44-
ConsoleURL *string `mapstructure:"console_url" json:"console_url,omitempty" yaml:"console_url,omitempty"`
45-
Debug *bool `mapstructure:"debug" json:"debug,omitempty" yaml:"debug,omitempty"`
46-
DocsMCP *bool `mapstructure:"docs_mcp" json:"docs_mcp,omitempty" yaml:"docs_mcp,omitempty"`
47-
DocsMCPURL *string `mapstructure:"docs_mcp_url" json:"docs_mcp_url,omitempty" yaml:"docs_mcp_url,omitempty"`
48-
GatewayURL *string `mapstructure:"gateway_url" json:"gateway_url,omitempty" yaml:"gateway_url,omitempty"`
49-
Output *string `mapstructure:"output" json:"output,omitempty" yaml:"output,omitempty"`
50-
PasswordStorage *string `mapstructure:"password_storage" json:"password_storage,omitempty" yaml:"password_storage,omitempty"`
51-
ReleasesURL *string `mapstructure:"releases_url" json:"releases_url,omitempty" yaml:"releases_url,omitempty"`
52-
ServiceID *string `mapstructure:"service_id" json:"service_id,omitempty" yaml:"service_id,omitempty"`
53-
VersionCheckInterval *time.Duration `mapstructure:"version_check_interval" json:"version_check_interval,omitempty" yaml:"version_check_interval,omitempty"`
54-
VersionCheckLastTime *time.Time `mapstructure:"version_check_last_time" json:"version_check_last_time,omitempty" yaml:"version_check_last_time,omitempty"`
41+
APIURL *string `mapstructure:"api_url" json:"api_url,omitempty"`
42+
Analytics *bool `mapstructure:"analytics" json:"analytics,omitempty"`
43+
Color *bool `mapstructure:"color" json:"color,omitempty"`
44+
ConfigDir *string `mapstructure:"config_dir" json:"config_dir,omitempty"`
45+
ConsoleURL *string `mapstructure:"console_url" json:"console_url,omitempty"`
46+
Debug *bool `mapstructure:"debug" json:"debug,omitempty"`
47+
DocsMCP *bool `mapstructure:"docs_mcp" json:"docs_mcp,omitempty"`
48+
DocsMCPURL *string `mapstructure:"docs_mcp_url" json:"docs_mcp_url,omitempty"`
49+
GatewayURL *string `mapstructure:"gateway_url" json:"gateway_url,omitempty"`
50+
Output *string `mapstructure:"output" json:"output,omitempty"`
51+
PasswordStorage *string `mapstructure:"password_storage" json:"password_storage,omitempty"`
52+
ReleasesURL *string `mapstructure:"releases_url" json:"releases_url,omitempty"`
53+
ServiceID *string `mapstructure:"service_id" json:"service_id,omitempty"`
54+
VersionCheckInterval *util.Duration `mapstructure:"version_check_interval" json:"version_check_interval,omitempty"` // [util.Duration] ensures value is marshaled in [time.Duration.String] format when output
55+
VersionCheckLastTime *time.Time `mapstructure:"version_check_last_time" json:"version_check_last_time,omitempty"`
5556
}
5657

5758
const (
@@ -83,7 +84,7 @@ var defaultValues = map[string]any{
8384
"password_storage": DefaultPasswordStorage,
8485
"releases_url": DefaultReleasesURL,
8586
"service_id": "",
86-
"version_check_interval": DefaultVersionCheckInterval,
87+
"version_check_interval": DefaultVersionCheckInterval.String(), // String can be interpreted as either [time.Duration] (for [Config]) or [util.Duration] (for [ConfigOutput])
8788
"version_check_last_time": time.Time{},
8889
}
8990

@@ -149,7 +150,10 @@ func ForOutputFromViper(v *viper.Viper) (*ConfigOutput, error) {
149150
ConfigDir: &configDir,
150151
}
151152

152-
if err := v.Unmarshal(cfg); err != nil {
153+
if err := v.Unmarshal(cfg,
154+
// Decode hook allows us to unmarshal a string into a [util.Duration] for the sake of VersionCheckInterval
155+
viper.DecodeHook(mapstructure.TextUnmarshallerHookFunc()),
156+
); err != nil {
153157
return nil, fmt.Errorf("error unmarshaling config for output: %w", err)
154158
}
155159

0 commit comments

Comments
 (0)