This is an automated email from the ASF dual-hosted git repository.

kriskras99 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/avro-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new cc37970  feat!: Rework `SpecificSingleObjectWriter` (#445)
cc37970 is described below

commit cc379701f727a00caf461558b406da5515371093
Author: Kriskras99 <[email protected]>
AuthorDate: Tue Jan 27 20:33:59 2026 +0100

    feat!: Rework `SpecificSingleObjectWriter` (#445)
    
    * feat: Share logic between `ResolvedSchema` and `ResolvedOwnedSchema`
    
    `ResolvedOwnedSchema` now uses a self-referential struct when resolving the 
schema, so it does not need to clone all named schemas contained in it.
    
    This also fixes a bug in `reader::Block::read_writer_schema` where it 
unnecessarily parses the schemata twice.
    
    * feat!: Rework `SpecificSingleObjectWriter`
    
    It now resolves the schema and caches it. It also no longer uses 
`GenericSingleObjectWriter` for the header and does it directly. This removes 
the need for a buffer.
    
    This is a breaking change:
    - `SpecificSingleObjectWriter::with_capcity` has been removed.
    - `write_avro_datum_ref` now also takes a `names: &NamesRef` argument
    
    * fix: Add back `SpecificSingleObjectWriter::with_capacity` as a deprecated 
function
---
 Cargo.lock                                         |  58 ++++++-
 avro/Cargo.toml                                    |   1 +
 avro/examples/specific_single_object.rs            |   2 +-
 .../test_interop_single_object_encoding.rs         |   2 +-
 avro/src/reader.rs                                 |  17 +-
 avro/src/schema/resolve.rs                         | 191 ++++++++-------------
 avro/src/writer.rs                                 | 143 +++++++++++----
 avro/tests/serde_human_readable_false.rs           |   9 +-
 avro/tests/serde_human_readable_true.rs            |   9 +-
 9 files changed, 254 insertions(+), 178 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 7bef0b5..3c2c74e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -26,6 +26,12 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "aliasable"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
+
 [[package]]
 name = "alloca"
 version = "0.4.0"
@@ -65,6 +71,7 @@ dependencies = [
  "md-5",
  "miniz_oxide 0.9.0",
  "num-bigint",
+ "ouroboros",
  "paste",
  "pretty_assertions",
  "quad-rand",
@@ -594,6 +601,12 @@ version = "0.16.1"
 source = "registry+https://github.com/rust-lang/crates.io-index";
 checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
 
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
 [[package]]
 name = "heck"
 version = "0.5.0"
@@ -816,6 +829,30 @@ version = "11.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index";
 checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
 
+[[package]]
+name = "ouroboros"
+version = "0.18.5"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59"
+dependencies = [
+ "aliasable",
+ "ouroboros_macro",
+ "static_assertions",
+]
+
+[[package]]
+name = "ouroboros_macro"
+version = "0.18.5"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro2",
+ "proc-macro2-diagnostics",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "page_size"
 version = "0.6.0"
@@ -888,6 +925,19 @@ dependencies = [
  "unicode-ident",
 ]
 
+[[package]]
+name = "proc-macro2-diagnostics"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+ "yansi",
+]
+
 [[package]]
 name = "proptest"
 version = "1.9.0"
@@ -1156,6 +1206,12 @@ version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index";
 checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b"
 
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
 [[package]]
 name = "strsim"
 version = "0.11.1"
@@ -1174,7 +1230,7 @@ version = "0.27.2"
 source = "registry+https://github.com/rust-lang/crates.io-index";
 checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
 dependencies = [
- "heck",
+ "heck 0.5.0",
  "proc-macro2",
  "quote",
  "syn",
diff --git a/avro/Cargo.toml b/avro/Cargo.toml
index 49c3c88..08fcb6a 100644
--- a/avro/Cargo.toml
+++ b/avro/Cargo.toml
@@ -62,6 +62,7 @@ digest = { default-features = false, version = "0.10.7", 
features = ["core-api"]
 miniz_oxide = { default-features = false, version = "0.9.0", features = 
["with-alloc"] }
 log = { workspace = true }
 num-bigint = { default-features = false, version = "0.4.6", features = ["std", 
"serde"] }
+ouroboros = { default-features = false, version = "0.18.5", features = ["std"] 
}
 regex-lite = { default-features = false, version = "0.1.8", features = ["std", 
"string"] }
 serde = { workspace = true }
 serde_bytes = { workspace = true }
diff --git a/avro/examples/specific_single_object.rs 
b/avro/examples/specific_single_object.rs
index fa756cf..347721a 100644
--- a/avro/examples/specific_single_object.rs
+++ b/avro/examples/specific_single_object.rs
@@ -32,7 +32,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
         b: "foo".to_string(),
     };
 
-    let mut writer = SpecificSingleObjectWriter::<Test>::with_capacity(1024)?;
+    let writer = SpecificSingleObjectWriter::<Test>::new()?;
     let reader = SpecificSingleObjectReader::<Test>::new()?;
 
     for test in repeat_n(test, 2) {
diff --git a/avro/examples/test_interop_single_object_encoding.rs 
b/avro/examples/test_interop_single_object_encoding.rs
index e65b35e..e16c221 100644
--- a/avro/examples/test_interop_single_object_encoding.rs
+++ b/avro/examples/test_interop_single_object_encoding.rs
@@ -65,7 +65,7 @@ fn main() -> Result<(), Box<dyn Error>> {
 
 fn test_write(expected: &[u8]) {
     let mut encoded: Vec<u8> = Vec::new();
-    
apache_avro::SpecificSingleObjectWriter::<InteropMessage>::with_capacity(1024)
+    apache_avro::SpecificSingleObjectWriter::<InteropMessage>::new()
         .expect("Resolving failed")
         .write_value(InteropMessage, &mut encoded)
         .expect("Encoding failed");
diff --git a/avro/src/reader.rs b/avro/src/reader.rs
index 967edb5..fed35fe 100644
--- a/avro/src/reader.rs
+++ b/avro/src/reader.rs
@@ -226,21 +226,20 @@ impl<'r, R: Read> Block<'r, R> {
             })
             .ok_or(Details::GetAvroSchemaFromMap)?;
         if !self.schemata.is_empty() {
-            let rs = ResolvedSchema::try_from(self.schemata.clone())?;
-            let names: Names = rs
-                .get_names()
-                .iter()
-                .map(|(name, schema)| (name.clone(), (*schema).clone()))
-                .collect();
-            self.writer_schema = Schema::parse_with_names(&json, names)?;
+            let mut names = HashMap::new();
             resolve_names_with_schemata(
                 self.schemata.iter().copied(),
-                &mut self.names_refs,
+                &mut names,
                 &None,
+                &HashMap::new(),
             )?;
+            self.names_refs = names.into_iter().map(|(n, s)| (n, 
s.clone())).collect();
+            self.writer_schema = Schema::parse_with_names(&json, 
self.names_refs.clone())?;
         } else {
             self.writer_schema = Schema::parse(&json)?;
-            resolve_names(&self.writer_schema, &mut self.names_refs, &None)?;
+            let mut names = HashMap::new();
+            resolve_names(&self.writer_schema, &mut names, &None, 
&HashMap::new())?;
+            self.names_refs = names.into_iter().map(|(n, s)| (n, 
s.clone())).collect();
         }
         Ok(())
     }
diff --git a/avro/src/schema/resolve.rs b/avro/src/schema/resolve.rs
index e82b87d..61b2e98 100644
--- a/avro/src/schema/resolve.rs
+++ b/avro/src/schema/resolve.rs
@@ -17,11 +17,10 @@
 
 use crate::error::Details;
 use crate::schema::{
-    DecimalSchema, EnumSchema, FixedSchema, InnerDecimalSchema, Names, 
NamesRef, Namespace,
-    RecordSchema, UnionSchema, UuidSchema,
+    DecimalSchema, EnumSchema, FixedSchema, InnerDecimalSchema, NamesRef, 
Namespace, RecordSchema,
+    UnionSchema, UuidSchema,
 };
 use crate::{AvroResult, Error, Schema};
-use std::borrow::Borrow;
 use std::collections::HashMap;
 
 #[derive(Debug)]
@@ -31,8 +30,8 @@ pub struct ResolvedSchema<'s> {
 }
 
 impl<'s> ResolvedSchema<'s> {
-    pub fn get_schemata(&self) -> Vec<&'s Schema> {
-        self.schemata.clone()
+    pub fn get_schemata(&self) -> &[&'s Schema] {
+        &self.schemata
     }
 
     pub fn get_names(&self) -> &NamesRef<'s> {
@@ -52,12 +51,7 @@ impl<'s> ResolvedSchema<'s> {
     /// These schemas will be resolved in order, so references to schemas 
later in the
     /// list is not supported.
     pub fn new_with_schemata(schemata: Vec<&'s Schema>) -> AvroResult<Self> {
-        let mut rs = ResolvedSchema {
-            names_ref: HashMap::new(),
-            schemata,
-        };
-        rs.resolve(rs.get_schemata(), &None, None)?;
-        Ok(rs)
+        Self::new_with_known_schemata(schemata, &None, &HashMap::new())
     }
 
     /// Creates `ResolvedSchema` with some already known schemas.
@@ -68,83 +62,17 @@ impl<'s> ResolvedSchema<'s> {
         enclosing_namespace: &Namespace,
         known_schemata: &'n NamesRef<'n>,
     ) -> AvroResult<Self> {
-        let names = HashMap::new();
-        let mut rs = ResolvedSchema {
+        let mut names = HashMap::new();
+        resolve_names_with_schemata(
+            schemata_to_resolve.iter().copied(),
+            &mut names,
+            enclosing_namespace,
+            known_schemata,
+        )?;
+        Ok(ResolvedSchema {
             names_ref: names,
             schemata: schemata_to_resolve,
-        };
-        rs.resolve(rs.get_schemata(), enclosing_namespace, 
Some(known_schemata))?;
-        Ok(rs)
-    }
-
-    fn resolve<'n>(
-        &mut self,
-        schemata: Vec<&'s Schema>,
-        enclosing_namespace: &Namespace,
-        known_schemata: Option<&'n NamesRef<'n>>,
-    ) -> AvroResult<()> {
-        for schema in schemata {
-            match schema {
-                Schema::Array(schema) => {
-                    self.resolve(vec![&schema.items], enclosing_namespace, 
known_schemata)?
-                }
-                Schema::Map(schema) => {
-                    self.resolve(vec![&schema.types], enclosing_namespace, 
known_schemata)?
-                }
-                Schema::Union(UnionSchema { schemas, .. }) => {
-                    for schema in schemas {
-                        self.resolve(vec![schema], enclosing_namespace, 
known_schemata)?
-                    }
-                }
-                Schema::Enum(EnumSchema { name, .. })
-                | Schema::Fixed(FixedSchema { name, .. })
-                | Schema::Uuid(UuidSchema::Fixed(FixedSchema { name, .. }))
-                | Schema::Decimal(DecimalSchema {
-                    inner: InnerDecimalSchema::Fixed(FixedSchema { name, .. }),
-                    ..
-                })
-                | Schema::Duration(FixedSchema { name, .. }) => {
-                    let fully_qualified_name = 
name.fully_qualified_name(enclosing_namespace);
-                    if self
-                        .names_ref
-                        .insert(fully_qualified_name.clone(), schema)
-                        .is_some()
-                    {
-                        return 
Err(Details::AmbiguousSchemaDefinition(fully_qualified_name).into());
-                    }
-                }
-                Schema::Record(RecordSchema { name, fields, .. }) => {
-                    let fully_qualified_name = 
name.fully_qualified_name(enclosing_namespace);
-                    if self
-                        .names_ref
-                        .insert(fully_qualified_name.clone(), schema)
-                        .is_some()
-                    {
-                        return 
Err(Details::AmbiguousSchemaDefinition(fully_qualified_name).into());
-                    } else {
-                        let record_namespace = fully_qualified_name.namespace;
-                        for field in fields {
-                            self.resolve(vec![&field.schema], 
&record_namespace, known_schemata)?
-                        }
-                    }
-                }
-                Schema::Ref { name } => {
-                    let fully_qualified_name = 
name.fully_qualified_name(enclosing_namespace);
-                    // first search for reference in current schemata, then 
look into external references.
-                    if !self.names_ref.contains_key(&fully_qualified_name) {
-                        let is_resolved_with_known_schemas = known_schemata
-                            .as_ref()
-                            .map(|names| 
names.contains_key(&fully_qualified_name))
-                            .unwrap_or(false);
-                        if !is_resolved_with_known_schemas {
-                            return 
Err(Details::SchemaResolutionError(fully_qualified_name).into());
-                        }
-                    }
-                }
-                _ => (),
-            }
-        }
-        Ok(())
+        })
     }
 }
 
@@ -164,26 +92,43 @@ impl<'s> TryFrom<Vec<&'s Schema>> for ResolvedSchema<'s> {
     }
 }
 
-pub struct ResolvedOwnedSchema {
-    names: Names,
+/// Implementation detail of [`ResolvedOwnedSchema`]
+///
+/// This struct is self-referencing. The references in `names` point to 
`root_schema`.
+/// This allows resolving an owned schema without having to clone all the 
named schemas.
+#[ouroboros::self_referencing]
+struct InnerResolvedOwnedSchema {
     root_schema: Schema,
+    #[borrows(root_schema)]
+    #[covariant]
+    names: NamesRef<'this>,
+}
+
+/// A variant of [`ResolvedSchema`] that owns the schema
+pub struct ResolvedOwnedSchema {
+    inner: InnerResolvedOwnedSchema,
 }
 
 impl ResolvedOwnedSchema {
     pub fn new(root_schema: Schema) -> AvroResult<Self> {
-        let mut rs = ResolvedOwnedSchema {
-            names: HashMap::new(),
-            root_schema,
-        };
-        resolve_names(&rs.root_schema, &mut rs.names, &None)?;
-        Ok(rs)
+        Ok(Self {
+            inner: InnerResolvedOwnedSchemaTryBuilder {
+                root_schema,
+                names_builder: |schema: &Schema| {
+                    let mut names = HashMap::new();
+                    resolve_names(schema, &mut names, &None, &HashMap::new())?;
+                    Ok::<_, Error>(names)
+                },
+            }
+            .try_build()?,
+        })
     }
 
     pub fn get_root_schema(&self) -> &Schema {
-        &self.root_schema
+        self.inner.borrow_root_schema()
     }
-    pub fn get_names(&self) -> &Names {
-        &self.names
+    pub fn get_names(&self) -> &NamesRef<'_> {
+        self.inner.borrow_names()
     }
 }
 
@@ -195,17 +140,25 @@ impl TryFrom<Schema> for ResolvedOwnedSchema {
     }
 }
 
-pub fn resolve_names(
-    schema: &Schema,
-    names: &mut Names,
+/// Resolve all references in the schema, saving any named type found in 
`names`
+///
+/// `known_schemata` will be used to resolve references but they won't be 
added to `names`.
+pub fn resolve_names<'s, 'n>(
+    schema: &'s Schema,
+    names: &mut NamesRef<'s>,
     enclosing_namespace: &Namespace,
+    known_schemata: &NamesRef<'n>,
 ) -> AvroResult<()> {
     match schema {
-        Schema::Array(schema) => resolve_names(&schema.items, names, 
enclosing_namespace),
-        Schema::Map(schema) => resolve_names(&schema.types, names, 
enclosing_namespace),
+        Schema::Array(schema) => {
+            resolve_names(&schema.items, names, enclosing_namespace, 
known_schemata)
+        }
+        Schema::Map(schema) => {
+            resolve_names(&schema.types, names, enclosing_namespace, 
known_schemata)
+        }
         Schema::Union(UnionSchema { schemas, .. }) => {
             for schema in schemas {
-                resolve_names(schema, names, enclosing_namespace)?
+                resolve_names(schema, names, enclosing_namespace, 
known_schemata)?
             }
             Ok(())
         }
@@ -218,10 +171,7 @@ pub fn resolve_names(
         })
         | Schema::Duration(FixedSchema { name, .. }) => {
             let fully_qualified_name = 
name.fully_qualified_name(enclosing_namespace);
-            if names
-                .insert(fully_qualified_name.clone(), schema.clone())
-                .is_some()
-            {
+            if names.insert(fully_qualified_name.clone(), schema).is_some() {
                 
Err(Details::AmbiguousSchemaDefinition(fully_qualified_name).into())
             } else {
                 Ok(())
@@ -229,37 +179,38 @@ pub fn resolve_names(
         }
         Schema::Record(RecordSchema { name, fields, .. }) => {
             let fully_qualified_name = 
name.fully_qualified_name(enclosing_namespace);
-            if names
-                .insert(fully_qualified_name.clone(), schema.clone())
-                .is_some()
-            {
+            if names.insert(fully_qualified_name.clone(), schema).is_some() {
                 
Err(Details::AmbiguousSchemaDefinition(fully_qualified_name).into())
             } else {
                 let record_namespace = fully_qualified_name.namespace;
                 for field in fields {
-                    resolve_names(&field.schema, names, &record_namespace)?
+                    resolve_names(&field.schema, names, &record_namespace, 
known_schemata)?
                 }
                 Ok(())
             }
         }
         Schema::Ref { name } => {
             let fully_qualified_name = 
name.fully_qualified_name(enclosing_namespace);
-            names
-                .get(&fully_qualified_name)
-                .map(|_| ())
-                .ok_or_else(|| 
Details::SchemaResolutionError(fully_qualified_name).into())
+            if names.contains_key(&fully_qualified_name)
+                || known_schemata.contains_key(&fully_qualified_name)
+            {
+                Ok(())
+            } else {
+                
Err(Details::SchemaResolutionError(fully_qualified_name).into())
+            }
         }
         _ => Ok(()),
     }
 }
 
-pub fn resolve_names_with_schemata(
-    schemata: impl IntoIterator<Item = impl Borrow<Schema>>,
-    names: &mut Names,
+pub fn resolve_names_with_schemata<'s, 'n>(
+    schemata: impl IntoIterator<Item = &'s Schema>,
+    names: &mut NamesRef<'s>,
     enclosing_namespace: &Namespace,
+    known_schemata: &NamesRef<'n>,
 ) -> AvroResult<()> {
     for schema in schemata {
-        resolve_names(schema.borrow(), names, enclosing_namespace)?;
+        resolve_names(schema, names, enclosing_namespace, known_schemata)?;
     }
     Ok(())
 }
diff --git a/avro/src/writer.rs b/avro/src/writer.rs
index 72f87d0..1dd3fbf 100644
--- a/avro/src/writer.rs
+++ b/avro/src/writer.rs
@@ -21,7 +21,7 @@ use crate::{
     encode::{encode, encode_internal, encode_to_vec},
     error::Details,
     headers::{HeaderBuilder, RabinFingerprintHeader},
-    schema::{Name, ResolvedOwnedSchema, ResolvedSchema, Schema},
+    schema::{NamesRef, ResolvedOwnedSchema, ResolvedSchema, Schema},
     serde::{AvroSchema, ser_schema::SchemaAwareWriteSerializer},
     types::Value,
 };
@@ -594,8 +594,8 @@ pub struct SpecificSingleObjectWriter<T>
 where
     T: AvroSchema,
 {
-    inner: GenericSingleObjectWriter,
-    schema: Schema,
+    resolved: ResolvedOwnedSchema,
+    header: Vec<u8>,
     _model: PhantomData<T>,
 }
 
@@ -603,25 +603,52 @@ impl<T> SpecificSingleObjectWriter<T>
 where
     T: AvroSchema,
 {
-    pub fn with_capacity(buffer_cap: usize) -> 
AvroResult<SpecificSingleObjectWriter<T>> {
+    pub fn new() -> AvroResult<Self> {
         let schema = T::get_schema();
-        Ok(SpecificSingleObjectWriter {
-            inner: GenericSingleObjectWriter::new_with_capacity(&schema, 
buffer_cap)?,
-            schema,
+        let header = 
RabinFingerprintHeader::from_schema(&schema).build_header();
+        let resolved = ResolvedOwnedSchema::new(schema)?;
+        // We don't use Self::new_with_header_builder as that would mean 
calling T::get_schema() twice
+        Ok(Self {
+            resolved,
+            header,
+            _model: PhantomData,
+        })
+    }
+
+    pub fn new_with_header_builder(header_builder: impl HeaderBuilder) -> 
AvroResult<Self> {
+        let header = header_builder.build_header();
+        let resolved = ResolvedOwnedSchema::new(T::get_schema())?;
+        Ok(Self {
+            resolved,
+            header,
             _model: PhantomData,
         })
     }
+
+    /// Deprecated. Use [`SpecificSingleObjectWriter::new`] instead.
+    #[deprecated(since = "0.22.0", note = "Use new() instead")]
+    pub fn with_capacity(_buffer_cap: usize) -> AvroResult<Self> {
+        Self::new()
+    }
 }
 
 impl<T> SpecificSingleObjectWriter<T>
 where
     T: AvroSchema + Into<Value>,
 {
-    /// Write the `Into<Value>` to the provided Write object. Returns a result 
with the number
-    /// of bytes written including the header
-    pub fn write_value<W: Write>(&mut self, data: T, writer: &mut W) -> 
AvroResult<usize> {
-        let v: Value = data.into();
-        self.inner.write_value_ref(&v, writer)
+    /// Write the value to the writer
+    ///
+    /// Returns the number of bytes written.
+    ///
+    /// Each call writes a complete single-object encoded message (header + 
data),
+    /// making each message independently decodable.
+    pub fn write_value<W: Write>(&self, data: T, writer: &mut W) -> 
AvroResult<usize> {
+        writer
+            .write_all(&self.header)
+            .map_err(Details::WriteBytes)?;
+        let value: Value = data.into();
+        let bytes = write_value_ref_owned_resolved(&self.resolved, &value, 
writer)?;
+        Ok(bytes + self.header.len())
     }
 }
 
@@ -629,32 +656,34 @@ impl<T> SpecificSingleObjectWriter<T>
 where
     T: AvroSchema + Serialize,
 {
-    /// Write the referenced `Serialize` object to the provided `Write` object.
+    /// Write the object to the writer.
     ///
     /// Returns the number of bytes written.
     ///
     /// Each call writes a complete single-object encoded message (header + 
data),
     /// making each message independently decodable.
-    pub fn write_ref<W: Write>(&mut self, data: &T, writer: &mut W) -> 
AvroResult<usize> {
-        // Always write the header for each message (single object encoding 
requires
-        // each message to be independently decodable)
+    pub fn write_ref<W: Write>(&self, data: &T, writer: &mut W) -> 
AvroResult<usize> {
         writer
-            .write_all(self.inner.buffer.as_slice())
+            .write_all(&self.header)
             .map_err(Details::WriteBytes)?;
 
-        let bytes_written =
-            self.inner.buffer.len() + write_avro_datum_ref(&self.schema, data, 
writer)?;
+        let bytes = write_avro_datum_ref(
+            self.resolved.get_root_schema(),
+            self.resolved.get_names(),
+            data,
+            writer,
+        )?;
 
-        Ok(bytes_written)
+        Ok(bytes + self.header.len())
     }
 
-    /// Write the Serialize object to the provided Write object.
+    /// Write the object to the writer.
     ///
     /// Returns the number of bytes written.
     ///
     /// Each call writes a complete single-object encoded message (header + 
data),
     /// making each message independently decodable.
-    pub fn write<W: Write>(&mut self, data: T, writer: &mut W) -> 
AvroResult<usize> {
+    pub fn write<W: Write>(&self, data: T, writer: &mut W) -> 
AvroResult<usize> {
         self.write_ref(&data, writer)
     }
 }
@@ -682,11 +711,11 @@ fn write_value_ref_resolved(
     }
 }
 
-fn write_value_ref_owned_resolved(
+fn write_value_ref_owned_resolved<W: Write>(
     resolved_schema: &ResolvedOwnedSchema,
     value: &Value,
-    buffer: &mut Vec<u8>,
-) -> AvroResult<()> {
+    writer: &mut W,
+) -> AvroResult<usize> {
     let root_schema = resolved_schema.get_root_schema();
     if let Some(reason) = value.validate_internal(
         root_schema,
@@ -705,9 +734,8 @@ fn write_value_ref_owned_resolved(
         root_schema,
         resolved_schema.get_names(),
         &root_schema.namespace(),
-        buffer,
-    )?;
-    Ok(())
+        writer,
+    )
 }
 
 /// Encode a compatible value (implementing the `ToAvro` trait) into Avro 
format, also
@@ -730,13 +758,12 @@ pub fn to_avro_datum<T: Into<Value>>(schema: &Schema, 
value: T) -> AvroResult<Ve
 /// if you don't know what you are doing, instead.
 pub fn write_avro_datum_ref<T: Serialize, W: Write>(
     schema: &Schema,
+    names: &NamesRef,
     data: &T,
     writer: &mut W,
 ) -> AvroResult<usize> {
-    let names: HashMap<Name, &Schema> = HashMap::new();
-    let mut serializer = SchemaAwareWriteSerializer::new(writer, schema, 
&names, None);
-    let bytes_written = data.serialize(&mut serializer)?;
-    Ok(bytes_written)
+    let mut serializer = SchemaAwareWriteSerializer::new(writer, schema, 
names, None);
+    data.serialize(&mut serializer)
 }
 
 /// Encode a compatible value (implementing the `ToAvro` trait) into Avro 
format, also
@@ -856,7 +883,7 @@ mod tests {
         zig_i64(3, &mut expected)?;
         expected.extend([b'f', b'o', b'o']);
 
-        let bytes = write_avro_datum_ref(&schema, &data, &mut writer)?;
+        let bytes = write_avro_datum_ref(&schema, &HashMap::new(), &data, &mut 
writer)?;
 
         assert_eq!(bytes, expected.len());
         assert_eq!(writer, expected);
@@ -1586,9 +1613,8 @@ mod tests {
             1024,
         )
         .expect("Should resolve schema");
-        let mut specific_writer =
-            
SpecificSingleObjectWriter::<TestSingleObjectWriter>::with_capacity(1024)
-                .expect("Resolved should pass");
+        let specific_writer = 
SpecificSingleObjectWriter::<TestSingleObjectWriter>::new()
+            .expect("Resolved should pass");
         specific_writer
             .write_ref(&obj1, &mut buf1)
             .expect("Serialization expected");
@@ -1734,4 +1760,49 @@ mod tests {
 
         Ok(())
     }
+
+    #[test]
+    fn avro_rs_439_specific_single_object_writer_ref() -> TestResult {
+        #[derive(Serialize)]
+        struct Recursive {
+            field: bool,
+            recurse: Option<Box<Recursive>>,
+        }
+
+        impl AvroSchema for Recursive {
+            fn get_schema() -> Schema {
+                Schema::parse_str(
+                    r#"{
+                    "name": "Recursive",
+                    "type": "record",
+                    "fields": [
+                        { "name": "field", "type": "boolean" },
+                        { "name": "recurse", "type": ["null", "Recursive"] }
+                    ]
+                }"#,
+                )
+                .unwrap()
+            }
+        }
+
+        let mut buffer = Vec::new();
+        let writer = SpecificSingleObjectWriter::new()?;
+
+        writer.write(
+            Recursive {
+                field: true,
+                recurse: Some(Box::new(Recursive {
+                    field: false,
+                    recurse: None,
+                })),
+            },
+            &mut buffer,
+        )?;
+        assert_eq!(
+            buffer,
+            &[195, 1, 83, 223, 43, 26, 181, 179, 227, 224, 1, 2, 0, 0][..]
+        );
+
+        Ok(())
+    }
 }
diff --git a/avro/tests/serde_human_readable_false.rs 
b/avro/tests/serde_human_readable_false.rs
index 9e87f52..2f8be3f 100644
--- a/avro/tests/serde_human_readable_false.rs
+++ b/avro/tests/serde_human_readable_false.rs
@@ -56,8 +56,7 @@ fn avro_rs_53_uuid_with_fixed() -> TestResult {
 
     // serialize the Uuid as Fixed
     assert!(!apache_avro::util::set_serde_human_readable(false));
-    let bytes = SpecificSingleObjectWriter::<Comment>::with_capacity(64)?
-        .write_ref(&payload, &mut buffer)?;
+    let bytes = 
SpecificSingleObjectWriter::<Comment>::new()?.write_ref(&payload, &mut buffer)?;
     assert_eq!(bytes, 26);
 
     Ok(())
@@ -77,7 +76,7 @@ fn avro_rs_440_uuid_string() -> TestResult {
     let mut buffer = Vec::new();
 
     assert!(!apache_avro::util::set_serde_human_readable(false));
-    let mut writer = SpecificSingleObjectWriter::with_capacity(64)?;
+    let writer = SpecificSingleObjectWriter::new()?;
     assert_eq!(
         writer.write(uuid, &mut buffer).unwrap_err().to_string(),
         "Failed to serialize value of type bytes using schema Uuid(String): 
55e840e29b41d4a7164466554400. Cause: Expected a string, but got 16 bytes. Did 
you mean to use `Schema::Uuid(UuidSchema::Fixed)` or 
`utils::serde_set_human_readable(true)`?"
@@ -100,7 +99,7 @@ fn avro_rs_440_uuid_bytes() -> TestResult {
     let mut buffer = Vec::new();
 
     assert!(!apache_avro::util::set_serde_human_readable(false));
-    let mut writer = SpecificSingleObjectWriter::with_capacity(64)?;
+    let writer = SpecificSingleObjectWriter::new()?;
     writer.write(uuid, &mut buffer)?;
 
     assert_eq!(
@@ -131,7 +130,7 @@ fn avro_rs_440_uuid_fixed() -> TestResult {
     let mut buffer = Vec::new();
 
     assert!(!apache_avro::util::set_serde_human_readable(false));
-    let mut writer = SpecificSingleObjectWriter::with_capacity(64)?;
+    let writer = SpecificSingleObjectWriter::new()?;
     writer.write(uuid, &mut buffer)?;
 
     assert_eq!(
diff --git a/avro/tests/serde_human_readable_true.rs 
b/avro/tests/serde_human_readable_true.rs
index 960f72f..182839e 100644
--- a/avro/tests/serde_human_readable_true.rs
+++ b/avro/tests/serde_human_readable_true.rs
@@ -55,8 +55,7 @@ fn avro_rs_53_uuid_with_string_true() -> TestResult {
 
     // serialize the Uuid as String
     assert!(apache_avro::util::set_serde_human_readable(true));
-    let bytes = SpecificSingleObjectWriter::<Comment>::with_capacity(64)?
-        .write_ref(&payload, &mut buffer)?;
+    let bytes = 
SpecificSingleObjectWriter::<Comment>::new()?.write_ref(&payload, &mut buffer)?;
     assert_eq!(bytes, 47);
 
     Ok(())
@@ -76,7 +75,7 @@ fn avro_rs_440_uuid_string() -> TestResult {
     let mut buffer = Vec::new();
 
     assert!(apache_avro::util::set_serde_human_readable(true));
-    let mut writer = SpecificSingleObjectWriter::with_capacity(64)?;
+    let writer = SpecificSingleObjectWriter::new()?;
     writer.write(uuid, &mut buffer)?;
 
     assert_eq!(
@@ -101,7 +100,7 @@ fn avro_rs_440_uuid_bytes() -> TestResult {
     let mut buffer = Vec::new();
 
     assert!(apache_avro::util::set_serde_human_readable(true));
-    let mut writer = SpecificSingleObjectWriter::with_capacity(64)?;
+    let writer = SpecificSingleObjectWriter::new()?;
     assert_eq!(
         writer.write(uuid, &mut buffer).unwrap_err().to_string(),
         "Failed to serialize value of type string using schema Uuid(Bytes): 
550e8400-e29b-41d4-a716-446655440000. Cause: Expected bytes but got a string. 
Did you mean to use `Schema::Uuid(UuidSchema::String)` or 
`utils::serde_set_human_readable(false)`?"
@@ -127,7 +126,7 @@ fn avro_rs_440_uuid_fixed() -> TestResult {
     let mut buffer = Vec::new();
 
     assert!(apache_avro::util::set_serde_human_readable(true));
-    let mut writer = SpecificSingleObjectWriter::with_capacity(64)?;
+    let writer = SpecificSingleObjectWriter::new()?;
     assert_eq!(
         writer.write(uuid, &mut buffer).unwrap_err().to_string(),
         r#"Failed to serialize value of type string using schema 
Uuid(Fixed(FixedSchema { name: Name { name: "uuid", namespace: None }, aliases: 
None, doc: None, size: 16, default: None, attributes: {} })): 
550e8400-e29b-41d4-a716-446655440000. Cause: Expected bytes but got a string. 
Did you mean to use `Schema::Uuid(UuidSchema::String)` or 
`utils::serde_set_human_readable(false)`?"#

Reply via email to