diff --git a/jsonschema/_format.py b/jsonschema/_format.py index 62c0e4ee..2ed23454 100644 --- a/jsonschema/_format.py +++ b/jsonschema/_format.py @@ -3,6 +3,7 @@ from contextlib import suppress from datetime import date, datetime from uuid import UUID +import decimal import ipaddress import re import string @@ -518,7 +519,7 @@ def is_uri_template(instance: object) -> bool: @_checks_drafts( draft201909="duration", draft202012="duration", - raises=isoduration.DurationParsingException, + raises=(isoduration.DurationParsingException, decimal.Overflow), ) def is_duration(instance: object) -> bool: if not isinstance(instance, str): diff --git a/jsonschema/tests/test_format.py b/jsonschema/tests/test_format.py index d829f984..ed777641 100644 --- a/jsonschema/tests/test_format.py +++ b/jsonschema/tests/test_format.py @@ -2,12 +2,17 @@ Tests for the parts of jsonschema related to the :kw:`format` keyword. """ -from unittest import TestCase +from unittest import TestCase, skipUnless from jsonschema import FormatChecker, ValidationError from jsonschema.exceptions import FormatError from jsonschema.validators import Draft4Validator +try: + import isoduration +except ImportError: + isoduration = None + BOOM = ValueError("Boom!") BANG = ZeroDivisionError("Bang!") @@ -80,6 +85,24 @@ def test_format_checkers_come_with_defaults(self): with self.assertRaises(FormatError): checker.check(instance="not-an-ipv4", format="ipv4") + @skipUnless(isoduration is not None, "isoduration not installed") + def test_duration_with_a_huge_exponent_is_invalid_not_an_error(self): + # A duration amount whose magnitude exceeds the decimal context's + # ``Emax`` makes isoduration's ``Decimal(...)`` raise + # ``decimal.Overflow`` rather than ``DurationParsingException``. Such + # a string is not a valid RFC 3339 Appendix A duration and should be + # reported as invalid, not propagate an uncaught exception. + checker = FormatChecker() + with self.assertRaises(FormatError): + checker.check(instance="P1E1000000D", format="duration") + + @skipUnless(isoduration is not None, "isoduration not installed") + def test_duration_with_a_huge_digit_run_is_invalid_not_an_error(self): + checker = FormatChecker() + instance = "P" + "1" * 1000001 + "D" + with self.assertRaises(FormatError): + checker.check(instance=instance, format="duration") + def test_repr(self): checker = FormatChecker(formats=()) checker.checks("foo")(lambda thing: True) # pragma: no cover