Skip to content

Commit

Permalink
fix: The path of the offending field is now printed for config valida…
Browse files Browse the repository at this point in the history
…tion errors (#2778)
  • Loading branch information
edgarrmondragon authored Nov 27, 2024
1 parent b425274 commit 8474a24
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 1 deletion.
24 changes: 23 additions & 1 deletion singer_sdk/plugin_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
extend_validator_with_defaults,
)

if t.TYPE_CHECKING:
from jsonschema import ValidationError

SDK_PACKAGE_NAME = "singer_sdk"

JSONSchemaValidator = extend_validator_with_defaults(DEFAULT_JSONSCHEMA_VALIDATOR)
Expand Down Expand Up @@ -88,6 +91,23 @@ def invoke(self, ctx: click.Context) -> t.Any: # noqa: ANN401
sys.exit(1)


def _format_validation_error(error: ValidationError) -> str:
"""Format a JSON Schema validation error.
Args:
error: A JSON Schema validation error.
Returns:
A formatted error message.
"""
result = f"{error.message}"

if error.path:
result += f" in config[{']['.join(repr(index) for index in error.path)}]"

return result


class PluginBase(metaclass=abc.ABCMeta): # noqa: PLR0904
"""Abstract base class for taps."""

Expand Down Expand Up @@ -402,7 +422,9 @@ def _validate_config(self, *, raise_errors: bool = True) -> list[str]:
config_jsonschema,
)
validator = JSONSchemaValidator(config_jsonschema)
errors = [e.message for e in validator.iter_errors(self._config)]
errors = [
_format_validation_error(e) for e in validator.iter_errors(self._config)
]

if errors:
summary = (
Expand Down
18 changes: 18 additions & 0 deletions tests/core/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
from singer_sdk import Stream, Tap
from singer_sdk.helpers._compat import datetime_fromisoformat
from singer_sdk.typing import (
ArrayType,
DateTimeType,
IntegerType,
ObjectType,
PropertiesList,
Property,
StringType,
Expand Down Expand Up @@ -82,6 +84,22 @@ class SimpleTestTap(Tap):
Property("username", StringType, required=True),
Property("password", StringType, required=True),
Property("start_date", DateTimeType),
Property(
"nested",
ObjectType(
Property("key", StringType, required=True),
),
required=False,
),
Property(
"array",
ArrayType(
ObjectType(
Property("key", StringType, required=True),
),
),
required=False,
),
additional_properties=False,
).to_dict()

Expand Down
24 changes: 24 additions & 0 deletions tests/core/test_tap_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,30 @@
["Additional properties are not allowed ('extra' was unexpected)"],
id="extra_property",
),
pytest.param(
{"username": None, "password": "ptest"},
pytest.raises(ConfigValidationError, match="Config validation failed"),
["None is not of type 'string' in config['username']"],
id="null_username",
),
pytest.param(
{"username": "utest", "password": "ptest", "nested": {}},
pytest.raises(ConfigValidationError, match="Config validation failed"),
["'key' is a required property in config['nested']"],
id="missing_required_nested_key",
),
pytest.param(
{"username": "utest", "password": "ptest", "array": []},
nullcontext(),
[],
id="empty_array",
),
pytest.param(
{"username": "utest", "password": "ptest", "array": [{}]},
pytest.raises(ConfigValidationError, match="Config validation failed"),
["'key' is a required property in config['array'][0]"],
id="array_with_empty_object",
),
pytest.param(
{"username": "utest", "password": "ptest"},
nullcontext(),
Expand Down

0 comments on commit 8474a24

Please sign in to comment.