martin-g commented on code in PR #477:
URL: https://github.com/apache/avro-rs/pull/477#discussion_r2832814140
##########
avro/src/serde/derive.rs:
##########
@@ -562,6 +614,13 @@ where
) -> Option<Vec<RecordField>> {
None
}
+
+ /// If `T` has a field default, this will return an array of with that
default. Otherwise there is no default.
+ fn field_default() -> Option<serde_json::Value> {
+ T::field_default().map(|default| {
+ serde_json::Value::Array(std::array::from_fn::<_, N, _>(|_|
default.clone()).to_vec())
Review Comment:
```suggestion
serde_json::Value::Array(vec![default; N])
```
Does this work ?
Or it needs to call `.clone()` explicitly ?
##########
avro/src/serde/derive.rs:
##########
@@ -562,6 +614,13 @@ where
) -> Option<Vec<RecordField>> {
None
}
+
+ /// If `T` has a field default, this will return an array of with that
default. Otherwise there is no default.
Review Comment:
```suggestion
/// If `T` has a field default, this will return an array of elements
with that default. Otherwise there is no default.
```
##########
avro/src/serde/derive.rs:
##########
@@ -113,11 +121,14 @@ use std::collections::{HashMap, HashSet};
///
/// Set the `doc` attribute of the field. Defaults to the documentation of
the field.
///
-/// - `#[avro(default = "null")]`
+/// - `#[avro(default = "null")]` or `#[avro(default = false)]`
///
-/// Set the `default` attribute of the field.
+/// Control the `default` attribute of the field. When not used, it will
use [`AvroSchemaComponent::field_default`]
+/// to get the default value for a type. This default value can be
overriden by providing a JSON string.
+/// To remove the `default` attribute for a field, set `default` to `false`.
Review Comment:
nit: What if one needs to set the default to `false` explicitly ?
Can we use some marker struct/constant instead ?
##########
avro/src/serde/derive.rs:
##########
@@ -479,6 +515,10 @@ where
macro_rules! impl_schema (
($type:ty, $variant_constructor:expr) => (
+ impl_schema!($type, $variant_constructor, <$type as
Default>::default());
Review Comment:
Is there a reason to use `Default::default()` by default ?
IMO the default should be `None` and only the app developer should
explicitly sets non-None defaults.
##########
avro_derive/src/attributes/mod.rs:
##########
@@ -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()))
Review Comment:
```suggestion
Some(#json)
```
This does not work ?!
##########
avro/src/serde/derive.rs:
##########
@@ -81,6 +85,10 @@ use std::collections::{HashMap, HashSet};
///
/// Set the `doc` attribute of the schema. Defaults to the documentation of
the type.
///
+/// - `#[avro(default = r#"{"field": 42, "other": "Spam"}"#)]`
Review Comment:
I am not sure this is a good idea.
Using such container level attribute with stringified map looks fragile. If
a field is renamed then its string key here may become obsolete/stale.
The field level attribute looks better to me. More type safe.
##########
avro_derive/src/attributes/mod.rs:
##########
@@ -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}"),
Review Comment:
```suggestion
format!("Invalid Avro `default` JSON: \n{e}"),
```
##########
avro_derive/src/attributes/mod.rs:
##########
@@ -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 =
Review Comment:
```suggestion
let json: serde_json::Value =
```
##########
avro_derive/src/attributes/mod.rs:
##########
@@ -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()))
Review Comment:
Why the second parsing is needed here ?
##########
avro_derive/tests/derive.rs:
##########
@@ -2377,3 +2378,203 @@ fn avro_rs_448_flatten_field_positions() {
.collect::<Vec<_>>();
assert_eq!(positions.as_slice(), &[0, 1, 2, 3][..]);
}
+
+#[test]
+fn avro_rs_476_field_default() {
+ #[derive(AvroSchema)]
+ struct Bar {
+ _field: Box<Bar>,
+ }
+
+ #[derive(AvroSchema)]
+ #[avro(default = r#"{"_field": true}"#)]
+ struct Spam {
+ _field: bool,
+ }
+
+ #[derive(AvroSchema)]
+ struct Foo {
+ _a: bool,
+ _b: i8,
+ _c: i16,
+ _d: i32,
+ _e: i64,
+ _f: u8,
+ _g: u16,
+ _h: u32,
+ _i: f32,
+ _j: f64,
+ _k: String,
+ _l: Box<str>,
+ _m: char,
+ _n: Box<Spam>,
+ _o: Vec<bool>,
+ _p: [u8; 5],
+ _p_alt: [Bar; 5],
+ _q: HashMap<String, String>,
+ _r: Option<f64>,
+ _s: Duration,
+ _t: Uuid,
+ _u: u64,
+ _v: u128,
+ _w: i128,
+ _x: Bar,
+ }
+
+ let schema = Foo::get_schema();
+ assert_eq!(
+ serde_json::to_string(&schema).unwrap(),
+
r#"{"type":"record","name":"Foo","fields":[{"name":"_a","type":"boolean","default":false},{"name":"_b","type":"int","default":0},{"name":"_c","type":"int","default":0},{"name":"_d","type":"int","default":0},{"name":"_e","type":"long","default":0},{"name":"_f","type":"int","default":0},{"name":"_g","type":"int","default":0},{"name":"_h","type":"long","default":0},{"name":"_i","type":"float","default":0.0},{"name":"_j","type":"double","default":0.0},{"name":"_k","type":"string","default":""},{"name":"_l","type":"string","default":""},{"name":"_m","type":"string","default":"\u0000"},{"name":"_n","type":{"type":"record","name":"Spam","fields":[{"name":"_field","type":"boolean","default":false}]},"default":{"_field":true}},{"name":"_o","type":{"type":"array","items":"boolean"},"default":[]},{"name":"_p","type":{"type":"array","items":"int"},"default":[0,0,0,0,0]},{"name":"_p_alt","type":{"type":"array","items":{"type":"record","name":"Bar","fields":[{"name":"_field","type":"Bar"}
]}}},{"name":"_q","type":{"type":"map","values":"string"},"default":{}},{"name":"_r","type":["null","double"],"default":null},{"name":"_s","type":{"type":"fixed","name":"duration","size":12,"logicalType":"duration"},"default":"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"},{"name":"_t","type":{"type":"fixed","name":"uuid","size":16,"logicalType":"uuid"},"default":"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"},{"name":"_u","type":{"type":"fixed","name":"u64","size":8},"default":"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"},{"name":"_v","type":{"type":"fixed","name":"u128","size":16},"default":"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"},{"name":"_w","type":{"type":"fixed","name":"i128","size":16},"default":"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"},{"name":"_x","type":"Bar"}]}"#
+ );
+}
+
+#[test]
+fn avro_rs_476_field_default_false() {
+ #[derive(AvroSchema)]
+ struct Bar {
+ _field: Box<Bar>,
+ }
+
+ #[derive(AvroSchema)]
+ #[avro(default = r#"{"_field": true}"#)]
+ struct Spam {
+ _field: bool,
+ }
+
+ #[derive(AvroSchema)]
+ struct Foo {
+ #[avro(default = false)]
+ _a: bool,
+ #[avro(default = false)]
+ _b: i8,
+ #[avro(default = false)]
+ _c: i16,
+ #[avro(default = false)]
+ _d: i32,
+ #[avro(default = false)]
+ _e: i64,
+ #[avro(default = false)]
+ _f: u8,
+ #[avro(default = false)]
+ _g: u16,
+ #[avro(default = false)]
+ _h: u32,
+ #[avro(default = false)]
+ _i: f32,
+ #[avro(default = false)]
+ _j: f64,
+ #[avro(default = false)]
+ _k: String,
+ #[avro(default = false)]
+ _l: Box<str>,
+ #[avro(default = false)]
+ _m: char,
+ #[avro(default = false)]
+ _n: Box<Spam>,
+ #[avro(default = false)]
+ _o: Vec<bool>,
+ #[avro(default = false)]
+ _p: [u8; 5],
+ #[avro(default = false)]
+ _p_alt: [Bar; 5],
+ #[avro(default = false)]
+ _q: HashMap<String, String>,
+ #[avro(default = false)]
+ _r: Option<f64>,
+ #[avro(default = false)]
+ _s: Duration,
+ #[avro(default = false)]
+ _t: Uuid,
+ #[avro(default = false)]
+ _u: u64,
+ #[avro(default = false)]
+ _v: u128,
+ #[avro(default = false)]
+ _w: i128,
+ #[avro(default = false)]
+ _x: Bar,
+ }
+
+ let schema = Foo::get_schema();
+ assert_eq!(
+ serde_json::to_string(&schema).unwrap(),
+
r#"{"type":"record","name":"Foo","fields":[{"name":"_a","type":"boolean"},{"name":"_b","type":"int"},{"name":"_c","type":"int"},{"name":"_d","type":"int"},{"name":"_e","type":"long"},{"name":"_f","type":"int"},{"name":"_g","type":"int"},{"name":"_h","type":"long"},{"name":"_i","type":"float"},{"name":"_j","type":"double"},{"name":"_k","type":"string"},{"name":"_l","type":"string"},{"name":"_m","type":"string"},{"name":"_n","type":{"type":"record","name":"Spam","fields":[{"name":"_field","type":"boolean","default":false}]}},{"name":"_o","type":{"type":"array","items":"boolean"}},{"name":"_p","type":{"type":"array","items":"int"}},{"name":"_p_alt","type":{"type":"array","items":{"type":"record","name":"Bar","fields":[{"name":"_field","type":"Bar"}]}}},{"name":"_q","type":{"type":"map","values":"string"}},{"name":"_r","type":["null","double"]},{"name":"_s","type":{"type":"fixed","name":"duration","size":12,"logicalType":"duration"}},{"name":"_t","type":{"type":"fixed","name":"u
uid","size":16,"logicalType":"uuid"}},{"name":"_u","type":{"type":"fixed","name":"u64","size":8}},{"name":"_v","type":{"type":"fixed","name":"u128","size":16}},{"name":"_w","type":{"type":"fixed","name":"i128","size":16}},{"name":"_x","type":"Bar"}]}"#
+ );
+}
+
+#[test]
+fn avro_rs_476_field_default_provided() {
+ #[derive(AvroSchema)]
+ #[avro(default = r#"{"_field": true}"#)]
+ struct Spam {
+ _field: bool,
+ }
+
+ #[derive(AvroSchema)]
+ struct Foo {
+ #[avro(default = "true")]
+ _a: bool,
+ #[avro(default = "42")]
+ _b: i8,
+ #[avro(default = "42")]
+ _c: i16,
+ #[avro(default = "42")]
+ _d: i32,
+ #[avro(default = "42")]
+ _e: i64,
+ #[avro(default = "42")]
+ _f: u8,
+ #[avro(default = "42")]
+ _g: u16,
+ #[avro(default = "42")]
+ _h: u32,
+ #[avro(default = "42.0")]
+ _i: f32,
+ #[avro(default = "42.0")]
+ _j: f64,
+ #[avro(default = r#""String""#)]
+ _k: String,
+ #[avro(default = r#""str""#)]
+ _l: Box<str>,
+ #[avro(default = r#""Z""#)]
+ _m: char,
+ #[avro(default = r#"{"_field": false}"#)]
+ _n: Box<Spam>,
+ #[avro(default = "[true, false, true]")]
+ _o: Vec<bool>,
+ #[avro(default = "[1,2,3,4,5]")]
+ _p: [u8; 5],
+ #[avro(
+ default = r#"[{"_field": true},{"_field": false},{"_field":
true},{"_field": false},{"_field": true}]"#
+ )]
+ _p_alt: [Spam; 5],
+ #[avro(default = r#"{"A": "B"}"#)]
+ _q: HashMap<String, String>,
+ #[avro(default = "42.0")]
Review Comment:
Since this is an Option it will generate a Union schema with Null being the
first variant, so the default must be `null`.
Another way is to detect that the default type is not Null and generate a
Union schema with a proper first variant that matches the default's type. But
this would be more complex.
##########
avro/src/serde/derive.rs:
##########
@@ -113,11 +121,14 @@ use std::collections::{HashMap, HashSet};
///
/// Set the `doc` attribute of the field. Defaults to the documentation of
the field.
///
-/// - `#[avro(default = "null")]`
+/// - `#[avro(default = "null")]` or `#[avro(default = false)]`
///
-/// Set the `default` attribute of the field.
+/// Control the `default` attribute of the field. When not used, it will
use [`AvroSchemaComponent::field_default`]
+/// to get the default value for a type. This default value can be
overriden by providing a JSON string.
Review Comment:
```suggestion
/// to get the default value for a type. This default value can be
overridden by providing a JSON string.
```
##########
avro_derive/src/attributes/mod.rs:
##########
@@ -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
Review Comment:
What happens if `avro.default` is `FieldDefault::Trait` but
`field_default()` returns `None` ?
--
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]