Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions avro/src/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,7 @@ mod tests {
use apache_avro_test_helper::TestResult;
use pretty_assertions::assert_eq;
use std::collections::HashMap;
use std::num::NonZero;
use uuid::Uuid;

#[test]
Expand Down Expand Up @@ -692,7 +693,7 @@ mod tests {
.size(2)
.build(),
),
precision: 4,
precision: NonZero::new(4).unwrap(),
scale: 2,
});
let bigint = (-423).to_bigint().unwrap();
Expand Down Expand Up @@ -720,7 +721,7 @@ mod tests {
doc: None,
attributes: Default::default(),
}),
precision: 4,
precision: NonZero::new(4).unwrap(),
scale: 2,
});
let value = Value::Decimal(Decimal::from(
Expand Down
30 changes: 21 additions & 9 deletions avro/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
// specific language governing permissions and limitations
// under the License.

use std::{error::Error as _, fmt};

use crate::{
schema::{Name, RecordSchema, Schema, SchemaKind, UnionSchema},
types::{Value, ValueKind},
};
use std::num::NonZero;
use std::{error::Error as _, fmt};

/// Errors encountered by Avro.
///
Expand Down Expand Up @@ -183,12 +183,18 @@ pub enum Details {
GetEnumUnknownIndexValue,

#[error("Scale {scale} is greater than precision {precision}")]
GetScaleAndPrecision { scale: usize, precision: usize },
GetScaleAndPrecision {
scale: usize,
precision: NonZero<usize>,
},

#[error(
"Fixed type number of bytes {size} is not large enough to hold decimal values of precision {precision}"
)]
GetScaleWithFixedSize { size: usize, precision: usize },
GetScaleWithFixedSize {
size: usize,
precision: NonZero<usize>,
},

#[error("Expected Value::Uuid, got: {0:?}")]
GetUuid(Value),
Expand All @@ -212,7 +218,10 @@ pub enum Details {
GetU8(Value),

#[error("Precision {precision} too small to hold decimal values with {num_bytes} bytes")]
ComparePrecisionAndSize { precision: usize, num_bytes: usize },
ComparePrecisionAndSize {
precision: NonZero<usize>,
num_bytes: usize,
},

#[error("Cannot convert length to i32: {1}")]
ConvertLengthToI32(#[source] std::num::TryFromIntError, usize),
Expand Down Expand Up @@ -403,9 +412,12 @@ pub enum Details {
},

#[error("The decimal precision ({precision}) must be bigger or equal to the scale ({scale})")]
DecimalPrecisionLessThanScale { precision: usize, scale: usize },
DecimalPrecisionLessThanScale {
precision: NonZero<usize>,
scale: usize,
},

#[error("The decimal precision ({precision}) must be a positive number")]
#[error("The decimal precision ({precision}) must be a non-zero positive number")]
DecimalPrecisionMuBePositive { precision: usize },

#[deprecated(since = "0.20.0", note = "This error variant is not generated anymore")]
Expand Down Expand Up @@ -771,9 +783,9 @@ pub enum CompatibilityError {
"Incompatible schemata! Decimal precision and/or scale don't match, reader: ({r_precision},{r_scale}), writer: ({w_precision},{w_scale})"
)]
DecimalMismatch {
r_precision: usize,
r_precision: NonZero<usize>,
r_scale: usize,
w_precision: usize,
w_precision: NonZero<usize>,
w_scale: usize,
},

Expand Down
18 changes: 9 additions & 9 deletions avro/src/schema/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ use serde::{
use serde_json::{Map, Value as JsonValue};
use std::borrow::Cow;
use std::fmt::Formatter;
use std::num::NonZero;
use std::{
collections::{BTreeMap, HashMap, HashSet},
fmt,
Expand Down Expand Up @@ -454,9 +455,8 @@ pub enum UuidSchema {
Fixed(FixedSchema),
}

type DecimalMetadata = usize;
pub(crate) type Precision = DecimalMetadata;
pub(crate) type Scale = DecimalMetadata;
pub(crate) type Precision = NonZero<usize>;
pub(crate) type Scale = usize;

impl Schema {
/// Converts `self` into its [Parsing Canonical Form].
Expand Down Expand Up @@ -4347,14 +4347,14 @@ mod tests {
"scale": 2
});
let parse_result = Schema::parse(schema)?;
assert!(matches!(
assert_eq!(
parse_result,
Schema::Decimal(DecimalSchema {
precision: 9,
precision: NonZero::new(9).unwrap(),
scale: 2,
..
inner: InnerDecimalSchema::Bytes
})
));
);

// long decimal, represents as native complex type.
let schema = json!(
Expand Down Expand Up @@ -4579,7 +4579,7 @@ mod tests {
#[test]
fn test_avro_3925_serialize_decimal_inner_fixed() -> TestResult {
let schema = Schema::Decimal(DecimalSchema {
precision: 36,
precision: NonZero::new(36).unwrap(),
scale: 10,
inner: InnerDecimalSchema::Fixed(FixedSchema {
name: Name::new("decimal_36_10").unwrap(),
Expand Down Expand Up @@ -4609,7 +4609,7 @@ mod tests {
#[test]
fn test_avro_3925_serialize_decimal_inner_bytes() -> TestResult {
let schema = Schema::Decimal(DecimalSchema {
precision: 36,
precision: NonZero::new(36).unwrap(),
scale: 10,
inner: InnerDecimalSchema::Bytes,
});
Expand Down
87 changes: 35 additions & 52 deletions avro/src/schema/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@

use crate::error::Details;
use crate::schema::{
Alias, Aliases, ArraySchema, DecimalMetadata, DecimalSchema, EnumSchema, FixedSchema,
MapSchema, Name, Names, NamespaceRef, Precision, RecordField, RecordSchema, Scale, Schema,
SchemaKind, UnionSchema, UuidSchema,
Alias, Aliases, ArraySchema, DecimalSchema, EnumSchema, FixedSchema, MapSchema, Name, Names,
NamespaceRef, Precision, RecordField, RecordSchema, Scale, Schema, SchemaKind, UnionSchema,
UuidSchema,
};
use crate::util::{JsonValueDescriber, MapHelper};
use crate::validator::validate_enum_symbol_name;
use crate::{AvroResult, Error};
use log::{debug, error, warn};
use serde_json::{Map, Value};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::num::NonZero;

#[derive(Default)]
pub(crate) struct Parser {
Expand Down Expand Up @@ -190,40 +191,43 @@ impl Parser {
Ok(Schema::Ref { name: full_name })
}

fn get_decimal_integer(
fn parse_precision_and_scale(
&self,
complex: &Map<String, Value>,
key: &'static str,
) -> AvroResult<DecimalMetadata> {
match complex.get(key) {
Some(Value::Number(value)) => self.parse_json_integer_for_decimal(value),
None => {
if key == "scale" {
Ok(0)
} else {
Err(Details::GetDecimalMetadataFromJson(key).into())
) -> AvroResult<(Precision, Scale)> {
let precision = match complex.get("precision") {
Some(Value::Number(value)) if value.is_u64() => {
let value = value.as_u64().expect("Is u64");
let value =
usize::try_from(value).map_err(|e| Details::ConvertU64ToUsize(e, value))?;
NonZero::new(value)
.ok_or(Details::DecimalPrecisionMuBePositive { precision: value })?
}
Some(value) => {
return Err(Details::GetDecimalMetadataValueFromJson {
key: "precision".into(),
value: value.clone(),
}
.into());
}
Some(value) => Err(Details::GetDecimalMetadataValueFromJson {
key: key.into(),
value: value.clone(),
None => return Err(Details::GetDecimalMetadataFromJson("precision").into()),
};
let scale = match complex.get("scale") {
Some(Value::Number(value)) if value.is_u64() => {
let value = value.as_u64().expect("Is u64");
usize::try_from(value).map_err(|e| Details::ConvertU64ToUsize(e, value))?
}
.into()),
}
}

fn parse_precision_and_scale(
&self,
complex: &Map<String, Value>,
) -> AvroResult<(Precision, Scale)> {
let precision = self.get_decimal_integer(complex, "precision")?;
let scale = self.get_decimal_integer(complex, "scale")?;

if precision < 1 {
return Err(Details::DecimalPrecisionMuBePositive { precision }.into());
}
Some(value) => {
return Err(Details::GetDecimalMetadataValueFromJson {
key: "scale".into(),
value: value.clone(),
}
.into());
}
None => 0,
};

if precision < scale {
if precision.get() < scale {
Err(Details::DecimalPrecisionLessThanScale { precision, scale }.into())
} else {
Ok((precision, scale))
Expand Down Expand Up @@ -798,25 +802,4 @@ impl Parser {
_ => Ok(name),
}
}

fn parse_json_integer_for_decimal(
&self,
value: &serde_json::Number,
) -> AvroResult<DecimalMetadata> {
Ok(if value.is_u64() {
let num = value
.as_u64()
.ok_or_else(|| Details::GetU64FromJson(value.clone()))?;
num.try_into()
.map_err(|e| Details::ConvertU64ToUsize(e, num))?
} else if value.is_i64() {
let num = value
.as_i64()
.ok_or_else(|| Details::GetI64FromJson(value.clone()))?;
num.try_into()
.map_err(|e| Details::ConvertI64ToUsize(e, num))?
} else {
return Err(Details::GetPrecisionOrScaleFromJson(value.clone()).into());
})
}
}
8 changes: 4 additions & 4 deletions avro/src/schema_compatibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -444,8 +444,6 @@ impl Checker {

#[cfg(test)]
mod tests {
use std::collections::BTreeMap;

use super::*;
use crate::{
Codec, Decimal, Reader, Writer,
Expand All @@ -454,6 +452,8 @@ mod tests {
};
use apache_avro_test_helper::TestResult;
use rstest::*;
use std::collections::BTreeMap;
use std::num::NonZero;

fn int_array_schema() -> Schema {
Schema::parse_str(r#"{"type":"array", "items":"int"}"#).unwrap()
Expand Down Expand Up @@ -1691,12 +1691,12 @@ mod tests {
#[test]
fn avro_rs_342_decimal_fixed_and_bytes() -> TestResult {
let bytes = Schema::Decimal(DecimalSchema {
precision: 20,
precision: NonZero::new(20).unwrap(),
scale: 0,
inner: InnerDecimalSchema::Bytes,
});
let fixed = Schema::Decimal(DecimalSchema {
precision: 20,
precision: NonZero::new(20).unwrap(),
scale: 0,
inner: InnerDecimalSchema::Fixed(FixedSchema {
name: Name::new("DecimalFixed")?,
Expand Down
5 changes: 3 additions & 2 deletions avro/src/schema_equality.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ mod tests {
use apache_avro_test_helper::TestResult;
use serde_json::Value;
use std::collections::BTreeMap;
use std::num::NonZero;

const SPECIFICATION_EQ: SpecificationEq = SpecificationEq;
const STRUCT_FIELD_EQ: StructFieldEq = StructFieldEq {
Expand Down Expand Up @@ -505,15 +506,15 @@ mod tests {
#[test]
fn test_avro_3939_compare_decimal_schemata() {
let schema_one = Schema::Decimal(DecimalSchema {
precision: 10,
precision: NonZero::new(10).unwrap(),
scale: 2,
inner: InnerDecimalSchema::Bytes,
});
assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));

let schema_two = Schema::Decimal(DecimalSchema {
precision: 10,
precision: NonZero::new(10).unwrap(),
scale: 2,
inner: InnerDecimalSchema::Bytes,
});
Expand Down
Loading
Loading