From f837061f953bda1e8b42095c6dba0496de11d993 Mon Sep 17 00:00:00 2001 From: Aleksandr Paramonov Date: Wed, 17 Jan 2024 21:56:54 +0800 Subject: Add missing-key flag to manage behavior in case of non-existing key (#1949) * Add missing-key flag to manage behavior in case of non-existing key * Fix typo * Added integration tests, added "default" to the allowed values for the missing-key flag * Use the "error" value for the MissingKey if it passes as empty string * Remove unnecessary writeFile from test * Add docs for the missin key feature * Add invalid to the allowed values of missing-key option * Remove unnecesary code from tests * Fix failed tests and linter errors * Update docs/content/usage.md Co-authored-by: Dave Henderson * Update feature description * Add missing dot --------- Co-authored-by: Aleksandr Paramonov Co-authored-by: Dave Henderson --- internal/cmd/config.go | 5 +++++ internal/cmd/main.go | 2 ++ internal/config/configfile.go | 14 ++++++++++++++ internal/config/configfile_test.go | 1 + internal/tests/integration/config_test.go | 20 ++++++++++++++++++++ internal/tests/integration/integration_test.go | 21 +++++++++++++++++++-- internal/tests/integration/missing_key_test.go | 25 +++++++++++++++++++++++++ 7 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 internal/tests/integration/missing_key_test.go (limited to 'internal') diff --git a/internal/cmd/config.go b/internal/cmd/config.go index 1fae0346..7f071417 100644 --- a/internal/cmd/config.go +++ b/internal/cmd/config.go @@ -160,6 +160,11 @@ func cobraConfig(cmd *cobra.Command, args []string) (cfg *config.Config, err err return nil, err } + cfg.MissingKey, err = getString(cmd, "missing-key") + if err != nil { + return nil, err + } + ds, err := getStringSlice(cmd, "datasource") if err != nil { return nil, err diff --git a/internal/cmd/main.go b/internal/cmd/main.go index 7533f855..3cce83e2 100644 --- a/internal/cmd/main.go +++ b/internal/cmd/main.go @@ -156,6 +156,8 @@ func InitFlags(command *cobra.Command) { command.Flags().String("left-delim", ldDefault, "override the default left-`delimiter` [$GOMPLATE_LEFT_DELIM]") command.Flags().String("right-delim", rdDefault, "override the default right-`delimiter` [$GOMPLATE_RIGHT_DELIM]") + command.Flags().String("missing-key", "error", "Control the behavior during execution if a map is indexed with a key that is not present in the map. error (default) - return an error, zero - fallback to zero value, default/invalid - print ") + command.Flags().Bool("experimental", false, "enable experimental features [$GOMPLATE_EXPERIMENTAL]") command.Flags().BoolP("verbose", "V", false, "output extra information about what gomplate is doing") diff --git a/internal/config/configfile.go b/internal/config/configfile.go index 712d7310..1ed266b3 100644 --- a/internal/config/configfile.go +++ b/internal/config/configfile.go @@ -13,6 +13,8 @@ import ( "strings" "time" + "golang.org/x/exp/slices" + "github.com/hairyhenderson/gomplate/v4/internal/datafs" "github.com/hairyhenderson/gomplate/v4/internal/iohelpers" "github.com/hairyhenderson/yaml" @@ -60,6 +62,8 @@ type Config struct { LDelim string `yaml:"leftDelim,omitempty"` RDelim string `yaml:"rightDelim,omitempty"` + MissingKey string `yaml:"missingKey,omitempty"` + PostExec []string `yaml:"postExec,omitempty,flow"` PluginTimeout time.Duration `yaml:"pluginTimeout,omitempty"` @@ -465,6 +469,13 @@ func (c Config) Validate() (err error) { } } + if err == nil { + missingKeyValues := []string{"", "error", "zero", "default", "invalid"} + if !slices.Contains(missingKeyValues, c.MissingKey) { + err = fmt.Errorf("not allowed value for the 'missing-key' flag: %s. Allowed values: %s", c.MissingKey, strings.Join(missingKeyValues, ",")) + } + } + return err } @@ -533,6 +544,9 @@ func (c *Config) ApplyDefaults() { if c.RDelim == "" { c.RDelim = "}}" } + if c.MissingKey == "" { + c.MissingKey = "error" + } if c.ExecPipe { pipe := &bytes.Buffer{} diff --git a/internal/config/configfile_test.go b/internal/config/configfile_test.go index e99fc8b4..449c379b 100644 --- a/internal/config/configfile_test.go +++ b/internal/config/configfile_test.go @@ -539,6 +539,7 @@ inputFiles: ['-'] outputFiles: ['-'] leftDelim: '{{' rightDelim: '}}' +missingKey: error pluginTimeout: 5s ` assert.Equal(t, expected, c.String()) diff --git a/internal/tests/integration/config_test.go b/internal/tests/integration/config_test.go index d56616ae..00ab4f89 100644 --- a/internal/tests/integration/config_test.go +++ b/internal/tests/integration/config_test.go @@ -275,3 +275,23 @@ templates: o, e, err := cmd(t).withDir(tmpDir.Path()).run() assertSuccess(t, o, e, err, "12345") } + +func TestConfig_MissingKeyDefault(t *testing.T) { + tmpDir := setupConfigTest(t) + writeConfig(t, tmpDir, `inputFiles: [in] +missingKey: default +`) + writeFile(t, tmpDir, "in", `{{ .name }}`) + + o, e, err := cmd(t).withDir(tmpDir.Path()).run() + assertSuccess(t, o, e, err, ``) +} + +func TestConfig_MissingKeyNotDefined(t *testing.T) { + tmpDir := setupConfigTest(t) + writeConfig(t, tmpDir, `inputFiles: [in]`) + writeFile(t, tmpDir, "in", `{{ .name }}`) + + o, e, err := cmd(t).withDir(tmpDir.Path()).run() + assertFailed(t, o, e, err, `map has no entry for key \"name\"`) +} diff --git a/internal/tests/integration/integration_test.go b/internal/tests/integration/integration_test.go index bc79862a..a57d8d0b 100644 --- a/internal/tests/integration/integration_test.go +++ b/internal/tests/integration/integration_test.go @@ -25,13 +25,22 @@ import ( const isWindows = runtime.GOOS == "windows" // a convenience... -func inOutTest(t *testing.T, i, o string) { +func inOutTest(t *testing.T, i, o string, args ...string) { t.Helper() - stdout, stderr, err := cmd(t, "-i", i).run() + args = append(args, "-i", i) + stdout, stderr, err := cmd(t, args...).run() assertSuccess(t, stdout, stderr, err, o) } +func inOutContainsError(t *testing.T, i, e string, args ...string) { + t.Helper() + + args = append(args, "-i", i) + stdout, stderr, err := cmd(t, args...).run() + assertFailed(t, stdout, stderr, err, e) +} + func inOutTestExperimental(t *testing.T, i, o string) { t.Helper() @@ -56,6 +65,14 @@ func assertSuccess(t *testing.T, o, e string, err error, expected string) { require.NoError(t, err) } +func assertFailed(t *testing.T, o, e string, err error, expected string) { + t.Helper() + + assert.Contains(t, e, expected) + assert.Equal(t, "", o) + require.Error(t, err) +} + // mirrorHandler - reflects back the HTTP headers from the request func mirrorHandler(w http.ResponseWriter, r *http.Request) { type Req struct { diff --git a/internal/tests/integration/missing_key_test.go b/internal/tests/integration/missing_key_test.go new file mode 100644 index 00000000..076ff236 --- /dev/null +++ b/internal/tests/integration/missing_key_test.go @@ -0,0 +1,25 @@ +package integration + +import ( + "testing" +) + +func TestMissingKey_Default(t *testing.T) { + inOutTest(t, `{{ .name }}`, "", "--missing-key", "default") +} + +func TestMissingKey_Zero(t *testing.T) { + inOutTest(t, `{{ .name }}`, "", "--missing-key", "zero") +} + +func TestMissingKey_Fallback(t *testing.T) { + inOutTest(t, `{{ .name | default "Alex" }}`, "Alex", "--missing-key", "default") +} + +func TestMissingKey_NotSpecified(t *testing.T) { + inOutContainsError(t, `{{ .name | default "Alex" }}`, `map has no entry for key \"name\"`) +} + +func TestMissingKey_Error(t *testing.T) { + inOutContainsError(t, `{{ .name | default "Alex" }}`, `map has no entry for key \"name\"`, "--missing-key", "error") +} -- cgit v1.2.3