Skip to content

Commit acef147

Browse files
authored
Improved error messaging (#260)
* failed_schema field to error messages for easier error handling * recommendation field to --verbose mode * update to v3.9.1
1 parent cd6225e commit acef147

File tree

13 files changed

+697
-21
lines changed

13 files changed

+697
-21
lines changed

.github/workflows/test-runner.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ jobs:
2626
python-version: ${{ matrix.python-version }}
2727

2828
- name: Run mypy
29+
if: matrix.python-version == '3.12'
2930
run: |
3031
pip install .
3132
pip install -r requirements-dev.txt
32-
pytest --mypy stac_validator
33+
mypy stac_validator/
3334
3435
- name: Run pre-commit
3536
if: matrix.python-version == 3.12

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ The format is (loosely) based on [Keep a Changelog](http://keepachangelog.com/)
77
## [Unreleased]
88

99

10+
## [v3.9.1] - 2025-06-13
11+
12+
### Added
13+
- `failed_schema` field to error messages for easier error handling [#260](https://github.com/stac-utils/stac-validator/pull/260)
14+
- `recommendation` field to --verbose mode [#260](https://github.com/stac-utils/stac-validator/pull/260)
15+
1016
## [v3.9.0] - 2025-06-13
1117

1218
### Added
@@ -266,7 +272,8 @@ The format is (loosely) based on [Keep a Changelog](http://keepachangelog.com/)
266272
- With the newest version - 1.0.0-beta.2 - items will run through jsonchema validation before the PySTAC validation. The reason for this is that jsonschema will give more informative error messages. This should be addressed better in the future. This is not the case with the --recursive option as time can be a concern here with larger collections.
267273
- Logging. Various additions were made here depending on the options selected. This was done to help assist people to update their STAC collections.
268274

269-
[Unreleased]: https://github.com/sparkgeo/stac-validator/compare/v3.9.0..main
275+
[Unreleased]: https://github.com/sparkgeo/stac-validator/compare/v3.9.1..main
276+
[v3.9.1]: https://github.com/sparkgeo/stac-validator/compare/v3.9.0..v3.9.1
270277
[v3.9.0]: https://github.com/sparkgeo/stac-validator/compare/v3.8.1..v3.9.0
271278
[v3.8.1]: https://github.com/sparkgeo/stac-validator/compare/v3.8.0..v3.8.1
272279
[v3.8.0]: https://github.com/sparkgeo/stac-validator/compare/v3.7.0..v3.8.0

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from setuptools import setup
44

5-
__version__ = "3.9.0"
5+
__version__ = "3.9.1"
66

77
with open("README.md", "r") as fh:
88
long_description = fh.read()

stac_validator/validate.py

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -267,21 +267,39 @@ def create_err_msg(
267267
schema_value = str(self.schema)
268268
schema_field: List[str] = [schema_value] if schema_value else []
269269

270+
# Initialize the message with common fields
270271
message: Dict[str, Union[str, bool, List[str], Dict[str, Any]]] = {
271272
"version": version_str,
272273
"path": path_str,
273-
"schema": schema_field, # Ensure schema is a list of strings or None
274+
"schema": schema_field, # All schemas that were checked
274275
"valid_stac": False,
275276
"error_type": err_type,
276277
"error_message": err_msg,
278+
"failed_schema": "", # Will be populated if we can determine which schema failed
277279
}
278280

281+
# Try to extract the failed schema from the error message if it's a validation error
282+
if error_obj and hasattr(error_obj, "schema"):
283+
if isinstance(error_obj.schema, dict) and "$id" in error_obj.schema:
284+
message["failed_schema"] = error_obj.schema["$id"]
285+
elif hasattr(error_obj, "schema_url"):
286+
message["failed_schema"] = error_obj.schema_url
287+
# If we can't find a schema ID, try to get it from the schema map
288+
elif schema_field and len(schema_field) == 1:
289+
message["failed_schema"] = schema_field[0]
290+
279291
if self.verbose and error_obj is not None:
280292
verbose_err = self._create_verbose_err_msg(error_obj)
281293
if isinstance(verbose_err, dict):
282294
message["error_verbose"] = verbose_err
283295
else:
284296
message["error_verbose"] = {"detail": str(verbose_err)}
297+
# Add recommendation to check the schema if the error is not clear
298+
if "failed_schema" in message and message["failed_schema"]:
299+
message["recommendation"] = (
300+
"If the error is unclear, please check the schema documentation at: "
301+
f"{message['failed_schema']}"
302+
)
285303
else:
286304
message["recommendation"] = (
287305
"For more accurate error information, rerun with --verbose."
@@ -459,25 +477,39 @@ def extensions_validator(self, stac_type: str) -> Dict:
459477
if e.context:
460478
e = best_match(e.context) # type: ignore
461479
valid = False
462-
if e.absolute_path:
463-
err_msg = (
464-
f"{e.message}. Error is in "
465-
f"{' -> '.join(map(str, e.absolute_path))} "
466-
)
467-
else:
468-
err_msg = f"{e.message}"
480+
# Get the current schema (extension) that caused the validation error
481+
failed_schema = self._original_schema_paths.get(extension, extension)
482+
# Build the error message with path information
483+
path_info = (
484+
f"Error is in {' -> '.join(map(str, e.absolute_path))} "
485+
if e.absolute_path
486+
else ""
487+
)
488+
err_msg = f"{e.message}. {path_info}"
489+
490+
# Create the error message with the original error object
469491
message = self.create_err_msg(
470492
err_type="JSONSchemaValidationError",
471493
err_msg=err_msg,
472494
error_obj=verbose_error,
473495
)
496+
497+
# Set the failed_schema in the message if we have it
498+
if failed_schema:
499+
message["failed_schema"] = failed_schema
474500
return message
475501

476502
except Exception as e:
477503
if self.recursive:
478504
raise
479505
valid = False
480-
err_msg = f"{e}. Error in Extensions."
506+
# Include the current schema in the error message for other types of exceptions
507+
current_schema = (
508+
self._original_schema_paths.get(extension, extension)
509+
if "extension" in locals()
510+
else "unknown schema"
511+
)
512+
err_msg = f"{e} [Schema: {current_schema}]. Error in Extensions."
481513
return self.create_err_msg(
482514
err_type="Exception", err_msg=err_msg, error_obj=e
483515
)

tests/test_assets.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ def test_assets_v090():
2222
],
2323
"valid_stac": False,
2424
"error_type": "JSONSchemaValidationError",
25+
"failed_schema": "https://cdn.staclint.com/v0.9.0/extension/view.json",
2526
"error_message": "-0.00751271 is less than the minimum of 0. Error is in properties -> view:off_nadir ",
2627
"recommendation": "For more accurate error information, rerun with --verbose.",
2728
"validation_method": "default",

tests/test_core.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ def test_core_bad_item_local_v090():
8383
"schema": ["https://cdn.staclint.com/v0.9.0/item.json"],
8484
"valid_stac": False,
8585
"error_type": "JSONSchemaValidationError",
86+
"failed_schema": "https://cdn.staclint.com/v0.9.0/item.json",
8687
"error_message": "'id' is a required property",
8788
"recommendation": "For more accurate error information, rerun with --verbose.",
8889
}

tests/test_custom.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def test_custom_item_remote_schema_v080():
2020
"validation_method": "custom",
2121
"valid_stac": False,
2222
"error_type": "JSONSchemaValidationError",
23+
"failed_schema": "https://cdn.staclint.com/v0.8.0/item.json",
2324
"error_message": "'bbox' is a required property",
2425
"recommendation": "For more accurate error information, rerun with --verbose.",
2526
}
@@ -75,6 +76,7 @@ def test_custom_bad_item_remote_schema_v090():
7576
"schema": ["https://cdn.staclint.com/v0.9.0/item.json"],
7677
"valid_stac": False,
7778
"error_type": "JSONSchemaValidationError",
79+
"failed_schema": "https://cdn.staclint.com/v0.9.0/item.json",
7880
"error_message": "'id' is a required property",
7981
"recommendation": "For more accurate error information, rerun with --verbose.",
8082
}
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
{
2+
"stac_version": "1.1.0",
3+
"stac_extensions": [
4+
"https://stac-extensions.github.io/eo/v2.0.0/schema.json",
5+
"https://stac-extensions.github.io/projection/v2.0.0/schema.json",
6+
"https://stac-extensions.github.io/scientific/v1.0.0/schema.json",
7+
"https://stac-extensions.github.io/view/v1.0.0/schema.json",
8+
"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json"
9+
],
10+
"type": "Feature",
11+
"id": "20201211_223832_CS2",
12+
"bbox": [
13+
172.91173669923782,
14+
1.3438851951615003,
15+
172.95469614953714,
16+
1.3690476620161975
17+
],
18+
"geometry": {
19+
"type": "Polygon",
20+
"coordinates": [
21+
[
22+
[
23+
172.91173669923782,
24+
1.3438851951615003
25+
],
26+
[
27+
172.95469614953714,
28+
1.3438851951615003
29+
],
30+
[
31+
172.95469614953714,
32+
1.3690476620161975
33+
],
34+
[
35+
172.91173669923782,
36+
1.3690476620161975
37+
],
38+
[
39+
172.91173669923782,
40+
1.3438851951615003
41+
]
42+
]
43+
]
44+
},
45+
"properties": {
46+
"title": "Extended Item",
47+
"description": "A sample STAC Item that includes a variety of examples from the stable extensions",
48+
"keywords": [
49+
"extended",
50+
"example",
51+
"item"
52+
],
53+
"datetime": "2020-12-14T18:02:31.437000Z",
54+
"created": "2020-12-15T01:48:13.725Z",
55+
"updated": "2020-12-15T01:48:13.725Z",
56+
"platform": "cool_sat2",
57+
"instruments": [
58+
"cool_sensor_v2"
59+
],
60+
"gsd": 0.66,
61+
"eo:cloud_cover": 1.2,
62+
"eo:snow_cover": 0,
63+
"statistics": {
64+
"vegetation": 12.57,
65+
"water": 1.23,
66+
"urban": 26.2
67+
},
68+
"proj:code": "EPSG:32659",
69+
"proj:shape": [
70+
5558,
71+
9559
72+
],
73+
"proj:transform": [
74+
0.5,
75+
0,
76+
712710,
77+
0,
78+
-0.5,
79+
151406,
80+
0,
81+
0,
82+
1
83+
],
84+
"view:sun_elevation": 54.9,
85+
"view:off_nadir": 3.8,
86+
"view:sun_azimuth": 135.7,
87+
"rd:type": "scene",
88+
"rd:anomalous_pixels": 0.14,
89+
"rd:earth_sun_distance": 1.014156,
90+
"rd:sat_id": "cool_sat2",
91+
"rd:product_level": "LV3A",
92+
"sci:doi": "10.5061/dryad.s2v81.2/27.2"
93+
},
94+
"collection": "simple-collection",
95+
"links": [
96+
{
97+
"rel": "collection",
98+
"href": "./collection.json",
99+
"type": "application/json",
100+
"title": "Simple Example Collection"
101+
},
102+
{
103+
"rel": "root",
104+
"href": "./collection.json",
105+
"type": "application/json",
106+
"title": "Simple Example Collection"
107+
},
108+
{
109+
"rel": "parent",
110+
"href": "./collection.json",
111+
"type": "application/json",
112+
"title": "Simple Example Collection"
113+
},
114+
{
115+
"rel": "alternate",
116+
"type": "text/html",
117+
"href": "http://remotedata.io/catalog/20201211_223832_CS2/index.html",
118+
"title": "HTML version of this STAC Item"
119+
}
120+
],
121+
"assets": {
122+
"analytic": {
123+
"href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic.tif",
124+
"type": "image/tiff; application=geotiff; profile=cloud-optimized",
125+
"title": "4-Band Analytic",
126+
"roles": [
127+
"data"
128+
],
129+
"bands": [
130+
{
131+
"name": "band1",
132+
"eo:common_name": "blue",
133+
"eo:center_wavelength": 0.47,
134+
"eo:full_width_half_max": 70
135+
},
136+
{
137+
"name": "band2",
138+
"eo:common_name": "green",
139+
"eo:center_wavelength": 0.56,
140+
"eo:full_width_half_max": 80
141+
},
142+
{
143+
"name": "band3",
144+
"eo:common_name": "red",
145+
"eo:center_wavelength": 0.645,
146+
"eo:full_width_half_max": 90
147+
},
148+
{
149+
"name": "band4",
150+
"eo:common_name": "nir",
151+
"eo:center_wavelength": 0.8,
152+
"eo:full_width_half_max": 152
153+
}
154+
]
155+
},
156+
"thumbnail": {
157+
"href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.jpg",
158+
"title": "Thumbnail",
159+
"type": "image/png",
160+
"roles": [
161+
"thumbnail"
162+
]
163+
},
164+
"visual": {
165+
"href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.tif",
166+
"type": "image/tiff; application=geotiff; profile=cloud-optimized",
167+
"title": "3-Band Visual",
168+
"roles": [
169+
"visual"
170+
],
171+
"bands": [
172+
{
173+
"name": "band3",
174+
"eo:common_name": "red",
175+
"eo:center_wavelength": 0.645,
176+
"eo:full_width_half_max": 90
177+
},
178+
{
179+
"name": "band2",
180+
"eo:common_name": "green",
181+
"eo:center_wavelength": 0.56,
182+
"eo:full_width_half_max": 80
183+
},
184+
{
185+
"name": "band1",
186+
"eo:common_name": "blue",
187+
"eo:center_wavelength": 0.47,
188+
"eo:full_width_half_max": 70
189+
}
190+
]
191+
},
192+
"udm": {
193+
"href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic_udm.tif",
194+
"title": "Unusable Data Mask",
195+
"type": "image/tiff; application=geotiff"
196+
},
197+
"json-metadata": {
198+
"href": "http://remotedata.io/catalog/20201211_223832_CS2/extended-metadata.json",
199+
"title": "Extended Metadata",
200+
"type": "application/json",
201+
"roles": [
202+
"metadata"
203+
]
204+
},
205+
"ephemeris": {
206+
"href": "http://cool-sat.com/catalog/20201211_223832_CS2/20201211_223832_CS2.EPH",
207+
"title": "Satellite Ephemeris Metadata"
208+
}
209+
}
210+
}

0 commit comments

Comments
 (0)