Skip to content

Commit d58ead2

Browse files
authored
Create universal verbose mode for better error messaging (#257)
* use trace-recursion * add verbose flag * add verbose message * add verbose dict * improve oneof error messaging * update tests, add validation errors to verbose mode * mypy * types * update changelog issue num
1 parent dda8529 commit d58ead2

File tree

13 files changed

+293
-41
lines changed

13 files changed

+293
-41
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ The format is (loosely) based on [Keep a Changelog](http://keepachangelog.com/)
66

77
## [Unreleased]
88

9+
### Added
10+
- Added separate `recommendation` field in error messages when running in non-verbose mode [#257](https://github.com/stac-utils/stac-validator/pull/257)
11+
- Verbose error messages for JSONSchemaValidationErrors [#257](https://github.com/stac-utils/stac-validator/pull/257)
12+
13+
### Changed
14+
- Changed --verbose for recursive mode to --trace-recursive to make way for more comprehensive error messaging [#257](https://github.com/stac-utils/stac-validator/pull/257)
15+
916
## [v3.8.1] - 2025-06-04
1017

1118
### Fixed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,13 +172,15 @@ Options:
172172
be used multiple times.
173173
-p, --pages INTEGER Maximum number of pages to validate via
174174
--item-collection. Defaults to one page.
175-
-v, --verbose Enables verbose output for recursive mode.
175+
-t, --trace-recursion Enables verbose output for recursive mode.
176176
--no_output Do not print output to console.
177177
--log_file TEXT Save full recursive output to log file
178178
(local filepath).
179179
--pydantic Validate using stac-pydantic models for enhanced
180180
type checking and validation.
181181
--schema-config TEXT Path to a YAML or JSON schema config file.
182+
--verbose Enable verbose output. This will output
183+
additional information during validation.
182184
--help Show this message and exit.
183185
```
184186
@@ -370,7 +372,7 @@ $ stac-validator https://raw.githubusercontent.com/radiantearth/stac-spec/master
370372
### --recursive
371373
372374
```bash
373-
$ stac-validator https://spot-canada-ortho.s3.amazonaws.com/catalog.json --recursive --max-depth 1 --verbose
375+
$ stac-validator https://spot-canada-ortho.s3.amazonaws.com/catalog.json --recursive --max-depth 1 --trace-recursion
374376
```
375377
376378
```bash

setup.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,7 @@
3333
"pyYAML>=6.0.1",
3434
],
3535
extras_require={
36-
"dev": [
37-
"pytest",
38-
"requests-mock",
39-
"types-setuptools",
40-
],
36+
"dev": ["pytest", "requests-mock", "types-setuptools", "stac-pydantic>=3.3.0"],
4137
"pydantic": [
4238
"stac-pydantic>=3.3.0",
4339
],

stac_validator/stac_validator.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,10 @@ def collections_summary(message: List[Dict[str, Any]]) -> None:
140140
help="Maximum number of pages to validate via --item-collection. Defaults to one page.",
141141
)
142142
@click.option(
143-
"-v", "--verbose", is_flag=True, help="Enables verbose output for recursive mode."
143+
"-t",
144+
"--trace-recursion",
145+
is_flag=True,
146+
help="Enables verbose output for recursive mode.",
144147
)
145148
@click.option("--no_output", is_flag=True, help="Do not print output to console.")
146149
@click.option(
@@ -153,6 +156,11 @@ def collections_summary(message: List[Dict[str, Any]]) -> None:
153156
is_flag=True,
154157
help="Validate using stac-pydantic models for enhanced type checking and validation.",
155158
)
159+
@click.option(
160+
"--verbose",
161+
is_flag=True,
162+
help="Enable verbose output. This will output additional information during validation.",
163+
)
156164
def main(
157165
stac_file: str,
158166
collections: bool,
@@ -169,10 +177,11 @@ def main(
169177
custom: str,
170178
schema_config: str,
171179
schema_map: List[Tuple],
172-
verbose: bool,
180+
trace_recursion: bool,
173181
no_output: bool,
174182
log_file: str,
175183
pydantic: bool,
184+
verbose: bool = False,
176185
) -> None:
177186
"""Main function for the `stac-validator` command line tool. Validates a STAC file
178187
against the STAC specification and prints the validation results to the console as JSON.
@@ -193,10 +202,11 @@ def main(
193202
custom (str): Path to a custom schema file to validate against.
194203
schema_config (str): Path to a custom schema config file to validate against.
195204
schema_map (list(tuple)): List of tuples each having two elememts. First element is the schema path to be replaced by the path in the second element.
196-
verbose (bool): Whether to enable verbose output for recursive mode.
205+
trace_recursion (bool): Whether to enable verbose output for recursive mode.
197206
no_output (bool): Whether to print output to console.
198207
log_file (str): Path to a log file to save full recursive output.
199208
pydantic (bool): Whether to validate using stac-pydantic models for enhanced type checking and validation.
209+
verbose (bool): Whether to enable verbose output. This will output additional information during validation.
200210
201211
Returns:
202212
None
@@ -226,9 +236,10 @@ def main(
226236
custom=custom,
227237
schema_config=schema_config,
228238
schema_map=schema_map_dict,
229-
verbose=verbose,
239+
trace_recursion=trace_recursion,
230240
log=log_file,
231241
pydantic=pydantic,
242+
verbose=verbose,
232243
)
233244
if not item_collection and not collections:
234245
valid = stac.run()

stac_validator/utilities.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
import requests # type: ignore
1010
import yaml # type: ignore
1111
from jsonschema import Draft202012Validator
12-
from referencing import Registry, Resource
13-
from referencing.jsonschema import DRAFT202012
14-
from referencing.typing import URI
12+
from referencing import Registry, Resource # type: ignore
13+
from referencing.jsonschema import DRAFT202012 # type: ignore
14+
from referencing.typing import URI # type: ignore
1515

1616
NEW_VERSIONS = [
1717
"1.0.0-beta.2",
@@ -292,3 +292,37 @@ def load_schema_config(config_path: str) -> dict:
292292
if "schemas" in data:
293293
return data["schemas"]
294294
return data
295+
296+
297+
def extract_relevant_oneof_error(error, instance=None):
298+
"""Extract the most relevant error from a 'oneOf' validation error.
299+
300+
Given a jsonschema.ValidationError for a 'oneOf' failure, this function returns
301+
the most relevant sub-error, with preference given to errors matching the instance's 'type'.
302+
If no matching type is found, it falls back to returning the first sub-error.
303+
304+
Args:
305+
error (jsonschema.ValidationError): The validation error from a 'oneOf' validation.
306+
instance (dict, optional): The instance being validated. If provided and contains a 'type'
307+
field, the function will try to find a matching schema for that type. Defaults to None.
308+
309+
Returns:
310+
jsonschema.ValidationError: The most relevant sub-error from the 'oneOf' validation.
311+
If the error is not a 'oneOf' validation error or has no context, returns the
312+
original error unchanged.
313+
"""
314+
if error.validator == "oneOf" and hasattr(error, "context") and error.context:
315+
if instance and "type" in instance:
316+
for suberror in error.context:
317+
# Try to match the instance 'type' to the schema's 'type'
318+
props = suberror.schema.get("properties", {})
319+
type_schema = props.get("type", {})
320+
if (
321+
isinstance(type_schema, dict)
322+
and "const" in type_schema
323+
and instance["type"] == type_schema["const"]
324+
):
325+
return suberror
326+
# Fallback to the first suberror
327+
return error.context[0]
328+
return error

0 commit comments

Comments
 (0)