martin-g commented on code in PR #377:
URL: https://github.com/apache/avro-rs/pull/377#discussion_r2654650078
##########
avro_derive/src/attributes/mod.rs:
##########
@@ -0,0 +1,206 @@
+use darling::FromAttributes;
+use proc_macro2::Span;
+use syn::Attribute;
+
+use crate::{case::RenameRule, darling_to_syn};
+
+pub mod avro;
+pub mod serde;
+
+#[derive(Default)]
+pub struct NamedTypeOptions {
+ pub name: Option<String>,
+ pub namespace: Option<String>,
+ pub doc: Option<String>,
+ pub alias: Vec<String>,
+ pub rename_all: RenameRule,
+}
+
+impl NamedTypeOptions {
+ pub fn new(attributes: &[Attribute], span: Span) -> Result<Self,
Vec<syn::Error>> {
+ let avro =
+
avro::ContainerAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+ let serde =
+
serde::ContainerAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+
+ // Collect errors so user gets all feedback at once
+ let mut errors = Vec::new();
+
+ // Check for any Serde attributes that are hard errors
+ if serde.tag.is_some()
+ || serde.content.is_some()
+ || serde.untagged
+ || serde.variant_identifier
+ || serde.field_identifier
+ {
+ errors.push(syn::Error::new(
+ span,
+ "AvroSchema derive does not support changing the tagging Serde
generates (`tag`, `content`, `untagged`, `variant_identifier`,
`field_identifier`)",
+ ));
+ }
+ if serde.remote.is_some() {
+ errors.push(syn::Error::new(
+ span,
+ "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,
+ "AvroSchema derive does not support different rename rules for
serializing and deserializing (`rename_all(serialize = \"..\", deserialize =
\"..\")`)"
+ ))
+ }
+
+ // Check for conflicts between Serde and Avro
+ if avro.rename_all != RenameRule::None && serde.rename_all.serialize
!= avro.rename_all {
+ errors.push(syn::Error::new(
+ span,
+ "#[avro(rename_all = \"..\")] must match #[serde(rename_all =
\"..\")], it's also deprecated. Please use only `#[serde(rename_all =
\"..\")]`",
+ ))
+ }
+
+ if !errors.is_empty() {
+ return Err(errors);
+ }
+
+ Ok(Self {
+ name: avro.name,
+ namespace: avro.namespace,
+ doc: avro.doc,
+ alias: avro.alias,
+ rename_all: serde.rename_all.serialize,
+ })
+ }
+}
+
+pub struct FieldOptions {
+ pub doc: Option<String>,
+ pub default: Option<String>,
+ pub alias: Vec<String>,
+ pub rename: Option<String>,
+ pub skip: bool,
+ pub flatten: bool,
+}
+
+impl FieldOptions {
+ pub fn new(attributes: &[Attribute], span: Span) -> Result<Self,
Vec<syn::Error>> {
+ let mut avro =
+
avro::FieldAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+ let mut serde =
+
serde::FieldAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+ // Sort the aliases, so our check for equality does not fail if they
are provided in a different order
+ avro.alias.sort();
+ serde.alias.sort();
+
+ // Collect errors so user gets all feedback at once
+ let mut errors = Vec::new();
+
+ // Check for any Serde attributes that are hard errors
+ if serde.getter.is_some() {
+ errors.push(syn::Error::new(
+ span,
+ "AvroSchema derive does not support the Serde `getter`
attribute",
+ ));
+ }
+
+ // Check for conflicts between Serde and Avro
+ if avro.skip && !serde.skip {
+ errors.push(syn::Error::new(
+ span,
+ "`#[avro(skip)]` requires `#[serde(skip)]`, it's also
deprecated. Please use only `#[serde(skip)]`"
+ ));
+ }
+ if avro.flatten && !serde.flatten {
+ errors.push(syn::Error::new(
+ span,
+ "`#[avro(flatten)]` requires `#[serde(flatten)]`, it's also
deprecated. Please use only `#[serde(flatten)]`"
+ ));
+ }
+ // TODO: rename and alias checking can be relaxed with a more complex
check, would require the field name
+ if avro.rename.is_some() && serde.rename != avro.rename {
+ errors.push(syn::Error::new(
+ span,
+ "`#[avro(rename = \"..\")]` must match `#[serde(rename =
\"..\")]`, it's also deprecated. Please use only `#[serde(rename = \"..\")]`"
+ ));
+ }
+ if !avro.alias.is_empty() && serde.alias != avro.alias {
+ errors.push(syn::Error::new(
+ span,
+ "`#[avro(alias = \"..\")]` must match `#[serde(alias =
\"..\")]`, it's also deprecated. Please use only `#[serde(alias = \"..\")]`"
+ ));
+ }
+ if serde.skip_serializing && serde.skip_deserializing {
+ errors.push(syn::Error::new(
+ span,
+ "Use `#[serde(skip)]` instead of `#[serde(skip_serializing,
skip_deserializing)]`",
+ ));
+
+ // Don't want to suggest default for skip_serializing as it's not
needed for skip
+ return Err(errors);
+ }
+ if (serde.skip_serializing || serde.skip_serializing_if.is_some()) &&
avro.default.is_none()
+ {
+ errors.push(syn::Error::new(
+ span,
+ "`#[serde(skip_serializing)]` and
`#[serde(skip_serializing_if)]` require `#[avro(default = \"..\")]`"
+ ));
+ }
+
+ if !errors.is_empty() {
+ return Err(errors);
+ }
+
+ Ok(Self {
+ doc: avro.doc,
+ default: avro.default,
+ alias: serde.alias,
+ rename: serde.rename,
+ skip: serde.skip,
+ flatten: serde.flatten,
+ })
+ }
+}
+
+pub struct VariantOptions {
+ pub rename: Option<String>,
+}
+
+impl VariantOptions {
+ pub fn new(attributes: &[Attribute], span: Span) -> Result<Self,
Vec<syn::Error>> {
+ let avro =
avro::VariantAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+ let serde =
+
serde::VariantAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+
+ // Collect errors so user gets all feedback at once
+ let mut errors = Vec::new();
+
+ // Check for any Serde attributes that are hard errors
+ if serde.other || serde.untagged {
+ errors.push(syn::Error::new(
+ span,
+ "AvroSchema derive does not support changing the tagging Serde
generates (`other`, `untagged`)",
+ ));
+ }
+
+ if avro.rename.is_some() && serde.rename != avro.rename {
+ errors.push(syn::Error::new(
+ span,
+ "`#[avro(rename = \"..\")]` must match `#[serde(rename =
\"..\")]`, it's also deprecated. Please use only `#[serde(rename = \"..\")]`"
Review Comment:
```suggestion
"`#[avro(rename = \"..\")]` must match `#[serde(rename =
\"..\")]`, it's also deprecated. Please use only `#[serde(rename = \"..\")]`"
```
##########
avro_derive/src/attributes/avro.rs:
##########
@@ -0,0 +1,87 @@
+//! Attribute parsing for Avro attributes
Review Comment:
```suggestion
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//! Attribute parsing for Avro attributes
```
##########
avro_derive/src/attributes/mod.rs:
##########
@@ -0,0 +1,206 @@
+use darling::FromAttributes;
Review Comment:
```suggestion
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use darling::FromAttributes;
```
##########
avro_derive/tests/ui/avro_rs_373_skip_de_serializing.rs:
##########
@@ -0,0 +1,11 @@
+use apache_avro::AvroSchema;
Review Comment:
```suggestion
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use apache_avro::AvroSchema;
```
##########
avro_derive/tests/ui/avro_rs_373_transparent.rs:
##########
@@ -0,0 +1,15 @@
+use apache_avro::AvroSchema;
Review Comment:
```suggestion
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use apache_avro::AvroSchema;
```
##########
avro_derive/tests/serde.rs:
##########
@@ -0,0 +1,659 @@
+use apache_avro::{AvroSchema, Error, Reader, Schema, Writer, from_value};
+use serde::{Deserialize, Serialize, de::DeserializeOwned};
+
+/// Takes in a type that implements the right combination of traits and runs
it through a Serde Cycle and asserts the result is the same
+fn serde_assert<T>(obj: T)
+where
+ T: std::fmt::Debug + Serialize + DeserializeOwned + AvroSchema + Clone +
PartialEq,
+{
+ assert_eq!(obj, serde(obj.clone()).unwrap());
+}
+
+/// Takes in a type that implements the right combination of traits and runs
it through a Serde Cycle and asserts that the error matches the expected string
+fn serde_assert_err<T>(obj: T, expected: &str)
+where
+ T: std::fmt::Debug + Serialize + DeserializeOwned + AvroSchema + Clone +
PartialEq,
+{
+ let error = serde(obj).unwrap_err().to_string();
+ assert!(
+ error.contains(expected),
+ "Error `{error}` does not contain `{expected}`"
+ );
+}
+
+fn serde<T>(obj: T) -> Result<T, Error>
+where
+ T: Serialize + DeserializeOwned + AvroSchema,
+{
+ de(ser(obj)?)
+}
+
+fn ser<T>(obj: T) -> Result<Vec<u8>, Error>
+where
+ T: Serialize + AvroSchema,
+{
+ let schema = T::get_schema();
+ let mut writer = Writer::new(&schema, Vec::new())?;
+ writer.append_ser(obj)?;
+ writer.into_inner()
+}
+
+fn de<T>(encoded: Vec<u8>) -> Result<T, Error>
+where
+ T: DeserializeOwned + AvroSchema,
+{
+ assert!(!encoded.is_empty());
+ let schema = T::get_schema();
+ let mut reader = Reader::with_schema(&schema, &encoded[..])?;
+ if let Some(res) = reader.next() {
+ return res.and_then(|v| from_value::<T>(&v));
+ }
+ unreachable!("Nothing was encoded!")
+}
+
+mod container_attributes {
+ use super::*;
+
+ #[test]
+ fn avro_rs_373_rename() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ #[serde(rename = "Bar")]
+ struct Foo {
+ a: String,
+ b: i32,
+ }
+
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"Foo",
Review Comment:
https://github.com/apache/avro-rs/pull/377/changes#r2654685022
##########
avro_derive/tests/ui/avro_rs_373_remote.rs:
##########
@@ -0,0 +1,16 @@
+use apache_avro::AvroSchema;
Review Comment:
```suggestion
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use apache_avro::AvroSchema;
```
##########
avro_derive/src/attributes/serde.rs:
##########
@@ -0,0 +1,321 @@
+//! Attribute parsing for Serde attributes
+//!
+//! # Serde attributes
+//! This module only parses the minimal amount to be able to read Serde
attributes. This means
+//! most attributes are decoded as an `Option<String>` as the actual content
is not relevant.
+//! Attributes which are not needed by the derive are prefixed with a `_` as
we can't ignore them
+//! (this would be a compile error).
+//!
+//! If Serde adds new attributes they need to be added here too.
+
+use darling::{FromAttributes, FromMeta};
+use syn::Expr;
+
+use crate::case::RenameRule;
+
+/// Represents `rename_all` and `rename_all_fields`.
+///
+/// These attributes can take the following forms:
+/// 1. `rename_all = ".."`
+/// 2. `rename_all(serialize = "..", deserialize = "..")`
+///
+/// To parse the first form, we use Darling's `from_expr`.
+/// To parse the second form, we use Darling's `FromMeta` derive.
+#[derive(Debug, Default, FromMeta, PartialEq, Eq)]
+#[darling(from_expr = RenameAll::from_expr)]
+pub struct RenameAll {
+ #[darling(default)]
+ pub serialize: RenameRule,
+ #[darling(default)]
+ pub deserialize: RenameRule,
+}
+
+impl From<RenameRule> for RenameAll {
+ fn from(value: RenameRule) -> Self {
+ Self {
+ serialize: value,
+ deserialize: value,
+ }
+ }
+}
+
+impl RenameAll {
+ fn from_expr(expr: &Expr) -> darling::Result<Self> {
+ let Expr::Lit(lit) = expr else {
+ return Err(darling::Error::custom("Expected a string or a
tuple!"));
Review Comment:
```suggestion
return Err(darling::Error::custom("Expected a string literal!"));
```
The tuple form is handled by FromMeta derive.
##########
avro_derive/src/case.rs:
##########
@@ -59,15 +72,20 @@ static RENAME_RULES: &[(&str, RenameRule)] = &[
];
impl RenameRule {
- pub fn from_str(rename_all_str: &str) -> Result<Self, ParseError<'_>> {
- for (name, rule) in RENAME_RULES {
- if rename_all_str == *name {
- return Ok(*rule);
- }
+ pub fn from_str(rename_all_str: &str) -> Result<Self, ParseError> {
+ match rename_all_str {
+ "lowercase" => Ok(Self::LowerCase),
Review Comment:
Why this no more uses `RENAME_RULES` ? Now the rules are duplicated.
```rust
RENAME_RULES
.iter()
.find(|(name, _)| *name == rename_all_str)
.map(|(_, rule)| *rule)
.ok_or_else(|| ParseError {
unknown: rename_all_str.to_string(),
})
```
##########
avro_derive/src/attributes/mod.rs:
##########
@@ -0,0 +1,206 @@
+use darling::FromAttributes;
+use proc_macro2::Span;
+use syn::Attribute;
+
+use crate::{case::RenameRule, darling_to_syn};
+
+pub mod avro;
+pub mod serde;
+
+#[derive(Default)]
+pub struct NamedTypeOptions {
+ pub name: Option<String>,
+ pub namespace: Option<String>,
+ pub doc: Option<String>,
+ pub alias: Vec<String>,
+ pub rename_all: RenameRule,
+}
+
+impl NamedTypeOptions {
+ pub fn new(attributes: &[Attribute], span: Span) -> Result<Self,
Vec<syn::Error>> {
+ let avro =
+
avro::ContainerAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+ let serde =
+
serde::ContainerAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+
+ // Collect errors so user gets all feedback at once
+ let mut errors = Vec::new();
+
+ // Check for any Serde attributes that are hard errors
+ if serde.tag.is_some()
+ || serde.content.is_some()
+ || serde.untagged
+ || serde.variant_identifier
+ || serde.field_identifier
+ {
+ errors.push(syn::Error::new(
+ span,
+ "AvroSchema derive does not support changing the tagging Serde
generates (`tag`, `content`, `untagged`, `variant_identifier`,
`field_identifier`)",
+ ));
+ }
+ if serde.remote.is_some() {
+ errors.push(syn::Error::new(
+ span,
+ "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,
+ "AvroSchema derive does not support different rename rules for
serializing and deserializing (`rename_all(serialize = \"..\", deserialize =
\"..\")`)"
+ ))
+ }
+
+ // Check for conflicts between Serde and Avro
+ if avro.rename_all != RenameRule::None && serde.rename_all.serialize
!= avro.rename_all {
+ errors.push(syn::Error::new(
+ span,
+ "#[avro(rename_all = \"..\")] must match #[serde(rename_all =
\"..\")], it's also deprecated. Please use only `#[serde(rename_all =
\"..\")]`",
+ ))
+ }
+
+ if !errors.is_empty() {
+ return Err(errors);
+ }
+
+ Ok(Self {
+ name: avro.name,
+ namespace: avro.namespace,
+ doc: avro.doc,
+ alias: avro.alias,
+ rename_all: serde.rename_all.serialize,
+ })
+ }
+}
+
+pub struct FieldOptions {
+ pub doc: Option<String>,
+ pub default: Option<String>,
+ pub alias: Vec<String>,
+ pub rename: Option<String>,
+ pub skip: bool,
+ pub flatten: bool,
+}
+
+impl FieldOptions {
+ pub fn new(attributes: &[Attribute], span: Span) -> Result<Self,
Vec<syn::Error>> {
+ let mut avro =
+
avro::FieldAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+ let mut serde =
+
serde::FieldAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+ // Sort the aliases, so our check for equality does not fail if they
are provided in a different order
+ avro.alias.sort();
+ serde.alias.sort();
+
+ // Collect errors so user gets all feedback at once
+ let mut errors = Vec::new();
+
+ // Check for any Serde attributes that are hard errors
+ if serde.getter.is_some() {
+ errors.push(syn::Error::new(
+ span,
+ "AvroSchema derive does not support the Serde `getter`
attribute",
+ ));
+ }
+
+ // Check for conflicts between Serde and Avro
+ if avro.skip && !serde.skip {
+ errors.push(syn::Error::new(
+ span,
+ "`#[avro(skip)]` requires `#[serde(skip)]`, it's also
deprecated. Please use only `#[serde(skip)]`"
+ ));
+ }
+ if avro.flatten && !serde.flatten {
+ errors.push(syn::Error::new(
+ span,
+ "`#[avro(flatten)]` requires `#[serde(flatten)]`, it's also
deprecated. Please use only `#[serde(flatten)]`"
+ ));
+ }
+ // TODO: rename and alias checking can be relaxed with a more complex
check, would require the field name
+ if avro.rename.is_some() && serde.rename != avro.rename {
+ errors.push(syn::Error::new(
+ span,
+ "`#[avro(rename = \"..\")]` must match `#[serde(rename =
\"..\")]`, it's also deprecated. Please use only `#[serde(rename = \"..\")]`"
Review Comment:
```suggestion
"`#[avro(rename = \"..\")]` must match `#[serde(rename =
\"..\")]`, it's also deprecated. Please use only `#[serde(rename = \"..\")]`"
```
##########
avro_derive/src/attributes/serde.rs:
##########
@@ -0,0 +1,321 @@
+//! Attribute parsing for Serde attributes
Review Comment:
```suggestion
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//! Attribute parsing for Serde attributes
```
##########
avro_derive/tests/ui/avro_rs_226_skip_serializing_if.rs:
##########
@@ -0,0 +1,11 @@
+use apache_avro::AvroSchema;
Review Comment:
```suggestion
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use apache_avro::AvroSchema;
```
##########
avro_derive/tests/ui/avro_rs_373_tag struct.rs:
##########
@@ -0,0 +1,10 @@
+use apache_avro::AvroSchema;
Review Comment:
```suggestion
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use apache_avro::AvroSchema;
```
##########
avro_derive/tests/ui/avro_rs_226_skip_serializing.rs:
##########
@@ -0,0 +1,11 @@
+use apache_avro::AvroSchema;
Review Comment:
```suggestion
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use apache_avro::AvroSchema;
```
##########
avro_derive/tests/ui/avro_rs_373_untagged_enum.rs:
##########
@@ -0,0 +1,10 @@
+use apache_avro::AvroSchema;
Review Comment:
```suggestion
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use apache_avro::AvroSchema;
```
##########
avro_derive/tests/serde.rs:
##########
@@ -0,0 +1,659 @@
+use apache_avro::{AvroSchema, Error, Reader, Schema, Writer, from_value};
Review Comment:
```suggestion
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use apache_avro::{AvroSchema, Error, Reader, Schema, Writer, from_value};
```
##########
avro_derive/tests/serde.rs:
##########
@@ -0,0 +1,659 @@
+use apache_avro::{AvroSchema, Error, Reader, Schema, Writer, from_value};
+use serde::{Deserialize, Serialize, de::DeserializeOwned};
+
+/// Takes in a type that implements the right combination of traits and runs
it through a Serde Cycle and asserts the result is the same
+fn serde_assert<T>(obj: T)
+where
+ T: std::fmt::Debug + Serialize + DeserializeOwned + AvroSchema + Clone +
PartialEq,
+{
+ assert_eq!(obj, serde(obj.clone()).unwrap());
+}
+
+/// Takes in a type that implements the right combination of traits and runs
it through a Serde Cycle and asserts that the error matches the expected string
+fn serde_assert_err<T>(obj: T, expected: &str)
+where
+ T: std::fmt::Debug + Serialize + DeserializeOwned + AvroSchema + Clone +
PartialEq,
+{
+ let error = serde(obj).unwrap_err().to_string();
+ assert!(
+ error.contains(expected),
+ "Error `{error}` does not contain `{expected}`"
+ );
+}
+
+fn serde<T>(obj: T) -> Result<T, Error>
+where
+ T: Serialize + DeserializeOwned + AvroSchema,
+{
+ de(ser(obj)?)
+}
+
+fn ser<T>(obj: T) -> Result<Vec<u8>, Error>
+where
+ T: Serialize + AvroSchema,
+{
+ let schema = T::get_schema();
+ let mut writer = Writer::new(&schema, Vec::new())?;
+ writer.append_ser(obj)?;
+ writer.into_inner()
+}
+
+fn de<T>(encoded: Vec<u8>) -> Result<T, Error>
+where
+ T: DeserializeOwned + AvroSchema,
+{
+ assert!(!encoded.is_empty());
+ let schema = T::get_schema();
+ let mut reader = Reader::with_schema(&schema, &encoded[..])?;
+ if let Some(res) = reader.next() {
+ return res.and_then(|v| from_value::<T>(&v));
+ }
+ unreachable!("Nothing was encoded!")
+}
+
+mod container_attributes {
+ use super::*;
+
+ #[test]
+ fn avro_rs_373_rename() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ #[serde(rename = "Bar")]
+ struct Foo {
+ a: String,
+ b: i32,
+ }
+
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"Foo",
+ "fields": [
+ {
+ "name":"a",
+ "type":"string"
+ },
+ {
+ "name":"b",
+ "type":"int"
+ }
+ ]
+ }
+ "#;
+
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, Foo::get_schema());
+
+ serde_assert(Foo {
+ a: "spam".to_string(),
+ b: 321,
+ });
+ }
+
+ #[test]
+ fn avro_rs_373_nested_rename() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ #[serde(rename = "Bar")]
+ struct Foo {
+ a: String,
+ b: i32,
+ }
+
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ struct Outer {
+ bar: Foo,
+ }
+
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"Outer",
+ "fields": [
+ {
+ "name":"bar",
+ "type": {
+ "type":"record",
+ "name":"Foo",
Review Comment:
Bar ?
##########
avro_derive/tests/ui.rs:
##########
@@ -0,0 +1,9 @@
+/// These tests only run on nightly as the output can change per compiler
version.
Review Comment:
```suggestion
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
/// These tests only run on nightly as the output can change per compiler
version.
```
##########
avro_derive/tests/ui/avro_rs_373_tag_enum.rs:
##########
@@ -0,0 +1,10 @@
+use apache_avro::AvroSchema;
Review Comment:
```suggestion
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use apache_avro::AvroSchema;
```
##########
avro_derive/tests/ui/avro_rs_373_tag_content_enum.rs:
##########
@@ -0,0 +1,10 @@
+use apache_avro::AvroSchema;
Review Comment:
```suggestion
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use apache_avro::AvroSchema;
```
##########
avro_derive/tests/ui/avro_rs_373_rename_all_fields.rs:
##########
@@ -0,0 +1,12 @@
+use apache_avro::AvroSchema;
Review Comment:
```suggestion
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use apache_avro::AvroSchema;
```
##########
avro_derive/src/attributes/mod.rs:
##########
@@ -0,0 +1,206 @@
+use darling::FromAttributes;
+use proc_macro2::Span;
+use syn::Attribute;
+
+use crate::{case::RenameRule, darling_to_syn};
+
+pub mod avro;
+pub mod serde;
+
+#[derive(Default)]
+pub struct NamedTypeOptions {
+ pub name: Option<String>,
+ pub namespace: Option<String>,
+ pub doc: Option<String>,
+ pub alias: Vec<String>,
+ pub rename_all: RenameRule,
+}
+
+impl NamedTypeOptions {
+ pub fn new(attributes: &[Attribute], span: Span) -> Result<Self,
Vec<syn::Error>> {
+ let avro =
+
avro::ContainerAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+ let serde =
+
serde::ContainerAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+
+ // Collect errors so user gets all feedback at once
+ let mut errors = Vec::new();
+
+ // Check for any Serde attributes that are hard errors
+ if serde.tag.is_some()
+ || serde.content.is_some()
+ || serde.untagged
+ || serde.variant_identifier
+ || serde.field_identifier
+ {
+ errors.push(syn::Error::new(
+ span,
+ "AvroSchema derive does not support changing the tagging Serde
generates (`tag`, `content`, `untagged`, `variant_identifier`,
`field_identifier`)",
+ ));
+ }
+ if serde.remote.is_some() {
+ errors.push(syn::Error::new(
+ span,
+ "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,
+ "AvroSchema derive does not support different rename rules for
serializing and deserializing (`rename_all(serialize = \"..\", deserialize =
\"..\")`)"
+ ))
+ }
+
+ // Check for conflicts between Serde and Avro
+ if avro.rename_all != RenameRule::None && serde.rename_all.serialize
!= avro.rename_all {
+ errors.push(syn::Error::new(
+ span,
+ "#[avro(rename_all = \"..\")] must match #[serde(rename_all =
\"..\")], it's also deprecated. Please use only `#[serde(rename_all =
\"..\")]`",
+ ))
+ }
+
+ if !errors.is_empty() {
+ return Err(errors);
+ }
+
+ Ok(Self {
+ name: avro.name,
+ namespace: avro.namespace,
+ doc: avro.doc,
+ alias: avro.alias,
+ rename_all: serde.rename_all.serialize,
+ })
+ }
+}
+
+pub struct FieldOptions {
+ pub doc: Option<String>,
+ pub default: Option<String>,
+ pub alias: Vec<String>,
+ pub rename: Option<String>,
+ pub skip: bool,
+ pub flatten: bool,
+}
+
+impl FieldOptions {
+ pub fn new(attributes: &[Attribute], span: Span) -> Result<Self,
Vec<syn::Error>> {
+ let mut avro =
+
avro::FieldAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+ let mut serde =
+
serde::FieldAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+ // Sort the aliases, so our check for equality does not fail if they
are provided in a different order
+ avro.alias.sort();
+ serde.alias.sort();
+
+ // Collect errors so user gets all feedback at once
+ let mut errors = Vec::new();
+
+ // Check for any Serde attributes that are hard errors
+ if serde.getter.is_some() {
+ errors.push(syn::Error::new(
+ span,
+ "AvroSchema derive does not support the Serde `getter`
attribute",
+ ));
+ }
+
+ // Check for conflicts between Serde and Avro
+ if avro.skip && !serde.skip {
+ errors.push(syn::Error::new(
+ span,
+ "`#[avro(skip)]` requires `#[serde(skip)]`, it's also
deprecated. Please use only `#[serde(skip)]`"
+ ));
+ }
+ if avro.flatten && !serde.flatten {
+ errors.push(syn::Error::new(
+ span,
+ "`#[avro(flatten)]` requires `#[serde(flatten)]`, it's also
deprecated. Please use only `#[serde(flatten)]`"
+ ));
+ }
+ // TODO: rename and alias checking can be relaxed with a more complex
check, would require the field name
+ if avro.rename.is_some() && serde.rename != avro.rename {
+ errors.push(syn::Error::new(
+ span,
+ "`#[avro(rename = \"..\")]` must match `#[serde(rename =
\"..\")]`, it's also deprecated. Please use only `#[serde(rename = \"..\")]`"
+ ));
+ }
+ if !avro.alias.is_empty() && serde.alias != avro.alias {
+ errors.push(syn::Error::new(
+ span,
+ "`#[avro(alias = \"..\")]` must match `#[serde(alias =
\"..\")]`, it's also deprecated. Please use only `#[serde(alias = \"..\")]`"
+ ));
+ }
+ if serde.skip_serializing && serde.skip_deserializing {
Review Comment:
Is this really an error that we (Avro) should break the build ?
It is up to the user whether to optimize the serde attributes or not.
##########
avro_derive/tests/serde.rs:
##########
@@ -0,0 +1,659 @@
+use apache_avro::{AvroSchema, Error, Reader, Schema, Writer, from_value};
+use serde::{Deserialize, Serialize, de::DeserializeOwned};
+
+/// Takes in a type that implements the right combination of traits and runs
it through a Serde Cycle and asserts the result is the same
+fn serde_assert<T>(obj: T)
+where
+ T: std::fmt::Debug + Serialize + DeserializeOwned + AvroSchema + Clone +
PartialEq,
+{
+ assert_eq!(obj, serde(obj.clone()).unwrap());
+}
+
+/// Takes in a type that implements the right combination of traits and runs
it through a Serde Cycle and asserts that the error matches the expected string
Review Comment:
```suggestion
/// Takes in a type that implements the right combination of traits and runs
it through a Serde round-trip and asserts that the error matches the expected
string
```
##########
avro_derive/tests/serde.rs:
##########
@@ -0,0 +1,659 @@
+use apache_avro::{AvroSchema, Error, Reader, Schema, Writer, from_value};
+use serde::{Deserialize, Serialize, de::DeserializeOwned};
+
+/// Takes in a type that implements the right combination of traits and runs
it through a Serde Cycle and asserts the result is the same
Review Comment:
```suggestion
/// Takes in a type that implements the right combination of traits and runs
it through a Serde round-trip and asserts the result is the same
```
##########
avro_derive/src/attributes/mod.rs:
##########
@@ -0,0 +1,202 @@
+use darling::FromAttributes;
+use proc_macro2::Span;
+use syn::Attribute;
+
+use crate::{case::RenameRule, darling_to_syn};
+
+pub mod avro;
+pub mod serde;
+
+#[derive(Default)]
+pub struct NamedTypeOptions {
+ pub name: Option<String>,
+ pub namespace: Option<String>,
+ pub doc: Option<String>,
+ pub alias: Vec<String>,
+ pub rename_all: RenameRule,
+}
+
+impl NamedTypeOptions {
+ pub fn new(attributes: &[Attribute], span: Span) -> Result<Self,
Vec<syn::Error>> {
+ let avro =
+
avro::ContainerAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+ let serde =
+
serde::ContainerAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+
+ // Collect errors so user gets all feedback at once
+ let mut errors = Vec::new();
+
+ // Check for any Serde attributes that are hard errors
+ if serde.tag.is_some()
+ || serde.content.is_some()
+ || serde.untagged
+ || serde.variant_identifier
+ || serde.field_identifier
+ {
+ errors.push(syn::Error::new(
+ span,
+ "AvroSchema derive does not support changing the tagging Serde
generates (`tag`, `content`, `untagged`, `variant_identifier`,
`field_identifier`)",
+ ));
+ }
+ if serde.remote.is_some() {
+ errors.push(syn::Error::new(
+ span,
+ "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",
+ ))
+ }
+ if serde.rename_all.deserialize != serde.rename_all.serialize {
+ errors.push(syn::Error::new(
+ span,
+ "AvroSchema derive does not support different rename rules for
serializing and deserializing (`rename_all(serialize = \"..\", deserialize =
\"..\")`)"
+ ))
+ }
+
+ // Check for conflicts between Serde and Avro
+ if avro.rename_all != RenameRule::None && serde.rename_all.serialize
!= avro.rename_all {
+ errors.push(syn::Error::new(
+ span,
+ "#[avro(rename_all = \"..\")] must match #[serde(rename_all =
\"..\")], it's also deprecated. Please use only `#[serde(rename_all =
\"..\")]`",
+ ))
+ }
+
+ if !errors.is_empty() {
+ return Err(errors);
+ }
+
+ Ok(Self {
+ name: avro.name,
+ namespace: avro.namespace,
+ doc: avro.doc,
+ alias: avro.alias,
+ rename_all: serde.rename_all.serialize,
+ })
+ }
+}
+
+pub struct FieldOptions {
+ pub doc: Option<String>,
+ pub default: Option<String>,
+ pub alias: Vec<String>,
+ pub rename: Option<String>,
+ pub skip: bool,
+ pub flatten: bool,
+}
+
+impl FieldOptions {
+ pub fn new(attributes: &[Attribute], span: Span) -> Result<Self,
Vec<syn::Error>> {
+ let avro =
avro::FieldAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+ let serde =
serde::FieldAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+
+ // Collect errors so user gets all feedback at once
+ let mut errors = Vec::new();
+
+ // Check for any Serde attributes that are hard errors
+ if serde.getter.is_some() {
+ errors.push(syn::Error::new(
+ span,
+ "AvroSchema derive does not support the Serde `getter`
attribute",
+ ));
+ }
+
+ // Check for conflicts between Serde and Avro
+ if avro.skip && !serde.skip {
+ errors.push(syn::Error::new(
+ span,
+ "`#[avro(skip)]` requires `#[serde(skip)]`, it's also
deprecated. Please use only `#[serde(skip)]`"
Review Comment:
Sounds good!
Let's do it!
Then once it is in `stable` and our MSRV is at least this stable version we
could delete just this attribute to enable it for all.
##########
avro_derive/src/attributes/serde.rs:
##########
@@ -0,0 +1,321 @@
+//! Attribute parsing for Serde attributes
+//!
+//! # Serde attributes
+//! This module only parses the minimal amount to be able to read Serde
attributes. This means
+//! most attributes are decoded as an `Option<String>` as the actual content
is not relevant.
+//! Attributes which are not needed by the derive are prefixed with a `_` as
we can't ignore them
+//! (this would be a compile error).
+//!
+//! If Serde adds new attributes they need to be added here too.
+
+use darling::{FromAttributes, FromMeta};
+use syn::Expr;
+
+use crate::case::RenameRule;
+
+/// Represents `rename_all` and `rename_all_fields`.
+///
+/// These attributes can take the following forms:
+/// 1. `rename_all = ".."`
+/// 2. `rename_all(serialize = "..", deserialize = "..")`
+///
+/// To parse the first form, we use Darling's `from_expr`.
+/// To parse the second form, we use Darling's `FromMeta` derive.
+#[derive(Debug, Default, FromMeta, PartialEq, Eq)]
+#[darling(from_expr = RenameAll::from_expr)]
+pub struct RenameAll {
+ #[darling(default)]
+ pub serialize: RenameRule,
+ #[darling(default)]
+ pub deserialize: RenameRule,
+}
+
+impl From<RenameRule> for RenameAll {
+ fn from(value: RenameRule) -> Self {
+ Self {
+ serialize: value,
+ deserialize: value,
+ }
+ }
+}
+
+impl RenameAll {
+ fn from_expr(expr: &Expr) -> darling::Result<Self> {
+ let Expr::Lit(lit) = expr else {
+ return Err(darling::Error::custom("Expected a string or a
tuple!"));
+ };
+ let rule = RenameRule::from_value(&lit.lit)?;
+ Ok(RenameAll::from(rule))
+ }
+}
+
+/// Represents `default`.
+///
+/// This attribute can take the following forms:
+/// 1. `default`
+/// 2. `default = ".."`
+///
+/// To parse the first form, we use Darling's `FromMeta` derive with `word,
skip`.
+/// To parse the second form, we use Darling's `from_expr`.
+#[derive(Debug, FromMeta, PartialEq)]
+#[darling(from_expr = |expr| Ok(SerdeDefault::Expr(expr.clone())))]
+pub enum SerdeDefault {
+ #[darling(word, skip)]
+ UseTrait,
+ Expr(Expr),
+}
+
+/// All Serde attributes that a container can have.
+#[derive(Debug, FromAttributes)]
+#[darling(attributes(serde))]
+pub struct ContainerAttributes {
+ #[darling(rename = "rename")]
+ /// Rename this container.
+ pub _rename: Option<String>,
Review Comment:
I just noticed in tests/serde.rs that this attribute is ignored.
Shall we try to support it too ?
1) if `#[avro(name=...)]` is provided then use it
2) if `#[serde(rename=...)]` is provided then use it
3) if both are provided then error
4) if none are provided then use the struct name
WDYT ?
##########
avro_derive/tests/serde.rs:
##########
@@ -0,0 +1,659 @@
+use apache_avro::{AvroSchema, Error, Reader, Schema, Writer, from_value};
+use serde::{Deserialize, Serialize, de::DeserializeOwned};
+
+/// Takes in a type that implements the right combination of traits and runs
it through a Serde Cycle and asserts the result is the same
+fn serde_assert<T>(obj: T)
+where
+ T: std::fmt::Debug + Serialize + DeserializeOwned + AvroSchema + Clone +
PartialEq,
+{
+ assert_eq!(obj, serde(obj.clone()).unwrap());
+}
+
+/// Takes in a type that implements the right combination of traits and runs
it through a Serde Cycle and asserts that the error matches the expected string
+fn serde_assert_err<T>(obj: T, expected: &str)
+where
+ T: std::fmt::Debug + Serialize + DeserializeOwned + AvroSchema + Clone +
PartialEq,
+{
+ let error = serde(obj).unwrap_err().to_string();
+ assert!(
+ error.contains(expected),
+ "Error `{error}` does not contain `{expected}`"
+ );
+}
+
+fn serde<T>(obj: T) -> Result<T, Error>
+where
+ T: Serialize + DeserializeOwned + AvroSchema,
+{
+ de(ser(obj)?)
+}
+
+fn ser<T>(obj: T) -> Result<Vec<u8>, Error>
+where
+ T: Serialize + AvroSchema,
+{
+ let schema = T::get_schema();
+ let mut writer = Writer::new(&schema, Vec::new())?;
+ writer.append_ser(obj)?;
+ writer.into_inner()
+}
+
+fn de<T>(encoded: Vec<u8>) -> Result<T, Error>
+where
+ T: DeserializeOwned + AvroSchema,
+{
+ assert!(!encoded.is_empty());
+ let schema = T::get_schema();
+ let mut reader = Reader::with_schema(&schema, &encoded[..])?;
+ if let Some(res) = reader.next() {
+ return res.and_then(|v| from_value::<T>(&v));
+ }
+ unreachable!("Nothing was encoded!")
+}
+
+mod container_attributes {
+ use super::*;
+
+ #[test]
+ fn avro_rs_373_rename() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ #[serde(rename = "Bar")]
+ struct Foo {
+ a: String,
+ b: i32,
+ }
+
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"Foo",
+ "fields": [
+ {
+ "name":"a",
+ "type":"string"
+ },
+ {
+ "name":"b",
+ "type":"int"
+ }
+ ]
+ }
+ "#;
+
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, Foo::get_schema());
+
+ serde_assert(Foo {
+ a: "spam".to_string(),
+ b: 321,
+ });
+ }
+
+ #[test]
+ fn avro_rs_373_nested_rename() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ #[serde(rename = "Bar")]
+ struct Foo {
+ a: String,
+ b: i32,
+ }
+
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ struct Outer {
+ bar: Foo,
+ }
+
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"Outer",
+ "fields": [
+ {
+ "name":"bar",
+ "type": {
+ "type":"record",
+ "name":"Foo",
Review Comment:
https://github.com/apache/avro-rs/pull/377/changes#r2654685022
##########
avro_derive/tests/serde.rs:
##########
@@ -0,0 +1,659 @@
+use apache_avro::{AvroSchema, Error, Reader, Schema, Writer, from_value};
+use serde::{Deserialize, Serialize, de::DeserializeOwned};
+
+/// Takes in a type that implements the right combination of traits and runs
it through a Serde Cycle and asserts the result is the same
+fn serde_assert<T>(obj: T)
+where
+ T: std::fmt::Debug + Serialize + DeserializeOwned + AvroSchema + Clone +
PartialEq,
+{
+ assert_eq!(obj, serde(obj.clone()).unwrap());
+}
+
+/// Takes in a type that implements the right combination of traits and runs
it through a Serde Cycle and asserts that the error matches the expected string
+fn serde_assert_err<T>(obj: T, expected: &str)
+where
+ T: std::fmt::Debug + Serialize + DeserializeOwned + AvroSchema + Clone +
PartialEq,
+{
+ let error = serde(obj).unwrap_err().to_string();
+ assert!(
+ error.contains(expected),
+ "Error `{error}` does not contain `{expected}`"
+ );
+}
+
+fn serde<T>(obj: T) -> Result<T, Error>
+where
+ T: Serialize + DeserializeOwned + AvroSchema,
+{
+ de(ser(obj)?)
+}
+
+fn ser<T>(obj: T) -> Result<Vec<u8>, Error>
+where
+ T: Serialize + AvroSchema,
+{
+ let schema = T::get_schema();
+ let mut writer = Writer::new(&schema, Vec::new())?;
+ writer.append_ser(obj)?;
+ writer.into_inner()
+}
+
+fn de<T>(encoded: Vec<u8>) -> Result<T, Error>
+where
+ T: DeserializeOwned + AvroSchema,
+{
+ assert!(!encoded.is_empty());
+ let schema = T::get_schema();
+ let mut reader = Reader::with_schema(&schema, &encoded[..])?;
+ if let Some(res) = reader.next() {
+ return res.and_then(|v| from_value::<T>(&v));
+ }
+ unreachable!("Nothing was encoded!")
Review Comment:
```suggestion
panic!("Nothing was encoded!")
```
##########
avro_derive/tests/serde.rs:
##########
@@ -0,0 +1,659 @@
+use apache_avro::{AvroSchema, Error, Reader, Schema, Writer, from_value};
+use serde::{Deserialize, Serialize, de::DeserializeOwned};
+
+/// Takes in a type that implements the right combination of traits and runs
it through a Serde Cycle and asserts the result is the same
+fn serde_assert<T>(obj: T)
+where
+ T: std::fmt::Debug + Serialize + DeserializeOwned + AvroSchema + Clone +
PartialEq,
+{
+ assert_eq!(obj, serde(obj.clone()).unwrap());
+}
+
+/// Takes in a type that implements the right combination of traits and runs
it through a Serde Cycle and asserts that the error matches the expected string
+fn serde_assert_err<T>(obj: T, expected: &str)
+where
+ T: std::fmt::Debug + Serialize + DeserializeOwned + AvroSchema + Clone +
PartialEq,
+{
+ let error = serde(obj).unwrap_err().to_string();
+ assert!(
+ error.contains(expected),
+ "Error `{error}` does not contain `{expected}`"
+ );
+}
+
+fn serde<T>(obj: T) -> Result<T, Error>
+where
+ T: Serialize + DeserializeOwned + AvroSchema,
+{
+ de(ser(obj)?)
+}
+
+fn ser<T>(obj: T) -> Result<Vec<u8>, Error>
+where
+ T: Serialize + AvroSchema,
+{
+ let schema = T::get_schema();
+ let mut writer = Writer::new(&schema, Vec::new())?;
+ writer.append_ser(obj)?;
+ writer.into_inner()
+}
+
+fn de<T>(encoded: Vec<u8>) -> Result<T, Error>
+where
+ T: DeserializeOwned + AvroSchema,
+{
+ assert!(!encoded.is_empty());
+ let schema = T::get_schema();
+ let mut reader = Reader::with_schema(&schema, &encoded[..])?;
+ if let Some(res) = reader.next() {
+ return res.and_then(|v| from_value::<T>(&v));
+ }
+ unreachable!("Nothing was encoded!")
+}
+
+mod container_attributes {
+ use super::*;
+
+ #[test]
+ fn avro_rs_373_rename() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ #[serde(rename = "Bar")]
+ struct Foo {
+ a: String,
+ b: i32,
+ }
+
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"Foo",
Review Comment:
Shouldn't this be `"Bar"` ?!
--
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]