blob: 3f50989737d48a13497175f72a9cc763bf471ede [file]
// 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.
//! **[Apache Avro](https://avro.apache.org/)** is a data serialization system which provides rich
//! data structures and a compact, fast, binary data format. If you are not familiar with the data
//! format, please read [`documentation::primer`] first.
//!
//! There are two ways of working with Avro data in this crate:
//!
//! 1. Via the generic [`Value`](types::Value) type.
//! 2. Via types implementing [`AvroSchema`] and Serde's [`Serialize`] and [`Deserialize`].
//!
//! The first option is great for dealing with Avro data in a dynamic way. For example, when working
//! with unknown or rapidly changing schemas or when you don't want or need to map to Rust types. The
//! module documentation of [`documentation::dynamic`] explains how to work in this dynamic way.
//!
//! The second option is great when dealing with static schemas that should be decoded to and encoded
//! from Rust types. The module documentation of [`serde`] explains how to work in this static way.
//!
//! # Features
//!
//! - `derive`: enable support for deriving [`AvroSchema`]
//! - `snappy`: enable support for the Snappy codec
//! - `zstandard`: enable support for the Zstandard codec
//! - `bzip`: enable support for the Bzip2 codec
//! - `xz`: enable support for the Xz codec
//!
//! # MSRV
//!
//! The current MSRV is 1.88.0.
//!
//! The MSRV may be bumped in minor releases.
//!
// These are links because otherwise `cargo rdme` gets angry
//! [`Serialize`]: https://docs.rs/serde/latest/serde/trait.Serialize.html
//! [`Deserialize`]: https://docs.rs/serde/latest/serde/trait.Deserialize.html
// Enable using `fake_variadic` on docs.rs
#![cfg_attr(docsrs, feature(rustdoc_internals))]
#![cfg_attr(docsrs, allow(internal_features))]
mod bigdecimal;
mod bytes;
mod codec;
mod decimal;
mod decode;
mod duration;
mod encode;
#[cfg(doc)]
pub mod documentation;
pub mod error;
pub mod headers;
pub mod rabin;
pub mod reader;
pub mod schema;
pub mod schema_compatibility;
pub mod schema_equality;
pub mod serde;
pub mod types;
pub mod util;
pub mod validator;
pub mod writer;
#[expect(deprecated)]
pub use crate::{
bigdecimal::BigDecimal,
bytes::{
serde_avro_bytes, serde_avro_bytes_opt, serde_avro_fixed, serde_avro_fixed_opt,
serde_avro_slice, serde_avro_slice_opt,
},
};
#[cfg(feature = "bzip")]
pub use codec::bzip::Bzip2Settings;
#[cfg(feature = "xz")]
pub use codec::xz::XzSettings;
#[cfg(feature = "zstandard")]
pub use codec::zstandard::ZstandardSettings;
pub use codec::{Codec, DeflateSettings};
pub use decimal::Decimal;
pub use duration::{Days, Duration, Millis, Months};
pub use error::Error;
#[expect(
deprecated,
reason = "Still need to export it until we remove it completely"
)]
pub use reader::{
Reader,
datum::{from_avro_datum, from_avro_datum_reader_schemata, from_avro_datum_schemata},
read_marker,
single_object::{GenericSingleObjectReader, SpecificSingleObjectReader},
};
pub use schema::Schema;
pub use serde::{AvroSchema, AvroSchemaComponent, from_value, to_value};
pub use uuid::Uuid;
#[expect(
deprecated,
reason = "Still need to export it until we remove it completely"
)]
pub use writer::{
Clearable, Writer, WriterBuilder,
datum::{to_avro_datum, to_avro_datum_schemata, write_avro_datum_ref},
single_object::{GenericSingleObjectWriter, SpecificSingleObjectWriter},
};
#[cfg(feature = "derive")]
pub use apache_avro_derive::AvroSchema;
/// A convenience type alias for `Result`s with `Error`s.
pub type AvroResult<T> = Result<T, Error>;
/// Set the maximum number of bytes that can be allocated when decoding data.
///
/// This function only changes the setting once. On subsequent calls the value will stay the same
/// as the first time it is called. It is automatically called on first allocation and defaults to
/// [`util::DEFAULT_MAX_ALLOCATION_BYTES`].
///
/// # Returns
/// The configured maximum, which might be different from what the function was called with if the
/// value was already set before.
#[deprecated(
since = "0.21.0",
note = "Please use apache_avro::util::max_allocation_bytes"
)]
pub fn max_allocation_bytes(num_bytes: usize) -> usize {
util::max_allocation_bytes(num_bytes)
}
/// Set whether the serializer and deserializer should indicate to types that the format is human-readable.
///
/// This function only changes the setting once. On subsequent calls the value will stay the same
/// as the first time it is called. It is automatically called on first allocation and defaults to
/// [`util::DEFAULT_SERDE_HUMAN_READABLE`].
///
/// *NOTE*: Changing this setting can change the output of [`from_value`] and the
/// accepted input of [`to_value`].
///
/// # Returns
/// The configured human-readable value, which might be different from what the function was called
/// with if the value was already set before.
#[deprecated(
since = "0.21.0",
note = "Please use apache_avro::util::set_serde_human_readable"
)]
pub fn set_serde_human_readable(human_readable: bool) -> bool {
util::set_serde_human_readable(human_readable)
}
#[cfg(test)]
mod tests {
use crate::{
Codec, Reader, Schema, Writer,
reader::datum::GenericDatumReader,
types::{Record, Value},
};
use apache_avro_test_helper::TestResult;
use pretty_assertions::assert_eq;
//TODO: move where it fits better
#[test]
fn test_enum_default() {
let writer_raw_schema = r#"
{
"type": "record",
"name": "test",
"fields": [
{"name": "a", "type": "long", "default": 42},
{"name": "b", "type": "string"}
]
}
"#;
let reader_raw_schema = r#"
{
"type": "record",
"name": "test",
"fields": [
{"name": "a", "type": "long", "default": 42},
{"name": "b", "type": "string"},
{
"name": "c",
"type": {
"type": "enum",
"name": "suit",
"symbols": ["diamonds", "spades", "clubs", "hearts"]
},
"default": "spades"
}
]
}
"#;
let writer_schema = Schema::parse_str(writer_raw_schema).unwrap();
let reader_schema = Schema::parse_str(reader_raw_schema).unwrap();
let mut writer = Writer::with_codec(&writer_schema, Vec::new(), Codec::Null).unwrap();
let mut record = Record::new(writer.schema()).unwrap();
record.put("a", 27i64);
record.put("b", "foo");
writer.append_value(record).unwrap();
let input = writer.into_inner().unwrap();
let mut reader = Reader::builder(&input[..])
.reader_schema(&reader_schema)
.build()
.unwrap();
assert_eq!(
reader.next().unwrap().unwrap(),
Value::Record(vec![
("a".to_string(), Value::Long(27)),
("b".to_string(), Value::String("foo".to_string())),
("c".to_string(), Value::Enum(1, "spades".to_string())),
])
);
assert!(reader.next().is_none());
}
//TODO: move where it fits better
#[test]
fn test_enum_string_value() {
let raw_schema = r#"
{
"type": "record",
"name": "test",
"fields": [
{"name": "a", "type": "long", "default": 42},
{"name": "b", "type": "string"},
{
"name": "c",
"type": {
"type": "enum",
"name": "suit",
"symbols": ["diamonds", "spades", "clubs", "hearts"]
},
"default": "spades"
}
]
}
"#;
let schema = Schema::parse_str(raw_schema).unwrap();
let mut writer = Writer::with_codec(&schema, Vec::new(), Codec::Null).unwrap();
let mut record = Record::new(writer.schema()).unwrap();
record.put("a", 27i64);
record.put("b", "foo");
record.put("c", "clubs");
writer.append_value(record).unwrap();
let input = writer.into_inner().unwrap();
let mut reader = Reader::builder(&input[..])
.reader_schema(&schema)
.build()
.unwrap();
assert_eq!(
reader.next().unwrap().unwrap(),
Value::Record(vec![
("a".to_string(), Value::Long(27)),
("b".to_string(), Value::String("foo".to_string())),
("c".to_string(), Value::Enum(2, "clubs".to_string())),
])
);
assert!(reader.next().is_none());
}
//TODO: move where it fits better
#[test]
fn test_enum_no_reader_schema() {
let writer_raw_schema = r#"
{
"type": "record",
"name": "test",
"fields": [
{"name": "a", "type": "long", "default": 42},
{"name": "b", "type": "string"},
{
"name": "c",
"type": {
"type": "enum",
"name": "suit",
"symbols": ["diamonds", "spades", "clubs", "hearts"]
},
"default": "spades"
}
]
}
"#;
let writer_schema = Schema::parse_str(writer_raw_schema).unwrap();
let mut writer = Writer::with_codec(&writer_schema, Vec::new(), Codec::Null).unwrap();
let mut record = Record::new(writer.schema()).unwrap();
record.put("a", 27i64);
record.put("b", "foo");
record.put("c", "clubs");
writer.append_value(record).unwrap();
let input = writer.into_inner().unwrap();
let mut reader = Reader::new(&input[..]).unwrap();
assert_eq!(
reader.next().unwrap().unwrap(),
Value::Record(vec![
("a".to_string(), Value::Long(27)),
("b".to_string(), Value::String("foo".to_string())),
("c".to_string(), Value::Enum(2, "clubs".to_string())),
])
);
}
#[test]
fn test_illformed_length() -> TestResult {
let raw_schema = r#"
{
"type": "record",
"name": "test",
"fields": [
{"name": "a", "type": "long", "default": 42},
{"name": "b", "type": "string"}
]
}
"#;
let schema = Schema::parse_str(raw_schema).unwrap();
// Would allocate 18446744073709551605 bytes
let illformed: &[u8] = &[0x3e, 0x15, 0xff, 0x1f, 0x15, 0xff];
let value = GenericDatumReader::builder(&schema)
.build()?
.read_value(&mut &*illformed);
assert!(value.is_err());
Ok(())
}
}