This is an automated email from the ASF dual-hosted git repository. kriskras99 pushed a commit to branch feat/avro_schema_default in repository https://gitbox.apache.org/repos/asf/avro-rs.git
commit 6a30e66afbba9c1037ce380a96fa5277ca185e43 Author: Kriskras99 <[email protected]> AuthorDate: Wed Feb 11 15:18:55 2026 +0100 feat: Allow types to provide default values `AvroSchemaComponent` is extended with a function `field_default` which will be called when deriving a record to set the default value for a field. The default implementation is to return `None`, which means no default. On the derive side, it is now possible to specify a default for a type using `#[avro(default = "..")]`. It is also possible to disable setting a default for a field with `#[avro(default = false)]`. This enables users to use `#[serde(skip_serializing{_if})]` on most fields without having to provide a default value. --- avro/src/serde/derive.rs | 125 +++++++++++++++------ avro/src/types.rs | 7 ++ avro/tests/serde_human_readable_true.rs | 2 +- avro_derive/src/attributes/avro.rs | 6 +- avro_derive/src/attributes/mod.rs | 54 ++++++++- avro_derive/src/lib.rs | 50 +++++++-- avro_derive/tests/derive.rs | 2 + .../tests/ui/avro_rs_226_skip_serializing.rs | 1 + .../tests/ui/avro_rs_226_skip_serializing.stderr | 5 +- .../tests/ui/avro_rs_226_skip_serializing_if.rs | 1 + .../ui/avro_rs_226_skip_serializing_if.stderr | 5 +- 11 files changed, 205 insertions(+), 53 deletions(-) diff --git a/avro/src/serde/derive.rs b/avro/src/serde/derive.rs index 451f0b1..51f9c70 100644 --- a/avro/src/serde/derive.rs +++ b/avro/src/serde/derive.rs @@ -22,6 +22,10 @@ use crate::schema::{ use std::borrow::Cow; use std::collections::HashMap; +const FIXED_8_DEFAULT: &str = "\0\0\0\0\0\0\0\0"; +const FIXED_12_DEFAULT: &str = "\0\0\0\0\0\0\0\0\0\0\0\0"; +const FIXED_16_DEFAULT: &str = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; + /// Trait for types that serve as an Avro data model. /// /// **Do not implement directly!** Either derive it or implement [`AvroSchemaComponent`] to get this trait @@ -219,6 +223,11 @@ pub trait AvroSchema { /// fn get_record_fields_in_ctxt(_: usize, _: &mut Names, _: &Namespace) -> Option<Vec<RecordField>> { /// None // A Schema::Int is not a Schema::Record so there are no fields to return /// } +/// +/// fn field_default() -> Option<serde_json::Value> { +/// // Zero as default value. Can also be None if you don't want to provide a default value +/// Some(0u8.into()) +/// } ///} /// ``` /// @@ -240,6 +249,10 @@ pub trait AvroSchema { /// fn get_record_fields_in_ctxt(first_field_position: usize, named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Option<Vec<RecordField>> { /// T::get_record_fields_in_ctxt(first_field_position, named_schemas, enclosing_namespace) /// } +/// +/// fn field_default() -> Option<serde_json::Value> { +/// T::field_default() +/// } ///} /// ``` /// @@ -254,6 +267,7 @@ pub trait AvroSchema { /// - Implement `get_record_fields_in_ctxt` as the default implementation has to be implemented /// with backtracking and a lot of cloning. /// - Even if your schema is not a record, still implement the function and just return `None` +/// - Implement `field_default()` if you want to use `#[serde(skip_serializing{,_if})]`. /// /// ``` /// # use apache_avro::{Schema, serde::{AvroSchemaComponent}, schema::{Name, Names, Namespace, RecordField, RecordSchema}}; @@ -304,6 +318,11 @@ pub trait AvroSchema { /// .build(), /// ]) /// } +/// +/// fn field_default() -> Option<serde_json::Value> { +/// // This type does not provide a default value +/// None +/// } ///} /// ``` pub trait AvroSchemaComponent { @@ -328,6 +347,16 @@ pub trait AvroSchemaComponent { Self::get_schema_in_ctxt, ) } + + /// The default value of this type when used for a record field. + /// + /// `None` means no default value, which is also the default implementation. + /// + /// Implementations of this trait provided by this crate use the [`Default::default`] value of + /// the type. + fn field_default() -> Option<serde_json::Value> { + None + } } /// Get the record fields from `schema_fn` without polluting `named_schemas` or causing duplicate names @@ -474,6 +503,10 @@ where macro_rules! impl_schema ( ($type:ty, $variant_constructor:expr) => ( + impl_schema!($type, $variant_constructor, <$type as Default>::default()); + ); + + ($type:ty, $variant_constructor:expr, $default_constructor:expr) => ( impl AvroSchemaComponent for $type { fn get_schema_in_ctxt(_: &mut Names, _: &Namespace) -> Schema { $variant_constructor @@ -482,6 +515,10 @@ macro_rules! impl_schema ( fn get_record_fields_in_ctxt(_: usize, _: &mut Names, _: &Namespace) -> Option<Vec<RecordField>> { None } + + fn field_default() -> Option<serde_json::Value> { + Some(serde_json::Value::from($default_constructor)) + } } ); ); @@ -497,8 +534,8 @@ impl_schema!(u32, Schema::Long); impl_schema!(f32, Schema::Float); impl_schema!(f64, Schema::Double); impl_schema!(String, Schema::String); -impl_schema!(str, Schema::String); -impl_schema!(char, Schema::String); +impl_schema!(str, Schema::String, String::default()); +impl_schema!(char, Schema::String, String::from(char::default())); macro_rules! impl_passthrough_schema ( ($type:ty where T: AvroSchemaComponent + ?Sized $(+ $bound:tt)*) => ( @@ -510,6 +547,10 @@ macro_rules! impl_passthrough_schema ( fn get_record_fields_in_ctxt(first_field_position: usize, named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Option<Vec<RecordField>> { T::get_record_fields_in_ctxt(first_field_position, named_schemas, enclosing_namespace) } + + fn field_default() -> Option<serde_json::Value> { + T::field_default() + } } ); ); @@ -530,6 +571,10 @@ macro_rules! impl_array_schema ( fn get_record_fields_in_ctxt(_: usize, _: &mut Names, _: &Namespace) -> Option<Vec<RecordField>> { None } + + fn field_default() -> Option<serde_json::Value> { + Some(serde_json::Value::Array(Vec::new())) + } } ); ); @@ -554,6 +599,10 @@ where ) -> Option<Vec<RecordField>> { None } + + fn field_default() -> Option<serde_json::Value> { + Some(serde_json::Value::Array(Vec::new())) + } } impl<T> AvroSchemaComponent for HashMap<String, T> @@ -571,6 +620,10 @@ where ) -> Option<Vec<RecordField>> { None } + + fn field_default() -> Option<serde_json::Value> { + Some(serde_json::Value::Object(serde_json::Map::new())) + } } impl<T> AvroSchemaComponent for Option<T> @@ -595,6 +648,10 @@ where ) -> Option<Vec<RecordField>> { None } + + fn field_default() -> Option<serde_json::Value> { + Some(serde_json::Value::Null) + } } impl AvroSchemaComponent for core::time::Duration { @@ -614,7 +671,7 @@ impl AvroSchemaComponent for core::time::Duration { aliases: None, doc: None, size: 12, - default: None, + default: Some(FIXED_12_DEFAULT.to_string()), attributes: Default::default(), }); named_schemas.insert(name, schema.clone()); @@ -629,6 +686,10 @@ impl AvroSchemaComponent for core::time::Duration { ) -> Option<Vec<RecordField>> { None } + + fn field_default() -> Option<serde_json::Value> { + Some(serde_json::Value::String(FIXED_12_DEFAULT.to_string())) + } } impl AvroSchemaComponent for uuid::Uuid { @@ -648,7 +709,7 @@ impl AvroSchemaComponent for uuid::Uuid { aliases: None, doc: None, size: 16, - default: None, + default: Some(FIXED_16_DEFAULT.to_string()), attributes: Default::default(), })); named_schemas.insert(name, schema.clone()); @@ -663,6 +724,10 @@ impl AvroSchemaComponent for uuid::Uuid { ) -> Option<Vec<RecordField>> { None } + + fn field_default() -> Option<serde_json::Value> { + Some(serde_json::Value::String(FIXED_16_DEFAULT.to_string())) + } } impl AvroSchemaComponent for u64 { @@ -680,7 +745,7 @@ impl AvroSchemaComponent for u64 { aliases: None, doc: None, size: 8, - default: None, + default: Some(FIXED_8_DEFAULT.to_string()), attributes: Default::default(), }); named_schemas.insert(name, schema.clone()); @@ -695,6 +760,10 @@ impl AvroSchemaComponent for u64 { ) -> Option<Vec<RecordField>> { None } + + fn field_default() -> Option<serde_json::Value> { + Some(serde_json::Value::String(FIXED_8_DEFAULT.to_string())) + } } impl AvroSchemaComponent for u128 { @@ -712,7 +781,7 @@ impl AvroSchemaComponent for u128 { aliases: None, doc: None, size: 16, - default: None, + default: Some(FIXED_16_DEFAULT.to_string()), attributes: Default::default(), }); named_schemas.insert(name, schema.clone()); @@ -727,6 +796,10 @@ impl AvroSchemaComponent for u128 { ) -> Option<Vec<RecordField>> { None } + + fn field_default() -> Option<serde_json::Value> { + Some(serde_json::Value::String(FIXED_16_DEFAULT.to_string())) + } } impl AvroSchemaComponent for i128 { @@ -744,7 +817,7 @@ impl AvroSchemaComponent for i128 { aliases: None, doc: None, size: 16, - default: None, + default: Some(FIXED_16_DEFAULT.to_string()), attributes: Default::default(), }); named_schemas.insert(name, schema.clone()); @@ -759,11 +832,14 @@ impl AvroSchemaComponent for i128 { ) -> Option<Vec<RecordField>> { None } + + fn field_default() -> Option<serde_json::Value> { + Some(serde_json::Value::String(FIXED_16_DEFAULT.to_string())) + } } #[cfg(test)] mod tests { - use crate::schema::{FixedSchema, Name}; use crate::{AvroSchema, Schema}; use apache_avro_test_helper::TestResult; @@ -828,15 +904,8 @@ mod tests { fn avro_rs_414_u64() -> TestResult { let schema = u64::get_schema(); assert_eq!( - schema, - Schema::Fixed(FixedSchema { - name: Name::new("u64")?, - aliases: None, - doc: None, - size: 8, - default: None, - attributes: Default::default(), - }) + serde_json::to_string(&schema)?, + r#"{"type":"fixed","name":"u64","size":8,"default":"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"}"# ); Ok(()) @@ -846,15 +915,8 @@ mod tests { fn avro_rs_414_i128() -> TestResult { let schema = i128::get_schema(); assert_eq!( - schema, - Schema::Fixed(FixedSchema { - name: Name::new("i128")?, - aliases: None, - doc: None, - size: 16, - default: None, - attributes: Default::default(), - }) + serde_json::to_string(&schema)?, + r#"{"type":"fixed","name":"i128","size":16,"default":"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"}"# ); Ok(()) @@ -864,15 +926,8 @@ mod tests { fn avro_rs_414_u128() -> TestResult { let schema = u128::get_schema(); assert_eq!( - schema, - Schema::Fixed(FixedSchema { - name: Name::new("u128")?, - aliases: None, - doc: None, - size: 16, - default: None, - attributes: Default::default(), - }) + serde_json::to_string(&schema)?, + r#"{"type":"fixed","name":"u128","size":16,"default":"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"}"# ); Ok(()) diff --git a/avro/src/types.rs b/avro/src/types.rs index 28c3749..b2ced87 100644 --- a/avro/src/types.rs +++ b/avro/src/types.rs @@ -755,6 +755,13 @@ impl Value { } Value::Uuid(Uuid::from_slice(bytes).map_err(Details::ConvertSliceToUuid)?) } + (Value::String(ref string), UuidSchema::Fixed(_)) => { + let bytes = string.as_bytes(); + if bytes.len() != 16 { + return Err(Details::ConvertFixedToUuid(bytes.len()).into()); + } + Value::Uuid(Uuid::from_slice(bytes).map_err(Details::ConvertSliceToUuid)?) + } (other, _) => return Err(Details::GetUuid(other).into()), }; Ok(value) diff --git a/avro/tests/serde_human_readable_true.rs b/avro/tests/serde_human_readable_true.rs index 182839e..4380940 100644 --- a/avro/tests/serde_human_readable_true.rs +++ b/avro/tests/serde_human_readable_true.rs @@ -129,7 +129,7 @@ fn avro_rs_440_uuid_fixed() -> TestResult { 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)`?"# + 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: Some("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"), 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)`?"# ); Ok(()) diff --git a/avro_derive/src/attributes/avro.rs b/avro_derive/src/attributes/avro.rs index ea171b5..fdf755e 100644 --- a/avro_derive/src/attributes/avro.rs +++ b/avro_derive/src/attributes/avro.rs @@ -21,6 +21,7 @@ //! Although a user will mostly use the Serde attributes, there are some Avro specific attributes //! a user can use. These add extra metadata to the generated schema. +use crate::attributes::FieldDefault; use crate::case::RenameRule; use darling::FromMeta; use proc_macro2::Span; @@ -53,6 +54,9 @@ pub struct ContainerAttributes { /// [`serde::ContainerAttributes::rename_all`]: crate::attributes::serde::ContainerAttributes::rename_all #[darling(default)] pub rename_all: RenameRule, + /// Set the default value if this schema is used as a field + #[darling(default)] + pub default: Option<String>, } impl ContainerAttributes { @@ -125,7 +129,7 @@ pub struct FieldAttributes { /// /// This is also used as the default when `skip_serializing{_if}` is used. #[darling(default)] - pub default: Option<String>, + pub default: FieldDefault, /// Deprecated. Use [`serde::FieldAttributes::alias`] instead. /// /// Adds the `aliases` field to the schema. diff --git a/avro_derive/src/attributes/mod.rs b/avro_derive/src/attributes/mod.rs index cc259f1..d05c8c3 100644 --- a/avro_derive/src/attributes/mod.rs +++ b/avro_derive/src/attributes/mod.rs @@ -17,7 +17,8 @@ use crate::case::RenameRule; use darling::{FromAttributes, FromMeta}; -use proc_macro2::Span; +use proc_macro2::{Span, TokenStream}; +use quote::quote; use syn::{AttrStyle, Attribute, Expr, Ident, Path, spanned::Spanned}; mod avro; @@ -30,6 +31,7 @@ pub struct NamedTypeOptions { pub aliases: Vec<String>, pub rename_all: RenameRule, pub transparent: bool, + pub default: TokenStream, } impl NamedTypeOptions { @@ -116,12 +118,29 @@ impl NamedTypeOptions { let doc = avro.doc.or_else(|| extract_rustdoc(attributes)); + let default = match avro.default { + None => quote! { None }, + Some(default_value) => { + let _: serde_json::Value = + serde_json::from_str(&default_value[..]).map_err(|e| { + vec![syn::Error::new( + ident.span(), + format!("Invalid avro default json: \n{e}"), + )] + })?; + quote! { + Some(serde_json::from_str(#default_value).expect(format!("Invalid JSON: {:?}", #default_value).as_str())) + } + } + }; + Ok(Self { name: full_schema_name, doc, aliases: avro.alias, rename_all: serde.rename_all.serialize, transparent: serde.transparent, + default, }) } } @@ -210,11 +229,38 @@ impl With { } } } +/// How to get the default value for a value. +#[derive(Debug, PartialEq, Default)] +pub enum FieldDefault { + /// Use `<T as AvroSchemaComponent>::field_default`. + #[default] + Trait, + /// Don't set a default. + Disabled, + /// Use this JSON value. + Value(String), +} + +impl FromMeta for FieldDefault { + fn from_string(value: &str) -> darling::Result<Self> { + Ok(Self::Value(value.to_string())) + } + + fn from_bool(value: bool) -> darling::Result<Self> { + if value { + Err(darling::Error::custom( + "Expected `false` or a JSON string, got `true`", + )) + } else { + Ok(Self::Disabled) + } + } +} #[derive(Default)] pub struct FieldOptions { pub doc: Option<String>, - pub default: Option<String>, + pub default: FieldDefault, pub alias: Vec<String>, pub rename: Option<String>, pub skip: bool, @@ -274,11 +320,11 @@ impl FieldOptions { } if ((serde.skip_serializing && !serde.skip_deserializing) || serde.skip_serializing_if.is_some()) - && avro.default.is_none() + && avro.default == FieldDefault::Disabled { errors.push(syn::Error::new( span, - "`#[serde(skip_serializing)]` and `#[serde(skip_serializing_if)]` require `#[avro(default = \"..\")]`" + "`#[serde(skip_serializing)]` and `#[serde(skip_serializing_if)]` are incompatible with `#[avro(default = false)]`" )); } let with = match With::from_avro_and_serde(&avro.with, &serde.with, span) { diff --git a/avro_derive/src/lib.rs b/avro_derive/src/lib.rs index 64e8fc9..779bf1d 100644 --- a/avro_derive/src/lib.rs +++ b/avro_derive/src/lib.rs @@ -40,7 +40,7 @@ use syn::{ }; use crate::{ - attributes::{FieldOptions, NamedTypeOptions, VariantOptions, With}, + attributes::{FieldDefault, FieldOptions, NamedTypeOptions, VariantOptions, With}, case::RenameRule, }; @@ -75,6 +75,7 @@ fn derive_avro_schema(input: DeriveInput) -> Result<TokenStream, Vec<syn::Error> &input.generics, get_schema_impl, get_record_fields_impl, + named_type_options.default, )) } syn::Data::Enum(data_enum) => { @@ -93,6 +94,7 @@ fn derive_avro_schema(input: DeriveInput) -> Result<TokenStream, Vec<syn::Error> &input.generics, inner, quote! { None }, + named_type_options.default, )) } syn::Data::Union(_) => Err(vec![syn::Error::new( @@ -108,6 +110,7 @@ fn create_trait_definition( generics: &Generics, get_schema_impl: TokenStream, get_record_fields_impl: TokenStream, + field_default_impl: TokenStream, ) -> TokenStream { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); quote! { @@ -120,6 +123,10 @@ fn create_trait_definition( fn get_record_fields_in_ctxt(mut field_position: usize, named_schemas: &mut ::apache_avro::schema::Names, enclosing_namespace: &::std::option::Option<::std::string::String>) -> ::std::option::Option<::std::vec::Vec<::apache_avro::schema::RecordField>> { #get_record_fields_impl } + + fn field_default() -> ::std::option::Option<::serde_json::Value> { + ::std::option::Option::#field_default_impl + } } } } @@ -191,7 +198,9 @@ fn get_struct_schema_def( continue; } let default_value = match field_attrs.default { - Some(default_value) => { + FieldDefault::Disabled => quote! { None }, + FieldDefault::Trait => type_to_field_default_expr(&field.ty)?, + FieldDefault::Value(default_value) => { let _: serde_json::Value = serde_json::from_str(&default_value[..]) .map_err(|e| { vec![syn::Error::new( @@ -203,7 +212,6 @@ fn get_struct_schema_def( Some(serde_json::from_str(#default_value).expect(format!("Invalid JSON: {:?}", #default_value).as_str())) } } - None => quote! { None }, }; let aliases = aliases(&field_attrs.alias); let schema_expr = get_field_schema_expr(&field, field_attrs.with)?; @@ -466,6 +474,28 @@ fn type_to_get_record_fields_expr(ty: &Type) -> Result<TokenStream, Vec<syn::Err } } +fn type_to_field_default_expr(ty: &Type) -> Result<TokenStream, Vec<syn::Error>> { + match ty { + Type::Array(_) | Type::Slice(_) | Type::Path(_) | Type::Reference(_) => { + Ok(quote! {<#ty as apache_avro::AvroSchemaComponent>::field_default()}) + } + Type::Ptr(_) => Err(vec![syn::Error::new_spanned( + ty, + "AvroSchema: derive does not support raw pointers", + )]), + Type::Tuple(_) => Err(vec![syn::Error::new_spanned( + ty, + "AvroSchema: derive does not support tuples", + )]), + _ => Err(vec![syn::Error::new_spanned( + ty, + format!( + "AvroSchema: Unexpected type encountered! Please open an issue if this kind of type should be supported: {ty:?}" + ), + )]), + } +} + fn default_enum_variant( data_enum: &syn::DataEnum, error_span: Span, @@ -671,6 +701,10 @@ mod tests { ) -> ::std::option::Option <::std::vec::Vec<::apache_avro::schema::RecordField>> { None } + + fn field_default () -> ::std::option::Option<::serde_json::Value> { + ::std::option::Option::None + } } }.to_string()); } @@ -797,7 +831,7 @@ mod tests { match syn::parse2::<DeriveInput>(test_struct) { 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 : & :: std :: option :: Option < :: std :: string :: 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 n [...] + 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 : & :: std :: option :: Option < :: std :: string :: 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 n [...] let schema_token_stream = schema_res.unwrap().to_string(); assert_eq!(schema_token_stream, expected_token_stream); } @@ -816,7 +850,7 @@ mod tests { match syn::parse2::<DeriveInput>(test_enum) { 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 : & :: std :: option :: Option < :: std :: string :: 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 n [...] + 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 : & :: std :: option :: Option < :: std :: string :: 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 n [...] let schema_token_stream = schema_res.unwrap().to_string(); assert_eq!(schema_token_stream, expected_token_stream); } @@ -839,7 +873,7 @@ mod tests { match syn::parse2::<DeriveInput>(test_struct) { 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 : & :: std :: option :: Option < :: std :: string :: 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 n [...] + 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 : & :: std :: option :: Option < :: std :: string :: 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 n [...] let schema_token_stream = schema_res.unwrap().to_string(); assert_eq!(schema_token_stream, expected_token_stream); } @@ -859,7 +893,7 @@ mod tests { match syn::parse2::<DeriveInput>(test_enum) { 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 : & :: std :: option :: Option < :: std :: string :: 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 n [...] + 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 : & :: std :: option :: Option < :: std :: string :: 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 n [...] let schema_token_stream = schema_res.unwrap().to_string(); assert_eq!(schema_token_stream, expected_token_stream); } @@ -883,7 +917,7 @@ mod tests { match syn::parse2::<DeriveInput>(test_struct) { 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 : & :: std :: option :: Option < :: std :: string :: 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 n [...] + 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 : & :: std :: option :: Option < :: std :: string :: 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 n [...] 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 d6b1c4e..03cd086 100644 --- a/avro_derive/tests/derive.rs +++ b/avro_derive/tests/derive.rs @@ -1365,6 +1365,7 @@ fn test_basic_struct_with_defaults() { #[avro(default = "true")] condition: bool, // no default value for 'c' + #[avro(default = false)] c: f64, #[avro(default = r#"{"a": 1, "b": 2}"#)] map: HashMap<String, i32>, @@ -1939,6 +1940,7 @@ fn avro_rs_397_uuid() { "type":"fixed", "logicalType":"uuid", "name":"uuid", + "default":"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000", "size":16 } } diff --git a/avro_derive/tests/ui/avro_rs_226_skip_serializing.rs b/avro_derive/tests/ui/avro_rs_226_skip_serializing.rs index 40f4756..28a7395 100644 --- a/avro_derive/tests/ui/avro_rs_226_skip_serializing.rs +++ b/avro_derive/tests/ui/avro_rs_226_skip_serializing.rs @@ -22,6 +22,7 @@ struct T { x: Option<i8>, y: Option<String>, #[serde(skip_serializing)] + #[avro(default = false)] z: Option<i8>, } diff --git a/avro_derive/tests/ui/avro_rs_226_skip_serializing.stderr b/avro_derive/tests/ui/avro_rs_226_skip_serializing.stderr index e974f85..d5316b5 100644 --- a/avro_derive/tests/ui/avro_rs_226_skip_serializing.stderr +++ b/avro_derive/tests/ui/avro_rs_226_skip_serializing.stderr @@ -1,6 +1,7 @@ -error: `#[serde(skip_serializing)]` and `#[serde(skip_serializing_if)]` require `#[avro(default = "..")]` +error: `#[serde(skip_serializing)]` and `#[serde(skip_serializing_if)]` are incompatible with `#[avro(default = false)]` --> tests/ui/avro_rs_226_skip_serializing.rs:24:5 | 24 | / #[serde(skip_serializing)] -25 | | z: Option<i8>, +25 | | #[avro(default = false)] +26 | | z: Option<i8>, | |_________________^ diff --git a/avro_derive/tests/ui/avro_rs_226_skip_serializing_if.rs b/avro_derive/tests/ui/avro_rs_226_skip_serializing_if.rs index 2e87a9c..e532066 100644 --- a/avro_derive/tests/ui/avro_rs_226_skip_serializing_if.rs +++ b/avro_derive/tests/ui/avro_rs_226_skip_serializing_if.rs @@ -21,6 +21,7 @@ use apache_avro::AvroSchema; struct T { x: Option<i8>, #[serde(skip_serializing_if = "Option::is_none")] + #[avro(default = false)] y: Option<String>, z: Option<i8>, } diff --git a/avro_derive/tests/ui/avro_rs_226_skip_serializing_if.stderr b/avro_derive/tests/ui/avro_rs_226_skip_serializing_if.stderr index 9794bfb..f3094d0 100644 --- a/avro_derive/tests/ui/avro_rs_226_skip_serializing_if.stderr +++ b/avro_derive/tests/ui/avro_rs_226_skip_serializing_if.stderr @@ -1,6 +1,7 @@ -error: `#[serde(skip_serializing)]` and `#[serde(skip_serializing_if)]` require `#[avro(default = "..")]` +error: `#[serde(skip_serializing)]` and `#[serde(skip_serializing_if)]` are incompatible with `#[avro(default = false)]` --> tests/ui/avro_rs_226_skip_serializing_if.rs:23:5 | 23 | / #[serde(skip_serializing_if = "Option::is_none")] -24 | | y: Option<String>, +24 | | #[avro(default = false)] +25 | | y: Option<String>, | |_____________________^
