Skip to content

Commit d282a19

Browse files
authored
Merge pull request #276 from stac-utils/pv/cleanup-extensions-validation
cleanup extensions validation
2 parents 07035a7 + 58fe7c1 commit d282a19

File tree

2 files changed

+81
-60
lines changed

2 files changed

+81
-60
lines changed

src/stac_api_validator/__main__.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@
4444
"collections",
4545
"children",
4646
"filter",
47+
"item-search#sort",
48+
"item-search#fields",
49+
"item-search#query",
50+
"features#sort",
51+
"features#fields",
52+
"features#query",
53+
"transaction",
4754
],
4855
case_sensitive=False,
4956
),
@@ -55,7 +62,7 @@
5562
)
5663
@click.option(
5764
"--auth-query-parameter",
58-
help="Query pararmeter key and value to pass for authorization, e.g., 'key=xyz'.",
65+
help="Query parameter key and value to pass for authorization, e.g., 'key=xyz'.",
5966
)
6067
def main(
6168
log_level: str,
@@ -72,7 +79,7 @@ def main(
7279
try:
7380
(warnings, errors) = validate_api(
7481
root_url=root_url,
75-
conformance_classes=conformance_classes,
82+
ccs_to_validate=conformance_classes,
7683
collection=collection,
7784
geometry=geometry,
7885
auth_bearer_token=auth_bearer_token,

src/stac_api_validator/validations.py

Lines changed: 72 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,17 @@ class Context(Enum):
9595
ITEM_SEARCH = "Item Search"
9696
FEATURES = "Features"
9797
COLLECTIONS = "Collections"
98-
ITEM_SEARCH_FILTER = "Item Search - Filter Ext"
99-
FEATURES_FILTER = "Features - Filter Ext"
10098
CHILDREN = "Children Ext"
10199
BROWSEABLE = "Browseable Ext"
100+
ITEM_SEARCH_FILTER = "Item Search - Filter Ext"
101+
ITEM_SEARCH_SORT = "Item Search - Sort Ext"
102+
ITEM_SEARCH_FIELDS = "Item Search - Fields Ext"
103+
ITEM_SEARCH_QUERY = "Item Search - Query Ext"
104+
FEATURES_FILTER = "Features - Filter Ext"
105+
FEATURES_SORT = "Features - Sort Ext"
106+
FEATURES_FIELDS = "Features - Fields Ext"
107+
FEATURES_QUERY = "Features - Query Ext"
108+
FEATURES_TXN = "Features - Transaction Ext"
102109

103110
def __str__(self) -> str:
104111
return self.value
@@ -148,19 +155,14 @@ def __iadd__(self, x: Union[Tuple[str, str], str]) -> "Warnings":
148155
cc_core_regex = re.compile(r"https://api\.stacspec\.org/(.+)/core")
149156
cc_browseable_regex = re.compile(r"https://api\.stacspec\.org/(.+)/browseable")
150157
cc_children_regex = re.compile(r"https://api\.stacspec\.org/(.+)/children")
151-
152158
cc_collections_regex = re.compile(r"https://api\.stacspec\.org/(.+)/collections")
153-
154159
cc_features_regex = re.compile(r"https://api\.stacspec\.org/(.+)/ogcapi-features")
155160
cc_features_transaction_regex = re.compile(
156161
r"https://api\.stacspec\.org/(.+)/ogcapi-features/extensions/transaction"
157162
)
158163
cc_features_fields_regex = re.compile(
159164
r"https://api\.stacspec\.org/(.+)/ogcapi-features#fields"
160165
)
161-
cc_features_context_regex = re.compile(
162-
r"https://api\.stacspec\.org/(.+)/ogcapi-features#context"
163-
)
164166
cc_features_sort_regex = re.compile(
165167
r"https://api\.stacspec\.org/(.+)/ogcapi-features#sort"
166168
)
@@ -172,13 +174,9 @@ def __iadd__(self, x: Union[Tuple[str, str], str]) -> "Warnings":
172174
)
173175

174176
cc_item_search_regex = re.compile(r"https://api\.stacspec\.org/(.+)/item-search")
175-
176177
cc_item_search_fields_regex = re.compile(
177178
r"https://api\.stacspec\.org/(.+)/item-search#fields"
178179
)
179-
cc_item_search_context_regex = re.compile(
180-
r"https://api\.stacspec\.org/(.+)/item-search#context"
181-
)
182180
cc_item_search_sort_regex = re.compile(
183181
r"https://api\.stacspec\.org/(.+)/item-search#sort"
184182
)
@@ -333,13 +331,17 @@ def stac_check(
333331
context: Context,
334332
method: Method = Method.GET,
335333
) -> None:
336-
linter = Linter(url)
337-
if not linter.valid_stac:
338-
errors += (
339-
f"[{context}] {method} {url} is not a valid STAC object: {linter.error_msg}"
340-
)
341-
if msgs := linter.best_practices_msg[1:]: # first msg is a header
342-
warnings += f"[{context}] {method} {url} has these stac-check recommendations: {''.join(msgs)}"
334+
try:
335+
linter = Linter(url)
336+
if not linter.valid_stac:
337+
errors += f"[{context}] {method} {url} is not a valid STAC object: {linter.error_msg}"
338+
if msgs := linter.best_practices_msg[1:]: # first msg is a header, so skip
339+
warnings += f"[{context}] {method} {url} has these stac-check recommendations: {','.join([x.strip() for x in msgs])}"
340+
except KeyError as e:
341+
# see https://github.com/stac-utils/stac-check/issues/104
342+
errors += f"[{Context.CORE}] Error running stac-check, probably because an item doesn't have a bbox defined, which is okay!: {e} "
343+
except Exception as e:
344+
errors += f"[{Context.CORE}] Error while running stac-check: {e} "
343345

344346

345347
def retrieve(
@@ -423,7 +425,7 @@ def validate_core_landing_page_body(
423425
):
424426
warnings += "STAC API Specification v1.0.0-rc.2 is the latest version, but API advertises an older version or older versions."
425427

426-
if not any(cc_core_regex.fullmatch(x) for x in conforms_to):
428+
if not supports(conforms_to, cc_core_regex):
427429
errors += ("CORE-4", "/: STAC API - Core not contained in 'conformsTo'")
428430

429431
if "browseable" in conformance_classes and not any(
@@ -467,7 +469,7 @@ def validate_core_landing_page_body(
467469
return False
468470

469471
if "item-search" in conformance_classes:
470-
if not any(cc_item_search_regex.fullmatch(x) for x in conforms_to):
472+
if not supports(conforms_to, cc_item_search_regex):
471473
errors += (
472474
"CORE-9",
473475
"/: Item Search configured for validation, but not contained in 'conformsTo'",
@@ -483,12 +485,20 @@ def validate_core_landing_page_body(
483485
)
484486
return False
485487

488+
if "children" in conformance_classes and not any(
489+
cc_children_regex.fullmatch(x) for x in conforms_to
490+
):
491+
errors += (
492+
"CORE-6",
493+
"/: Children configured for validation, but not contained in 'conformsTo'",
494+
)
495+
486496
return True
487497

488498

489499
def validate_api(
490500
root_url: str,
491-
conformance_classes: List[str],
501+
ccs_to_validate: List[str],
492502
collection: Optional[str],
493503
geometry: Optional[str],
494504
auth_bearer_token: Optional[str],
@@ -520,7 +530,7 @@ def validate_api(
520530
landing_page_headers,
521531
errors,
522532
warnings,
523-
conformance_classes,
533+
ccs_to_validate,
524534
collection,
525535
geometry,
526536
):
@@ -529,21 +539,21 @@ def validate_api(
529539
logger.info("Validating STAC API - Core conformance class.")
530540
validate_core(landing_page_body, errors, warnings, r_session)
531541

532-
if "browseable" in conformance_classes:
542+
if "browseable" in ccs_to_validate:
533543
logger.info("Validating STAC API - Browseable conformance class.")
534544
validate_browseable(landing_page_body, errors, warnings, r_session)
535545

536-
if "children" in conformance_classes:
546+
if "children" in ccs_to_validate:
537547
logger.info("Validating STAC API - Children conformance class.")
538548
validate_children(landing_page_body, errors, warnings, r_session)
539549

540-
if "collections" in conformance_classes:
550+
if "collections" in ccs_to_validate:
541551
logger.info("Validating STAC API - Collections conformance class.")
542552
validate_collections(landing_page_body, collection, errors, warnings, r_session)
543553

544554
conforms_to = landing_page_body.get("conformsTo", [])
545555

546-
if "features" in conformance_classes:
556+
if "features" in ccs_to_validate:
547557
logger.info("Validating STAC API - Features conformance class.")
548558
validate_collections(landing_page_body, collection, errors, warnings, r_session)
549559
validate_features(
@@ -556,7 +566,7 @@ def validate_api(
556566
r_session,
557567
)
558568

559-
if "item-search" in conformance_classes:
569+
if "item-search" in ccs_to_validate:
560570
logger.info("Validating STAC API - Item Search conformance class.")
561571
validate_item_search(
562572
root_url=root_url,
@@ -566,7 +576,7 @@ def validate_api(
566576
warnings=warnings,
567577
errors=errors,
568578
geometry=geometry, # type:ignore
569-
conformance_classes=conformance_classes,
579+
conformance_classes=ccs_to_validate,
570580
r_session=r_session,
571581
)
572582

@@ -685,6 +695,11 @@ def validate_core(
685695
f"[{Context.CORE}] Error while traversing Catalog child/item links to find Items: {e} "
686696
"This can be reproduced with 'list(pystac.Catalog.from_file(root_url).get_all_items())'"
687697
)
698+
except UnicodeEncodeError as e:
699+
# see https://github.com/jjrom/resto/issues/356#issuecomment-1443818163
700+
errors += f"[{Context.CORE}] Error while traversing Catalog, a non-ascii character is encoded incorrectly somewhere: {e} "
701+
except Exception as e:
702+
errors += f"[{Context.CORE}] Error while traversing Catalog with pystac: {e} "
688703

689704

690705
def validate_browseable(
@@ -1101,24 +1116,25 @@ def validate_features(
11011116
r_session=r_session,
11021117
)
11031118

1104-
# Validate Extensions
1105-
#
1106-
# if any(cc_features_fields_regex.fullmatch(x) for x in conforms_to):
1107-
# logger.info("STAC API - Features - Fields extension conformance class found.")
1108-
#
1109-
# if any(cc_features_context_regex.fullmatch(x) for x in conforms_to):
1110-
# logger.info("STAC API - Features - Context extension conformance class found.")
1111-
#
1112-
# if any(cc_features_sort_regex.fullmatch(x) for x in conforms_to):
1113-
# logger.info("STAC API - Features - Sort extension conformance class found.")
1114-
#
1115-
# if any(cc_features_query_regex.fullmatch(x) for x in conforms_to):
1116-
# logger.info("STAC API - Features - Query extension conformance class found.")
1117-
#
1118-
# if any(cc_features_filter_regex.fullmatch(x) for x in conforms_to):
1119-
# logger.info("STAC API - Features - Filter extension conformance class found.")
1120-
1121-
if any(cc_features_filter_regex.fullmatch(x) for x in conforms_to):
1119+
if supports(conforms_to, cc_features_fields_regex):
1120+
logger.info("STAC API - Features - Fields extension conformance class found.")
1121+
logger.info("STAC API - Features - Fields extension is not yet supported.")
1122+
1123+
if supports(conforms_to, cc_features_transaction_regex):
1124+
logger.info(
1125+
"STAC API - Features - Transaction extension conformance class found."
1126+
)
1127+
logger.info("STAC API - Features - Transaction extension is not yet supported.")
1128+
1129+
if supports(conforms_to, cc_features_sort_regex):
1130+
logger.info("STAC API - Features - Sort extension conformance class found.")
1131+
logger.info("STAC API - Features - Sort extension is not yet supported.")
1132+
1133+
if supports(conforms_to, cc_features_query_regex):
1134+
logger.info("STAC API - Features - Query extension conformance class found.")
1135+
logger.info("STAC API - Features - Query extension is not yet supported.")
1136+
1137+
if supports(conforms_to, cc_features_filter_regex):
11221138
logger.info("STAC API - Features - Filter Extension conformance class found.")
11231139
validate_features_filter(
11241140
root_body=root_body,
@@ -1218,18 +1234,16 @@ def validate_item_search(
12181234
r_session=r_session,
12191235
)
12201236

1221-
# if any(cc_item_search_fields_regex.fullmatch(x) for x in conforms_to):
1222-
# logger.info("STAC API - Item Search - Fields extension conformance class found.")
1223-
#
1224-
# if any(cc_item_search_context_regex.fullmatch(x) for x in conforms_to):
1225-
# logger.info("STAC API - Item Search - Context extension conformance class found.")
1226-
#
1227-
# if any(cc_item_search_sort_regex.fullmatch(x) for x in conforms_to):
1228-
# logger.info("STAC API - Item Search - Sort extension conformance class found.")
1229-
#
1230-
# if any(cc_item_search_query_regex.fullmatch(x) for x in conforms_to):
1231-
# logger.info("STAC API - Item Search - Query extension conformance class found.")
1232-
#
1237+
if supports(conforms_to, cc_item_search_fields_regex):
1238+
logger.info(
1239+
"STAC API - Item Search - Fields extension conformance class found."
1240+
)
1241+
1242+
if supports(conforms_to, cc_item_search_sort_regex):
1243+
logger.info("STAC API - Item Search - Sort extension conformance class found.")
1244+
1245+
if supports(conforms_to, cc_item_search_query_regex):
1246+
logger.info("STAC API - Item Search - Query extension conformance class found.")
12331247

12341248
if any(
12351249
x.endswith("item-search#filter:basic-cql")

0 commit comments

Comments
 (0)