diff --git a/CHANGELOG.txt b/CHANGELOG.txt index d505096..4cc89f5 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -7,6 +7,7 @@ * Added `message` to `JsonSchemaValuesException` joining messages of all collected errors * Fixed the `date-time` format accepting months, days and hours out of range (such as `2020-19-39T29:00:00Z`) and rejecting a leap second (`23:59:60Z`) * Fixed the `time` format accepting hours, minutes, seconds and offsets out of range (such as `29:99:99`) +* Fixed `minimum` and `maximum` being exclusive in draft 06 and later when `exclusiveMinimum` or `exclusiveMaximum` is set to a number other than zero === 2.22.2 (2026-08-15) diff --git a/fastjsonschema/draft06.py b/fastjsonschema/draft06.py index e744ef7..30eb517 100644 --- a/fastjsonschema/draft06.py +++ b/fastjsonschema/draft06.py @@ -83,6 +83,28 @@ def generate_type(self): with self.l('if not isinstance({variable}, ({})){}:', python_types, extra): self.exc('{name} must be {}', ' or '.join(types), rule='type') + def generate_minimum(self): + """ + Since draft 06 exclusiveMinimum is a number validated on its own, + so minimum is always inclusive. + """ + with self.l('if isinstance({variable}, (int, float, Decimal)) and not isinstance({variable}, bool):'): + if not isinstance(self._definition['minimum'], (int, float, decimal.Decimal)): + raise JsonSchemaDefinitionException('minimum must be a number') + with self.l('if {variable} < {minimum}:'): + self.exc('{name} must be bigger than or equal to {minimum}', rule='minimum') + + def generate_maximum(self): + """ + Since draft 06 exclusiveMaximum is a number validated on its own, + so maximum is always inclusive. + """ + with self.l('if isinstance({variable}, (int, float, Decimal)) and not isinstance({variable}, bool):'): + if not isinstance(self._definition['maximum'], (int, float, decimal.Decimal)): + raise JsonSchemaDefinitionException('maximum must be a number') + with self.l('if {variable} > {maximum}:'): + self.exc('{name} must be smaller than or equal to {maximum}', rule='maximum') + def generate_exclusive_minimum(self): with self.l('if isinstance({variable}, (int, float, Decimal)) and not isinstance({variable}, bool):'): if not isinstance(self._definition['exclusiveMinimum'], (int, float, decimal.Decimal)): diff --git a/tests/test_number.py b/tests/test_number.py index add0228..d278d26 100644 --- a/tests/test_number.py +++ b/tests/test_number.py @@ -177,6 +177,21 @@ def test_exclusive_numeric_keywords_ignore_boolean(asserter, schema_version, key }, value, value) +@pytest.mark.parametrize('schema_version', ( + 'http://json-schema.org/draft-06/schema', + 'http://json-schema.org/draft-07/schema', +)) +@pytest.mark.parametrize('keywords', ( + {'minimum': 10, 'exclusiveMinimum': 5}, + {'maximum': 10, 'exclusiveMaximum': 15}, +)) +def test_minimum_and_maximum_stay_inclusive_next_to_exclusive_keywords(asserter, schema_version, keywords): + asserter({ + '$schema': schema_version, + **keywords, + }, 10, 10) + + @pytest.mark.parametrize('value', ( 1.0, 0.1,