Repository: meltano/meltano

Issue:
bug: Setting an invalid log level causes subsequent commands to fail
### Meltano Version

3.1.0

### Python Version

NA

### Bug scope

CLI (options, error messages, logging, etc.)

### Operating System

NA

### Description

For example, this command succeeds:

```console
$ meltano config meltano set cli log_level iamnotaloglevel
Meltano setting 'cli.log_level' was set in `.env`: 'iamnotaloglevel'
```

But if I try to reset the value or run any other command, I see:

```console
$ meltano config meltano set cli log_level debug
Need help fixing this problem? Visit http://melta.no/ for troubleshooting steps, or to
join our friendly Slack community.

Unable to configure handler 'console'
```

## Workaround

Use a env var to override the current value and set a new one:

```console
$ MELTANO_CLI_LOG_LEVEL=debug meltano config meltano set cli log_level debug
Meltano setting 'cli.log_level' was set in `.env`: 'debug'
Current value is still: 'debug' (from the environment)
```

## Proposal: Add validation/casting for `kind: options` settings

```console
$ meltano config meltano set cli log_level iamnotaloglevel
Need help fixing this problem? Visit http://melta.no/ for troubleshooting steps, or to
join our friendly Slack community.

Invalid value for cli.log_level: 'iamnotaloglevel' (expected one of {'info', 'error', 'warning', 'debug', 'critical'})
```

### Code

_No response_

### References

* Slack: https://meltano.slack.com/archives/C01TCRBBJD7/p1697221621970919
* Linen: https://www.linen.dev/s/meltano/t/15985735/hi-all-i-made-a-mistake-calling-this-meltano-config-meltano-#004f5eae-7d3d-4ea1-a112-87c189b00ef4


Discussion:

Resolving patch:
diff --git a/src/meltano/core/setting_definition.py b/src/meltano/core/setting_definition.py
--- a/src/meltano/core/setting_definition.py
+++ b/src/meltano/core/setting_definition.py
@@ -424,6 +424,9 @@ def cast_value(self, value: t.Any) -> t.Any:  # noqa: C901
 
         Returns:
             Value cast according to specified setting definition kind.
+
+        Raises:
+            ValueError: If value is not of the expected type.
         """
         value = value.isoformat() if isinstance(value, (date, datetime)) else value
 
@@ -441,6 +444,14 @@ def cast_value(self, value: t.Any) -> t.Any:  # noqa: C901
                     self._parse_value(value, "array", Sequence),  # type: ignore
                 )
 
+        if (
+            value is not None
+            and self.kind == SettingKind.OPTIONS
+            and all(opt["value"] != value for opt in self.options)
+        ):
+            error_message = f"'{value}' is not a valid choice for '{self.name}'"
+            raise ValueError(error_message)
+
         processor = self.value_processor
         if value is not None and processor:
             if isinstance(processor, str):


Test patch:
diff --git a/tests/meltano/core/test_setting_definition.py b/tests/meltano/core/test_setting_definition.py
--- a/tests/meltano/core/test_setting_definition.py
+++ b/tests/meltano/core/test_setting_definition.py
@@ -55,3 +55,19 @@ def test_cast_value_array(
                     setting_definition.cast_value(uncast)
             else:
                 assert setting_definition.cast_value(uncast) == expected
+
+    def test_cast_options(self):
+        setting_definition = SettingDefinition(
+            "test_setting",
+            kind=SettingKind.OPTIONS,
+            options=[
+                {"value": "abc", "label": "ABC"},
+                {"value": "xyz", "label": "XYZ"},
+            ],
+        )
+
+        assert setting_definition.cast_value("abc") == "abc"
+        assert setting_definition.cast_value("xyz") == "xyz"
+        assert setting_definition.cast_value(None) is None
+        with pytest.raises(ValueError, match="is not a valid choice"):
+            setting_definition.cast_value("def")