diff --git a/src/mavedb/routers/hgvs.py b/src/mavedb/routers/hgvs.py index c2352c81..ac627730 100644 --- a/src/mavedb/routers/hgvs.py +++ b/src/mavedb/routers/hgvs.py @@ -5,10 +5,11 @@ from cdot.hgvs.dataproviders import RESTDataProvider from fastapi import APIRouter, Depends, HTTPException from hgvs import parser, validator -from hgvs.exceptions import HGVSDataNotAvailableError, HGVSInvalidVariantError +from hgvs.exceptions import HGVSDataNotAvailableError, HGVSError from mavedb.deps import hgvs_data_provider -from mavedb.routers.shared import BASE_400_RESPONSE, PUBLIC_ERROR_RESPONSES, ROUTER_BASE_PREFIX +from mavedb.routers.shared import PUBLIC_ERROR_RESPONSES, ROUTER_BASE_PREFIX, VALIDATION_ERROR_RESPONSES +from mavedb.view_models.hgvs import HgvsValidationRequest TAG_NAME = "Transcripts" @@ -44,22 +45,23 @@ def hgvs_fetch(accession: str, hdp: RESTDataProvider = Depends(hgvs_data_provide "/validate", status_code=200, response_model=bool, - responses={**BASE_400_RESPONSE}, + responses={**VALIDATION_ERROR_RESPONSES}, summary="Validate a provided variant", ) -def hgvs_validate(variant: dict[str, str], hdp: RESTDataProvider = Depends(hgvs_data_provider)) -> bool: +def hgvs_validate(request: HgvsValidationRequest, hdp: RESTDataProvider = Depends(hgvs_data_provider)) -> bool: """ Validate the provided HGVS variant string. + + Parsing and validation failures both stem from caller-supplied input, so any ``HGVSError`` — a syntactic + parse failure, an inconsistent variant, an unknown accession — is surfaced as a 400 rather than escaping + to the catch-all 500 handler. """ hp = parser.Parser() - variant_hgvs = hp.parse(variant["variant"]) - try: - valid = validator.Validator(hdp=hdp).validate(variant_hgvs, strict=False) - except HGVSInvalidVariantError as e: + variant_hgvs = hp.parse(request.variant) + return validator.Validator(hdp=hdp).validate(variant_hgvs, strict=False) + except HGVSError as e: raise HTTPException(400, str(e)) - else: - return valid @router.get("/assemblies", status_code=200, response_model=list[str], summary="List stored assemblies") diff --git a/src/mavedb/view_models/hgvs.py b/src/mavedb/view_models/hgvs.py new file mode 100644 index 00000000..e295cc9d --- /dev/null +++ b/src/mavedb/view_models/hgvs.py @@ -0,0 +1,5 @@ +from mavedb.view_models.base.base import BaseModel + + +class HgvsValidationRequest(BaseModel): + variant: str diff --git a/tests/routers/test_hgvs.py b/tests/routers/test_hgvs.py index 6011953f..b8a689df 100644 --- a/tests/routers/test_hgvs.py +++ b/tests/routers/test_hgvs.py @@ -24,6 +24,7 @@ INVALID_TRANSCRIPT = "NX_99999.1" VALID_VARIANT = VALID_NT_ACCESSION + ":c.1G>A" INVALID_VARIANT = VALID_NT_ACCESSION + ":c.1delA" +UNPARSEABLE_VARIANT = "not a valid hgvs string" HAS_PROTEIN_ACCESSION = "NM_000014.4" PROTEIN_ACCESSION = "NP_000005.2" @@ -64,6 +65,21 @@ def test_hgvs_validate_invalid(client, setup_router_db): assert "does not agree" in response.json()["detail"] +def test_hgvs_validate_unparseable(client, setup_router_db): + # A syntactically invalid HGVS string fails at the parse stage, before validation. This is a caller + # error and must surface as a 400 rather than escaping to the catch-all 500 handler. + payload = {"variant": UNPARSEABLE_VARIANT} + response = client.post("/api/v1/hgvs/validate", json=payload) + + assert response.status_code == 400 + + +def test_hgvs_validate_missing_field(client, setup_router_db): + response = client.post("/api/v1/hgvs/validate", json={}) + + assert response.status_code == 422 + + def test_hgvs_list_assemblies(client, setup_router_db): response = client.get("/api/v1/hgvs/assemblies") assert response.status_code == 200