Better decimals schema validation:
Added missing check for positiveness of scale and precision
Added check for scale <= precision
Added check for fixed type dicimal precision limit
MODIFY
fastavro/_schema.pyx
fastavro/_schema.pyx
from os import path
from copy import deepcopy
import json
+from libc.math cimport floor, log10
from ._six import iteritems
from ._schema_common import (
@@ -135,17 +136,35 @@ cdef _parse_schema(schema, namespace, expand, _write_hint, names, named_schemas)
logical_type = parsed_schema.get("logicalType")
if logical_type == "decimal":
scale = parsed_schema.get("scale")
- if scale and not isinstance(scale, int):
+ if scale and (not isinstance(scale, int) or scale < 0):
raise SchemaParseException(
"decimal scale must be a postive integer, "
+ "not {}".format(scale)
)
+
precision = parsed_schema.get("precision")
- if precision and not isinstance(precision, int):
+ if precision:
+ if not isinstance(precision, int) or precision <= 0:
raise SchemaParseException(
"decimal precision must be a postive integer, "
+ "not {}".format(precision)
)
+ if schema_type == "fixed":
+ # https://avro.apache.org/docs/current/spec.html#Decimal
+ size = schema["size"]
+ max_precision = int(floor(log10(2) * (8 * size - 1)))
+ if precision > max_precision:
+ msg = "decimal precision of {} doesn't fit into " \
+ "array of length {}"
+ raise SchemaParseException(
+ msg.format(precision, size)
+ )
+
+ if scale and precision and precision < scale:
+ raise SchemaParseException(
+ "decimal scale must be less than or equal to "
+ + "the precision of {}".format(precision)
+ )
if schema_type == "array":
parsed_schema["items"] = _parse_schema(

MODIFY
fastavro/_schema_py.py
fastavro/_schema_py.py
# cython: auto_cpdef=True
-
+import math
from os import path
from copy import deepcopy
import json
@@ -267,17 +267,37 @@ def _parse_schema(
logical_type = parsed_schema.get("logicalType")
if logical_type == "decimal":
scale = parsed_schema.get("scale")
- if scale and not isinstance(scale, int):
+ if scale and (not isinstance(scale, int) or scale < 0):
raise SchemaParseException(
"decimal scale must be a postive integer, "
+ "not {}".format(scale)
)
+
precision = parsed_schema.get("precision")
- if precision and not isinstance(precision, int):
+ if precision:
+ if not isinstance(precision, int) or precision <= 0:
raise SchemaParseException(
"decimal precision must be a postive integer, "
+ "not {}".format(precision)
)
+ if schema_type == "fixed":
+ # https://avro.apache.org/docs/current/spec.html#Decimal
+ size = schema["size"]
+ max_precision = int(
+ math.floor(math.log10(2) * (8 * size - 1))
+ )
+ if precision > max_precision:
+ msg = "decimal precision of {} doesn't fit into " \
+ "array of length {}"
+ raise SchemaParseException(
+ msg.format(precision, size)
+ )
+
+ if scale and precision and precision < scale:
+ raise SchemaParseException(
+ "decimal scale must be less than or equal to "
+ + "the precision of {}".format(precision)
+ )
if schema_type == "array":
parsed_schema["items"] = _parse_schema(

MODIFY
tests/test_schema.py
tests/test_schema.py
@@ -107,7 +107,7 @@ def test_aliases_is_a_list():
parse_schema(schema)
-def test_scale_is_an_int():
+def test_decimal_scale_is_an_int():
"""https://github.com/fastavro/fastavro/issues/262"""
schema = {
"type": "record",
@@ -129,7 +129,29 @@ def test_scale_is_an_int():
parse_schema(schema)
-def test_precision_is_an_int():
+def test_decimal_scale_is_a_positive_int():
+ """https://github.com/fastavro/fastavro/issues/457"""
+ schema = {
+ "type": "record",
+ "name": "test_scale_is_an_int",
+ "fields": [{
+ "name": "field",
+ "type": {
+ "logicalType": "decimal",
+ "precision": 5,
+ "scale": -2,
+ "type": "bytes",
+ },
+ }],
+ }
+
+ with pytest.raises(
+ SchemaParseException, match="decimal scale must be a postive integer"
+ ):
+ parse_schema(schema)
+
+
+def test_decimal_precision_is_an_int():
"""https://github.com/fastavro/fastavro/issues/262"""
schema = {
"type": "record",
@@ -152,6 +174,77 @@ def test_precision_is_an_int():
parse_schema(schema)
+def test_decimal_precision_is_a_positive_int():
+ """https://github.com/fastavro/fastavro/issues/457"""
+ schema = {
+ "type": "record",
+ "name": "test_scale_is_an_int",
+ "fields": [{
+ "name": "field",
+ "type": {
+ "logicalType": "decimal",
+ "precision": -5,
+ "scale": 2,
+ "type": "bytes",
+ },
+ }],
+ }
+
+ with pytest.raises(
+ SchemaParseException,
+ match="decimal precision must be a postive integer",
+ ):
+ parse_schema(schema)
+
+
+def test_decimal_precision_is_greater_than_scale():
+ """https://github.com/fastavro/fastavro/issues/457"""
+ schema = {
+ "type": "record",
+ "name": "test_scale_is_an_int",
+ "fields": [{
+ "name": "field",
+ "type": {
+ "logicalType": "decimal",
+ "precision": 5,
+ "scale": 10,
+ "type": "bytes",
+ },
+ }],
+ }
+
+ with pytest.raises(
+ SchemaParseException,
+ match="decimal scale must be less than or equal to",
+ ):
+ parse_schema(schema)
+
+
+def test_decimal_fixed_accommodates_precision():
+ """https://github.com/fastavro/fastavro/issues/457"""
+ schema = {
+ "type": "record",
+ "name": "test_scale_is_an_int",
+ "fields": [{
+ "name": "field",
+ "type": {
+ "name": "fixed_decimal",
+ "logicalType": "decimal",
+ "precision": 10,
+ "scale": 2,
+ "type": "fixed",
+ "size": 2
+ },
+ }],
+ }
+
+ with pytest.raises(
+ SchemaParseException,
+ match=r"decimal precision of \d+ doesn't fit into array of length \d+",
+ ):
+ parse_schema(schema)
+
+
def test_named_type_cannot_be_redefined():
schema = {
"type": "record",

Better decimals schema validation:
- Added missing check for positiveness of scale and precision
- Added check for scale <= precision
- Added check for fixed type dicimal precision limit