diff --git a/cmd/config/config.go b/cmd/config/config.go index 51e7486d..10f2b0f1 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -2,7 +2,6 @@ package config import ( "encoding/json" - "errors" "fmt" "os" @@ -14,6 +13,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/internal/analytics" "ldcli/internal/config" + "ldcli/internal/errors" "ldcli/internal/output" ) @@ -76,7 +76,7 @@ func run() func(*cobra.Command, []string) error { case viper.GetBool(SetFlag): // flag needs two arguments: a key and value if len(args)%2 != 0 { - return errors.New("flag needs an argument: --set") + return errors.NewError("flag needs an argument: --set") } rawConfig, v, err := getRawConfig() @@ -98,7 +98,12 @@ func run() func(*cobra.Command, []string) error { v.Set(key, value) } - return writeConfig(config.NewConfig(rawConfig), v, setKeyFn) + configFile, err := config.NewConfig(rawConfig) + if err != nil { + return errors.NewError(err.Error()) + } + + return writeConfig(configFile, v, setKeyFn) case viper.IsSet(UnsetFlag): config, v, err := getConfig() if err != nil { diff --git a/cmd/validators/validators.go b/cmd/validators/validators.go index b1697e4f..8e29e0e8 100644 --- a/cmd/validators/validators.go +++ b/cmd/validators/validators.go @@ -49,13 +49,9 @@ func CmdError(err error, commandPath string) error { } func validateOutput(outputFlag string) error { - validKinds := map[string]struct{}{ - "json": {}, - "plaintext": {}, - } - _, ok := validKinds[outputFlag] - if !ok { - return output.ErrInvalidOutputKind + _, err := output.NewOutputKind(outputFlag) + if err != nil { + return err } return nil diff --git a/internal/config/config.go b/internal/config/config.go index 18eadf1f..43b2d55d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -7,6 +7,8 @@ import ( "github.com/mitchellh/go-homedir" "ldcli/cmd/cliflags" + "ldcli/internal/errors" + "ldcli/internal/output" ) const Filename = ".ldcli-config.yml" @@ -16,28 +18,43 @@ type ConfigFile struct { AccessToken string `json:"access-token,omitempty" yaml:"access-token,omitempty"` AnalyticsOptOut *bool `json:"analytics-opt-out,omitempty" yaml:"analytics-opt-out,omitempty"` BaseURI string `json:"base-uri,omitempty" yaml:"base-uri,omitempty"` + Output string `json:"output,omitempty" yaml:"output,omitempty"` } -func NewConfig(rawConfig map[string]interface{}) ConfigFile { - var accessToken string +func NewConfig(rawConfig map[string]interface{}) (ConfigFile, error) { + var ( + accessToken string + analyticsOptOut bool + baseURI string + err error + outputKind output.OutputKind + ) if rawConfig[cliflags.AccessTokenFlag] != nil { accessToken = rawConfig[cliflags.AccessTokenFlag].(string) } - var analyticsOptOut bool if rawConfig[cliflags.AnalyticsOptOut] != nil { - stringValue := rawConfig[cliflags.AnalyticsOptOut].(string) - analyticsOptOut, _ = strconv.ParseBool(stringValue) + stringValue := fmt.Sprintf("%v", rawConfig[cliflags.AnalyticsOptOut]) + analyticsOptOut, err = strconv.ParseBool(stringValue) + if err != nil { + return ConfigFile{}, errors.NewError("analytics-opt-out must be true or false") + } } - var baseURI string if rawConfig[cliflags.BaseURIFlag] != nil { baseURI = rawConfig[cliflags.BaseURIFlag].(string) } + if rawConfig[cliflags.OutputFlag] != nil { + outputKind, err = output.NewOutputKind(rawConfig[cliflags.OutputFlag].(string)) + if err != nil { + return ConfigFile{}, err + } + } return ConfigFile{ AccessToken: accessToken, AnalyticsOptOut: &analyticsOptOut, BaseURI: baseURI, - } + Output: outputKind.String(), + }, nil } func GetConfigPath() string { diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 00000000..27e5a035 --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,95 @@ +package config_test + +import ( + "fmt" + "ldcli/internal/config" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewConfig(t *testing.T) { + t.Run("analytics-opt-out", func(t *testing.T) { + tests := map[string]struct { + expected bool + input interface{} + }{ + "when value is true": { + expected: true, + input: true, + }, + "when value is 1": { + expected: true, + input: 1, + }, + "when value is false": { + expected: false, + input: false, + }, + "when value is 0": { + expected: false, + input: 0, + }, + } + for name, tt := range tests { + tt := tt + t.Run(fmt.Sprintf("%s is %t", name, tt.expected), func(t *testing.T) { + rawConfig := map[string]interface{}{ + "analytics-opt-out": tt.input, + } + + configFile, err := config.NewConfig(rawConfig) + + require.NoError(t, err) + assert.Equal(t, tt.expected, *configFile.AnalyticsOptOut) + }) + } + }) + + t.Run("is an error when analytics-opt-out is something else", func(t *testing.T) { + rawConfig := map[string]interface{}{ + "analytics-opt-out": "something", + } + + _, err := config.NewConfig(rawConfig) + + assert.EqualError(t, err, "analytics-opt-out must be true or false") + }) + + t.Run("analytics-opt-out", func(t *testing.T) { + tests := map[string]struct { + input string + }{ + "is valid when value is json": { + input: "json", + }, + "is valid when value is plaintext": { + input: "json", + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + rawConfig := map[string]interface{}{ + "output": tt.input, + } + + configFile, err := config.NewConfig(rawConfig) + + require.NoError(t, err) + assert.Equal(t, tt.input, configFile.Output) + }) + } + }) + + t.Run("is invalid with an invalid output", func(t *testing.T) { + rawConfig := map[string]interface{}{ + "output": "invalid", + } + + _, err := config.NewConfig(rawConfig) + + assert.EqualError(t, err, "output is invalid") + }) +} diff --git a/internal/output/output.go b/internal/output/output.go index 4d9e188e..63ace22d 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -8,6 +8,30 @@ import ( var ErrInvalidOutputKind = errors.NewError("output is invalid") +type OutputKind string + +func (o OutputKind) String() string { + return string(o) +} + +var ( + OutputKindJSON = OutputKind("json") + OutputKindNull = OutputKind("") + OutputKindPlaintext = OutputKind("plaintext") +) + +func NewOutputKind(s string) (OutputKind, error) { + validKinds := map[string]struct{}{ + OutputKindJSON.String(): {}, + OutputKindPlaintext.String(): {}, + } + if _, isValid := validKinds[s]; !isValid { + return OutputKindNull, ErrInvalidOutputKind + } + + return OutputKind(s), nil +} + // Outputter defines the different ways a command's response can be formatted based on // user input. type Outputter interface {