martin-g commented on code in PR #448:
URL: https://github.com/apache/avro-rs/pull/448#discussion_r2744932047


##########
avro/src/serde/derive.rs:
##########
@@ -82,7 +83,134 @@ pub trait AvroSchema {
 ///}
 /// ```
 pub trait AvroSchemaComponent {
+    /// Get the schema for this component
     fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: 
&Namespace) -> Schema;
+
+    /// Get the fields of this schema if it is a record.
+    ///
+    /// This returns `None` if the schema is not a record.
+    ///
+    /// The default implementation has to do a lot of extra work, so it is 
strongly recommended to
+    /// implement this function when manually implementing this trait.
+    fn get_record_fields_in_ctxt(
+        named_schemas: &mut Names,
+        enclosing_namespace: &Namespace,
+    ) -> Option<Vec<RecordField>> {
+        get_record_fields_in_ctxt(named_schemas, enclosing_namespace, 
Self::get_schema_in_ctxt)
+    }
+}
+
+/// Get the record fields from `schema_fn` without polluting `named_schemas` 
or causing duplicate names
+///
+/// This is public so the derive macro can use it for `#[avro(with = ||)]` and 
`#[avro(with = path)]`
+pub fn get_record_fields_in_ctxt(
+    named_schemas: &mut Names,
+    enclosing_namespace: &Namespace,
+    schema_fn: fn(named_schemas: &mut Names, enclosing_namespace: &Namespace) 
-> Schema,
+) -> Option<Vec<RecordField>> {
+    let mut record = match schema_fn(named_schemas, enclosing_namespace) {
+        Schema::Record(record) => record,
+        Schema::Ref { name } => {
+            // This schema already exists in `named_schemas` so temporarily 
remove it so we can
+            // get the actual schema.
+            let temp = named_schemas
+                .remove(&name)
+                .expect("Name should exist in `named_schemas` otherwise Ref is 
invalid");
+            // Get the schema
+            let schema = schema_fn(named_schemas, enclosing_namespace);
+            // Reinsert the old value
+            named_schemas.insert(name, temp);
+
+            // Now check if we actually got a record and return the fields if 
that is the case
+            let Schema::Record(record) = schema else {
+                return None;
+            };
+            return Some(record.fields);
+        }
+        _ => return None,
+    };
+    // This schema did not yet exist in `named_schemas`, so we need to remove 
it if and only if
+    // it isn't used somewhere in the schema (recursive type).
+
+    // Find the first Schema::Ref that has the target name
+    fn find_first_ref<'a>(schema: &'a mut Schema, target: &Name) -> Option<&'a 
mut Schema> {
+        match schema {
+            Schema::Ref { name } if name == target => Some(schema),
+            Schema::Array(array) => find_first_ref(&mut array.items, target),
+            Schema::Map(map) => find_first_ref(&mut map.types, target),
+            Schema::Union(union) => {
+                for schema in &mut union.schemas {
+                    if let Some(schema) = find_first_ref(schema, target) {
+                        return Some(schema);
+                    }
+                }
+                None
+            }
+            Schema::Record(record) => {
+                assert_ne!(
+                    &record.name, target,
+                    "Only expecting a Ref named {target:?}"
+                );
+                for field in &mut record.fields {
+                    if let Some(schema) = find_first_ref(&mut field.schema, 
target) {
+                        return Some(schema);
+                    }
+                }
+                None
+            }
+            _ => None,
+        }
+    }
+
+    // Prepare the fields for the new record. All named types will become 
references.
+    let new_fields = record
+        .fields
+        .iter()
+        .map(|field| RecordField {
+            name: field.name.clone(),
+            doc: field.doc.clone(),
+            aliases: field.aliases.clone(),
+            default: field.default.clone(),
+            schema: if field.schema.is_named() {
+                Schema::Ref {
+                    name: field.schema.name().expect("Schema is 
named").clone(),
+                }
+            } else {
+                field.schema.clone()
+            },
+            order: field.order.clone(),
+            position: field.position,
+            custom_attributes: field.custom_attributes.clone(),
+        })
+        .collect();
+
+    // Remove the name in case it is not used
+    named_schemas.remove(&record.name);
+
+    // Find the first reference to this schema so we can replace it with the 
actual schema
+    for field in &mut record.fields {
+        if let Some(schema) = find_first_ref(&mut field.schema, &record.name) {
+            let new_schema = RecordSchema {
+                name: record.name,
+                aliases: record.aliases,
+                doc: record.doc,
+                fields: new_fields,
+                lookup: record.lookup,
+                attributes: record.attributes,
+            };
+
+            let Schema::Ref { name } = std::mem::replace(schema, 
Schema::Record(new_schema)) else {
+                panic!("Expected only Refs from find_first_ref");

Review Comment:
   Let's use a `match` to be able to print the non-Ref schema in case of panic.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to