Skip to content

Commit 8d06b3b

Browse files
fix(#254): PR feedback reuse bbox validation
Co-authored-by: Vincent Sarago <vincent.sarago@gmail.com>
1 parent 6ef0055 commit 8d06b3b

File tree

4 files changed

+63
-53
lines changed

4 files changed

+63
-53
lines changed

tests/test_collections.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -808,17 +808,32 @@ def test_bbox_validation(app) -> None:
808808
},
809809
)
810810
assert valid_response_3d.status_code == 200
811+
811812
valid_response_2d = app.get(
812813
f"/collections/{collection_id}/tiles",
813814
params={
814815
"bbox": "-180,-90,180,90",
815816
},
816817
)
817818
assert valid_response_2d.status_code == 200
818-
invalid_response = app.get(
819+
820+
invalid_format_1_response = app.get(
819821
f"/collections/{collection_id}/tiles",
820822
params={
821823
"bbox": "invalid bbox string",
822824
},
823825
)
824-
assert invalid_response.status_code == 422
826+
assert invalid_format_1_response.status_code == 422
827+
828+
invalid_format_2_response = app.get(
829+
f"/collections/{collection_id}/tiles",
830+
params={
831+
"bbox": "-180,-90,180",
832+
},
833+
)
834+
assert invalid_format_2_response.status_code == 422
835+
836+
invalid_values_response = app.get(
837+
f"/collections/{collection_id}/tiles", params={"bbox": "180,-90,-180,90"}
838+
)
839+
assert invalid_values_response.status_code == 422

titiler/pgstac/dependencies.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from titiler.pgstac.errors import MosaicNotFoundError, ReadOnlyPgSTACError
2929
from titiler.pgstac.settings import CacheSettings, RetrySettings
3030
from titiler.pgstac.utils import retry
31-
from titiler.pgstac.validation import validate_bbox, validate_filter
31+
from titiler.pgstac.validation import parse_and_validate_bbox, validate_filter
3232

3333
cache_config = CacheSettings()
3434
retry_config = RetrySettings()
@@ -219,7 +219,7 @@ def CollectionIdParams(
219219
] = None,
220220
bbox: Annotated[
221221
str | None,
222-
BeforeValidator(validate_bbox),
222+
BeforeValidator(parse_and_validate_bbox),
223223
Query(
224224
description="Filters items intersecting this bounding box",
225225
openapi_examples={

titiler/pgstac/model.py

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -203,36 +203,6 @@ def validate_spatial(cls, v: Geometry | None, info: ValidationInfo):
203203

204204
return v
205205

206-
@field_validator("bbox")
207-
def validate_bbox(cls, v: BBox):
208-
"""Validate BBOX."""
209-
if v:
210-
# Validate order
211-
if len(v) == 4:
212-
xmin, ymin, xmax, ymax = v
213-
else:
214-
xmin, ymin, min_elev, xmax, ymax, max_elev = v
215-
if max_elev < min_elev:
216-
raise ValueError(
217-
"Maximum elevation must greater than minimum elevation"
218-
)
219-
220-
if xmax < xmin:
221-
raise ValueError(
222-
"Maximum longitude must be greater than minimum longitude"
223-
)
224-
225-
if ymax < ymin:
226-
raise ValueError(
227-
"Maximum longitude must be greater than minimum longitude"
228-
)
229-
230-
# Validate against WGS84
231-
if xmin < -180 or ymin < -90 or xmax > 180 or ymax > 90:
232-
raise ValueError("Bounding box must be within (-180, -90, 180, 90)")
233-
234-
return v
235-
236206

237207
class RegisterMosaic(PgSTACSearch):
238208
"""Model of /register endpoint input."""

titiler/pgstac/validation.py

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
Validation functions for caller-provided data.
33
"""
44

5-
import re
6-
from typing import Literal
5+
from typing import Literal, cast
76

87
from cql2 import Expr
8+
from geojson_pydantic.types import BBox
99
from pydantic import ValidationError
1010

1111
from titiler.core.validation import validate_json
@@ -35,22 +35,47 @@ def validate_filter(
3535
raise ValidationError(str(e), []) from e
3636

3737

38-
def validate_bbox(bbox_str: str | None) -> str | None:
39-
"""
40-
Verify that a BBOX string can be parsed.
41-
:param bbox_str: Caller-provided bbox value.
42-
:type bbox_str: str | None
43-
:return: Caller-provided bbox value if validated, otherwise an exception is raised.
44-
:rtype: str
38+
def validate_bbox(v: BBox):
39+
"""Validate BBOX values."""
40+
if v:
41+
# Validate order
42+
if len(v) == 4:
43+
xmin, ymin, xmax, ymax = v
44+
elif len(v) == 6:
45+
xmin, ymin, min_elev, xmax, ymax, max_elev = v
46+
if max_elev < min_elev:
47+
raise ValueError(
48+
"Maximum elevation must greater than minimum elevation"
49+
)
50+
else:
51+
raise ValueError("Bounding box must have 4 or 6 numbers")
4552

46-
"""
47-
if bbox_str is None:
53+
if xmax < xmin:
54+
raise ValueError("Maximum longitude must be greater than minimum longitude")
55+
56+
if ymax < ymin:
57+
raise ValueError("Maximum longitude must be greater than minimum longitude")
58+
59+
# Validate against WGS84
60+
if xmin < -180 or ymin < -90 or xmax > 180 or ymax > 90:
61+
raise ValueError("Bounding box must be within (-180, -90, 180, 90)")
62+
63+
return v
64+
65+
66+
def parse_and_validate_bbox(value: str | BBox | None):
67+
"""Validate BBOX format and values."""
68+
if value is None:
4869
return None
49-
parseable_float_regex = r"\s*(-)?\d+((\.\d+)(e\d+)?)?\s*" # can simply call titiler.core.validation.validate_bbox on titiler.core > 1.1.1 when released
50-
if re.match(
51-
"^{}$".format(",".join([parseable_float_regex for _ in range(4)])), bbox_str
52-
) or re.match(
53-
"^{}$".format(",".join([parseable_float_regex for _ in range(6)])), bbox_str
54-
):
55-
return bbox_str
56-
raise ValueError("invalid bbox content")
70+
if isinstance(value, str):
71+
try:
72+
parsed_value = [float(x) for x in value.split(",")]
73+
except ValueError as e:
74+
raise ValueError(
75+
"Bounding box must be a comma-separated list of numbers"
76+
) from e
77+
else:
78+
parsed_value = value
79+
80+
validate_bbox(cast(BBox, parsed_value))
81+
return value

0 commit comments

Comments
 (0)