avro/src/error.rs (377 lines of code) (raw):
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use crate::{
schema::{Name, Schema, SchemaKind},
types::{Value, ValueKind},
};
use std::{error::Error as _, fmt};
#[derive(thiserror::Error)]
pub enum Error {
#[error("Bad Snappy CRC32; expected {expected:x} but got {actual:x}")]
SnappyCrc32 { expected: u32, actual: u32 },
#[error("Invalid u8 for bool: {0}")]
BoolValue(u8),
#[error("Not a fixed value, required for decimal with fixed schema: {0:?}")]
FixedValue(Value),
#[error("Not a bytes value, required for decimal with bytes schema: {0:?}")]
BytesValue(Value),
#[error("Not a string value, required for uuid: {0:?}")]
GetUuidFromStringValue(Value),
#[error("Two schemas with the same fullname were given: {0:?}")]
NameCollision(String),
#[error("Not a fixed or bytes type, required for decimal schema, got: {0:?}")]
ResolveDecimalSchema(SchemaKind),
#[error("Invalid utf-8 string")]
ConvertToUtf8(#[source] std::string::FromUtf8Error),
#[error("Invalid utf-8 string")]
ConvertToUtf8Error(#[source] std::str::Utf8Error),
/// Describes errors happened while validating Avro data.
#[error("Value does not match schema")]
Validation,
/// Describes errors happened while validating Avro data.
#[error("Value {value:?} does not match schema {schema:?}: Reason: {reason}")]
ValidationWithReason {
value: Value,
schema: Schema,
reason: String,
},
#[error("Unable to allocate {desired} bytes (maximum allowed: {maximum})")]
MemoryAllocation { desired: usize, maximum: usize },
/// Describe a specific error happening with decimal representation
#[error("Number of bytes requested for decimal sign extension {requested} is less than the number of bytes needed to decode {needed}")]
SignExtend { requested: usize, needed: usize },
#[error("Failed to read boolean bytes: {0}")]
ReadBoolean(#[source] std::io::Error),
#[error("Failed to read bytes: {0}")]
ReadBytes(#[source] std::io::Error),
#[error("Failed to read string: {0}")]
ReadString(#[source] std::io::Error),
#[error("Failed to read double: {0}")]
ReadDouble(#[source] std::io::Error),
#[error("Failed to read float: {0}")]
ReadFloat(#[source] std::io::Error),
#[error("Failed to read duration: {0}")]
ReadDuration(#[source] std::io::Error),
#[error("Failed to read fixed number of bytes '{1}': : {0}")]
ReadFixed(#[source] std::io::Error, usize),
#[error("Failed to convert &str to UUID: {0}")]
ConvertStrToUuid(#[source] uuid::Error),
#[error("Failed to convert Fixed bytes to UUID. It must be exactly 16 bytes, got {0}")]
ConvertFixedToUuid(usize),
#[error("Failed to convert Fixed bytes to UUID: {0}")]
ConvertSliceToUuid(#[source] uuid::Error),
#[error("Map key is not a string; key type is {0:?}")]
MapKeyType(ValueKind),
#[error("Union index {index} out of bounds: {num_variants}")]
GetUnionVariant { index: i64, num_variants: usize },
#[error("Enum symbol index out of bounds: {num_variants}")]
EnumSymbolIndex { index: usize, num_variants: usize },
#[error("Enum symbol not found {0}")]
GetEnumSymbol(String),
#[error("Unable to decode enum index")]
GetEnumUnknownIndexValue,
#[error("Scale {scale} is greater than precision {precision}")]
GetScaleAndPrecision { scale: usize, precision: usize },
#[error(
"Fixed type number of bytes {size} is not large enough to hold decimal values of precision {precision}"
)]
GetScaleWithFixedSize { size: usize, precision: usize },
#[error("Expected Value::Uuid, got: {0:?}")]
GetUuid(Value),
#[error("Expected Value::BigDecimal, got: {0:?}")]
GetBigDecimal(Value),
#[error("Fixed bytes of size 12 expected, got Fixed of size {0}")]
GetDecimalFixedBytes(usize),
#[error("Expected Value::Duration or Value::Fixed(12), got: {0:?}")]
ResolveDuration(Value),
#[error("Expected Value::Decimal, Value::Bytes or Value::Fixed, got: {0:?}")]
ResolveDecimal(Value),
#[error("Missing field in record: {0:?}")]
GetField(String),
#[error("Unable to convert to u8, got {0:?}")]
GetU8(Value),
#[error("Precision {precision} too small to hold decimal values with {num_bytes} bytes")]
ComparePrecisionAndSize { precision: usize, num_bytes: usize },
#[error("Cannot convert length to i32: {1}")]
ConvertLengthToI32(#[source] std::num::TryFromIntError, usize),
#[error("Expected Value::Date or Value::Int, got: {0:?}")]
GetDate(Value),
#[error("Expected Value::TimeMillis or Value::Int, got: {0:?}")]
GetTimeMillis(Value),
#[error("Expected Value::TimeMicros, Value::Long or Value::Int, got: {0:?}")]
GetTimeMicros(Value),
#[error("Expected Value::TimestampMillis, Value::Long or Value::Int, got: {0:?}")]
GetTimestampMillis(Value),
#[error("Expected Value::TimestampMicros, Value::Long or Value::Int, got: {0:?}")]
GetTimestampMicros(Value),
#[error("Expected Value::TimestampNanos, Value::Long or Value::Int, got: {0:?}")]
GetTimestampNanos(Value),
#[error("Expected Value::LocalTimestampMillis, Value::Long or Value::Int, got: {0:?}")]
GetLocalTimestampMillis(Value),
#[error("Expected Value::LocalTimestampMicros, Value::Long or Value::Int, got: {0:?}")]
GetLocalTimestampMicros(Value),
#[error("Expected Value::LocalTimestampNanos, Value::Long or Value::Int, got: {0:?}")]
GetLocalTimestampNanos(Value),
#[error("Expected Value::Null, got: {0:?}")]
GetNull(Value),
#[error("Expected Value::Boolean, got: {0:?}")]
GetBoolean(Value),
#[error("Expected Value::Int, got: {0:?}")]
GetInt(Value),
#[error("Expected Value::Long or Value::Int, got: {0:?}")]
GetLong(Value),
#[error(r#"Expected Value::Double, Value::Float, Value::Int, Value::Long or Value::String ("NaN", "INF", "Infinity", "-INF" or "-Infinity"), got: {0:?}"#)]
GetDouble(Value),
#[error(r#"Expected Value::Float, Value::Double, Value::Int, Value::Long or Value::String ("NaN", "INF", "Infinity", "-INF" or "-Infinity"), got: {0:?}"#)]
GetFloat(Value),
#[error("Expected Value::Bytes, got: {0:?}")]
GetBytes(Value),
#[error("Expected Value::String, Value::Bytes or Value::Fixed, got: {0:?}")]
GetString(Value),
#[error("Expected Value::Enum, got: {0:?}")]
GetEnum(Value),
#[error("Fixed size mismatch, expected: {size}, got: {n}")]
CompareFixedSizes { size: usize, n: usize },
#[error("String expected for fixed, got: {0:?}")]
GetStringForFixed(Value),
#[error("Enum default {symbol:?} is not among allowed symbols {symbols:?}")]
GetEnumDefault {
symbol: String,
symbols: Vec<String>,
},
#[error("Enum value index {index} is out of bounds {nsymbols}")]
GetEnumValue { index: usize, nsymbols: usize },
#[error("Key {0} not found in decimal metadata JSON")]
GetDecimalMetadataFromJson(&'static str),
#[error("Could not find matching type in union")]
FindUnionVariant,
#[error("Union type should not be empty")]
EmptyUnion,
#[error("Array({expected:?}) expected, got {other:?}")]
GetArray { expected: SchemaKind, other: Value },
#[error("Map({expected:?}) expected, got {other:?}")]
GetMap { expected: SchemaKind, other: Value },
#[error("Record with fields {expected:?} expected, got {other:?}")]
GetRecord {
expected: Vec<(String, SchemaKind)>,
other: Value,
},
#[error("No `name` field")]
GetNameField,
#[error("No `name` in record field")]
GetNameFieldFromRecord,
#[error("Unions may not directly contain a union")]
GetNestedUnion,
#[error("Unions cannot contain duplicate types")]
GetUnionDuplicate,
#[error("One union type {0:?} must match the `default`'s value type {1:?}")]
GetDefaultUnion(SchemaKind, ValueKind),
#[error("`default`'s value type of field {0:?} in {1:?} must be {2:?}")]
GetDefaultRecordField(String, String, String),
#[error("JSON value {0} claims to be u64 but cannot be converted")]
GetU64FromJson(serde_json::Number),
#[error("JSON value {0} claims to be i64 but cannot be converted")]
GetI64FromJson(serde_json::Number),
#[error("Cannot convert u64 to usize: {1}")]
ConvertU64ToUsize(#[source] std::num::TryFromIntError, u64),
#[error("Cannot convert u32 to usize: {1}")]
ConvertU32ToUsize(#[source] std::num::TryFromIntError, u32),
#[error("Cannot convert i64 to usize: {1}")]
ConvertI64ToUsize(#[source] std::num::TryFromIntError, i64),
#[error("Cannot convert i32 to usize: {1}")]
ConvertI32ToUsize(#[source] std::num::TryFromIntError, i32),
#[error("Invalid JSON value for decimal precision/scale integer: {0}")]
GetPrecisionOrScaleFromJson(serde_json::Number),
#[error("Failed to parse schema from JSON")]
ParseSchemaJson(#[source] serde_json::Error),
#[error("Failed to read schema")]
ReadSchemaFromReader(#[source] std::io::Error),
#[error("Must be a JSON string, object or array")]
ParseSchemaFromValidJson,
#[error("Unknown primitive type: {0}")]
ParsePrimitive(String),
#[error("invalid JSON for {key:?}: {value:?}")]
GetDecimalMetadataValueFromJson {
key: String,
value: serde_json::Value,
},
#[error("The decimal precision ({precision}) must be bigger or equal to the scale ({scale})")]
DecimalPrecisionLessThanScale { precision: usize, scale: usize },
#[error("The decimal precision ({precision}) must be a positive number")]
DecimalPrecisionMuBePositive { precision: usize },
#[error("Unreadable big decimal sign")]
BigDecimalSign,
#[error("Unreadable length for big decimal inner bytes: {0}")]
BigDecimalLen(#[source] Box<Error>),
#[error("Unreadable big decimal scale")]
BigDecimalScale,
#[error("Unexpected `type` {0} variant for `logicalType`")]
GetLogicalTypeVariant(serde_json::Value),
#[error("No `type` field found for `logicalType`")]
GetLogicalTypeField,
#[error("logicalType must be a string, but is {0:?}")]
GetLogicalTypeFieldType(serde_json::Value),
#[error("Unknown complex type: {0}")]
GetComplexType(serde_json::Value),
#[error("No `type` in complex type")]
GetComplexTypeField,
#[error("No `fields` in record")]
GetRecordFieldsJson,
#[error("No `symbols` field in enum")]
GetEnumSymbolsField,
#[error("Unable to parse `symbols` in enum")]
GetEnumSymbols,
#[error("Invalid enum symbol name {0}")]
EnumSymbolName(String),
#[error("Invalid field name {0}")]
FieldName(String),
#[error("Duplicate field name {0}")]
FieldNameDuplicate(String),
#[error("Invalid schema name {0}. It must match the regex '{1}'")]
InvalidSchemaName(String, &'static str),
#[error("Invalid namespace {0}. It must match the regex '{1}'")]
InvalidNamespace(String, &'static str),
#[error("Invalid schema: There is no type called '{0}', if you meant to define a non-primitive schema, it should be defined inside `type` attribute. Please review the specification")]
InvalidSchemaRecord(String),
#[error("Duplicate enum symbol {0}")]
EnumSymbolDuplicate(String),
#[error("Default value for enum must be a string! Got: {0}")]
EnumDefaultWrongType(serde_json::Value),
#[error("No `items` in array")]
GetArrayItemsField,
#[error("No `values` in map")]
GetMapValuesField,
#[error("Fixed schema `size` value must be a positive integer: {0}")]
GetFixedSizeFieldPositive(serde_json::Value),
#[error("Fixed schema has no `size`")]
GetFixedSizeField,
#[error("Fixed schema's default value length ({0}) does not match its size ({1})")]
FixedDefaultLenSizeMismatch(usize, u64),
#[error("Failed to compress with flate: {0}")]
DeflateCompress(#[source] std::io::Error),
// no longer possible after migration from libflate to miniz_oxide
#[deprecated(since = "0.19.0", note = "This error can no longer occur")]
#[error("Failed to finish flate compressor: {0}")]
DeflateCompressFinish(#[source] std::io::Error),
#[error("Failed to decompress with flate: {0}")]
DeflateDecompress(#[source] std::io::Error),
#[cfg(feature = "snappy")]
#[error("Failed to compress with snappy: {0}")]
SnappyCompress(#[source] snap::Error),
#[cfg(feature = "snappy")]
#[error("Failed to get snappy decompression length: {0}")]
GetSnappyDecompressLen(#[source] snap::Error),
#[cfg(feature = "snappy")]
#[error("Failed to decompress with snappy: {0}")]
SnappyDecompress(#[source] snap::Error),
#[error("Failed to compress with zstd: {0}")]
ZstdCompress(#[source] std::io::Error),
#[error("Failed to decompress with zstd: {0}")]
ZstdDecompress(#[source] std::io::Error),
#[error("Failed to read header: {0}")]
ReadHeader(#[source] std::io::Error),
#[error("wrong magic in header")]
HeaderMagic,
#[error("Message Header mismatch. Expected: {0:?}. Actual: {1:?}")]
SingleObjectHeaderMismatch(Vec<u8>, Vec<u8>),
#[error("Failed to get JSON from avro.schema key in map")]
GetAvroSchemaFromMap,
#[error("no metadata in header")]
GetHeaderMetadata,
#[error("Failed to read marker bytes: {0}")]
ReadMarker(#[source] std::io::Error),
#[error("Failed to read block marker bytes: {0}")]
ReadBlockMarker(#[source] std::io::Error),
#[error("Read into buffer failed: {0}")]
ReadIntoBuf(#[source] std::io::Error),
#[error("block marker does not match header marker")]
GetBlockMarker,
#[error("Overflow when decoding integer value")]
IntegerOverflow,
#[error("Failed to read bytes for decoding variable length integer: {0}")]
ReadVariableIntegerBytes(#[source] std::io::Error),
#[error("Decoded integer out of range for i32: {1}: {0}")]
ZagI32(#[source] std::num::TryFromIntError, i64),
#[error("unable to read block")]
ReadBlock,
#[error("Failed to serialize value into Avro value: {0}")]
SerializeValue(String),
#[error("Failed to serialize value of type {value_type} using schema {schema:?}: {value}")]
SerializeValueWithSchema {
value_type: &'static str,
value: String,
schema: Schema,
},
#[error("Failed to serialize field '{field_name}' for record {record_schema:?}: {error}")]
SerializeRecordFieldWithSchema {
field_name: &'static str,
record_schema: Schema,
error: Box<Error>,
},
#[error("Failed to deserialize Avro value into value: {0}")]
DeserializeValue(String),
#[error("Failed to write buffer bytes during flush: {0}")]
WriteBytes(#[source] std::io::Error),
#[error("Failed to flush inner writer during flush: {0}")]
FlushWriter(#[source] std::io::Error),
#[error("Failed to write marker: {0}")]
WriteMarker(#[source] std::io::Error),
#[error("Failed to convert JSON to string: {0}")]
ConvertJsonToString(#[source] serde_json::Error),
/// Error while converting float to json value
#[error("failed to convert avro float to json: {0}")]
ConvertF64ToJson(f64),
/// Error while resolving Schema::Ref
#[error("Unresolved schema reference: {0}")]
SchemaResolutionError(Name),
#[error("The file metadata is already flushed.")]
FileHeaderAlreadyWritten,
#[error("Metadata keys starting with 'avro.' are reserved for internal usage: {0}.")]
InvalidMetadataKey(String),
/// Error when two named schema have the same fully qualified name
#[error("Two named schema defined for same fullname: {0}.")]
AmbiguousSchemaDefinition(Name),
#[error("Signed decimal bytes length {0} not equal to fixed schema size {1}.")]
EncodeDecimalAsFixedError(usize, usize),
#[error("There is no entry for '{0}' in the lookup table: {1}.")]
NoEntryInLookupTable(String, String),
#[error("Can only encode value type {value_kind:?} as one of {supported_schema:?}")]
EncodeValueAsSchemaError {
value_kind: ValueKind,
supported_schema: Vec<SchemaKind>,
},
#[error(
"Internal buffer not drained properly. Re-initialize the single object writer struct!"
)]
IllegalSingleObjectWriterState,
#[error("Codec '{0}' is not supported/enabled")]
CodecNotSupported(String),
#[error("Invalid Avro data! Cannot read codec type from value that is not Value::Bytes.")]
BadCodecMetadata,
#[error("Cannot convert a slice to Uuid: {0}")]
UuidFromSlice(#[source] uuid::Error),
}
#[derive(thiserror::Error, PartialEq)]
pub enum CompatibilityError {
#[error("Incompatible schema types! Writer schema is '{writer_schema_type}', but reader schema is '{reader_schema_type}'")]
WrongType {
writer_schema_type: String,
reader_schema_type: String,
},
#[error("Incompatible schema types! The {schema_type} should have been {expected_type:?}")]
TypeExpected {
schema_type: String,
expected_type: Vec<SchemaKind>,
},
#[error("Incompatible schemata! Field '{0}' in reader schema does not match the type in the writer schema")]
FieldTypeMismatch(String, #[source] Box<CompatibilityError>),
#[error("Incompatible schemata! Field '{0}' in reader schema must have a default value")]
MissingDefaultValue(String),
#[error("Incompatible schemata! Reader's symbols must contain all writer's symbols")]
MissingSymbols,
#[error("Incompatible schemata! All elements in union must match for both schemas")]
MissingUnionElements,
#[error("Incompatible schemata! Name and size don't match for fixed")]
FixedMismatch,
#[error("Incompatible schemata! The name must be the same for both schemas. Writer's name {writer_name} and reader's name {reader_name}")]
NameMismatch {
writer_name: String,
reader_name: String,
},
#[error(
"Incompatible schemata! Unknown type for '{0}'. Make sure that the type is a valid one"
)]
Inconclusive(String),
}
impl serde::ser::Error for Error {
fn custom<T: fmt::Display>(msg: T) -> Self {
Error::SerializeValue(msg.to_string())
}
}
impl serde::de::Error for Error {
fn custom<T: fmt::Display>(msg: T) -> Self {
Error::DeserializeValue(msg.to_string())
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut msg = self.to_string();
if let Some(e) = self.source() {
msg.extend([": ", &e.to_string()]);
}
write!(f, "{}", msg)
}
}
impl fmt::Debug for CompatibilityError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut msg = self.to_string();
if let Some(e) = self.source() {
msg.extend([": ", &e.to_string()]);
}
write!(f, "{}", msg)
}
}