This is an automated email from the ASF dual-hosted git repository.
mgrigorov 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 060c34f feat(derive): Support `#[serde(transparent)]` (#398)
060c34f is described below
commit 060c34f5de1f1b78c9bc78ed7a36aa194c9773bb
Author: Kriskras99 <[email protected]>
AuthorDate: Mon Jan 19 13:05:31 2026 +0100
feat(derive): Support `#[serde(transparent)]` (#398)
* feat(derive): Support `#[serde(transparent)]`
* feat(derive): Support `with` and `skip` for `#[serde(transparent)]`
* fix(derive): Actually test the right thing
---
avro_derive/src/attributes/mod.rs | 83 ++++-
avro_derive/src/lib.rs | 382 +++++++++++----------
avro_derive/tests/derive.rs | 21 ++
avro_derive/tests/serde.rs | 63 ++++
.../tests/ui/avro_rs_373_rename_all_fields.stderr | 2 +-
.../tests/ui/avro_rs_373_transparent.stderr | 8 -
...3_transparent.rs => avro_rs_398_transparent.rs} | 5 +-
.../tests/ui/avro_rs_398_transparent.stderr | 18 +
...sparent.rs => avro_rs_398_transparent_skips.rs} | 11 +-
.../tests/ui/avro_rs_398_transparent_skips.stderr | 11 +
10 files changed, 392 insertions(+), 212 deletions(-)
diff --git a/avro_derive/src/attributes/mod.rs
b/avro_derive/src/attributes/mod.rs
index 5657179..ecf2797 100644
--- a/avro_derive/src/attributes/mod.rs
+++ b/avro_derive/src/attributes/mod.rs
@@ -18,22 +18,26 @@
use crate::case::RenameRule;
use darling::{FromAttributes, FromMeta};
use proc_macro2::Span;
-use syn::{Attribute, Expr, Path, spanned::Spanned};
+use syn::{AttrStyle, Attribute, Expr, Ident, Path, spanned::Spanned};
mod avro;
mod serde;
#[derive(Default)]
pub struct NamedTypeOptions {
- pub name: Option<String>,
- pub namespace: Option<String>,
+ pub name: String,
pub doc: Option<String>,
- pub alias: Vec<String>,
+ pub aliases: Vec<String>,
pub rename_all: RenameRule,
+ pub transparent: bool,
}
impl NamedTypeOptions {
- pub fn new(attributes: &[Attribute], span: Span) -> Result<Self,
Vec<syn::Error>> {
+ pub fn new(
+ ident: &Ident,
+ attributes: &[Attribute],
+ span: Span,
+ ) -> Result<Self, Vec<syn::Error>> {
let avro =
avro::ContainerAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
let serde =
@@ -63,12 +67,6 @@ impl NamedTypeOptions {
"AvroSchema derive does not support the Serde `remote`
attribute",
));
}
- if serde.transparent {
- errors.push(syn::Error::new(
- span,
- "AvroSchema derive does not support Serde `transparent`
attribute",
- ));
- }
if serde.rename_all.deserialize != serde.rename_all.serialize {
errors.push(syn::Error::new(
span,
@@ -89,17 +87,41 @@ impl NamedTypeOptions {
"#[avro(rename_all = \"..\")] must match #[serde(rename_all =
\"..\")], it's also deprecated. Please use only `#[serde(rename_all =
\"..\")]`",
));
}
+ if serde.transparent
+ && (serde.rename.is_some()
+ || avro.name.is_some()
+ || avro.namespace.is_some()
+ || avro.doc.is_some()
+ || !avro.alias.is_empty()
+ || avro.rename_all != RenameRule::None
+ || serde.rename_all.serialize != RenameRule::None
+ || serde.rename_all.deserialize != RenameRule::None)
+ {
+ errors.push(syn::Error::new(
+ span,
+ "AvroSchema: #[serde(transparent)] is incompatible with all
other attributes",
+ ));
+ }
if !errors.is_empty() {
return Err(errors);
}
+ let name = serde.rename.unwrap_or(ident.to_string());
+ let full_schema_name = vec![avro.namespace, Some(name)]
+ .into_iter()
+ .flatten()
+ .collect::<Vec<String>>()
+ .join(".");
+
+ let doc = avro.doc.or_else(|| extract_rustdoc(attributes));
+
Ok(Self {
- name: serde.rename,
- namespace: avro.namespace,
- doc: avro.doc,
- alias: avro.alias,
+ name: full_schema_name,
+ doc,
+ aliases: avro.alias,
rename_all: serde.rename_all.serialize,
+ transparent: serde.transparent,
})
}
}
@@ -171,7 +193,9 @@ impl With {
let path = Path::from_string(serde).map_err(|err| {
syn::Error::new(
span,
- format!("Expected a path for `#[serde(with =
\"..\")]`: {err:?}"),
+ format!(
+ "AvroSchema: Expected a path for `#[serde(with
= \"..\")]`: {err:?}"
+ ),
)
})?;
Ok(Self::Serde(path))
@@ -187,6 +211,7 @@ impl With {
}
}
+#[derive(Default)]
pub struct FieldOptions {
pub doc: Option<String>,
pub default: Option<String>,
@@ -269,8 +294,10 @@ impl FieldOptions {
return Err(errors);
}
+ let doc = avro.doc.or_else(|| extract_rustdoc(attributes));
+
Ok(Self {
- doc: avro.doc,
+ doc,
default: avro.default,
alias: serde.alias,
rename: serde.rename,
@@ -281,6 +308,28 @@ impl FieldOptions {
}
}
+fn extract_rustdoc(attributes: &[Attribute]) -> Option<String> {
+ let doc = attributes
+ .iter()
+ .filter(|attr| attr.style == AttrStyle::Outer &&
attr.path().is_ident("doc"))
+ .filter_map(|attr| {
+ let name_value = attr.meta.require_name_value();
+ match name_value {
+ Ok(name_value) => match &name_value.value {
+ syn::Expr::Lit(expr_lit) => match expr_lit.lit {
+ syn::Lit::Str(ref lit_str) =>
Some(lit_str.value().trim().to_string()),
+ _ => None,
+ },
+ _ => None,
+ },
+ Err(_) => None,
+ }
+ })
+ .collect::<Vec<String>>()
+ .join("\n");
+ if doc.is_empty() { None } else { Some(doc) }
+}
+
fn darling_to_syn(e: darling::Error) -> Vec<syn::Error> {
let msg = format!("{e}");
let token_errors = e.write_errors();
diff --git a/avro_derive/src/lib.rs b/avro_derive/src/lib.rs
index b976756..12551f8 100644
--- a/avro_derive/src/lib.rs
+++ b/avro_derive/src/lib.rs
@@ -23,7 +23,8 @@ mod case;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{
- AttrStyle, Attribute, DeriveInput, Expr, Ident, Meta, Type,
parse_macro_input, spanned::Spanned,
+ Attribute, DataEnum, DataStruct, DeriveInput, Expr, Field, Fields,
Generics, Ident, Meta, Type,
+ parse_macro_input, spanned::Spanned,
};
use crate::{
@@ -34,86 +35,93 @@ use crate::{
#[proc_macro_derive(AvroSchema, attributes(avro, serde))]
// Templated from Serde
pub fn proc_macro_derive_avro_schema(input: proc_macro::TokenStream) ->
proc_macro::TokenStream {
- let mut input = parse_macro_input!(input as DeriveInput);
- derive_avro_schema(&mut input)
+ let input = parse_macro_input!(input as DeriveInput);
+ derive_avro_schema(input)
.unwrap_or_else(to_compile_errors)
.into()
}
-fn derive_avro_schema(input: &mut DeriveInput) -> Result<TokenStream,
Vec<syn::Error>> {
- let named_type_options = NamedTypeOptions::new(&input.attrs,
input.span())?;
-
- let rename_all = named_type_options.rename_all;
- let name = named_type_options.name.unwrap_or(input.ident.to_string());
-
- let full_schema_name = vec![named_type_options.namespace, Some(name)]
- .into_iter()
- .flatten()
- .collect::<Vec<String>>()
- .join(".");
- let schema_def = match &input.data {
- syn::Data::Struct(s) => get_data_struct_schema_def(
- &full_schema_name,
- named_type_options
- .doc
- .or_else(|| extract_outer_doc(&input.attrs)),
- named_type_options.alias,
- rename_all,
- s,
- input.ident.span(),
- )?,
- syn::Data::Enum(e) => get_data_enum_schema_def(
- &full_schema_name,
- named_type_options
- .doc
- .or_else(|| extract_outer_doc(&input.attrs)),
- named_type_options.alias,
- rename_all,
- e,
- input.ident.span(),
- )?,
- _ => {
- return Err(vec![syn::Error::new(
- input.ident.span(),
- "AvroSchema derive only works for structs and simple enums ",
- )]);
+fn derive_avro_schema(input: DeriveInput) -> Result<TokenStream,
Vec<syn::Error>> {
+ // It would be nice to parse the attributes before the `match`, but we
first need to validate that `input` is not a union.
+ // Otherwise a user could get errors related to the attributes and after
fixing those get an error because the attributes were on a union.
+ let input_span = input.span();
+ match input.data {
+ syn::Data::Struct(data_struct) => {
+ let named_type_options = NamedTypeOptions::new(&input.ident,
&input.attrs, input_span)?;
+ let inner = if named_type_options.transparent {
+ get_transparent_struct_schema_def(data_struct.fields,
input_span)?
+ } else {
+ let schema_def =
+ get_struct_schema_def(&named_type_options, data_struct,
input.ident.span())?;
+ handle_named_schemas(named_type_options.name, schema_def)
+ };
+ Ok(create_trait_definition(input.ident, &input.generics, inner))
}
- };
- let ident = &input.ident;
- let (impl_generics, ty_generics, where_clause) =
input.generics.split_for_impl();
- Ok(quote! {
+ syn::Data::Enum(data_enum) => {
+ let named_type_options = NamedTypeOptions::new(&input.ident,
&input.attrs, input_span)?;
+ if named_type_options.transparent {
+ return Err(vec![syn::Error::new(
+ input_span,
+ "AvroSchema: `#[serde(transparent)]` is only supported on
structs",
+ )]);
+ }
+ let schema_def =
+ get_data_enum_schema_def(&named_type_options, data_enum,
input.ident.span())?;
+ let inner = handle_named_schemas(named_type_options.name,
schema_def);
+ Ok(create_trait_definition(input.ident, &input.generics, inner))
+ }
+ syn::Data::Union(_) => Err(vec![syn::Error::new(
+ input_span,
+ "AvroSchema: derive only works for structs and simple enums",
+ )]),
+ }
+}
+
+/// Generate the trait definition with the correct generics
+fn create_trait_definition(
+ ident: Ident,
+ generics: &Generics,
+ implementation: TokenStream,
+) -> TokenStream {
+ let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+ quote! {
#[automatically_derived]
impl #impl_generics apache_avro::AvroSchemaComponent for #ident
#ty_generics #where_clause {
- fn get_schema_in_ctxt(named_schemas: &mut
std::collections::HashMap<apache_avro::schema::Name,
apache_avro::schema::Schema>, enclosing_namespace: &Option<String>) ->
apache_avro::schema::Schema {
- let name =
apache_avro::schema::Name::new(#full_schema_name).expect(concat!("Unable to
parse schema name ",
#full_schema_name)).fully_qualified_name(enclosing_namespace);
- if named_schemas.contains_key(&name) {
- apache_avro::schema::Schema::Ref{name}
- } else {
- let enclosing_namespace = &name.namespace;
- // This is needed because otherwise recursive types will
recurse forever and cause a stack overflow
- // TODO: Breaking change to AvroSchemaComponent, have
named_schemas be a set instead
- named_schemas.insert(name.clone(),
apache_avro::schema::Schema::Ref{name: name.clone()});
- let schema = #schema_def;
- named_schemas.insert(name, schema.clone());
- schema
- }
+ fn get_schema_in_ctxt(named_schemas: &mut
apache_avro::schema::Names, enclosing_namespace: &Option<String>) ->
apache_avro::schema::Schema {
+ #implementation
}
}
- })
+ }
}
-fn get_data_struct_schema_def(
- full_schema_name: &str,
- record_doc: Option<String>,
- aliases: Vec<String>,
- rename_all: RenameRule,
- s: &syn::DataStruct,
- error_span: Span,
+/// Generate the code to check `named_schemas` if this schema already exist
+fn handle_named_schemas(full_schema_name: String, schema_def: TokenStream) ->
TokenStream {
+ quote! {
+ let name =
apache_avro::schema::Name::new(#full_schema_name).expect(concat!("Unable to
parse schema name ",
#full_schema_name)).fully_qualified_name(enclosing_namespace);
+ if named_schemas.contains_key(&name) {
+ apache_avro::schema::Schema::Ref{name}
+ } else {
+ let enclosing_namespace = &name.namespace;
+ // This is needed because otherwise recursive types will recurse
forever and cause a stack overflow
+ // TODO: Breaking change to AvroSchemaComponent, have
named_schemas be a set instead
+ named_schemas.insert(name.clone(),
apache_avro::schema::Schema::Ref{name: name.clone()});
+ let schema = #schema_def;
+ named_schemas.insert(name, schema.clone());
+ schema
+ }
+ }
+}
+
+/// Generate a schema definition for a struct.
+fn get_struct_schema_def(
+ container_attrs: &NamedTypeOptions,
+ data_struct: DataStruct,
+ ident_span: Span,
) -> Result<TokenStream, Vec<syn::Error>> {
let mut record_field_exprs = vec![];
- match s.fields {
- syn::Fields::Named(ref a) => {
- for field in a.named.iter() {
+ match data_struct.fields {
+ Fields::Named(a) => {
+ for field in a.named {
let mut name = field
.ident
.as_ref()
@@ -123,9 +131,8 @@ fn get_data_struct_schema_def(
name = raw_name.to_string();
}
let field_attrs = FieldOptions::new(&field.attrs,
field.span())?;
- let doc =
- preserve_optional(field_attrs.doc.or_else(||
extract_outer_doc(&field.attrs)));
- match (field_attrs.rename, rename_all) {
+ let doc = preserve_optional(field_attrs.doc);
+ match (field_attrs.rename, container_attrs.rename_all) {
(Some(rename), _) => {
name = rename;
}
@@ -169,32 +176,8 @@ fn get_data_struct_schema_def(
}
None => quote! { None },
};
- let aliases = preserve_vec(field_attrs.alias);
- let schema_expr = match field_attrs.with {
- With::Trait => type_to_schema_expr(&field.ty)?,
- With::Serde(path) => {
- quote! { #path::get_schema_in_ctxt(named_schemas,
enclosing_namespace) }
- }
- With::Expr(Expr::Closure(closure)) => {
- if closure.inputs.is_empty() {
- quote! { (#closure)() }
- } else {
- return Err(vec![syn::Error::new(
- field.span(),
- "Expected closure with 0 parameters",
- )]);
- }
- }
- With::Expr(Expr::Path(path)) => {
- quote! { #path(named_schemas, enclosing_namespace) }
- }
- With::Expr(_expr) => {
- return Err(vec![syn::Error::new(
- field.span(),
- "Invalid expression, expected function or closure",
- )]);
- }
- };
+ let aliases = preserve_vec(&field_attrs.alias);
+ let schema_expr = get_field_schema_expr(&field,
field_attrs.with)?;
record_field_exprs.push(quote! {
schema_fields.push(::apache_avro::schema::RecordField {
name: #name.to_string(),
@@ -209,24 +192,28 @@ fn get_data_struct_schema_def(
});
}
}
- syn::Fields::Unnamed(_) => {
+ Fields::Unnamed(_) => {
return Err(vec![syn::Error::new(
- error_span,
+ ident_span,
"AvroSchema derive does not work for tuple structs",
)]);
}
- syn::Fields::Unit => {
+ Fields::Unit => {
return Err(vec![syn::Error::new(
- error_span,
+ ident_span,
"AvroSchema derive does not work for unit structs",
)]);
}
}
- let record_doc = preserve_optional(record_doc);
- let record_aliases = preserve_vec(aliases);
+
+ let record_doc = preserve_optional(container_attrs.doc.as_ref());
+ let record_aliases = preserve_vec(&container_attrs.aliases);
+ let full_schema_name = &container_attrs.name;
+
// When flatten is involved, there will be more but we don't know how
many. This optimises for
// the most common case where there is no flatten.
let minimum_fields = record_field_exprs.len();
+
Ok(quote! {
{
let mut schema_fields = Vec::with_capacity(#minimum_fields);
@@ -250,23 +237,86 @@ fn get_data_struct_schema_def(
})
}
+/// Use the schema definition of the only field in the struct as the schema
+fn get_transparent_struct_schema_def(
+ fields: Fields,
+ input_span: Span,
+) -> Result<TokenStream, Vec<syn::Error>> {
+ match fields {
+ Fields::Named(fields_named) => {
+ let mut found = None;
+ for field in fields_named.named {
+ let attrs = FieldOptions::new(&field.attrs, field.span())?;
+ if attrs.skip {
+ continue;
+ }
+ if found.replace((field, attrs)).is_some() {
+ return Err(vec![syn::Error::new(
+ input_span,
+ "AvroSchema: #[serde(transparent)] is only allowed on
structs with one unskipped field",
+ )]);
+ }
+ }
+
+ if let Some((field, attrs)) = found {
+ get_field_schema_expr(&field, attrs.with)
+ } else {
+ Err(vec![syn::Error::new(
+ input_span,
+ "AvroSchema: #[serde(transparent)] is only allowed on
structs with one unskipped field",
+ )])
+ }
+ }
+ Fields::Unnamed(_) => Err(vec![syn::Error::new(
+ input_span,
+ "AvroSchema: derive does not work for tuple structs",
+ )]),
+ Fields::Unit => Err(vec![syn::Error::new(
+ input_span,
+ "AvroSchema: derive does not work for unit structs",
+ )]),
+ }
+}
+
+fn get_field_schema_expr(field: &Field, with: With) -> Result<TokenStream,
Vec<syn::Error>> {
+ match with {
+ With::Trait => Ok(type_to_schema_expr(&field.ty)?),
+ With::Serde(path) => {
+ Ok(quote! { #path::get_schema_in_ctxt(named_schemas,
enclosing_namespace) })
+ }
+ With::Expr(Expr::Closure(closure)) => {
+ if closure.inputs.is_empty() {
+ Ok(quote! { (#closure)() })
+ } else {
+ Err(vec![syn::Error::new(
+ field.span(),
+ "Expected closure with 0 parameters",
+ )])
+ }
+ }
+ With::Expr(Expr::Path(path)) => Ok(quote! { #path(named_schemas,
enclosing_namespace) }),
+ With::Expr(_expr) => Err(vec![syn::Error::new(
+ field.span(),
+ "Invalid expression, expected function or closure",
+ )]),
+ }
+}
+
+/// Generate a schema definition for a enum.
fn get_data_enum_schema_def(
- full_schema_name: &str,
- doc: Option<String>,
- aliases: Vec<String>,
- rename_all: RenameRule,
- e: &syn::DataEnum,
- error_span: Span,
+ container_attrs: &NamedTypeOptions,
+ data_enum: DataEnum,
+ ident_span: Span,
) -> Result<TokenStream, Vec<syn::Error>> {
- let doc = preserve_optional(doc);
- let enum_aliases = preserve_vec(aliases);
- if e.variants.iter().all(|v| syn::Fields::Unit == v.fields) {
- let default_value = default_enum_variant(e, error_span)?;
+ let doc = preserve_optional(container_attrs.doc.as_ref());
+ let enum_aliases = preserve_vec(&container_attrs.aliases);
+ if data_enum.variants.iter().all(|v| Fields::Unit == v.fields) {
+ let default_value = default_enum_variant(&data_enum, ident_span)?;
let default = preserve_optional(default_value);
let mut symbols = Vec::new();
- for variant in &e.variants {
+ for variant in &data_enum.variants {
let field_attrs = VariantOptions::new(&variant.attrs,
variant.span())?;
- let name = match (field_attrs.rename, rename_all) {
+ let name = match (field_attrs.rename, container_attrs.rename_all) {
(Some(rename), _) => rename,
(None, rename_all) if !matches!(rename_all, RenameRule::None)
=> {
rename_all.apply_to_variant(&variant.ident.to_string())
@@ -275,6 +325,7 @@ fn get_data_enum_schema_def(
};
symbols.push(name);
}
+ let full_schema_name = &container_attrs.name;
Ok(quote! {
apache_avro::schema::Schema::Enum(apache_avro::schema::EnumSchema {
name:
apache_avro::schema::Name::new(#full_schema_name).expect(&format!("Unable to
parse enum name for schema {}", #full_schema_name)[..]),
@@ -287,8 +338,8 @@ fn get_data_enum_schema_def(
})
} else {
Err(vec![syn::Error::new(
- error_span,
- "AvroSchema derive does not work for enums with non unit structs",
+ ident_span,
+ "AvroSchema: derive does not work for enums with non unit structs",
)])
}
}
@@ -351,28 +402,6 @@ fn to_compile_errors(errors: Vec<syn::Error>) ->
proc_macro2::TokenStream {
quote!(#(#compile_errors)*)
}
-fn extract_outer_doc(attributes: &[Attribute]) -> Option<String> {
- let doc = attributes
- .iter()
- .filter(|attr| attr.style == AttrStyle::Outer &&
attr.path().is_ident("doc"))
- .filter_map(|attr| {
- let name_value = attr.meta.require_name_value();
- match name_value {
- Ok(name_value) => match &name_value.value {
- syn::Expr::Lit(expr_lit) => match expr_lit.lit {
- syn::Lit::Str(ref lit_str) =>
Some(lit_str.value().trim().to_string()),
- _ => None,
- },
- _ => None,
- },
- Err(_) => None,
- }
- })
- .collect::<Vec<String>>()
- .join("\n");
- if doc.is_empty() { None } else { Some(doc) }
-}
-
fn preserve_optional(op: Option<impl quote::ToTokens>) -> TokenStream {
match op {
Some(tt) => quote! {Some(#tt.into())},
@@ -380,7 +409,7 @@ fn preserve_optional(op: Option<impl quote::ToTokens>) ->
TokenStream {
}
}
-fn preserve_vec(op: Vec<impl quote::ToTokens>) -> TokenStream {
+fn preserve_vec(op: &[impl quote::ToTokens]) -> TokenStream {
let items: Vec<TokenStream> = op.iter().map(|tt| quote!
{#tt.into()}).collect();
if items.is_empty() {
quote! {None}
@@ -404,8 +433,8 @@ mod tests {
};
match syn::parse2::<DeriveInput>(test_struct) {
- Ok(mut input) => {
- assert!(derive_avro_schema(&mut input).is_ok())
+ Ok(input) => {
+ assert!(derive_avro_schema(input).is_ok())
}
Err(error) => panic!(
"Failed to parse as derive input when it should be able to.
Error: {error:?}"
@@ -420,8 +449,8 @@ mod tests {
};
match syn::parse2::<DeriveInput>(test_tuple_struct) {
- Ok(mut input) => {
- assert!(derive_avro_schema(&mut input).is_err())
+ Ok(input) => {
+ assert!(derive_avro_schema(input).is_err())
}
Err(error) => panic!(
"Failed to parse as derive input when it should be able to.
Error: {error:?}"
@@ -436,8 +465,8 @@ mod tests {
};
match syn::parse2::<DeriveInput>(test_tuple_struct) {
- Ok(mut input) => {
- assert!(derive_avro_schema(&mut input).is_err())
+ Ok(input) => {
+ assert!(derive_avro_schema(input).is_err())
}
Err(error) => panic!(
"Failed to parse as derive input when it should be able to.
Error: {error:?}"
@@ -453,8 +482,8 @@ mod tests {
}
};
match syn::parse2::<DeriveInput>(struct_with_optional) {
- Ok(mut input) => {
- assert!(derive_avro_schema(&mut input).is_ok())
+ Ok(input) => {
+ assert!(derive_avro_schema(input).is_ok())
}
Err(error) => panic!(
"Failed to parse as derive input when it should be able to.
Error: {error:?}"
@@ -473,8 +502,8 @@ mod tests {
}
};
match syn::parse2::<DeriveInput>(basic_enum) {
- Ok(mut input) => {
- assert!(derive_avro_schema(&mut input).is_ok())
+ Ok(input) => {
+ assert!(derive_avro_schema(input).is_ok())
}
Err(error) => panic!(
"Failed to parse as derive input when it should be able to.
Error: {error:?}"
@@ -494,17 +523,14 @@ mod tests {
}
};
match syn::parse2::<DeriveInput>(basic_enum) {
- Ok(mut input) => {
- let derived = derive_avro_schema(&mut input);
+ Ok(input) => {
+ let derived = derive_avro_schema(input);
assert!(derived.is_ok());
assert_eq!(derived.unwrap().to_string(), quote! {
#[automatically_derived]
impl apache_avro::AvroSchemaComponent for Basic {
fn get_schema_in_ctxt(
- named_schemas: &mut std::collections::HashMap<
- apache_avro::schema::Name,
- apache_avro::schema::Schema
- >,
+ named_schemas: &mut apache_avro::schema::Names,
enclosing_namespace: &Option<String>
) -> apache_avro::schema::Schema {
let name = apache_avro::schema::Name::new("Basic")
@@ -557,7 +583,7 @@ mod tests {
}
};
match syn::parse2::<DeriveInput>(non_basic_enum) {
- Ok(mut input) => match derive_avro_schema(&mut input) {
+ Ok(input) => match derive_avro_schema(input) {
Ok(_) => {
panic!("Should not be able to derive schema for enum with
multiple defaults")
}
@@ -586,8 +612,8 @@ mod tests {
}
};
match syn::parse2::<DeriveInput>(non_basic_enum) {
- Ok(mut input) => {
- assert!(derive_avro_schema(&mut input).is_err())
+ Ok(input) => {
+ assert!(derive_avro_schema(input).is_err())
}
Err(error) => panic!(
"Failed to parse as derive input when it should be able to.
Error: {error:?}"
@@ -606,8 +632,8 @@ mod tests {
};
match syn::parse2::<DeriveInput>(test_struct) {
- Ok(mut input) => {
- let schema_token_stream = derive_avro_schema(&mut input);
+ Ok(input) => {
+ let schema_token_stream = derive_avro_schema(input);
assert!(&schema_token_stream.is_ok());
assert!(
schema_token_stream
@@ -632,8 +658,8 @@ mod tests {
};
match syn::parse2::<DeriveInput>(test_reference_struct) {
- Ok(mut input) => {
- assert!(derive_avro_schema(&mut input).is_ok())
+ Ok(input) => {
+ assert!(derive_avro_schema(input).is_ok())
}
Err(error) => panic!(
"Failed to parse as derive input when it should be able to.
Error: {error:?}"
@@ -659,9 +685,9 @@ mod tests {
};
match syn::parse2::<DeriveInput>(test_struct) {
- Ok(mut input) => {
- let schema_res = derive_avro_schema(&mut input);
- let expected_token_stream = r#"# [automatically_derived] impl
apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas
: & mut std :: collections :: HashMap < apache_avro :: schema :: Name ,
apache_avro :: schema :: Schema > , enclosing_namespace : & Option < String >)
-> apache_avro :: schema :: Schema { let name = apache_avro :: schema :: Name
:: new ("A") . expect (concat ! ("Unable to parse schema name " , "A")) .
fully_qualified_name (enclosing [...]
+ Ok(input) => {
+ let schema_res = derive_avro_schema(input);
+ let expected_token_stream = r#"# [automatically_derived] impl
apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas
: & mut apache_avro :: schema :: Names , enclosing_namespace : & Option <
String >) -> apache_avro :: schema :: Schema { let name = apache_avro :: schema
:: Name :: new ("A") . expect (concat ! ("Unable to parse schema name " , "A"))
. fully_qualified_name (enclosing_namespace) ; if named_schemas . contains_key
(& name) { apache_avr [...]
let schema_token_stream = schema_res.unwrap().to_string();
assert_eq!(schema_token_stream, expected_token_stream);
}
@@ -678,9 +704,9 @@ mod tests {
};
match syn::parse2::<DeriveInput>(test_enum) {
- Ok(mut input) => {
- let schema_res = derive_avro_schema(&mut input);
- let expected_token_stream = r#"# [automatically_derived] impl
apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas
: & mut std :: collections :: HashMap < apache_avro :: schema :: Name ,
apache_avro :: schema :: Schema > , enclosing_namespace : & Option < String >)
-> apache_avro :: schema :: Schema { let name = apache_avro :: schema :: Name
:: new ("A") . expect (concat ! ("Unable to parse schema name " , "A")) .
fully_qualified_name (enclosing [...]
+ Ok(input) => {
+ let schema_res = derive_avro_schema(input);
+ let expected_token_stream = r#"# [automatically_derived] impl
apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas
: & mut apache_avro :: schema :: Names , enclosing_namespace : & Option <
String >) -> apache_avro :: schema :: Schema { let name = apache_avro :: schema
:: Name :: new ("A") . expect (concat ! ("Unable to parse schema name " , "A"))
. fully_qualified_name (enclosing_namespace) ; if named_schemas . contains_key
(& name) { apache_avr [...]
let schema_token_stream = schema_res.unwrap().to_string();
assert_eq!(schema_token_stream, expected_token_stream);
}
@@ -701,9 +727,9 @@ mod tests {
};
match syn::parse2::<DeriveInput>(test_struct) {
- Ok(mut input) => {
- let schema_res = derive_avro_schema(&mut input);
- let expected_token_stream = r#"# [automatically_derived] impl
apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas
: & mut std :: collections :: HashMap < apache_avro :: schema :: Name ,
apache_avro :: schema :: Schema > , enclosing_namespace : & Option < String >)
-> apache_avro :: schema :: Schema { let name = apache_avro :: schema :: Name
:: new ("A") . expect (concat ! ("Unable to parse schema name " , "A")) .
fully_qualified_name (enclosing [...]
+ Ok(input) => {
+ let schema_res = derive_avro_schema(input);
+ let expected_token_stream = r#"# [automatically_derived] impl
apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas
: & mut apache_avro :: schema :: Names , enclosing_namespace : & Option <
String >) -> apache_avro :: schema :: Schema { let name = apache_avro :: schema
:: Name :: new ("A") . expect (concat ! ("Unable to parse schema name " , "A"))
. fully_qualified_name (enclosing_namespace) ; if named_schemas . contains_key
(& name) { apache_avr [...]
let schema_token_stream = schema_res.unwrap().to_string();
assert_eq!(schema_token_stream, expected_token_stream);
}
@@ -721,9 +747,9 @@ mod tests {
};
match syn::parse2::<DeriveInput>(test_enum) {
- Ok(mut input) => {
- let schema_res = derive_avro_schema(&mut input);
- let expected_token_stream = r#"# [automatically_derived] impl
apache_avro :: AvroSchemaComponent for B { fn get_schema_in_ctxt (named_schemas
: & mut std :: collections :: HashMap < apache_avro :: schema :: Name ,
apache_avro :: schema :: Schema > , enclosing_namespace : & Option < String >)
-> apache_avro :: schema :: Schema { let name = apache_avro :: schema :: Name
:: new ("B") . expect (concat ! ("Unable to parse schema name " , "B")) .
fully_qualified_name (enclosing [...]
+ Ok(input) => {
+ let schema_res = derive_avro_schema(input);
+ let expected_token_stream = r#"# [automatically_derived] impl
apache_avro :: AvroSchemaComponent for B { fn get_schema_in_ctxt (named_schemas
: & mut apache_avro :: schema :: Names , enclosing_namespace : & Option <
String >) -> apache_avro :: schema :: Schema { let name = apache_avro :: schema
:: Name :: new ("B") . expect (concat ! ("Unable to parse schema name " , "B"))
. fully_qualified_name (enclosing_namespace) ; if named_schemas . contains_key
(& name) { apache_avr [...]
let schema_token_stream = schema_res.unwrap().to_string();
assert_eq!(schema_token_stream, expected_token_stream);
}
@@ -745,9 +771,9 @@ mod tests {
};
match syn::parse2::<DeriveInput>(test_struct) {
- Ok(mut input) => {
- let schema_res = derive_avro_schema(&mut input);
- let expected_token_stream = r#"# [automatically_derived] impl
apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas
: & mut std :: collections :: HashMap < apache_avro :: schema :: Name ,
apache_avro :: schema :: Schema > , enclosing_namespace : & Option < String >)
-> apache_avro :: schema :: Schema { let name = apache_avro :: schema :: Name
:: new ("A") . expect (concat ! ("Unable to parse schema name " , "A")) .
fully_qualified_name (enclosing [...]
+ Ok(input) => {
+ let schema_res = derive_avro_schema(input);
+ let expected_token_stream = r#"# [automatically_derived] impl
apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas
: & mut apache_avro :: schema :: Names , enclosing_namespace : & Option <
String >) -> apache_avro :: schema :: Schema { let name = apache_avro :: schema
:: Name :: new ("A") . expect (concat ! ("Unable to parse schema name " , "A"))
. fully_qualified_name (enclosing_namespace) ; if named_schemas . contains_key
(& name) { apache_avr [...]
let schema_token_stream = schema_res.unwrap().to_string();
assert_eq!(schema_token_stream, expected_token_stream);
}
diff --git a/avro_derive/tests/derive.rs b/avro_derive/tests/derive.rs
index 758774e..74c0e1e 100644
--- a/avro_derive/tests/derive.rs
+++ b/avro_derive/tests/derive.rs
@@ -1996,6 +1996,27 @@ fn avro_rs_397_derive_with_expr_lambda() {
assert_eq!(expected_schema, derived_schema);
}
+#[test]
+fn avro_rs_398_transparent_with_skip() {
+ fn long_schema(_named_schemas: &mut Names, _enclosing_namespace:
&Namespace) -> Schema {
+ Schema::Long
+ }
+
+ #[allow(dead_code)]
+ #[derive(AvroSchema)]
+ #[serde(transparent)]
+ struct Foo {
+ #[serde(skip)]
+ a: String,
+ #[avro(with = long_schema)]
+ b: i32,
+ #[serde(skip)]
+ c: String,
+ }
+
+ assert_eq!(Schema::Long, Foo::get_schema());
+}
+
#[test]
fn avro_rs_401_do_not_match_typename() {
#[expect(nonstandard_style, reason = "It needs to be exactly this")]
diff --git a/avro_derive/tests/serde.rs b/avro_derive/tests/serde.rs
index 5283b5e..043a245 100644
--- a/avro_derive/tests/serde.rs
+++ b/avro_derive/tests/serde.rs
@@ -314,6 +314,69 @@ mod container_attributes {
"Invalid field name c",
);
}
+
+ #[test]
+ fn avro_rs_398_transparent() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ #[serde(transparent)]
+ struct Foo {
+ a: String,
+ }
+
+ let schema = r#"
+ {
+ "type":"string"
+ }
+ "#;
+
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, Foo::get_schema());
+
+ serde_assert(Foo {
+ a: "spam".to_string(),
+ });
+ }
+
+ #[test]
+ fn avro_rs_398_transparent_ref() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ #[serde(transparent)]
+ struct Foo<'a> {
+ a: &'a str,
+ }
+
+ let schema = r#"
+ {
+ "type":"string"
+ }
+ "#;
+
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, Foo::get_schema());
+ }
+
+ #[test]
+ fn avro_rs_398_transparent_array() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ #[serde(transparent)]
+ struct Foo {
+ a: Vec<String>,
+ }
+
+ let schema = r#"
+ {
+ "type":"array",
+ "items":"string"
+ }
+ "#;
+
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, Foo::get_schema());
+
+ serde_assert(Foo {
+ a: vec!["spam".to_string()],
+ });
+ }
}
mod variant_attributes {
diff --git a/avro_derive/tests/ui/avro_rs_373_rename_all_fields.stderr
b/avro_derive/tests/ui/avro_rs_373_rename_all_fields.stderr
index 6784672..7a5a531 100644
--- a/avro_derive/tests/ui/avro_rs_373_rename_all_fields.stderr
+++ b/avro_derive/tests/ui/avro_rs_373_rename_all_fields.stderr
@@ -1,4 +1,4 @@
-error: AvroSchema derive does not work for enums with non unit structs
+error: AvroSchema: derive does not work for enums with non unit structs
--> tests/ui/avro_rs_373_rename_all_fields.rs:22:6
|
22 | enum Foo {
diff --git a/avro_derive/tests/ui/avro_rs_373_transparent.stderr
b/avro_derive/tests/ui/avro_rs_373_transparent.stderr
deleted file mode 100644
index d300405..0000000
--- a/avro_derive/tests/ui/avro_rs_373_transparent.stderr
+++ /dev/null
@@ -1,8 +0,0 @@
-error: AvroSchema derive does not support Serde `transparent` attribute
- --> tests/ui/avro_rs_373_transparent.rs:27:1
- |
-27 | / #[serde(transparent)]
-28 | | struct Bar {
-29 | | foo: Foo,
-30 | | }
- | |_^
diff --git a/avro_derive/tests/ui/avro_rs_373_transparent.rs
b/avro_derive/tests/ui/avro_rs_398_transparent.rs
similarity index 96%
copy from avro_derive/tests/ui/avro_rs_373_transparent.rs
copy to avro_derive/tests/ui/avro_rs_398_transparent.rs
index 83fe2b1..3b6ba49 100644
--- a/avro_derive/tests/ui/avro_rs_373_transparent.rs
+++ b/avro_derive/tests/ui/avro_rs_398_transparent.rs
@@ -18,6 +18,7 @@
use apache_avro::AvroSchema;
#[derive(AvroSchema)]
+#[serde(transparent)]
struct Foo {
a: String,
b: i32,
@@ -25,8 +26,8 @@ struct Foo {
#[derive(AvroSchema)]
#[serde(transparent)]
-struct Bar {
- foo: Foo,
+enum Bar {
+ A
}
pub fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_398_transparent.stderr
b/avro_derive/tests/ui/avro_rs_398_transparent.stderr
new file mode 100644
index 0000000..617dc43
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_398_transparent.stderr
@@ -0,0 +1,18 @@
+error: AvroSchema: #[serde(transparent)] is only allowed on structs with one
unskipped field
+ --> tests/ui/avro_rs_398_transparent.rs:21:1
+ |
+21 | / #[serde(transparent)]
+22 | | struct Foo {
+23 | | a: String,
+24 | | b: i32,
+25 | | }
+ | |_^
+
+error: AvroSchema: `#[serde(transparent)]` is only supported on structs
+ --> tests/ui/avro_rs_398_transparent.rs:28:1
+ |
+28 | / #[serde(transparent)]
+29 | | enum Bar {
+30 | | A
+31 | | }
+ | |_^
diff --git a/avro_derive/tests/ui/avro_rs_373_transparent.rs
b/avro_derive/tests/ui/avro_rs_398_transparent_skips.rs
similarity index 92%
rename from avro_derive/tests/ui/avro_rs_373_transparent.rs
rename to avro_derive/tests/ui/avro_rs_398_transparent_skips.rs
index 83fe2b1..7f324f6 100644
--- a/avro_derive/tests/ui/avro_rs_373_transparent.rs
+++ b/avro_derive/tests/ui/avro_rs_398_transparent_skips.rs
@@ -18,15 +18,14 @@
use apache_avro::AvroSchema;
#[derive(AvroSchema)]
+#[serde(transparent)]
struct Foo {
+ #[serde(skip)]
a: String,
+ #[serde(skip)]
b: i32,
-}
-
-#[derive(AvroSchema)]
-#[serde(transparent)]
-struct Bar {
- foo: Foo,
+ #[serde(skip)]
+ c: Vec<u8>
}
pub fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_398_transparent_skips.stderr
b/avro_derive/tests/ui/avro_rs_398_transparent_skips.stderr
new file mode 100644
index 0000000..21eb874
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_398_transparent_skips.stderr
@@ -0,0 +1,11 @@
+error: AvroSchema: #[serde(transparent)] is only allowed on structs with one
unskipped field
+ --> tests/ui/avro_rs_398_transparent_skips.rs:21:1
+ |
+21 | / #[serde(transparent)]
+22 | | struct Foo {
+23 | | #[serde(skip)]
+24 | | a: String,
+... |
+28 | | c: Vec<u8>
+29 | | }
+ | |_^