Repository: commitizen-tools/commitizen (Python)

Issue:
`cz` commands failed if there is already a config file but in wrong format
### Description

Run `cz` commands, e.g. `cz init`, `cz version` will fail if there is already a config file with default name but in wrong format.

Although this might not be a common case, IMO it would be better to tell users there is a wrong config (if cannot find others).


### Steps to reproduce

```
mkdir /tmp/cz-init
cd /tmp/cz-init
echo "I am not json" > .cz.json
cz init
```


### Current behavior

`cz init` failed caused by decode error. Outputs attached below.

```
Traceback (most recent call last):
  File "/home/bogay/.local/bin/cz", line 8, in <module>
    sys.exit(main())
  File "/home/bogay/.local/pipx/venvs/commitizen/lib/python3.8/site-packages/commitizen/cli.py", line 354, in main
    conf = config.read_cfg()
  File "/home/bogay/.local/pipx/venvs/commitizen/lib/python3.8/site-packages/commitizen/config/__init__.py", line 37, in read_cfg
    _conf = JsonConfig(data=data, path=filename)
  File "/home/bogay/.local/pipx/venvs/commitizen/lib/python3.8/site-packages/commitizen/config/json_config.py", line 14, in __init__
    self._parse_setting(data)
  File "/home/bogay/.local/pipx/venvs/commitizen/lib/python3.8/site-packages/commitizen/config/json_config.py", line 46, in _parse_setting
    doc = json.loads(data)
  File "/usr/lib/python3.8/json/__init__.py", line 357, in loads
    return _default_decoder.decode(s)
  File "/usr/lib/python3.8/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/lib/python3.8/json/decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
```

### Desired behavior

Maybe try to search the next possible config? If none of them are found. Report an error to user. Tell them there is a config with wrong format.

### Screenshots

_No response_

### Environment

Commitizen Version: 2.32.3
Python Version: 3.8.10 (default, Jun 22 2022, 20:18:18) 
[GCC 9.4.0]
Operating System: Linux


Discussion:
Thanks for reporting! I think we can solve it by catching such exception in [read_cfg](https://github.com/commitizen-tools/commitizen/blob/master/commitizen/config/__init__.py)



Patch:
diff --git a/commitizen/config/json_config.py b/commitizen/config/json_config.py
index a22d3485db..5e821a5a95 100644
--- a/commitizen/config/json_config.py
+++ b/commitizen/config/json_config.py
@@ -47,8 +47,8 @@ def _parse_setting(self, data: bytes | str) -> None:
         """
         try:
             doc = json.loads(data)
-        except json.JSONDecodeError:
-            raise InvalidConfigurationError(f"Failed to parse {self.path}")
+        except json.JSONDecodeError as e:
+            raise InvalidConfigurationError(f"Failed to parse {self.path}: {e}")
 
         try:
             self.settings.update(doc["commitizen"])
diff --git a/commitizen/config/toml_config.py b/commitizen/config/toml_config.py
index 1c8ea8ef61..722e6fcfbc 100644
--- a/commitizen/config/toml_config.py
+++ b/commitizen/config/toml_config.py
@@ -5,6 +5,7 @@
 
 from tomlkit import exceptions, parse, table
 
+from commitizen.exceptions import InvalidConfigurationError
 from .base_config import BaseConfig
 
 
@@ -12,8 +13,8 @@ class TomlConfig(BaseConfig):
     def __init__(self, *, data: bytes | str, path: Path | str):
         super(TomlConfig, self).__init__()
         self.is_empty_config = False
-        self._parse_setting(data)
         self.add_path(path)
+        self._parse_setting(data)
 
     def init_empty_config_content(self):
         if os.path.isfile(self.path):
@@ -50,7 +51,11 @@ def _parse_setting(self, data: bytes | str) -> None:
         name = "cz_conventional_commits"
         ```
         """
-        doc = parse(data)
+        try:
+            doc = parse(data)
+        except exceptions.ParseError as e:
+            raise InvalidConfigurationError(f"Failed to parse {self.path}: {e}")
+
         try:
             self.settings.update(doc["tool"]["commitizen"])  # type: ignore
         except exceptions.NonExistentKey:
diff --git a/commitizen/config/yaml_config.py b/commitizen/config/yaml_config.py
index 5863aaa7b5..4cf78cf3ee 100644
--- a/commitizen/config/yaml_config.py
+++ b/commitizen/config/yaml_config.py
@@ -5,6 +5,7 @@
 import yaml
 
 from commitizen.git import smart_open
+from commitizen.exceptions import InvalidConfigurationError
 
 from .base_config import BaseConfig
 
@@ -13,8 +14,8 @@ class YAMLConfig(BaseConfig):
     def __init__(self, *, data: bytes | str, path: Path | str):
         super(YAMLConfig, self).__init__()
         self.is_empty_config = False
-        self._parse_setting(data)
         self.add_path(path)
+        self._parse_setting(data)
 
     def init_empty_config_content(self):
         with smart_open(self.path, "a", encoding=self.encoding) as json_file:
@@ -28,7 +29,13 @@ def _parse_setting(self, data: bytes | str) -> None:
           name: cz_conventional_commits
         ```
         """
-        doc = yaml.safe_load(data)
+        import yaml.scanner
+
+        try:
+            doc = yaml.safe_load(data)
+        except yaml.YAMLError as e:
+            raise InvalidConfigurationError(f"Failed to parse {self.path}: {e}")
+
         try:
             self.settings.update(doc["commitizen"])
         except (KeyError, TypeError):


Test patch:
diff --git a/tests/test_conf.py b/tests/test_conf.py
index 9406ff53f2..43d7d045e0 100644
--- a/tests/test_conf.py
+++ b/tests/test_conf.py
@@ -6,6 +6,7 @@
 import yaml
 
 from commitizen import config, defaults, git
+from commitizen.exceptions import InvalidConfigurationError
 
 PYPROJECT = """
 [tool.commitizen]
@@ -177,6 +178,13 @@ def test_init_empty_config_content_with_existing_content(self, tmpdir):
         with open(path, "r", encoding="utf-8") as toml_file:
             assert toml_file.read() == existing_content + "\n[tool.commitizen]\n"
 
+    def test_init_with_invalid_config_content(self, tmpdir):
+        existing_content = "invalid toml content"
+        path = tmpdir.mkdir("commitizen").join(".cz.toml")
+
+        with pytest.raises(InvalidConfigurationError, match=r"\.cz\.toml"):
+            config.TomlConfig(data=existing_content, path=path)
+
 
 class TestJsonConfig:
     def test_init_empty_config_content(self, tmpdir):
@@ -187,6 +195,13 @@ def test_init_empty_config_content(self, tmpdir):
         with open(path, "r", encoding="utf-8") as json_file:
             assert json.load(json_file) == {"commitizen": {}}
 
+    def test_init_with_invalid_config_content(self, tmpdir):
+        existing_content = "invalid json content"
+        path = tmpdir.mkdir("commitizen").join(".cz.json")
+
+        with pytest.raises(InvalidConfigurationError, match=r"\.cz\.json"):
+            config.JsonConfig(data=existing_content, path=path)
+
 
 class TestYamlConfig:
     def test_init_empty_config_content(self, tmpdir):
@@ -196,3 +211,10 @@ def test_init_empty_config_content(self, tmpdir):
 
         with open(path, "r") as yaml_file:
             assert yaml.safe_load(yaml_file) == {"commitizen": {}}
+
+    def test_init_with_invalid_content(self, tmpdir):
+        existing_content = "invalid: .cz.yaml: content: maybe?"
+        path = tmpdir.mkdir("commitizen").join(".cz.yaml")
+
+        with pytest.raises(InvalidConfigurationError, match=r"\.cz\.yaml"):
+            config.YAMLConfig(data=existing_content, path=path)