blob: d65ca2f8e690fe7fdcc674f1d80d6e95544e4625 [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.
use std::{io::Read, marker::PhantomData};
use bon::bon;
use serde::de::DeserializeOwned;
use crate::{
AvroResult, AvroSchema, Schema,
decode::decode_internal,
error::Details,
headers::{HeaderBuilder, RabinFingerprintHeader},
schema::ResolvedOwnedSchema,
serde::deser_schema::{Config, SchemaAwareDeserializer},
types::Value,
util::is_human_readable,
};
pub struct GenericSingleObjectReader {
write_schema: ResolvedOwnedSchema,
expected_header: Vec<u8>,
human_readable: bool,
}
#[bon]
impl GenericSingleObjectReader {
#[builder]
pub fn new(
schema: Schema,
/// The expected header.
#[builder(default = RabinFingerprintHeader::from_schema(&schema).build_header())]
header: Vec<u8>,
/// Was the data serialized with `human_readable`.
#[builder(default = is_human_readable())]
human_readable: bool,
) -> AvroResult<GenericSingleObjectReader> {
Ok(Self {
write_schema: schema.try_into()?,
expected_header: header,
human_readable,
})
}
}
impl GenericSingleObjectReader {
pub fn read_value<R: Read>(&self, reader: &mut R) -> AvroResult<Value> {
self.read_header(reader)?;
decode_internal(
self.write_schema.get_root_schema(),
self.write_schema.get_names(),
None,
reader,
)
}
pub fn read_deser<T: DeserializeOwned>(&self, reader: &mut impl Read) -> AvroResult<T> {
self.read_header(reader)?;
let config = Config {
names: self.write_schema.get_names(),
human_readable: self.human_readable,
};
T::deserialize(SchemaAwareDeserializer::new(
reader,
self.write_schema.get_root_schema(),
config,
)?)
}
fn read_header(&self, reader: &mut impl Read) -> AvroResult<()> {
let mut header = vec![0; self.expected_header.len()];
reader
.read_exact(&mut header)
.map_err(Details::ReadHeader)?;
if self.expected_header == header {
Ok(())
} else {
Err(Details::SingleObjectHeaderMismatch(self.expected_header.clone(), header).into())
}
}
}
pub struct SpecificSingleObjectReader<T>
where
T: AvroSchema,
{
inner: GenericSingleObjectReader,
_model: PhantomData<T>,
}
impl<T> SpecificSingleObjectReader<T>
where
T: AvroSchema,
{
pub fn new() -> AvroResult<SpecificSingleObjectReader<T>> {
Ok(SpecificSingleObjectReader {
inner: GenericSingleObjectReader::builder()
.schema(T::get_schema())
.build()?,
_model: PhantomData,
})
}
}
impl<T> SpecificSingleObjectReader<T>
where
T: AvroSchema + From<Value>,
{
pub fn read_from_value<R: Read>(&self, reader: &mut R) -> AvroResult<T> {
self.inner.read_value(reader).map(|v| v.into())
}
}
impl<T> SpecificSingleObjectReader<T>
where
T: AvroSchema + DeserializeOwned,
{
pub fn read<R: Read>(&self, reader: &mut R) -> AvroResult<T> {
self.inner.read_deser(reader)
}
}
#[cfg(test)]
mod tests {
use apache_avro_test_helper::TestResult;
use serde::Deserialize;
use uuid::Uuid;
use super::*;
use crate::{AvroSchema, Schema, encode::encode, headers::GlueSchemaUuidHeader, rabin::Rabin};
#[derive(Deserialize, Clone, PartialEq, Debug)]
struct TestSingleObjectReader {
a: i64,
b: f64,
c: Vec<String>,
}
impl AvroSchema for TestSingleObjectReader {
fn get_schema() -> Schema {
let schema = r#"
{
"type":"record",
"name":"TestSingleObjectReader",
"fields":[
{
"name":"a",
"type":"long"
},
{
"name":"b",
"type":"double"
},
{
"name":"c",
"type":{
"type":"array",
"items":"string"
}
}
]
}
"#;
Schema::parse_str(schema).unwrap()
}
}
impl From<Value> for TestSingleObjectReader {
fn from(obj: Value) -> TestSingleObjectReader {
if let Value::Record(fields) = obj {
let mut a = None;
let mut b = None;
let mut c = vec![];
for (field_name, v) in fields {
match (field_name.as_str(), v) {
("a", Value::Long(i)) => a = Some(i),
("b", Value::Double(d)) => b = Some(d),
("c", Value::Array(v)) => {
for inner_val in v {
if let Value::String(s) = inner_val {
c.push(s);
}
}
}
(key, value) => panic!("Unexpected pair: {key:?} -> {value:?}"),
}
}
TestSingleObjectReader {
a: a.unwrap(),
b: b.unwrap(),
c,
}
} else {
panic!("Expected a Value::Record but was {obj:?}")
}
}
}
impl From<TestSingleObjectReader> for Value {
fn from(obj: TestSingleObjectReader) -> Value {
Value::Record(vec![
("a".into(), obj.a.into()),
("b".into(), obj.b.into()),
(
"c".into(),
Value::Array(obj.c.into_iter().map(|s| s.into()).collect()),
),
])
}
}
#[test]
fn test_avro_3507_single_object_reader() -> TestResult {
let obj = TestSingleObjectReader {
a: 42,
b: 3.33,
c: vec!["cat".into(), "dog".into()],
};
let mut to_read = Vec::<u8>::new();
to_read.extend_from_slice(&[0xC3, 0x01]);
to_read.extend_from_slice(
&TestSingleObjectReader::get_schema()
.fingerprint::<Rabin>()
.bytes[..],
);
encode(
&obj.clone().into(),
&TestSingleObjectReader::get_schema(),
&mut to_read,
)
.expect("Encode should succeed");
let mut to_read = &to_read[..];
let generic_reader = GenericSingleObjectReader::builder()
.schema(TestSingleObjectReader::get_schema())
.build()
.expect("Schema should resolve");
let val = generic_reader
.read_value(&mut to_read)
.expect("Should read");
let expected_value: Value = obj.into();
pretty_assertions::assert_eq!(expected_value, val);
Ok(())
}
#[test]
fn avro_3642_test_single_object_reader_incomplete_reads() -> TestResult {
let obj = TestSingleObjectReader {
a: 42,
b: 3.33,
c: vec!["cat".into(), "dog".into()],
};
// The two-byte marker, to show that the message uses this single-record format
let to_read_1 = [0xC3, 0x01];
let mut to_read_2 = Vec::<u8>::new();
to_read_2.extend_from_slice(
&TestSingleObjectReader::get_schema()
.fingerprint::<Rabin>()
.bytes[..],
);
let mut to_read_3 = Vec::<u8>::new();
encode(
&obj.clone().into(),
&TestSingleObjectReader::get_schema(),
&mut to_read_3,
)
.expect("Encode should succeed");
let mut to_read = (&to_read_1[..]).chain(&to_read_2[..]).chain(&to_read_3[..]);
let generic_reader = GenericSingleObjectReader::builder()
.schema(TestSingleObjectReader::get_schema())
.build()
.expect("Schema should resolve");
let val = generic_reader
.read_value(&mut to_read)
.expect("Should read");
let expected_value: Value = obj.into();
pretty_assertions::assert_eq!(expected_value, val);
Ok(())
}
#[test]
fn test_avro_3507_reader_parity() -> TestResult {
let obj = TestSingleObjectReader {
a: 42,
b: 3.33,
c: vec!["cat".into(), "dog".into()],
};
let mut to_read = Vec::<u8>::new();
to_read.extend_from_slice(&[0xC3, 0x01]);
to_read.extend_from_slice(
&TestSingleObjectReader::get_schema()
.fingerprint::<Rabin>()
.bytes[..],
);
encode(
&obj.clone().into(),
&TestSingleObjectReader::get_schema(),
&mut to_read,
)
.expect("Encode should succeed");
let generic_reader = GenericSingleObjectReader::builder()
.schema(TestSingleObjectReader::get_schema())
.build()
.expect("Schema should resolve");
let specific_reader = SpecificSingleObjectReader::<TestSingleObjectReader>::new()
.expect("schema should resolve");
let mut to_read1 = &to_read[..];
let mut to_read2 = &to_read[..];
let mut to_read3 = &to_read[..];
let val = generic_reader
.read_value(&mut to_read1)
.expect("Should read");
let read_obj1 = specific_reader
.read_from_value(&mut to_read2)
.expect("Should read from value");
let read_obj2 = specific_reader
.read(&mut to_read3)
.expect("Should read from deserilize");
let expected_value: Value = obj.clone().into();
pretty_assertions::assert_eq!(obj, read_obj1);
pretty_assertions::assert_eq!(obj, read_obj2);
pretty_assertions::assert_eq!(val, expected_value);
Ok(())
}
#[test]
fn avro_rs_164_generic_reader_alternate_header() -> TestResult {
let schema_uuid = Uuid::parse_str("b2f1cf00-0434-013e-439a-125eb8485a5f")?;
let header_builder = GlueSchemaUuidHeader::from_uuid(schema_uuid);
let generic_reader = GenericSingleObjectReader::builder()
.schema(TestSingleObjectReader::get_schema())
.header(header_builder.build_header())
.build()
.expect("failed to build reader");
// First 18 bytes are the header, then it's a varint 0, double 0 and an empty list (0)
let data_to_read: Vec<u8> = vec![
3, 0, 178, 241, 207, 0, 4, 52, 1, 62, 67, 154, 18, 94, 184, 72, 90, 95, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
];
let mut to_read = &data_to_read[..];
let read_result = generic_reader.read_value(&mut to_read)?;
assert_eq!(
read_result,
Value::Record(vec![
("a".to_string(), Value::Long(0)),
("b".to_string(), Value::Double(0.0)),
("c".to_string(), Value::Array(vec![]))
])
);
Ok(())
}
}