Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/test-runner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ jobs:
python-version: ${{ matrix.python-version }}

- name: Run mypy
if: matrix.python-version == '3.12'
run: |
pip install .
pip install -r requirements-dev.txt
pytest --mypy stac_validator
mypy stac_validator/

- name: Run pre-commit
if: matrix.python-version == 3.12
Expand Down
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ The format is (loosely) based on [Keep a Changelog](http://keepachangelog.com/)
## [Unreleased]


## [v3.9.1] - 2025-06-13

### Added
- `failed_schema` field to error messages for easier error handling [#260](https://github.com/stac-utils/stac-validator/pull/260)
- `recommendation` field to --verbose mode [#260](https://github.com/stac-utils/stac-validator/pull/260)

## [v3.9.0] - 2025-06-13

### Added
Expand Down Expand Up @@ -266,7 +272,8 @@ The format is (loosely) based on [Keep a Changelog](http://keepachangelog.com/)
- 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.
- Logging. Various additions were made here depending on the options selected. This was done to help assist people to update their STAC collections.

[Unreleased]: https://github.com/sparkgeo/stac-validator/compare/v3.9.0..main
[Unreleased]: https://github.com/sparkgeo/stac-validator/compare/v3.9.1..main
[v3.9.1]: https://github.com/sparkgeo/stac-validator/compare/v3.9.0..v3.9.1
[v3.9.0]: https://github.com/sparkgeo/stac-validator/compare/v3.8.1..v3.9.0
[v3.8.1]: https://github.com/sparkgeo/stac-validator/compare/v3.8.0..v3.8.1
[v3.8.0]: https://github.com/sparkgeo/stac-validator/compare/v3.7.0..v3.8.0
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from setuptools import setup

__version__ = "3.9.0"
__version__ = "3.9.1"

with open("README.md", "r") as fh:
long_description = fh.read()
Expand Down
50 changes: 41 additions & 9 deletions stac_validator/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,21 +267,39 @@ def create_err_msg(
schema_value = str(self.schema)
schema_field: List[str] = [schema_value] if schema_value else []

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

# Try to extract the failed schema from the error message if it's a validation error
if error_obj and hasattr(error_obj, "schema"):
if isinstance(error_obj.schema, dict) and "$id" in error_obj.schema:
message["failed_schema"] = error_obj.schema["$id"]
elif hasattr(error_obj, "schema_url"):
message["failed_schema"] = error_obj.schema_url
# If we can't find a schema ID, try to get it from the schema map
elif schema_field and len(schema_field) == 1:
message["failed_schema"] = schema_field[0]

if self.verbose and error_obj is not None:
verbose_err = self._create_verbose_err_msg(error_obj)
if isinstance(verbose_err, dict):
message["error_verbose"] = verbose_err
else:
message["error_verbose"] = {"detail": str(verbose_err)}
# Add recommendation to check the schema if the error is not clear
if "failed_schema" in message and message["failed_schema"]:
message["recommendation"] = (
"If the error is unclear, please check the schema documentation at: "
f"{message['failed_schema']}"
)
else:
message["recommendation"] = (
"For more accurate error information, rerun with --verbose."
Expand Down Expand Up @@ -459,25 +477,39 @@ def extensions_validator(self, stac_type: str) -> Dict:
if e.context:
e = best_match(e.context) # type: ignore
valid = False
if e.absolute_path:
err_msg = (
f"{e.message}. Error is in "
f"{' -> '.join(map(str, e.absolute_path))} "
)
else:
err_msg = f"{e.message}"
# Get the current schema (extension) that caused the validation error
failed_schema = self._original_schema_paths.get(extension, extension)
# Build the error message with path information
path_info = (
f"Error is in {' -> '.join(map(str, e.absolute_path))} "
if e.absolute_path
else ""
)
err_msg = f"{e.message}. {path_info}"

# Create the error message with the original error object
message = self.create_err_msg(
err_type="JSONSchemaValidationError",
err_msg=err_msg,
error_obj=verbose_error,
)

# Set the failed_schema in the message if we have it
if failed_schema:
message["failed_schema"] = failed_schema
return message

except Exception as e:
if self.recursive:
raise
valid = False
err_msg = f"{e}. Error in Extensions."
# Include the current schema in the error message for other types of exceptions
current_schema = (
self._original_schema_paths.get(extension, extension)
if "extension" in locals()
else "unknown schema"
)
err_msg = f"{e} [Schema: {current_schema}]. Error in Extensions."
return self.create_err_msg(
err_type="Exception", err_msg=err_msg, error_obj=e
)
Expand Down
1 change: 1 addition & 0 deletions tests/test_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def test_assets_v090():
],
"valid_stac": False,
"error_type": "JSONSchemaValidationError",
"failed_schema": "https://cdn.staclint.com/v0.9.0/extension/view.json",
"error_message": "-0.00751271 is less than the minimum of 0. Error is in properties -> view:off_nadir ",
"recommendation": "For more accurate error information, rerun with --verbose.",
"validation_method": "default",
Expand Down
1 change: 1 addition & 0 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def test_core_bad_item_local_v090():
"schema": ["https://cdn.staclint.com/v0.9.0/item.json"],
"valid_stac": False,
"error_type": "JSONSchemaValidationError",
"failed_schema": "https://cdn.staclint.com/v0.9.0/item.json",
"error_message": "'id' is a required property",
"recommendation": "For more accurate error information, rerun with --verbose.",
}
Expand Down
2 changes: 2 additions & 0 deletions tests/test_custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def test_custom_item_remote_schema_v080():
"validation_method": "custom",
"valid_stac": False,
"error_type": "JSONSchemaValidationError",
"failed_schema": "https://cdn.staclint.com/v0.8.0/item.json",
"error_message": "'bbox' is a required property",
"recommendation": "For more accurate error information, rerun with --verbose.",
}
Expand Down Expand Up @@ -75,6 +76,7 @@ def test_custom_bad_item_remote_schema_v090():
"schema": ["https://cdn.staclint.com/v0.9.0/item.json"],
"valid_stac": False,
"error_type": "JSONSchemaValidationError",
"failed_schema": "https://cdn.staclint.com/v0.9.0/item.json",
"error_message": "'id' is a required property",
"recommendation": "For more accurate error information, rerun with --verbose.",
}
Expand Down
210 changes: 210 additions & 0 deletions tests/test_data/v110/extended-item.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
{
"stac_version": "1.1.0",
"stac_extensions": [
"https://stac-extensions.github.io/eo/v2.0.0/schema.json",
"https://stac-extensions.github.io/projection/v2.0.0/schema.json",
"https://stac-extensions.github.io/scientific/v1.0.0/schema.json",
"https://stac-extensions.github.io/view/v1.0.0/schema.json",
"https://stac-extensions.github.io/remote-data/v1.0.0/schema.json"
],
"type": "Feature",
"id": "20201211_223832_CS2",
"bbox": [
172.91173669923782,
1.3438851951615003,
172.95469614953714,
1.3690476620161975
],
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
172.91173669923782,
1.3438851951615003
],
[
172.95469614953714,
1.3438851951615003
],
[
172.95469614953714,
1.3690476620161975
],
[
172.91173669923782,
1.3690476620161975
],
[
172.91173669923782,
1.3438851951615003
]
]
]
},
"properties": {
"title": "Extended Item",
"description": "A sample STAC Item that includes a variety of examples from the stable extensions",
"keywords": [
"extended",
"example",
"item"
],
"datetime": "2020-12-14T18:02:31.437000Z",
"created": "2020-12-15T01:48:13.725Z",
"updated": "2020-12-15T01:48:13.725Z",
"platform": "cool_sat2",
"instruments": [
"cool_sensor_v2"
],
"gsd": 0.66,
"eo:cloud_cover": 1.2,
"eo:snow_cover": 0,
"statistics": {
"vegetation": 12.57,
"water": 1.23,
"urban": 26.2
},
"proj:code": "EPSG:32659",
"proj:shape": [
5558,
9559
],
"proj:transform": [
0.5,
0,
712710,
0,
-0.5,
151406,
0,
0,
1
],
"view:sun_elevation": 54.9,
"view:off_nadir": 3.8,
"view:sun_azimuth": 135.7,
"rd:type": "scene",
"rd:anomalous_pixels": 0.14,
"rd:earth_sun_distance": 1.014156,
"rd:sat_id": "cool_sat2",
"rd:product_level": "LV3A",
"sci:doi": "10.5061/dryad.s2v81.2/27.2"
},
"collection": "simple-collection",
"links": [
{
"rel": "collection",
"href": "./collection.json",
"type": "application/json",
"title": "Simple Example Collection"
},
{
"rel": "root",
"href": "./collection.json",
"type": "application/json",
"title": "Simple Example Collection"
},
{
"rel": "parent",
"href": "./collection.json",
"type": "application/json",
"title": "Simple Example Collection"
},
{
"rel": "alternate",
"type": "text/html",
"href": "http://remotedata.io/catalog/20201211_223832_CS2/index.html",
"title": "HTML version of this STAC Item"
}
],
"assets": {
"analytic": {
"href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic.tif",
"type": "image/tiff; application=geotiff; profile=cloud-optimized",
"title": "4-Band Analytic",
"roles": [
"data"
],
"bands": [
{
"name": "band1",
"eo:common_name": "blue",
"eo:center_wavelength": 0.47,
"eo:full_width_half_max": 70
},
{
"name": "band2",
"eo:common_name": "green",
"eo:center_wavelength": 0.56,
"eo:full_width_half_max": 80
},
{
"name": "band3",
"eo:common_name": "red",
"eo:center_wavelength": 0.645,
"eo:full_width_half_max": 90
},
{
"name": "band4",
"eo:common_name": "nir",
"eo:center_wavelength": 0.8,
"eo:full_width_half_max": 152
}
]
},
"thumbnail": {
"href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.jpg",
"title": "Thumbnail",
"type": "image/png",
"roles": [
"thumbnail"
]
},
"visual": {
"href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.tif",
"type": "image/tiff; application=geotiff; profile=cloud-optimized",
"title": "3-Band Visual",
"roles": [
"visual"
],
"bands": [
{
"name": "band3",
"eo:common_name": "red",
"eo:center_wavelength": 0.645,
"eo:full_width_half_max": 90
},
{
"name": "band2",
"eo:common_name": "green",
"eo:center_wavelength": 0.56,
"eo:full_width_half_max": 80
},
{
"name": "band1",
"eo:common_name": "blue",
"eo:center_wavelength": 0.47,
"eo:full_width_half_max": 70
}
]
},
"udm": {
"href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic_udm.tif",
"title": "Unusable Data Mask",
"type": "image/tiff; application=geotiff"
},
"json-metadata": {
"href": "http://remotedata.io/catalog/20201211_223832_CS2/extended-metadata.json",
"title": "Extended Metadata",
"type": "application/json",
"roles": [
"metadata"
]
},
"ephemeris": {
"href": "http://cool-sat.com/catalog/20201211_223832_CS2/20201211_223832_CS2.EPH",
"title": "Satellite Ephemeris Metadata"
}
}
}
Loading