Repository: oscal-compass/compliance-trestle (python)

Issue:
trestle task csv-to-oscal-cd should give better error message when set parameter fails
## Issue description / feature objectives

Below is the current error issued by the trestle task when a invalid set parameter cell is encountered. This can be re-created by having a value with blanks, for example `Cloud Network Interfaces count`, in the column` Parameter_Value_Default`.

```
trestle.tasks.csv_to_oscal_cd:293 ERROR: Traceback (most recent call last):
  File "/home/degenaro/git/trestle.develop/trestle/tasks/csv_to_oscal_cd.py", line 672, in _create_set_parameters
    set_parameter = SetParameter(
  File "pydantic/main.py", line 341, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for SetParameter
param-id
  string does not match regex "^[_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-\.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$" (type=value_error.str.regex; pattern=^[_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-\.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$)
```
An improved error message might look like:

```
Traceback (most recent call last):
  File "/home/degenaro/git/trestle.develop/trestle/tasks/csv_to_oscal_cd.py", line 291, in execute
    return self._execute()
  File "/home/degenaro/git/trestle.develop/trestle/tasks/csv_to_oscal_cd.py", line 330, in _execute
    self.rules_add(rules[1])
  File "/home/degenaro/git/trestle.develop/trestle/tasks/csv_to_oscal_cd.py", line 566, in rules_add
    set_parameters = self._create_set_parameters(rule_key)
  File "/home/degenaro/git/trestle.develop/trestle/tasks/csv_to_oscal_cd.py", line 680, in _create_set_parameters
    raise RuntimeError(text)
RuntimeError: row "132" name "IBM Cloud Network Interfaces count" and/or values "['numeric']" invalid for "Parameter_Value_Default"
```
## Caveats / Assumptions

Code fix might look like:

![image](https://github.com/oscal-compass/compliance-trestle/assets/520029/4822b780-7f8b-4534-b017-7ea5a93460d3)


## Completion Criteria

- Improved error message is provided.
- Test case coverage.


Pull request:
refactor: update the error message when set parameters have invalid values


## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [ ] Hot fix (emergency fix and release)
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Documentation (change which affects the documentation site)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Release (`develop` -> `main`)

## Quality assurance (all should be covered).

- [ ] My code follows the code style of this project.
- [ ] Documentation for my change is up to date?
- [ ] My PR meets testing requirements.
- [ ] All new and existing tests passed.
- [ ] All commits are signed-off.

## Summary

## Key links:

Resolved #1542
Note that I used `_execute()` instead of its wrapper `execute()` when testing because `execute()` would handle the exception and return `TaskOutcome('failure')`, preventing us to test the raised exception.

# Before you merge

- Ensure it is a 'squash commit' if not a release.
- Ensure CI is currently passing
- Check sonar. If you are working for a fork a maintainer will reach out, if required.


Patch:
diff --git a/trestle/tasks/csv_to_oscal_cd.py b/trestle/tasks/csv_to_oscal_cd.py
index 367359ca..a7343e02 100644
--- a/trestle/tasks/csv_to_oscal_cd.py
+++ b/trestle/tasks/csv_to_oscal_cd.py
@@ -673,12 +673,20 @@ class CsvToOscalComponentDefinition(TaskBase):
             name = self._csv_mgr.get_value(rule_key, parameter_id_column_name)
             value = self._csv_mgr.get_value(rule_key, parameter_value_default_column_name)
             if name and value:
-                values = self._str_to_list(value)
-                set_parameter = SetParameter(
-                    param_id=name,
-                    values=values,
-                )
-                set_parameters.append(set_parameter)
+                try:
+                    values = self._str_to_list(value)
+                    set_parameter = SetParameter(
+                        param_id=name,
+                        values=values,
+                    )
+                    set_parameters.append(set_parameter)
+                except Exception:
+                    row_number = self._csv_mgr.get_row_number(rule_key)
+                    text = (
+                        f'row {row_number}: "{name}" is invalid for column {parameter_id_column_name} '
+                        f'and/or "{",".join(values)}" is invalid for column {parameter_value_default_column_name}'
+                    )
+                    raise RuntimeError(text)
             elif name:
                 row_number = self._csv_mgr.get_row_number(rule_key)
                 text = f'row "{row_number}" missing value for "{parameter_value_default_column_name}"'


Test patch:
diff --git a/tests/trestle/tasks/csv_to_oscal_cd_test.py b/tests/trestle/tasks/csv_to_oscal_cd_test.py
index e2ebc1fa..3fd7d280 100644
--- a/tests/trestle/tasks/csv_to_oscal_cd_test.py
+++ b/tests/trestle/tasks/csv_to_oscal_cd_test.py
@@ -23,6 +23,8 @@ from unittest import mock
 
 from _pytest.monkeypatch import MonkeyPatch
 
+import pytest
+
 from tests import test_utils
 
 import trestle.tasks.csv_to_oscal_cd as csv_to_oscal_cd
@@ -1419,6 +1421,28 @@ def test_execute_validation(tmp_path: pathlib.Path) -> None:
     assert len(component.control_implementations) == 0
 
 
+def test_execute_invalid_set_parameter_values(tmp_path: pathlib.Path) -> None:
+    """Test execute invalid set parameter (PARAMETER_ID, Parameter_Value_Default) values."""
+    _, section = _get_config_section_init(tmp_path, 'test-csv-to-oscal-cd-bp.config')
+    # inject invalid set parameter values
+    rows = _get_rows('tests/data/csv/bp.sample.v2.csv')
+    assert rows[3][13] == 'allowed_admins_per_account'
+    assert rows[3][15] == '10'
+    rows[3][13] = 'allowed admins per account'
+    rows[3][15] = '10 '
+    with mock.patch('trestle.tasks.csv_to_oscal_cd.csv.reader') as mock_csv_reader:
+        mock_csv_reader.return_value = rows
+        tgt = csv_to_oscal_cd.CsvToOscalComponentDefinition(section)
+        with pytest.raises(RuntimeError) as exc_info:
+            tgt._execute()
+
+        exeception_value = str(exc_info.value)
+        assert exeception_value == (
+            'row 4: "allowed admins per account" is invalid for column Parameter_Id '
+            'and/or "10 " is invalid for column Parameter_Value_Default'
+        )
+
+
 def test_row_property_builder(tmp_path):
     """Test row property builder."""
     # valid