diff --git a/stac_fastapi/api/stac_fastapi/api/errors.py b/stac_fastapi/api/stac_fastapi/api/errors.py index dd1880fca..1575fad30 100644 --- a/stac_fastapi/api/stac_fastapi/api/errors.py +++ b/stac_fastapi/api/stac_fastapi/api/errors.py @@ -13,6 +13,7 @@ ConflictError, DatabaseError, ForeignKeyError, + InvalidQueryParameter, NotFoundError, ) @@ -25,6 +26,7 @@ ForeignKeyError: status.HTTP_422_UNPROCESSABLE_ENTITY, DatabaseError: status.HTTP_424_FAILED_DEPENDENCY, Exception: status.HTTP_500_INTERNAL_SERVER_ERROR, + InvalidQueryParameter: status.HTTP_400_BAD_REQUEST, } diff --git a/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py b/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py index f53850fed..b6aba0637 100644 --- a/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py +++ b/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py @@ -6,6 +6,7 @@ import attr import orjson +from asyncpg.exceptions import InvalidDatetimeFormatError from buildpg import render from fastapi import HTTPException from pydantic import ValidationError @@ -16,7 +17,7 @@ from stac_fastapi.pgstac.models.links import CollectionLinks, ItemLinks, PagingLinks from stac_fastapi.pgstac.types.search import PgstacSearch from stac_fastapi.types.core import AsyncBaseCoreClient -from stac_fastapi.types.errors import NotFoundError +from stac_fastapi.types.errors import InvalidQueryParameter, NotFoundError from stac_fastapi.types.stac import Collection, Collections, Item, ItemCollection NumType = Union[float, int] @@ -116,14 +117,20 @@ async def _search_base( # pool = kwargs["request"].app.state.readpool req = search_request.json(exclude_none=True) - async with pool.acquire() as conn: - q, p = render( - """ - SELECT * FROM search(:req::text::jsonb); - """, - req=req, + try: + async with pool.acquire() as conn: + q, p = render( + """ + SELECT * FROM search(:req::text::jsonb); + """, + req=req, + ) + items = await conn.fetchval(q, *p) + except InvalidDatetimeFormatError: + raise InvalidQueryParameter( + f"Datetime parameter {search_request.datetime} is invalid." ) - items = await conn.fetchval(q, *p) + next: Optional[str] = items.pop("next", None) prev: Optional[str] = items.pop("prev", None) collection = ItemCollection(**items) diff --git a/stac_fastapi/pgstac/tests/api/test_api.py b/stac_fastapi/pgstac/tests/api/test_api.py index 52c6ed5eb..71ee70e4b 100644 --- a/stac_fastapi/pgstac/tests/api/test_api.py +++ b/stac_fastapi/pgstac/tests/api/test_api.py @@ -104,3 +104,19 @@ async def test_app_sort_extension(load_test_data, app_client, load_test_collecti resp_json = resp.json() assert resp_json["features"][1]["id"] == first_item["id"] assert resp_json["features"][0]["id"] == second_item["id"] + + +@pytest.mark.asyncio +async def test_search_invalid_date(load_test_data, app_client, load_test_collection): + coll = load_test_collection + first_item = load_test_data("test_item.json") + resp = await app_client.post(f"/collections/{coll.id}/items", json=first_item) + assert resp.status_code == 200 + + params = { + "datetime": "2020-XX-01/2020-10-30", + "collections": [coll.id], + } + + resp = await app_client.post("/search", json=params) + assert resp.status_code == 400 diff --git a/stac_fastapi/sqlalchemy/tests/api/test_api.py b/stac_fastapi/sqlalchemy/tests/api/test_api.py index cd07f4500..cd860b642 100644 --- a/stac_fastapi/sqlalchemy/tests/api/test_api.py +++ b/stac_fastapi/sqlalchemy/tests/api/test_api.py @@ -101,3 +101,19 @@ def test_app_sort_extension(load_test_data, app_client, postgres_transactions): resp_json = resp.json() assert resp_json["features"][0]["id"] == first_item["id"] assert resp_json["features"][1]["id"] == second_item["id"] + + +def test_search_invalid_date(load_test_data, app_client, postgres_transactions): + item = load_test_data("test_item.json") + postgres_transactions.create_item(item, request=MockStarletteRequest) + + params = { + "datetime": "2020-XX-01/2020-10-30", + "collections": [item["collection"]], + } + + resp = app_client.post("/search", json=params) + import json + + print(json.dumps(resp.json(), indent=2)) + assert resp.status_code == 400 diff --git a/stac_fastapi/types/stac_fastapi/types/errors.py b/stac_fastapi/types/stac_fastapi/types/errors.py index 8796dc0a6..9bd51ed08 100644 --- a/stac_fastapi/types/stac_fastapi/types/errors.py +++ b/stac_fastapi/types/stac_fastapi/types/errors.py @@ -29,3 +29,13 @@ class DatabaseError(StacApiError): """Generic database errors.""" pass + + +class InvalidQueryParameter(StacApiError): + """Error for unknown or invalid query parameters. + + Used to capture errors that should respond according to + http://docs.opengeospatial.org/is/17-069r3/17-069r3.html#query_parameters + """ + + pass