You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@avro.apache.org by kr...@apache.org on 2025/12/23 12:38:08 UTC
(avro-rs) 01/01: feat: Use the Serde attributes and check for conflicting attributes
This is an automated email from the ASF dual-hosted git repository.
kriskras99 pushed a commit to branch feat/use_serde_attributes
in repository https://gitbox.apache.org/repos/asf/avro-rs.git
commit fad24999eec034da45372285175cda715ac31e98
Author: default <ad...@kriskras99.nl>
AuthorDate: Thu Dec 18 14:25:07 2025 +0000
feat: Use the Serde attributes and check for conflicting attributes
---
Cargo.lock | 107 +
avro/tests/avro-rs-226.rs | 66 -
avro_derive/Cargo.toml | 1 +
avro_derive/src/attributes/avro.rs | 40 +
avro_derive/src/attributes/mod.rs | 202 ++
avro_derive/src/attributes/serde.rs | 313 ++
avro_derive/src/case.rs | 44 +-
avro_derive/src/lib.rs | 78 +-
avro_derive/tests/derive.rs | 3205 ++++++++++----------
avro_derive/tests/serde.rs | 698 +++++
avro_derive/tests/ui.rs | 5 +
.../tests/ui/avro_rs_226_skip_serializing.rs | 11 +
.../tests/ui/avro_rs_226_skip_serializing.stderr | 5 +
.../tests/ui/avro_rs_226_skip_serializing_if.rs | 11 +
.../ui/avro_rs_226_skip_serializing_if.stderr | 5 +
avro_derive/tests/ui/avro_rs_xxx_remote.rs | 16 +
avro_derive/tests/ui/avro_rs_xxx_remote.stderr | 5 +
.../tests/ui/avro_rs_xxx_rename_all_fields.rs | 12 +
.../tests/ui/avro_rs_xxx_rename_all_fields.stderr | 5 +
.../tests/ui/avro_rs_xxx_skip_de_serializing.rs | 11 +
.../ui/avro_rs_xxx_skip_de_serializing.stderr | 5 +
avro_derive/tests/ui/avro_rs_xxx_tag struct.rs | 10 +
avro_derive/tests/ui/avro_rs_xxx_tag struct.stderr | 5 +
.../tests/ui/avro_rs_xxx_tag_content_enum.rs | 10 +
.../tests/ui/avro_rs_xxx_tag_content_enum.stderr | 5 +
avro_derive/tests/ui/avro_rs_xxx_tag_enum.rs | 10 +
avro_derive/tests/ui/avro_rs_xxx_tag_enum.stderr | 5 +
avro_derive/tests/ui/avro_rs_xxx_transparent.rs | 15 +
.../tests/ui/avro_rs_xxx_transparent.stderr | 5 +
avro_derive/tests/ui/avro_rs_xxx_untagged_enum.rs | 10 +
.../tests/ui/avro_rs_xxx_untagged_enum.stderr | 5 +
31 files changed, 3176 insertions(+), 1749 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 588cf6b..3cb7a47 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -103,6 +103,7 @@ dependencies = [
"serde",
"serde_json",
"syn",
+ "trybuild",
]
[[package]]
@@ -505,6 +506,12 @@ dependencies = [
"log",
]
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
[[package]]
name = "find-msvc-tools"
version = "0.1.4"
@@ -562,6 +569,12 @@ dependencies = [
"zerocopy",
]
+[[package]]
+name = "hashbrown"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
+
[[package]]
name = "heck"
version = "0.5.0"
@@ -591,6 +604,16 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+[[package]]
+name = "indexmap"
+version = "2.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
[[package]]
name = "itertools"
version = "0.13.0"
@@ -1061,6 +1084,15 @@ dependencies = [
"serde_core",
]
+[[package]]
+name = "serde_spanned"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
+dependencies = [
+ "serde_core",
+]
+
[[package]]
name = "sha2"
version = "0.10.9"
@@ -1119,6 +1151,21 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "target-triple"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "591ef38edfb78ca4771ee32cf494cb8771944bee237a9b91fc9c1424ac4b777b"
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
[[package]]
name = "thiserror"
version = "2.0.17"
@@ -1149,6 +1196,60 @@ dependencies = [
"serde_json",
]
+[[package]]
+name = "toml"
+version = "0.9.10+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48"
+dependencies = [
+ "indexmap",
+ "serde_core",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_parser",
+ "toml_writer",
+ "winnow",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.7.5+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_parser"
+version = "1.0.6+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
+dependencies = [
+ "winnow",
+]
+
+[[package]]
+name = "toml_writer"
+version = "1.0.6+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
+
+[[package]]
+name = "trybuild"
+version = "1.0.114"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e17e807bff86d2a06b52bca4276746584a78375055b6e45843925ce2802b335"
+dependencies = [
+ "glob",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "target-triple",
+ "termcolor",
+ "toml",
+]
+
[[package]]
name = "typenum"
version = "1.19.0"
@@ -1422,6 +1523,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+[[package]]
+name = "winnow"
+version = "0.7.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
+
[[package]]
name = "wit-bindgen"
version = "0.46.0"
diff --git a/avro/tests/avro-rs-226.rs b/avro/tests/avro-rs-226.rs
index 15f060b..060fc9d 100644
--- a/avro/tests/avro-rs-226.rs
+++ b/avro/tests/avro-rs-226.rs
@@ -133,69 +133,3 @@ fn avro_rs_226_index_out_of_bounds_with_serde_skip_multiple_fields() -> TestResu
},
)
}
-
-#[test]
-#[should_panic(expected = "Missing default for skipped field 'y' for schema")]
-fn avro_rs_351_no_default_for_serde_skip_serializing_if_should_panic() {
- #[derive(AvroSchema, Clone, Debug, Deserialize, PartialEq, Serialize)]
- struct T {
- x: Option<i8>,
- #[serde(skip_serializing_if = "Option::is_none")]
- y: Option<String>,
- z: Option<i8>,
- }
-
- ser_deser::<T>(
- &T::get_schema(),
- T {
- x: None,
- y: None,
- z: Some(1),
- },
- )
- .unwrap()
-}
-
-#[test]
-#[should_panic(expected = "Missing default for skipped field 'x' for schema")]
-fn avro_rs_351_no_default_for_serde_skip_should_panic() {
- #[derive(AvroSchema, Clone, Debug, Deserialize, PartialEq, Serialize)]
- struct T {
- #[serde(skip)]
- x: Option<i8>,
- y: Option<String>,
- z: Option<i8>,
- }
-
- ser_deser::<T>(
- &T::get_schema(),
- T {
- x: None,
- y: None,
- z: Some(1),
- },
- )
- .unwrap()
-}
-
-#[test]
-#[should_panic(expected = "Missing default for skipped field 'z' for schema")]
-fn avro_rs_351_no_default_for_serde_skip_serializing_should_panic() {
- #[derive(AvroSchema, Clone, Debug, Deserialize, PartialEq, Serialize)]
- struct T {
- x: Option<i8>,
- y: Option<String>,
- #[serde(skip_serializing)]
- z: Option<i8>,
- }
-
- ser_deser::<T>(
- &T::get_schema(),
- T {
- x: Some(0),
- y: None,
- z: None,
- },
- )
- .unwrap()
-}
diff --git a/avro_derive/Cargo.toml b/avro_derive/Cargo.toml
index 9b13bd3..793f419 100644
--- a/avro_derive/Cargo.toml
+++ b/avro_derive/Cargo.toml
@@ -43,6 +43,7 @@ apache-avro = { default-features = false, path = "../avro", features = ["derive"
proptest = { default-features = false, version = "1.9.0", features = ["std"] }
serde = { workspace = true }
pretty_assertions = { workspace = true }
+trybuild = "1.0.114"
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
diff --git a/avro_derive/src/attributes/avro.rs b/avro_derive/src/attributes/avro.rs
new file mode 100644
index 0000000..b478ec5
--- /dev/null
+++ b/avro_derive/src/attributes/avro.rs
@@ -0,0 +1,40 @@
+use crate::case::RenameRule;
+
+#[derive(darling::FromAttributes)]
+#[darling(attributes(avro))]
+pub struct FieldAttributes {
+ #[darling(default)]
+ pub doc: Option<String>,
+ #[darling(default)]
+ pub default: Option<String>,
+ #[darling(multiple)]
+ pub alias: Vec<String>,
+ #[darling(default)]
+ pub rename: Option<String>,
+ #[darling(default)]
+ pub skip: bool,
+ #[darling(default)]
+ pub flatten: bool,
+}
+
+#[derive(darling::FromAttributes)]
+#[darling(attributes(avro))]
+pub struct VariantAttributes {
+ #[darling(default)]
+ pub rename: Option<String>,
+}
+
+#[derive(darling::FromAttributes)]
+#[darling(attributes(avro))]
+pub struct ContainerAttributes {
+ #[darling(default)]
+ pub name: Option<String>,
+ #[darling(default)]
+ pub namespace: Option<String>,
+ #[darling(default)]
+ pub doc: Option<String>,
+ #[darling(multiple)]
+ pub alias: Vec<String>,
+ #[darling(default)]
+ pub rename_all: RenameRule,
+}
diff --git a/avro_derive/src/attributes/mod.rs b/avro_derive/src/attributes/mod.rs
new file mode 100644
index 0000000..2865d3e
--- /dev/null
+++ b/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 = \"..\")]",
+ ))
+ }
+
+ 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)]`"
+ ));
+ }
+ 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 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.is_some() {
+ 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 = \"..\")]`"
+ ));
+ }
+
+ if !errors.is_empty() {
+ return Err(errors);
+ }
+
+ Ok(Self {
+ rename: serde.rename,
+ })
+ }
+}
diff --git a/avro_derive/src/attributes/serde.rs b/avro_derive/src/attributes/serde.rs
new file mode 100644
index 0000000..0624ee6
--- /dev/null
+++ b/avro_derive/src/attributes/serde.rs
@@ -0,0 +1,313 @@
+//! 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;
+
+// from_expr is used for `rename_all = ".."`
+// FromMeta is used for `rename_all(serialize = "..", ..)`
+#[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))
+ }
+}
+
+#[derive(Debug, FromMeta, PartialEq)]
+#[darling(from_expr = |expr| Ok(SerdeDefault::Expr(expr.clone())))]
+pub enum SerdeDefault {
+ #[darling(word, skip)]
+ UseTrait,
+ Expr(Expr),
+}
+
+/// All relevant Serde attributes that a container can have.
+///
+/// This skips the following attributes:
+/// - `rename`: renames the container name, but it's not used in the (de)serialisation logic.
+/// - `rename_all_fields`: renames the fields of struct variants in an enum, but struct variants are not (yet) supported.
+/// - `deny_unknown_fields`: makes Serialize/Deserialize derives error on unkown fields, not relevant.
+/// - `bound`: type bound for Serialize/Deserialize derives, not relevent.
+/// - `from`/`try_from`/`into`: override the (de)serialize implementations. Can be problematic but impossible to know when.
+/// - `crate`: only relevant for macros messing with Serialize/Deserialize.
+/// - `expecting`: changes the error message, not relevant.
+#[derive(Debug, FromAttributes)]
+#[darling(attributes(serde))]
+pub struct ContainerAttributes {
+ #[darling(rename = "rename")]
+ /// Rename this container.
+ pub _rename: Option<String>,
+ /// Rename all the fields (if this is a struct) or variants (if this is an enum) according to the given case convention.
+ #[darling(default)]
+ pub rename_all: RenameAll,
+ /// Rename all the fields of the struct variants in this enum.
+ #[darling(default, rename = "rename_all_fields")]
+ pub _rename_all_fields: RenameAll,
+ /// Error when encountering unknown fields when deserialising.
+ #[darling(default, rename = "deny_unknown_fields")]
+ pub _deny_unknown_fields: bool,
+ /// Add/expect a tag during serialisation.
+ ///
+ /// When used on a struct, this adds an extra field which is not in the schema definition.
+ /// When used on an enum, serde transforms it into a struct which does not match the schema definition.
+ pub tag: Option<String>,
+ /// Put the content in a field with this name.
+ pub content: Option<String>,
+ /// This makes the enum transparent, (de)serializing based on the variant directly.
+ ///
+ /// This does not match the schema definition.
+ #[darling(default)]
+ pub untagged: bool,
+ /// Add a bound to the Serialize/Deserialize trait.
+ #[darling(default, rename = "bound")]
+ pub _bound: Option<String>,
+ /// When deserializing, any missing fields should be filled in from the struct's implementation of `Default`.
+ #[darling(rename = "default")]
+ pub _default: Option<SerdeDefault>,
+ /// This type is the serde implementation for a "remote" type.
+ ///
+ /// This makes the (de)serialisation use/return a different type.
+ pub remote: Option<String>,
+ /// Directly use the inner type for (de)serialisation.
+ #[darling(default)]
+ pub transparent: bool,
+ /// Deserialize using the given type and then convert to this type with `From`.
+ #[darling(default, rename = "from")]
+ pub _from: Option<String>,
+ /// Deserialize using the given type and then convert to this type with `TryFrom`.
+ #[darling(default, rename = "try_from")]
+ pub _try_from: Option<String>,
+ /// Convert this type to the given type using `Into` and then serialize using the given type.
+ #[darling(default, rename = "into")]
+ pub _into: Option<String>,
+ /// Use the Serde API at this path.
+ #[darling(default, rename = "crate")]
+ pub _crate: Option<String>,
+ /// Custom error text.
+ #[darling(default, rename = "expecting")]
+ pub _expecting: Option<String>,
+ /// This does something with tags, which are incompatible.
+ #[darling(default)]
+ pub variant_identifier: bool,
+ /// This does something with tags, `` which are incompatible.
+ #[darling(default)]
+ pub field_identifier: bool,
+}
+
+/// All relevant Serde attributes that a enum variant can have.
+///
+/// This skips the following attributes:
+/// - `rename_all`: renames the the struct variant fields, but struct variants are not (yet) supported.
+/// - `serialize_with`/`deserialize_with`/`with`: override the (de)serialize implementations. Can be problematic but impossible to know when.
+/// - `borrow`: lifetime stuff, not relevant.
+/// - `bound`: type bound for Serialize/Deserialize derives, not relevent.
+#[derive(Debug, FromAttributes)]
+#[darling(attributes(serde))]
+pub struct VariantAttributes {
+ /// Rename the variant.
+ #[darling(default)]
+ pub rename: Option<String>,
+ /// Aliases for this variant, only used during deserialisation.
+ #[darling(multiple, rename = "alias")]
+ pub _alias: Vec<String>,
+ #[darling(default, rename = "rename_all")]
+ pub _rename_all: RenameAll,
+ /// Do not serialize or deserialize this variant.
+ #[darling(default, rename = "skip")]
+ pub _skip: bool,
+ /// Do not serialize this variant.
+ #[darling(default, rename = "skip_serializing")]
+ pub _skip_serializing: bool,
+ /// Do not deserialize this variant.
+ #[darling(default, rename = "skip_deserializing")]
+ pub _skip_deserializing: bool,
+ /// Use this function for serializing.
+ #[darling(rename = "serialize_with")]
+ pub _serialize_with: Option<String>,
+ /// Use this function for deserializing.
+ #[darling(rename = "deserialize_with")]
+ pub _deserialize_with: Option<String>,
+ /// Use this module for (de)serializing.
+ #[darling(rename = "with")]
+ pub _with: Option<String>,
+ /// Put trait bounds on the implementations.
+ #[darling(rename = "bound")]
+ pub _bound: Option<String>,
+ /// Put bounds on the lifetimes.
+ #[darling(rename = "borrow")]
+ pub _borrow: Option<SerdeBorrow>,
+ /// This does something with tags, which are incompatible.
+ #[darling(default)]
+ pub other: bool,
+ /// (De)serialize this variant as if it was not part of the enum.
+ pub untagged: Option<String>,
+}
+
+#[derive(Debug, FromMeta, PartialEq)]
+#[darling(from_expr = |expr| Ok(SerdeBorrow::Expr(expr.clone())))]
+pub enum SerdeBorrow {
+ #[darling(word, skip)]
+ Default,
+ Expr(Expr),
+}
+
+/// All relevant Serde attributes that a field can have.
+#[derive(Debug, FromAttributes)]
+#[darling(attributes(serde))]
+pub struct FieldAttributes {
+ /// Rename the field.
+ #[darling(default)]
+ pub rename: Option<String>,
+ /// Aliases for this field, only used during deserialisation.
+ #[darling(multiple)]
+ pub alias: Vec<String>,
+ /// When deserializing, if this field is missing use `Default` or the given function.
+ #[darling(rename = "default")]
+ pub _default: Option<SerdeDefault>,
+ #[darling(default)]
+ pub flatten: bool,
+ /// Do not serialize or deserialize this field.
+ #[darling(default)]
+ pub skip: bool,
+ /// Do not serialize this field.
+ #[darling(default)]
+ pub skip_serializing: bool,
+ /// Do not deserialize this field.
+ #[darling(default)]
+ pub skip_deserializing: bool,
+ /// Do not serialize this field if the function returns `false`.
+ pub skip_serializing_if: Option<String>,
+ /// Use this function for serializing.
+ #[darling(rename = "serialize_with")]
+ pub _serialize_with: Option<String>,
+ /// Use this function for deserializing.
+ #[darling(rename = "deserialize_with")]
+ pub _deserialize_with: Option<String>,
+ /// Use this module for (de)serializing.
+ #[darling(rename = "with")]
+ pub _with: Option<String>,
+ /// Put bounds on the lifetimes.
+ #[darling(rename = "borrow")]
+ pub _borrow: Option<SerdeBorrow>,
+ /// Used for remote types.
+ pub getter: Option<String>,
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{
+ RenameRule,
+ attributes::serde::{ContainerAttributes, RenameAll, SerdeDefault},
+ };
+ use darling::FromAttributes;
+ use syn::DeriveInput;
+
+ #[test]
+ fn test_rename_all() {
+ let derive: DeriveInput = syn::parse_quote! {
+ #[serde(rename_all = "lowercase")]
+ struct Config {
+ field: String,
+ }
+ };
+ let input = ContainerAttributes::from_attributes(&derive.attrs).unwrap();
+ assert_eq!(input.rename_all, RenameAll::from(RenameRule::LowerCase));
+
+ let derive: DeriveInput = syn::parse_quote! {
+ #[serde(rename_all(serialize = "lowercase"))]
+ struct Config {
+ field: String,
+ }
+ };
+ let input = ContainerAttributes::from_attributes(&derive.attrs).unwrap();
+ assert_eq!(
+ input.rename_all,
+ RenameAll {
+ serialize: RenameRule::LowerCase,
+ deserialize: RenameRule::None
+ }
+ );
+
+ let derive: DeriveInput = syn::parse_quote! {
+ #[serde(rename_all(deserialize = "lowercase"))]
+ struct Config {
+ field: String,
+ }
+ };
+ let input = ContainerAttributes::from_attributes(&derive.attrs).unwrap();
+ assert_eq!(
+ input.rename_all,
+ RenameAll {
+ serialize: RenameRule::None,
+ deserialize: RenameRule::LowerCase
+ }
+ );
+
+ let derive: DeriveInput = syn::parse_quote! {
+ struct Config {
+ field: String,
+ }
+ };
+ let input = ContainerAttributes::from_attributes(&derive.attrs).unwrap();
+ assert_eq!(input.rename_all, RenameAll::from(RenameRule::None));
+ }
+
+ #[test]
+ fn test_default() {
+ let derive: DeriveInput = syn::parse_quote! {
+ #[serde(default)]
+ struct Config {
+ field: String,
+ }
+ };
+ let input = ContainerAttributes::from_attributes(&derive.attrs).unwrap();
+ assert_eq!(input._default, Some(SerdeDefault::UseTrait));
+
+ let derive: DeriveInput = syn::parse_quote! {
+ #[serde(default = "some::path")]
+ struct Config {
+ field: String,
+ }
+ };
+ let input = ContainerAttributes::from_attributes(&derive.attrs).unwrap();
+ assert!(matches!(input._default, Some(SerdeDefault::Expr(_))));
+
+ let derive: DeriveInput = syn::parse_quote! {
+ struct Config {
+ field: String,
+ }
+ };
+ let input = ContainerAttributes::from_attributes(&derive.attrs).unwrap();
+ assert_eq!(input._default, None);
+ }
+}
diff --git a/avro_derive/src/case.rs b/avro_derive/src/case.rs
index 8dbaafd..d0f3c8c 100644
--- a/avro_derive/src/case.rs
+++ b/avro_derive/src/case.rs
@@ -18,13 +18,17 @@
//! Code to convert the Rust-styled field/variant (e.g. `my_field`, `MyType`) to the
//! case of the source (e.g. `my-field`, `MY_FIELD`).
//! Code copied from serde <https://github.com/serde-rs/serde/blob/master/serde_derive/src/internals/case.rs>
+use darling::FromMeta;
+use syn::Lit;
+
use self::RenameRule::*;
use std::fmt::{self, Debug, Display};
/// The different possible ways to change case of fields in a struct, or variants in an enum.
-#[derive(Copy, Clone, PartialEq)]
+#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
pub enum RenameRule {
/// Don't apply a default rename rule.
+ #[default]
None,
/// Rename direct children to "lowercase" style.
LowerCase,
@@ -47,6 +51,15 @@ pub enum RenameRule {
ScreamingKebabCase,
}
+impl FromMeta for RenameRule {
+ fn from_value(value: &Lit) -> darling::Result<Self> {
+ let Lit::Str(litstr) = value else {
+ panic!("Only expected a string");
+ };
+ Self::from_str(&litstr.value()).map_err(darling::Error::custom)
+ }
+}
+
static RENAME_RULES: &[(&str, RenameRule)] = &[
("lowercase", LowerCase),
("UPPERCASE", UpperCase),
@@ -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),
+ "UPPERCASE" => Ok(Self::UpperCase),
+ "PascalCase" => Ok(Self::PascalCase),
+ "camelCase" => Ok(Self::CamelCase),
+ "snake_case" => Ok(Self::SnakeCase),
+ "SCREAMING_SNAKE_CASE" => Ok(Self::ScreamingSnakeCase),
+ "kebab-case" => Ok(Self::KebabCase),
+ "SCREAMING-KEBAB-CASE" => Ok(Self::ScreamingKebabCase),
+ _ => Err(ParseError {
+ unknown: rename_all_str.to_string(),
+ }),
}
- Err(ParseError {
- unknown: rename_all_str,
- })
}
/// Apply a renaming rule to an enum variant, returning the version expected in the source.
@@ -126,14 +144,14 @@ impl RenameRule {
}
}
-pub struct ParseError<'a> {
- unknown: &'a str,
+pub struct ParseError {
+ unknown: String,
}
-impl Display for ParseError<'_> {
+impl Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("unknown rename rule `rename_all = ")?;
- Debug::fmt(self.unknown, f)?;
+ Debug::fmt(&self.unknown, f)?;
f.write_str("`, expected one of ")?;
for (i, (name, _rule)) in RENAME_RULES.iter().enumerate() {
if i > 0 {
diff --git a/avro_derive/src/lib.rs b/avro_derive/src/lib.rs
index 2b225fb..a331bdc 100644
--- a/avro_derive/src/lib.rs
+++ b/avro_derive/src/lib.rs
@@ -15,57 +15,20 @@
// specific language governing permissions and limitations
// under the License.
+mod attributes;
mod case;
-use case::RenameRule;
-use darling::FromAttributes;
+
use proc_macro2::{Span, TokenStream};
use quote::quote;
-
use syn::{
AttrStyle, Attribute, DeriveInput, Ident, Meta, Type, TypePath, parse_macro_input,
spanned::Spanned,
};
-#[derive(darling::FromAttributes)]
-#[darling(attributes(avro))]
-struct FieldOptions {
- #[darling(default)]
- doc: Option<String>,
- #[darling(default)]
- default: Option<String>,
- #[darling(multiple)]
- alias: Vec<String>,
- #[darling(default)]
- rename: Option<String>,
- #[darling(default)]
- skip: Option<bool>,
- #[darling(default)]
- flatten: Option<bool>,
-}
+use crate::attributes::{FieldOptions, NamedTypeOptions, VariantOptions};
+use crate::case::RenameRule;
-#[derive(darling::FromAttributes)]
-#[darling(attributes(avro))]
-struct VariantOptions {
- #[darling(default)]
- rename: Option<String>,
-}
-
-#[derive(darling::FromAttributes)]
-#[darling(attributes(avro))]
-struct NamedTypeOptions {
- #[darling(default)]
- name: Option<String>,
- #[darling(default)]
- namespace: Option<String>,
- #[darling(default)]
- doc: Option<String>,
- #[darling(multiple)]
- alias: Vec<String>,
- #[darling(default)]
- rename_all: Option<String>,
-}
-
-#[proc_macro_derive(AvroSchema, attributes(avro))]
+#[proc_macro_derive(AvroSchema, attributes(avro, serde))]
// Templated from Serde
pub fn proc_macro_derive_avro_schema(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let mut input = parse_macro_input!(input as DeriveInput);
@@ -75,10 +38,9 @@ pub fn proc_macro_derive_avro_schema(input: proc_macro::TokenStream) -> proc_mac
}
fn derive_avro_schema(input: &mut DeriveInput) -> Result<TokenStream, Vec<syn::Error>> {
- let named_type_options =
- NamedTypeOptions::from_attributes(&input.attrs[..]).map_err(darling_to_syn)?;
+ let named_type_options = NamedTypeOptions::new(&input.attrs, input.span())?;
- let rename_all = parse_case(named_type_options.rename_all.as_deref(), input.span())?;
+ let rename_all = named_type_options.rename_all;
let name = named_type_options.name.unwrap_or(input.ident.to_string());
let full_schema_name = vec![named_type_options.namespace, Some(name)]
@@ -153,8 +115,7 @@ fn get_data_struct_schema_def(
if let Some(raw_name) = name.strip_prefix("r#") {
name = raw_name.to_string();
}
- let field_attrs =
- FieldOptions::from_attributes(&field.attrs).map_err(darling_to_syn)?;
+ let field_attrs = FieldOptions::new(&field.attrs, field.span())?;
let doc =
preserve_optional(field_attrs.doc.or_else(|| extract_outer_doc(&field.attrs)));
match (field_attrs.rename, rename_all) {
@@ -166,9 +127,9 @@ fn get_data_struct_schema_def(
}
_ => {}
}
- if Some(true) == field_attrs.skip {
+ if field_attrs.skip {
continue;
- } else if Some(true) == field_attrs.flatten {
+ } else if field_attrs.flatten {
// Inline the fields of the child record at runtime, as we don't have access to
// the schema here.
let flatten_ty = &field.ty;
@@ -271,8 +232,7 @@ fn get_data_enum_schema_def(
let default = preserve_optional(default_value);
let mut symbols = Vec::new();
for variant in &e.variants {
- let field_attrs =
- VariantOptions::from_attributes(&variant.attrs[..]).map_err(darling_to_syn)?;
+ let field_attrs = VariantOptions::new(&variant.attrs, variant.span())?;
let name = match (field_attrs.rename, rename_all) {
(Some(rename), _) => rename,
(None, rename_all) if !matches!(rename_all, RenameRule::None) => {
@@ -430,16 +390,6 @@ fn darling_to_syn(e: darling::Error) -> Vec<syn::Error> {
vec![syn::Error::new(token_errors.span(), msg)]
}
-fn parse_case(case: Option<&str>, span: Span) -> Result<RenameRule, Vec<syn::Error>> {
- match case {
- None => Ok(RenameRule::None),
- Some(case) => {
- Ok(RenameRule::from_str(case)
- .map_err(|e| vec![syn::Error::new(span, e.to_string())])?)
- }
- }
-}
-
#[cfg(test)]
mod tests {
use super::*;
@@ -701,6 +651,7 @@ mod tests {
fn test_avro_3709_record_field_attributes() {
let test_struct = quote! {
struct A {
+ #[serde(alias = "a1", alias = "a2", rename = "a3")]
#[avro(alias = "a1", alias = "a2", doc = "a doc", default = "123", rename = "a3")]
a: i32
}
@@ -720,6 +671,7 @@ mod tests {
let test_enum = quote! {
enum A {
+ #[serde(rename = "A3")]
#[avro(rename = "A3")]
Item1,
}
@@ -741,6 +693,7 @@ mod tests {
#[test]
fn test_avro_rs_207_rename_all_attribute() {
let test_struct = quote! {
+ #[serde(rename_all="SCREAMING_SNAKE_CASE")]
#[avro(rename_all="SCREAMING_SNAKE_CASE")]
struct A {
item: i32,
@@ -761,6 +714,7 @@ mod tests {
};
let test_enum = quote! {
+ #[serde(rename_all="SCREAMING_SNAKE_CASE")]
#[avro(rename_all="SCREAMING_SNAKE_CASE")]
enum B {
Item,
@@ -784,9 +738,11 @@ mod tests {
#[test]
fn test_avro_rs_207_rename_attr_has_priority_over_rename_all_attribute() {
let test_struct = quote! {
+ #[serde(rename_all="SCREAMING_SNAKE_CASE")]
#[avro(rename_all="SCREAMING_SNAKE_CASE")]
struct A {
item: i32,
+ #[serde(rename="DoubleItem")]
#[avro(rename="DoubleItem")]
double_item: i32
}
diff --git a/avro_derive/tests/derive.rs b/avro_derive/tests/derive.rs
index 6972e9a..9b9c8a7 100644
--- a/avro_derive/tests/derive.rs
+++ b/avro_derive/tests/derive.rs
@@ -24,331 +24,491 @@ use proptest::prelude::*;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use std::collections::HashMap;
-#[cfg(test)]
-mod test_derive {
- use apache_avro::schema::{Alias, EnumSchema, RecordSchema};
- use std::{borrow::Cow, sync::Mutex};
-
- use super::*;
- use pretty_assertions::assert_eq;
-
- /// 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()));
- }
+use apache_avro::schema::{Alias, EnumSchema, RecordSchema};
+use pretty_assertions::assert_eq;
+use std::{borrow::Cow, sync::Mutex};
+
+/// 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()));
+}
- fn serde<T>(obj: T) -> T
- where
- T: Serialize + DeserializeOwned + AvroSchema,
- {
- de(ser(obj))
+fn serde<T>(obj: T) -> T
+where
+ T: Serialize + DeserializeOwned + AvroSchema,
+{
+ de(ser(obj))
+}
+
+fn ser<T>(obj: T) -> Vec<u8>
+where
+ T: Serialize + AvroSchema,
+{
+ let schema = T::get_schema();
+ let mut writer = Writer::new(&schema, Vec::new()).unwrap();
+ if let Err(e) = writer.append_ser(obj) {
+ panic!("{e:?}");
}
+ writer.into_inner().unwrap()
+}
- fn ser<T>(obj: T) -> Vec<u8>
- where
- T: Serialize + AvroSchema,
- {
- let schema = T::get_schema();
- let mut writer = Writer::new(&schema, Vec::new()).unwrap();
- if let Err(e) = writer.append_ser(obj) {
- panic!("{e:?}");
+fn de<T>(encoded: Vec<u8>) -> T
+where
+ T: DeserializeOwned + AvroSchema,
+{
+ assert!(!encoded.is_empty());
+ let schema = T::get_schema();
+ let mut reader = Reader::with_schema(&schema, &encoded[..]).unwrap();
+ if let Some(res) = reader.next() {
+ match res {
+ Ok(value) => {
+ return from_value::<T>(&value).unwrap();
+ }
+ Err(e) => panic!("{e:?}"),
}
- writer.into_inner().unwrap()
}
+ unreachable!()
+}
+
+#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
+struct TestBasic {
+ a: i32,
+ b: String,
+}
- fn de<T>(encoded: Vec<u8>) -> T
- where
- T: DeserializeOwned + AvroSchema,
+proptest! {
+#[test]
+fn test_smoke_test(a: i32, b: String) {
+ let schema = r#"
{
- assert!(!encoded.is_empty());
- let schema = T::get_schema();
- let mut reader = Reader::with_schema(&schema, &encoded[..]).unwrap();
- if let Some(res) = reader.next() {
- match res {
- Ok(value) => {
- return from_value::<T>(&value).unwrap();
- }
- Err(e) => panic!("{e:?}"),
+ "type":"record",
+ "name":"TestBasic",
+ "fields":[
+ {
+ "name":"a",
+ "type":"int"
+ },
+ {
+ "name":"b",
+ "type":"string"
}
- }
- unreachable!()
+ ]
}
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, TestBasic::get_schema());
+ let test = TestBasic {
+ a,
+ b,
+ };
+ serde_assert(test);
+}}
+
+#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
+#[avro(namespace = "com.testing.namespace")]
+struct TestBasicNamespace {
+ a: i32,
+ b: String,
+}
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
- struct TestBasic {
- a: i32,
- b: String,
+#[test]
+fn test_basic_namespace() {
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"com.testing.namespace.TestBasicNamespace",
+ "fields":[
+ {
+ "name":"a",
+ "type":"int"
+ },
+ {
+ "name":"b",
+ "type":"string"
+ }
+ ]
}
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, TestBasicNamespace::get_schema());
+ if let Schema::Record(RecordSchema { name, .. }) = TestBasicNamespace::get_schema() {
+ assert_eq!("com.testing.namespace".to_owned(), name.namespace.unwrap())
+ } else {
+ panic!("TestBasicNamespace schema must be a record schema")
+ }
+}
- proptest! {
- #[test]
- fn test_smoke_test(a: i32, b: String) {
- let schema = r#"
- {
- "type":"record",
- "name":"TestBasic",
- "fields":[
- {
- "name":"a",
- "type":"int"
- },
- {
- "name":"b",
- "type":"string"
+#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
+#[avro(namespace = "com.testing.complex.namespace")]
+struct TestComplexNamespace {
+ a: TestBasicNamespace,
+ b: String,
+}
+
+#[test]
+fn test_complex_namespace() {
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"com.testing.complex.namespace.TestComplexNamespace",
+ "fields":[
+ {
+ "name":"a",
+ "type":{
+ "type":"record",
+ "name":"com.testing.namespace.TestBasicNamespace",
+ "fields":[
+ {
+ "name":"a",
+ "type":"int"
+ },
+ {
+ "name":"b",
+ "type":"string"
+ }
+ ]
}
- ]
+ },
+ {
+ "name":"b",
+ "type":"string"
+ }
+ ]
+ }
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, TestComplexNamespace::get_schema());
+ if let Schema::Record(RecordSchema { name, fields, .. }) = TestComplexNamespace::get_schema() {
+ assert_eq!(
+ "com.testing.complex.namespace".to_owned(),
+ name.namespace.unwrap()
+ );
+ let inner_schema = fields
+ .iter()
+ .filter(|field| field.name == "a")
+ .map(|field| &field.schema)
+ .next();
+ if let Some(Schema::Record(RecordSchema { name, .. })) = inner_schema {
+ assert_eq!(
+ "com.testing.namespace".to_owned(),
+ name.namespace.clone().unwrap()
+ )
+ } else {
+ panic!("Field 'a' must have a record schema")
}
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- assert_eq!(schema, TestBasic::get_schema());
- let test = TestBasic {
- a,
- b,
- };
- serde_assert(test);
- }}
+ } else {
+ panic!("TestComplexNamespace schema must be a record schema")
+ }
+}
+#[test]
+fn avro_rs_239_test_named_record() {
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
- #[avro(namespace = "com.testing.namespace")]
- struct TestBasicNamespace {
+ #[avro(name = "Other", namespace = "com.testing.namespace")]
+ struct TestNamedRecord {
a: i32,
b: String,
}
- #[test]
- fn test_basic_namespace() {
- let schema = r#"
- {
- "type":"record",
- "name":"com.testing.namespace.TestBasicNamespace",
- "fields":[
- {
- "name":"a",
- "type":"int"
- },
- {
- "name":"b",
- "type":"string"
- }
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- assert_eq!(schema, TestBasicNamespace::get_schema());
- if let Schema::Record(RecordSchema { name, .. }) = TestBasicNamespace::get_schema() {
- assert_eq!("com.testing.namespace".to_owned(), name.namespace.unwrap())
- } else {
- panic!("TestBasicNamespace schema must be a record schema")
- }
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"com.testing.namespace.Other",
+ "fields":[
+ {
+ "name":"a",
+ "type":"int"
+ },
+ {
+ "name":"b",
+ "type":"string"
+ }
+ ]
+ }
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, TestNamedRecord::get_schema());
+ if let Schema::Record(RecordSchema { name, .. }) = TestNamedRecord::get_schema() {
+ assert_eq!("Other", name.name.as_str());
+ assert_eq!(Some("com.testing.namespace"), name.namespace.as_deref())
+ } else {
+ panic!("TestNamedRecord schema must be a record schema")
}
+}
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
- #[avro(namespace = "com.testing.complex.namespace")]
- struct TestComplexNamespace {
- a: TestBasicNamespace,
- b: String,
+#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+struct TestAllSupportedBaseTypes {
+ //Basics test
+ a: bool,
+ b: i8,
+ c: i16,
+ d: i32,
+ e: u8,
+ f: u16,
+ g: i64,
+ h: f32,
+ i: f64,
+ j: String,
+}
+
+proptest! {
+#[test]
+fn test_basic_types(a: bool, b: i8, c: i16, d: i32, e: u8, f: u16, g: i64, h: f32, i: f64, j: String) {
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"TestAllSupportedBaseTypes",
+ "fields":[
+ {
+ "name":"a",
+ "type": "boolean"
+ },
+ {
+ "name":"b",
+ "type":"int"
+ },
+ {
+ "name":"c",
+ "type":"int"
+ },
+ {
+ "name":"d",
+ "type":"int"
+ },
+ {
+ "name":"e",
+ "type":"int"
+ },
+ {
+ "name":"f",
+ "type":"int"
+ },
+ {
+ "name":"g",
+ "type":"long"
+ },
+ {
+ "name":"h",
+ "type":"float"
+ },
+ {
+ "name":"i",
+ "type":"double"
+ },
+ {
+ "name":"j",
+ "type":"string"
+ }
+ ]
}
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, TestAllSupportedBaseTypes::get_schema());
+ let all_basic = TestAllSupportedBaseTypes {
+ a,
+ b,
+ c,
+ d,
+ e,
+ f,
+ g,
+ h,
+ i,
+ j,
+ };
+ serde_assert(all_basic);
+}}
+
+#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+struct TestNested {
+ a: i32,
+ b: TestAllSupportedBaseTypes,
+}
- #[test]
- fn test_complex_namespace() {
- let schema = r#"
- {
- "type":"record",
- "name":"com.testing.complex.namespace.TestComplexNamespace",
- "fields":[
- {
- "name":"a",
- "type":{
- "type":"record",
- "name":"com.testing.namespace.TestBasicNamespace",
- "fields":[
- {
- "name":"a",
- "type":"int"
- },
- {
- "name":"b",
- "type":"string"
- }
- ]
- }
- },
- {
- "name":"b",
- "type":"string"
+proptest! {
+#[test]
+fn test_inner_struct(a: bool, b: i8, c: i16, d: i32, e: u8, f: u16, g: i64, h: f32, i: f64, j: String, aa: i32) {
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"TestNested",
+ "fields":[
+ {
+ "name":"a",
+ "type":"int"
+ },
+ {
+ "name":"b",
+ "type":{
+ "type":"record",
+ "name":"TestAllSupportedBaseTypes",
+ "fields":[
+ {
+ "name":"a",
+ "type": "boolean"
+ },
+ {
+ "name":"b",
+ "type":"int"
+ },
+ {
+ "name":"c",
+ "type":"int"
+ },
+ {
+ "name":"d",
+ "type":"int"
+ },
+ {
+ "name":"e",
+ "type":"int"
+ },
+ {
+ "name":"f",
+ "type":"int"
+ },
+ {
+ "name":"g",
+ "type":"long"
+ },
+ {
+ "name":"h",
+ "type":"float"
+ },
+ {
+ "name":"i",
+ "type":"double"
+ },
+ {
+ "name":"j",
+ "type":"string"
+ }
+ ]
}
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- assert_eq!(schema, TestComplexNamespace::get_schema());
- if let Schema::Record(RecordSchema { name, fields, .. }) =
- TestComplexNamespace::get_schema()
- {
- assert_eq!(
- "com.testing.complex.namespace".to_owned(),
- name.namespace.unwrap()
- );
- let inner_schema = fields
- .iter()
- .filter(|field| field.name == "a")
- .map(|field| &field.schema)
- .next();
- if let Some(Schema::Record(RecordSchema { name, .. })) = inner_schema {
- assert_eq!(
- "com.testing.namespace".to_owned(),
- name.namespace.clone().unwrap()
- )
- } else {
- panic!("Field 'a' must have a record schema")
}
- } else {
- panic!("TestComplexNamespace schema must be a record schema")
- }
+ ]
}
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, TestNested::get_schema());
+ let all_basic = TestAllSupportedBaseTypes {
+ a,
+ b,
+ c,
+ d,
+ e,
+ f,
+ g,
+ h,
+ i,
+ j,
+ };
+ let inner_struct = TestNested {
+ a: aa,
+ b: all_basic,
+ };
+ serde_assert(inner_struct);
+}}
+
+#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
+struct TestOptional {
+ a: Option<i32>,
+}
- #[test]
- fn avro_rs_239_test_named_record() {
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
- #[avro(name = "Other", namespace = "com.testing.namespace")]
- struct TestNamedRecord {
- a: i32,
- b: String,
- }
-
- let schema = r#"
- {
- "type":"record",
- "name":"com.testing.namespace.Other",
- "fields":[
- {
- "name":"a",
- "type":"int"
- },
- {
- "name":"b",
- "type":"string"
- }
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- assert_eq!(schema, TestNamedRecord::get_schema());
- if let Schema::Record(RecordSchema { name, .. }) = TestNamedRecord::get_schema() {
- assert_eq!("Other", name.name.as_str());
- assert_eq!(Some("com.testing.namespace"), name.namespace.as_deref())
- } else {
- panic!("TestNamedRecord schema must be a record schema")
- }
+proptest! {
+#[test]
+fn test_optional_field_some(a: i32) {
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"TestOptional",
+ "fields":[
+ {
+ "name":"a",
+ "type":["null","int"]
+ }
+ ]
}
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, TestOptional::get_schema());
+ let optional_field = TestOptional { a: Some(a) };
+ serde_assert(optional_field);
+}}
+
+#[test]
+fn test_optional_field_none() {
+ let optional_field = TestOptional { a: None };
+ serde_assert(optional_field);
+}
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
- struct TestAllSupportedBaseTypes {
- //Basics test
- a: bool,
- b: i8,
- c: i16,
- d: i32,
- e: u8,
- f: u16,
- g: i64,
- h: f32,
- i: f64,
- j: String,
- }
-
- proptest! {
- #[test]
- fn test_basic_types(a: bool, b: i8, c: i16, d: i32, e: u8, f: u16, g: i64, h: f32, i: f64, j: String) {
- let schema = r#"
- {
- "type":"record",
- "name":"TestAllSupportedBaseTypes",
- "fields":[
- {
- "name":"a",
- "type": "boolean"
- },
- {
- "name":"b",
- "type":"int"
- },
- {
- "name":"c",
- "type":"int"
- },
- {
- "name":"d",
- "type":"int"
- },
- {
- "name":"e",
- "type":"int"
- },
- {
- "name":"f",
- "type":"int"
- },
- {
- "name":"g",
- "type":"long"
- },
- {
- "name":"h",
- "type":"float"
- },
- {
- "name":"i",
- "type":"double"
- },
- {
- "name":"j",
- "type":"string"
- }
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- assert_eq!(schema, TestAllSupportedBaseTypes::get_schema());
- let all_basic = TestAllSupportedBaseTypes {
- a,
- b,
- c,
- d,
- e,
- f,
- g,
- h,
- i,
- j,
- };
- serde_assert(all_basic);
- }}
+/// Generic Containers
+#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+struct TestGeneric<T: AvroSchemaComponent> {
+ a: String,
+ b: Vec<T>,
+ c: HashMap<String, T>,
+}
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
- struct TestNested {
- a: i32,
- b: TestAllSupportedBaseTypes,
- }
-
- proptest! {
- #[test]
- fn test_inner_struct(a: bool, b: i8, c: i16, d: i32, e: u8, f: u16, g: i64, h: f32, i: f64, j: String, aa: i32) {
- let schema = r#"
- {
- "type":"record",
- "name":"TestNested",
- "fields":[
- {
- "name":"a",
- "type":"int"
- },
- {
- "name":"b",
- "type":{
+proptest! {
+#[test]
+fn test_generic_container_1(a: String, b: Vec<i32>, c: HashMap<String, i32>) {
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"TestGeneric",
+ "fields":[
+ {
+ "name":"a",
+ "type":"string"
+ },
+ {
+ "name":"b",
+ "type": {
+ "type":"array",
+ "items":"int"
+ }
+ },
+ {
+ "name":"c",
+ "type": {
+ "type":"map",
+ "values":"int"
+ }
+ }
+ ]
+ }
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, TestGeneric::<i32>::get_schema());
+ let test_generic = TestGeneric::<i32> {
+ a,
+ b,
+ c,
+ };
+ serde_assert(test_generic);
+}}
+
+proptest! {
+#[test]
+fn test_generic_container_2(a: bool, b: i8, c: i16, d: i32, e: u8, f: u16, g: i64, h: f32, i: f64, j: String) {
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"TestGeneric",
+ "fields":[
+ {
+ "name":"a",
+ "type":"string"
+ },
+ {
+ "name":"b",
+ "type": {
+ "type":"array",
+ "items":{
"type":"record",
"name":"TestAllSupportedBaseTypes",
"fields":[
@@ -395,12 +555,25 @@ mod test_derive {
]
}
}
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- assert_eq!(schema, TestNested::get_schema());
- let all_basic = TestAllSupportedBaseTypes {
+ },
+ {
+ "name":"c",
+ "type": {
+ "type":"map",
+ "values":"TestAllSupportedBaseTypes"
+ }
+ }
+ ]
+ }
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(
+ schema,
+ TestGeneric::<TestAllSupportedBaseTypes>::get_schema()
+ );
+ let test_generic = TestGeneric::<TestAllSupportedBaseTypes> {
+ a: "testing".to_owned(),
+ b: vec![TestAllSupportedBaseTypes {
a,
b,
c,
@@ -410,176 +583,11 @@ mod test_derive {
g,
h,
i,
- j,
- };
- let inner_struct = TestNested {
- a: aa,
- b: all_basic,
- };
- serde_assert(inner_struct);
- }}
-
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
- struct TestOptional {
- a: Option<i32>,
- }
-
- proptest! {
- #[test]
- fn test_optional_field_some(a: i32) {
- let schema = r#"
- {
- "type":"record",
- "name":"TestOptional",
- "fields":[
- {
- "name":"a",
- "type":["null","int"]
- }
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- assert_eq!(schema, TestOptional::get_schema());
- let optional_field = TestOptional { a: Some(a) };
- serde_assert(optional_field);
- }}
-
- #[test]
- fn test_optional_field_none() {
- let optional_field = TestOptional { a: None };
- serde_assert(optional_field);
- }
-
- /// Generic Containers
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
- struct TestGeneric<T: AvroSchemaComponent> {
- a: String,
- b: Vec<T>,
- c: HashMap<String, T>,
- }
-
- proptest! {
- #[test]
- fn test_generic_container_1(a: String, b: Vec<i32>, c: HashMap<String, i32>) {
- let schema = r#"
- {
- "type":"record",
- "name":"TestGeneric",
- "fields":[
- {
- "name":"a",
- "type":"string"
- },
- {
- "name":"b",
- "type": {
- "type":"array",
- "items":"int"
- }
- },
- {
- "name":"c",
- "type": {
- "type":"map",
- "values":"int"
- }
- }
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- assert_eq!(schema, TestGeneric::<i32>::get_schema());
- let test_generic = TestGeneric::<i32> {
- a,
- b,
- c,
- };
- serde_assert(test_generic);
- }}
-
- proptest! {
- #[test]
- fn test_generic_container_2(a: bool, b: i8, c: i16, d: i32, e: u8, f: u16, g: i64, h: f32, i: f64, j: String) {
- let schema = r#"
- {
- "type":"record",
- "name":"TestGeneric",
- "fields":[
- {
- "name":"a",
- "type":"string"
- },
- {
- "name":"b",
- "type": {
- "type":"array",
- "items":{
- "type":"record",
- "name":"TestAllSupportedBaseTypes",
- "fields":[
- {
- "name":"a",
- "type": "boolean"
- },
- {
- "name":"b",
- "type":"int"
- },
- {
- "name":"c",
- "type":"int"
- },
- {
- "name":"d",
- "type":"int"
- },
- {
- "name":"e",
- "type":"int"
- },
- {
- "name":"f",
- "type":"int"
- },
- {
- "name":"g",
- "type":"long"
- },
- {
- "name":"h",
- "type":"float"
- },
- {
- "name":"i",
- "type":"double"
- },
- {
- "name":"j",
- "type":"string"
- }
- ]
- }
- }
- },
- {
- "name":"c",
- "type": {
- "type":"map",
- "values":"TestAllSupportedBaseTypes"
- }
- }
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- assert_eq!(
- schema,
- TestGeneric::<TestAllSupportedBaseTypes>::get_schema()
- );
- let test_generic = TestGeneric::<TestAllSupportedBaseTypes> {
- a: "testing".to_owned(),
- b: vec![TestAllSupportedBaseTypes {
+ j: j.clone(),
+ }],
+ c: vec![(
+ "key".to_owned(),
+ TestAllSupportedBaseTypes {
a,
b,
c,
@@ -589,185 +597,259 @@ mod test_derive {
g,
h,
i,
- j: j.clone(),
- }],
- c: vec![(
- "key".to_owned(),
- TestAllSupportedBaseTypes {
- a,
- b,
- c,
- d,
- e,
- f,
- g,
- h,
- i,
- j,
- },
- )]
- .into_iter()
- .collect(),
- };
- serde_assert(test_generic);
- }}
+ j,
+ },
+ )]
+ .into_iter()
+ .collect(),
+ };
+ serde_assert(test_generic);
+}}
+
+#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
+enum TestAllowedEnum {
+ A,
+ B,
+ C,
+ D,
+}
+
+#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
+struct TestAllowedEnumNested {
+ a: TestAllowedEnum,
+ b: String,
+}
+
+#[test]
+fn test_enum() {
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"TestAllowedEnumNested",
+ "fields":[
+ {
+ "name":"a",
+ "type": {
+ "type":"enum",
+ "name":"TestAllowedEnum",
+ "symbols":["A","B","C","D"]
+ }
+ },
+ {
+ "name":"b",
+ "type":"string"
+ }
+ ]
+ }
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, TestAllowedEnumNested::get_schema());
+ let enum_included = TestAllowedEnumNested {
+ a: TestAllowedEnum::B,
+ b: "hey".to_owned(),
+ };
+ serde_assert(enum_included);
+}
+#[test]
+fn avro_rs_239_test_enum_named() {
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
- enum TestAllowedEnum {
+ #[avro(name = "Other", rename_all = "snake_case")]
+ #[serde(rename_all = "snake_case")]
+ enum TestNamedEnum {
A,
B,
C,
+ #[avro(rename = "e")]
+ #[serde(rename = "e")]
D,
}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
- struct TestAllowedEnumNested {
- a: TestAllowedEnum,
+ struct TestNamedEnumNested {
+ a: TestNamedEnum,
b: String,
}
- #[test]
- fn test_enum() {
- let schema = r#"
- {
- "type":"record",
- "name":"TestAllowedEnumNested",
- "fields":[
- {
- "name":"a",
- "type": {
- "type":"enum",
- "name":"TestAllowedEnum",
- "symbols":["A","B","C","D"]
- }
- },
- {
- "name":"b",
- "type":"string"
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"TestNamedEnumNested",
+ "fields":[
+ {
+ "name":"a",
+ "type": {
+ "type":"enum",
+ "name":"Other",
+ "symbols":["a","b","c","e"]
}
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- assert_eq!(schema, TestAllowedEnumNested::get_schema());
- let enum_included = TestAllowedEnumNested {
- a: TestAllowedEnum::B,
- b: "hey".to_owned(),
- };
- serde_assert(enum_included);
- }
-
- #[test]
- fn avro_rs_239_test_enum_named() {
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
- #[avro(name = "Other", rename_all = "snake_case")]
- #[serde(rename_all = "snake_case")]
- enum TestNamedEnum {
- A,
- B,
- C,
- #[avro(rename = "e")]
- #[serde(rename = "e")]
- D,
- }
+ },
+ {
+ "name":"b",
+ "type":"string"
+ }
+ ]
+ }
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, TestNamedEnumNested::get_schema());
+ let enum_included = TestNamedEnumNested {
+ a: TestNamedEnum::B,
+ b: "hey".to_owned(),
+ };
+ serde_assert(enum_included);
+}
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
- struct TestNamedEnumNested {
- a: TestNamedEnum,
- b: String,
- }
+#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+struct ConsList {
+ value: i32,
+ next: Option<Box<ConsList>>,
+}
- let schema = r#"
- {
- "type":"record",
- "name":"TestNamedEnumNested",
- "fields":[
- {
- "name":"a",
- "type": {
- "type":"enum",
- "name":"Other",
- "symbols":["a","b","c","e"]
- }
- },
- {
- "name":"b",
- "type":"string"
+#[test]
+fn test_cons() {
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"ConsList",
+ "fields":[
+ {
+ "name":"value",
+ "type":"int"
+ },
+ {
+ "name":"next",
+ "type":["null","ConsList"]
+ }
+ ]
+ }
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, ConsList::get_schema());
+ let list = ConsList {
+ value: 34,
+ next: Some(Box::new(ConsList {
+ value: 42,
+ next: None,
+ })),
+ };
+ serde_assert(list)
+}
+
+#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+struct ConsListGeneric<T: AvroSchemaComponent> {
+ value: T,
+ next: Option<Box<ConsListGeneric<T>>>,
+}
+
+#[test]
+fn test_cons_generic() {
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"ConsListGeneric",
+ "fields":[
+ {
+ "name":"value",
+ "type":{
+ "type":"record",
+ "name":"TestAllowedEnumNested",
+ "fields":[
+ {
+ "name":"a",
+ "type": {
+ "type":"enum",
+ "name":"TestAllowedEnum",
+ "symbols":["A","B","C","D"]
+ }
+ },
+ {
+ "name":"b",
+ "type":"string"
+ }
+ ]
}
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- assert_eq!(schema, TestNamedEnumNested::get_schema());
- let enum_included = TestNamedEnumNested {
- a: TestNamedEnum::B,
- b: "hey".to_owned(),
- };
- serde_assert(enum_included);
+ },
+ {
+ "name":"next",
+ "type":["null","ConsListGeneric"]
+ }
+ ]
}
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(
+ schema,
+ ConsListGeneric::<TestAllowedEnumNested>::get_schema()
+ );
+ let list = ConsListGeneric::<TestAllowedEnumNested> {
+ value: TestAllowedEnumNested {
+ a: TestAllowedEnum::B,
+ b: "testing".into(),
+ },
+ next: Some(Box::new(ConsListGeneric::<TestAllowedEnumNested> {
+ value: TestAllowedEnumNested {
+ a: TestAllowedEnum::D,
+ b: "testing2".into(),
+ },
+ next: None,
+ })),
+ };
+ serde_assert(list)
+}
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
- struct ConsList {
- value: i32,
- next: Option<Box<ConsList>>,
- }
-
- #[test]
- fn test_cons() {
- let schema = r#"
- {
- "type":"record",
- "name":"ConsList",
- "fields":[
- {
- "name":"value",
- "type":"int"
- },
- {
- "name":"next",
- "type":["null","ConsList"]
+#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
+struct TestSimpleArray {
+ a: [i32; 4],
+}
+
+proptest! {
+#[test]
+fn test_simple_array(a: [i32; 4]) {
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"TestSimpleArray",
+ "fields":[
+ {
+ "name":"a",
+ "type": {
+ "type":"array",
+ "items":"int"
}
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- assert_eq!(schema, ConsList::get_schema());
- let list = ConsList {
- value: 34,
- next: Some(Box::new(ConsList {
- value: 42,
- next: None,
- })),
- };
- serde_assert(list)
+ }
+ ]
}
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, TestSimpleArray::get_schema());
+ let test = TestSimpleArray { a };
+ serde_assert(test)
+}}
+
+#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+struct TestComplexArray<T: AvroSchemaComponent> {
+ a: [T; 2],
+}
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
- struct ConsListGeneric<T: AvroSchemaComponent> {
- value: T,
- next: Option<Box<ConsListGeneric<T>>>,
- }
-
- #[test]
- fn test_cons_generic() {
- let schema = r#"
- {
- "type":"record",
- "name":"ConsListGeneric",
- "fields":[
- {
- "name":"value",
- "type":{
+#[test]
+fn test_complex_array() {
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"TestComplexArray",
+ "fields":[
+ {
+ "name":"a",
+ "type": {
+ "type":"array",
+ "items":{
"type":"record",
- "name":"TestAllowedEnumNested",
+ "name":"TestBasic",
"fields":[
{
"name":"a",
- "type": {
- "type":"enum",
- "name":"TestAllowedEnum",
- "symbols":["A","B","C","D"]
- }
+ "type":"int"
},
{
"name":"b",
@@ -775,1079 +857,984 @@ mod test_derive {
}
]
}
- },
- {
- "name":"next",
- "type":["null","ConsListGeneric"]
}
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- assert_eq!(
- schema,
- ConsListGeneric::<TestAllowedEnumNested>::get_schema()
- );
- let list = ConsListGeneric::<TestAllowedEnumNested> {
- value: TestAllowedEnumNested {
- a: TestAllowedEnum::B,
- b: "testing".into(),
- },
- next: Some(Box::new(ConsListGeneric::<TestAllowedEnumNested> {
- value: TestAllowedEnumNested {
- a: TestAllowedEnum::D,
- b: "testing2".into(),
- },
- next: None,
- })),
- };
- serde_assert(list)
+ }
+ ]
}
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, TestComplexArray::<TestBasic>::get_schema());
+ let test = TestComplexArray::<TestBasic> {
+ a: [
+ TestBasic {
+ a: 27,
+ b: "foo".to_owned(),
+ },
+ TestBasic {
+ a: 28,
+ b: "bar".to_owned(),
+ },
+ ],
+ };
+ serde_assert(test)
+}
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
- struct TestSimpleArray {
- a: [i32; 4],
- }
-
- proptest! {
- #[test]
- fn test_simple_array(a: [i32; 4]) {
- let schema = r#"
- {
- "type":"record",
- "name":"TestSimpleArray",
- "fields":[
- {
- "name":"a",
- "type": {
- "type":"array",
- "items":"int"
- }
- }
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- assert_eq!(schema, TestSimpleArray::get_schema());
- let test = TestSimpleArray { a };
- serde_assert(test)
- }}
+#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
+struct Testu8 {
+ a: Vec<u8>,
+ b: [u8; 2],
+}
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
- struct TestComplexArray<T: AvroSchemaComponent> {
- a: [T; 2],
- }
-
- #[test]
- fn test_complex_array() {
- let schema = r#"
- {
- "type":"record",
- "name":"TestComplexArray",
- "fields":[
- {
- "name":"a",
- "type": {
- "type":"array",
- "items":{
- "type":"record",
- "name":"TestBasic",
- "fields":[
- {
- "name":"a",
- "type":"int"
- },
- {
- "name":"b",
- "type":"string"
- }
- ]
- }
- }
+proptest! {
+#[test]
+fn test_bytes_handled(a: Vec<u8>, b: [u8; 2]) {
+ let test = Testu8 {
+ a,
+ b,
+ };
+ serde_assert(test)
+ // don't check for schema equality to allow for transitioning to bytes or fixed types in the future
+}}
+
+#[derive(Debug, Serialize, Deserialize, AvroSchema)]
+struct TestSmartPointers<'a> {
+ a: String,
+ b: Mutex<Vec<i64>>,
+ c: Cow<'a, i32>,
+}
+
+#[test]
+fn test_smart_pointers() {
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"TestSmartPointers",
+ "fields":[
+ {
+ "name":"a",
+ "type": "string"
+ },
+ {
+ "name":"b",
+ "type":{
+ "type":"array",
+ "items":"long"
}
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- assert_eq!(schema, TestComplexArray::<TestBasic>::get_schema());
- let test = TestComplexArray::<TestBasic> {
- a: [
- TestBasic {
- a: 27,
- b: "foo".to_owned(),
- },
- TestBasic {
- a: 28,
- b: "bar".to_owned(),
- },
- ],
- };
- serde_assert(test)
+ },
+ {
+ "name":"c",
+ "type":"int"
+ }
+ ]
}
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, TestSmartPointers::get_schema());
+ let test = TestSmartPointers {
+ a: "hey".into(),
+ b: Mutex::new(vec![42]),
+ c: Cow::Owned(32),
+ };
+ // test serde with manual equality for mutex
+ let test = serde(test);
+ assert_eq!("hey", test.a);
+ assert_eq!(vec![42], *test.b.lock().unwrap());
+ assert_eq!(Cow::Owned::<i32>(32), test.c);
+}
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
- struct Testu8 {
- a: Vec<u8>,
- b: [u8; 2],
- }
+#[derive(Debug, Serialize, AvroSchema, Clone, PartialEq)]
+struct TestReference<'a> {
+ a: &'a Vec<i32>,
+ b: &'static str,
+ c: &'a f64,
+}
- proptest! {
- #[test]
- fn test_bytes_handled(a: Vec<u8>, b: [u8; 2]) {
- let test = Testu8 {
- a,
- b,
- };
- serde_assert(test)
- // don't check for schema equality to allow for transitioning to bytes or fixed types in the future
- }}
-
- #[derive(Debug, Serialize, Deserialize, AvroSchema)]
- struct TestSmartPointers<'a> {
- a: String,
- b: Mutex<Vec<i64>>,
- c: Cow<'a, i32>,
- }
-
- #[test]
- fn test_smart_pointers() {
- let schema = r#"
- {
- "type":"record",
- "name":"TestSmartPointers",
- "fields":[
- {
- "name":"a",
- "type": "string"
- },
- {
- "name":"b",
- "type":{
- "type":"array",
- "items":"long"
- }
- },
- {
- "name":"c",
- "type":"int"
- }
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- assert_eq!(schema, TestSmartPointers::get_schema());
- let test = TestSmartPointers {
- a: "hey".into(),
- b: Mutex::new(vec![42]),
- c: Cow::Owned(32),
- };
- // test serde with manual equality for mutex
- let test = serde(test);
- assert_eq!("hey", test.a);
- assert_eq!(vec![42], *test.b.lock().unwrap());
- assert_eq!(Cow::Owned::<i32>(32), test.c);
- }
-
- #[derive(Debug, Serialize, AvroSchema, Clone, PartialEq)]
- struct TestReference<'a> {
- a: &'a Vec<i32>,
- b: &'static str,
- c: &'a f64,
- }
-
- proptest! {
- #[test]
- fn test_reference_struct(a: Vec<i32>, c: f64) {
- let schema = r#"
- {
- "type":"record",
- "name":"TestReference",
- "fields":[
- {
- "name":"a",
- "type": {
- "type":"array",
- "items":"int"
- }
- },
- {
- "name":"b",
- "type":"string"
- },
- {
- "name":"c",
- "type":"double"
+proptest! {
+#[test]
+fn test_reference_struct(a: Vec<i32>, c: f64) {
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"TestReference",
+ "fields":[
+ {
+ "name":"a",
+ "type": {
+ "type":"array",
+ "items":"int"
}
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- assert_eq!(schema, TestReference::get_schema());
- // let a = vec![34];
- // let c = 4.55555555_f64;
- let test = TestReference {
- a: &a,
- b: "testing_static",
- c: &c,
- };
- ser(test);
- }}
-
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
- #[avro(namespace = "com.testing.namespace", doc = "A Documented Record")]
- struct TestBasicWithAttributes {
- #[avro(doc = "Milliseconds since Queen released Bohemian Rhapsody")]
- a: i32,
- #[avro(doc = "Full lyrics of Bohemian Rhapsody")]
- b: String,
+ },
+ {
+ "name":"b",
+ "type":"string"
+ },
+ {
+ "name":"c",
+ "type":"double"
+ }
+ ]
}
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, TestReference::get_schema());
+ // let a = vec![34];
+ // let c = 4.55555555_f64;
+ let test = TestReference {
+ a: &a,
+ b: "testing_static",
+ c: &c,
+ };
+ ser(test);
+}}
+
+#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
+#[avro(namespace = "com.testing.namespace", doc = "A Documented Record")]
+struct TestBasicWithAttributes {
+ #[avro(doc = "Milliseconds since Queen released Bohemian Rhapsody")]
+ a: i32,
+ #[avro(doc = "Full lyrics of Bohemian Rhapsody")]
+ b: String,
+}
- #[test]
- fn test_basic_with_attributes() {
- let schema = r#"
- {
- "type":"record",
- "name":"com.testing.namespace.TestBasicWithAttributes",
- "doc":"A Documented Record",
- "fields":[
- {
- "name":"a",
- "type":"int",
- "doc":"Milliseconds since Queen released Bohemian Rhapsody"
- },
- {
- "name":"b",
- "type": "string",
- "doc": "Full lyrics of Bohemian Rhapsody"
- }
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- if let Schema::Record(RecordSchema { name, doc, .. }) =
- TestBasicWithAttributes::get_schema()
- {
- assert_eq!("com.testing.namespace".to_owned(), name.namespace.unwrap());
- assert_eq!("A Documented Record", doc.unwrap())
- } else {
- panic!("TestBasicWithAttributes schema must be a record schema")
- }
- assert_eq!(schema, TestBasicWithAttributes::get_schema());
+#[test]
+fn test_basic_with_attributes() {
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"com.testing.namespace.TestBasicWithAttributes",
+ "doc":"A Documented Record",
+ "fields":[
+ {
+ "name":"a",
+ "type":"int",
+ "doc":"Milliseconds since Queen released Bohemian Rhapsody"
+ },
+ {
+ "name":"b",
+ "type": "string",
+ "doc": "Full lyrics of Bohemian Rhapsody"
+ }
+ ]
}
-
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
- #[avro(namespace = "com.testing.namespace")]
- /// A Documented Record
- struct TestBasicWithOuterDocAttributes {
- #[avro(doc = "Milliseconds since Queen released Bohemian Rhapsody")]
- a: i32,
- #[avro(doc = "Full lyrics of Bohemian Rhapsody")]
- b: String,
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ if let Schema::Record(RecordSchema { name, doc, .. }) = TestBasicWithAttributes::get_schema() {
+ assert_eq!("com.testing.namespace".to_owned(), name.namespace.unwrap());
+ assert_eq!("A Documented Record", doc.unwrap())
+ } else {
+ panic!("TestBasicWithAttributes schema must be a record schema")
}
+ assert_eq!(schema, TestBasicWithAttributes::get_schema());
+}
- #[test]
- fn test_basic_with_out_doc_attributes() {
- let schema = r#"
- {
- "type":"record",
- "name":"com.testing.namespace.TestBasicWithOuterDocAttributes",
- "doc":"A Documented Record",
- "fields":[
- {
- "name":"a",
- "type":"int",
- "doc":"Milliseconds since Queen released Bohemian Rhapsody"
- },
- {
- "name":"b",
- "type": "string",
- "doc": "Full lyrics of Bohemian Rhapsody"
- }
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- let derived_schema = TestBasicWithOuterDocAttributes::get_schema();
- assert_eq!(&schema, &derived_schema);
- if let Schema::Record(RecordSchema { name, doc, .. }) = derived_schema {
- assert_eq!("com.testing.namespace".to_owned(), name.namespace.unwrap());
- assert_eq!("A Documented Record", doc.unwrap())
- } else {
- panic!("TestBasicWithOuterDocAttributes schema must be a record schema")
- }
+#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
+#[avro(namespace = "com.testing.namespace")]
+/// A Documented Record
+struct TestBasicWithOuterDocAttributes {
+ #[avro(doc = "Milliseconds since Queen released Bohemian Rhapsody")]
+ a: i32,
+ #[avro(doc = "Full lyrics of Bohemian Rhapsody")]
+ b: String,
+}
+
+#[test]
+fn test_basic_with_out_doc_attributes() {
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"com.testing.namespace.TestBasicWithOuterDocAttributes",
+ "doc":"A Documented Record",
+ "fields":[
+ {
+ "name":"a",
+ "type":"int",
+ "doc":"Milliseconds since Queen released Bohemian Rhapsody"
+ },
+ {
+ "name":"b",
+ "type": "string",
+ "doc": "Full lyrics of Bohemian Rhapsody"
+ }
+ ]
}
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ let derived_schema = TestBasicWithOuterDocAttributes::get_schema();
+ assert_eq!(&schema, &derived_schema);
+ if let Schema::Record(RecordSchema { name, doc, .. }) = derived_schema {
+ assert_eq!("com.testing.namespace".to_owned(), name.namespace.unwrap());
+ assert_eq!("A Documented Record", doc.unwrap())
+ } else {
+ panic!("TestBasicWithOuterDocAttributes schema must be a record schema")
+ }
+}
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
- #[avro(namespace = "com.testing.namespace")]
- /// A Documented Record
- /// that spans
- /// multiple lines
- struct TestBasicWithLargeDoc {
- #[avro(doc = "Milliseconds since Queen released Bohemian Rhapsody")]
- a: i32,
- #[avro(doc = "Full lyrics of Bohemian Rhapsody")]
- b: String,
+#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
+#[avro(namespace = "com.testing.namespace")]
+/// A Documented Record
+/// that spans
+/// multiple lines
+struct TestBasicWithLargeDoc {
+ #[avro(doc = "Milliseconds since Queen released Bohemian Rhapsody")]
+ a: i32,
+ #[avro(doc = "Full lyrics of Bohemian Rhapsody")]
+ b: String,
+}
+
+#[test]
+fn test_basic_with_large_doc() {
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"com.testing.namespace.TestBasicWithLargeDoc",
+ "doc":"A Documented Record",
+ "fields":[
+ {
+ "name":"a",
+ "type":"int",
+ "doc":"Milliseconds since Queen released Bohemian Rhapsody"
+ },
+ {
+ "name":"b",
+ "type": "string",
+ "doc": "Full lyrics of Bohemian Rhapsody"
+ }
+ ]
}
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ if let Schema::Record(RecordSchema { name, doc, .. }) = TestBasicWithLargeDoc::get_schema() {
+ assert_eq!("com.testing.namespace".to_owned(), name.namespace.unwrap());
+ assert_eq!(
+ "A Documented Record\nthat spans\nmultiple lines",
+ doc.unwrap()
+ )
+ } else {
+ panic!("TestBasicWithLargeDoc schema must be a record schema")
+ }
+ assert_eq!(schema, TestBasicWithLargeDoc::get_schema());
+}
- #[test]
- fn test_basic_with_large_doc() {
- let schema = r#"
- {
- "type":"record",
- "name":"com.testing.namespace.TestBasicWithLargeDoc",
- "doc":"A Documented Record",
- "fields":[
- {
- "name":"a",
- "type":"int",
- "doc":"Milliseconds since Queen released Bohemian Rhapsody"
- },
- {
- "name":"b",
- "type": "string",
- "doc": "Full lyrics of Bohemian Rhapsody"
- }
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- if let Schema::Record(RecordSchema { name, doc, .. }) = TestBasicWithLargeDoc::get_schema()
- {
- assert_eq!("com.testing.namespace".to_owned(), name.namespace.unwrap());
- assert_eq!(
- "A Documented Record\nthat spans\nmultiple lines",
- doc.unwrap()
- )
- } else {
- panic!("TestBasicWithLargeDoc schema must be a record schema")
- }
- assert_eq!(schema, TestBasicWithLargeDoc::get_schema());
+#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
+struct TestBasicWithBool {
+ a: bool,
+ b: Option<bool>,
+}
+
+proptest! {
+#[test]
+fn avro_3634_test_basic_with_bool(a in any::<bool>(), b in any::<Option<bool>>()) {
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"TestBasicWithBool",
+ "fields":[
+ {
+ "name":"a",
+ "type":"boolean"
+ },
+ {
+ "name":"b",
+ "type":["null","boolean"]
+ }
+ ]
+ }
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ let derived_schema = TestBasicWithBool::get_schema();
+
+ if let Schema::Record(RecordSchema { name, .. }) = derived_schema {
+ assert_eq!("TestBasicWithBool", name.fullname(None))
+ } else {
+ panic!("TestBasicWithBool schema must be a record schema")
}
+ assert_eq!(schema, TestBasicWithBool::get_schema());
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
- struct TestBasicWithBool {
- a: bool,
- b: Option<bool>,
- }
-
- proptest! {
- #[test]
- fn avro_3634_test_basic_with_bool(a in any::<bool>(), b in any::<Option<bool>>()) {
- let schema = r#"
- {
- "type":"record",
- "name":"TestBasicWithBool",
- "fields":[
- {
- "name":"a",
- "type":"boolean"
- },
- {
- "name":"b",
- "type":["null","boolean"]
- }
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- let derived_schema = TestBasicWithBool::get_schema();
+ serde_assert(TestBasicWithBool { a, b });
+}}
- if let Schema::Record(RecordSchema { name, .. }) = derived_schema {
- assert_eq!("TestBasicWithBool", name.fullname(None))
- } else {
- panic!("TestBasicWithBool schema must be a record schema")
- }
- assert_eq!(schema, TestBasicWithBool::get_schema());
+#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
+struct TestBasicWithU32 {
+ a: u32,
+}
- serde_assert(TestBasicWithBool { a, b });
- }}
+proptest! {
+#[test]
+fn test_basic_with_u32(a in any::<u32>()) {
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"TestBasicWithU32",
+ "fields":[
+ {
+ "name":"a",
+ "type":"long"
+ }
+ ]
+ }
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ if let Schema::Record(RecordSchema { name, .. }) = TestBasicWithU32::get_schema() {
+ assert_eq!("TestBasicWithU32", name.fullname(None))
+ } else {
+ panic!("TestBasicWithU32 schema must be a record schema")
+ }
+ assert_eq!(schema, TestBasicWithU32::get_schema());
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
- struct TestBasicWithU32 {
- a: u32,
- }
-
- proptest! {
- #[test]
- fn test_basic_with_u32(a in any::<u32>()) {
- let schema = r#"
- {
- "type":"record",
- "name":"TestBasicWithU32",
- "fields":[
- {
- "name":"a",
- "type":"long"
- }
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- if let Schema::Record(RecordSchema { name, .. }) = TestBasicWithU32::get_schema() {
- assert_eq!("TestBasicWithU32", name.fullname(None))
- } else {
- panic!("TestBasicWithU32 schema must be a record schema")
- }
- assert_eq!(schema, TestBasicWithU32::get_schema());
+ serde_assert(TestBasicWithU32 { a });
+}}
- serde_assert(TestBasicWithU32 { a });
- }}
+#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
+#[avro(alias = "a", alias = "b", alias = "c")]
+struct TestBasicStructWithAliases {
+ a: i32,
+}
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
- #[avro(alias = "a", alias = "b", alias = "c")]
- struct TestBasicStructWithAliases {
- a: i32,
+#[test]
+fn test_basic_struct_with_aliases() {
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"TestBasicStructWithAliases",
+ "aliases":["a", "b", "c"],
+ "fields":[
+ {
+ "name":"a",
+ "type":"int"
+ }
+ ]
+ }
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ if let Schema::Record(RecordSchema { name, aliases, .. }) =
+ TestBasicStructWithAliases::get_schema()
+ {
+ assert_eq!("TestBasicStructWithAliases", name.fullname(None));
+ assert_eq!(
+ Some(vec![
+ Alias::new("a").unwrap(),
+ Alias::new("b").unwrap(),
+ Alias::new("c").unwrap()
+ ]),
+ aliases
+ );
+ } else {
+ panic!("TestBasicStructWithAliases schema must be a record schema")
}
+ assert_eq!(schema, TestBasicStructWithAliases::get_schema());
- #[test]
- fn test_basic_struct_with_aliases() {
- let schema = r#"
- {
- "type":"record",
- "name":"TestBasicStructWithAliases",
- "aliases":["a", "b", "c"],
- "fields":[
- {
- "name":"a",
- "type":"int"
- }
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- if let Schema::Record(RecordSchema { name, aliases, .. }) =
- TestBasicStructWithAliases::get_schema()
- {
- assert_eq!("TestBasicStructWithAliases", name.fullname(None));
- assert_eq!(
- Some(vec![
- Alias::new("a").unwrap(),
- Alias::new("b").unwrap(),
- Alias::new("c").unwrap()
- ]),
- aliases
- );
- } else {
- panic!("TestBasicStructWithAliases schema must be a record schema")
- }
- assert_eq!(schema, TestBasicStructWithAliases::get_schema());
+ serde_assert(TestBasicStructWithAliases { a: i32::MAX });
+}
- serde_assert(TestBasicStructWithAliases { a: i32::MAX });
- }
+#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
+#[avro(alias = "d")]
+#[avro(alias = "e")]
+#[avro(alias = "f")]
+struct TestBasicStructWithAliases2 {
+ a: i32,
+}
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
- #[avro(alias = "d")]
- #[avro(alias = "e")]
- #[avro(alias = "f")]
- struct TestBasicStructWithAliases2 {
- a: i32,
+#[test]
+fn test_basic_struct_with_aliases2() {
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"TestBasicStructWithAliases2",
+ "aliases":["d", "e", "f"],
+ "fields":[
+ {
+ "name":"a",
+ "type":"int"
+ }
+ ]
}
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ if let Schema::Record(RecordSchema { name, aliases, .. }) =
+ TestBasicStructWithAliases2::get_schema()
+ {
+ assert_eq!("TestBasicStructWithAliases2", name.fullname(None));
+ assert_eq!(
+ Some(vec![
+ Alias::new("d").unwrap(),
+ Alias::new("e").unwrap(),
+ Alias::new("f").unwrap()
+ ]),
+ aliases
+ );
+ } else {
+ panic!("TestBasicStructWithAliases2 schema must be a record schema")
+ }
+ assert_eq!(schema, TestBasicStructWithAliases2::get_schema());
- #[test]
- fn test_basic_struct_with_aliases2() {
- let schema = r#"
- {
- "type":"record",
- "name":"TestBasicStructWithAliases2",
- "aliases":["d", "e", "f"],
- "fields":[
- {
- "name":"a",
- "type":"int"
- }
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- if let Schema::Record(RecordSchema { name, aliases, .. }) =
- TestBasicStructWithAliases2::get_schema()
- {
- assert_eq!("TestBasicStructWithAliases2", name.fullname(None));
- assert_eq!(
- Some(vec![
- Alias::new("d").unwrap(),
- Alias::new("e").unwrap(),
- Alias::new("f").unwrap()
- ]),
- aliases
- );
- } else {
- panic!("TestBasicStructWithAliases2 schema must be a record schema")
- }
- assert_eq!(schema, TestBasicStructWithAliases2::get_schema());
+ serde_assert(TestBasicStructWithAliases2 { a: i32::MAX });
+}
- serde_assert(TestBasicStructWithAliases2 { a: i32::MAX });
- }
+#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
+#[avro(alias = "a", alias = "b", alias = "c")]
+enum TestBasicEnumWithAliases {
+ A,
+ B,
+}
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
- #[avro(alias = "a", alias = "b", alias = "c")]
- enum TestBasicEnumWithAliases {
- A,
- B,
+#[test]
+fn test_basic_enum_with_aliases() {
+ let schema = r#"
+ {
+ "type":"enum",
+ "name":"TestBasicEnumWithAliases",
+ "aliases":["a", "b", "c"],
+ "symbols":[
+ "A",
+ "B"
+ ]
+ }
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ if let Schema::Enum(EnumSchema { name, aliases, .. }) = TestBasicEnumWithAliases::get_schema() {
+ assert_eq!("TestBasicEnumWithAliases", name.fullname(None));
+ assert_eq!(
+ Some(vec![
+ Alias::new("a").unwrap(),
+ Alias::new("b").unwrap(),
+ Alias::new("c").unwrap()
+ ]),
+ aliases
+ );
+ } else {
+ panic!("TestBasicEnumWithAliases schema must be an enum schema")
}
+ assert_eq!(schema, TestBasicEnumWithAliases::get_schema());
- #[test]
- fn test_basic_enum_with_aliases() {
- let schema = r#"
- {
- "type":"enum",
- "name":"TestBasicEnumWithAliases",
- "aliases":["a", "b", "c"],
- "symbols":[
- "A",
- "B"
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- if let Schema::Enum(EnumSchema { name, aliases, .. }) =
- TestBasicEnumWithAliases::get_schema()
- {
- assert_eq!("TestBasicEnumWithAliases", name.fullname(None));
- assert_eq!(
- Some(vec![
- Alias::new("a").unwrap(),
- Alias::new("b").unwrap(),
- Alias::new("c").unwrap()
- ]),
- aliases
- );
- } else {
- panic!("TestBasicEnumWithAliases schema must be an enum schema")
- }
- assert_eq!(schema, TestBasicEnumWithAliases::get_schema());
+ serde_assert(TestBasicEnumWithAliases::A);
+}
- serde_assert(TestBasicEnumWithAliases::A);
- }
+#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
+#[avro(alias = "d")]
+#[avro(alias = "e")]
+#[avro(alias = "f")]
+enum TestBasicEnumWithAliases2 {
+ A,
+ B,
+}
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
- #[avro(alias = "d")]
- #[avro(alias = "e")]
- #[avro(alias = "f")]
- enum TestBasicEnumWithAliases2 {
- A,
- B,
+#[test]
+fn test_basic_enum_with_aliases2() {
+ let schema = r#"
+ {
+ "type":"enum",
+ "name":"TestBasicEnumWithAliases2",
+ "aliases":["d", "e", "f"],
+ "symbols":[
+ "A",
+ "B"
+ ]
}
+ "#;
+ let schema = Schema::parse_str(schema).unwrap();
+ if let Schema::Enum(EnumSchema { name, aliases, .. }) = TestBasicEnumWithAliases2::get_schema()
+ {
+ assert_eq!("TestBasicEnumWithAliases2", name.fullname(None));
+ assert_eq!(
+ Some(vec![
+ Alias::new("d").unwrap(),
+ Alias::new("e").unwrap(),
+ Alias::new("f").unwrap()
+ ]),
+ aliases
+ );
+ } else {
+ panic!("TestBasicEnumWithAliases2 schema must be an enum schema")
+ }
+ assert_eq!(schema, TestBasicEnumWithAliases2::get_schema());
- #[test]
- fn test_basic_enum_with_aliases2() {
- let schema = r#"
- {
- "type":"enum",
- "name":"TestBasicEnumWithAliases2",
- "aliases":["d", "e", "f"],
- "symbols":[
- "A",
- "B"
- ]
- }
- "#;
- let schema = Schema::parse_str(schema).unwrap();
- if let Schema::Enum(EnumSchema { name, aliases, .. }) =
- TestBasicEnumWithAliases2::get_schema()
- {
- assert_eq!("TestBasicEnumWithAliases2", name.fullname(None));
- assert_eq!(
- Some(vec![
- Alias::new("d").unwrap(),
- Alias::new("e").unwrap(),
- Alias::new("f").unwrap()
- ]),
- aliases
- );
- } else {
- panic!("TestBasicEnumWithAliases2 schema must be an enum schema")
- }
- assert_eq!(schema, TestBasicEnumWithAliases2::get_schema());
+ serde_assert(TestBasicEnumWithAliases2::B);
+}
- serde_assert(TestBasicEnumWithAliases2::B);
+#[test]
+fn test_basic_struct_with_defaults() {
+ #[derive(Debug, Deserialize, Serialize, AvroSchema, Clone, PartialEq, Eq)]
+ enum MyEnum {
+ Foo,
+ Bar,
+ Baz,
}
- #[test]
- fn test_basic_struct_with_defaults() {
- #[derive(Debug, Deserialize, Serialize, AvroSchema, Clone, PartialEq, Eq)]
- enum MyEnum {
- Foo,
- Bar,
- Baz,
- }
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ struct TestBasicStructWithDefaultValues {
+ #[avro(default = "123")]
+ a: i32,
+ #[avro(default = r#""The default value for 'b'""#)]
+ b: String,
+ #[avro(default = "true")]
+ condition: bool,
+ // no default value for 'c'
+ c: f64,
+ #[avro(default = r#"{"a": 1, "b": 2}"#)]
+ map: HashMap<String, i32>,
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
- struct TestBasicStructWithDefaultValues {
- #[avro(default = "123")]
- a: i32,
- #[avro(default = r#""The default value for 'b'""#)]
- b: String,
- #[avro(default = "true")]
- condition: bool,
- // no default value for 'c'
- c: f64,
- #[avro(default = r#"{"a": 1, "b": 2}"#)]
- map: HashMap<String, i32>,
-
- #[avro(default = "[1, 2, 3]")]
- array: Vec<i32>,
-
- #[avro(default = r#""Foo""#)]
- myenum: MyEnum,
-
- #[avro(default = "null")]
- optional: Option<String>,
- }
+ #[avro(default = "[1, 2, 3]")]
+ array: Vec<i32>,
- let schema = r#"
- {
- "type":"record",
- "name":"TestBasicStructWithDefaultValues",
- "fields": [
- {
- "name":"a",
- "type":"int",
- "default":123
- },
- {
- "name":"b",
- "type":"string",
- "default": "The default value for 'b'"
+ #[avro(default = r#""Foo""#)]
+ myenum: MyEnum,
+
+ #[avro(default = "null")]
+ optional: Option<String>,
+ }
+
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"TestBasicStructWithDefaultValues",
+ "fields": [
+ {
+ "name":"a",
+ "type":"int",
+ "default":123
+ },
+ {
+ "name":"b",
+ "type":"string",
+ "default": "The default value for 'b'"
+ },
+ {
+ "name":"condition",
+ "type":"boolean",
+ "default":true
+ },
+ {
+ "name":"c",
+ "type":"double"
+ },
+ {
+ "name":"map",
+ "type":{
+ "type":"map",
+ "values":"int"
},
- {
- "name":"condition",
- "type":"boolean",
- "default":true
+ "default": {
+ "a": 1,
+ "b": 2
+ }
+ },
+ {
+ "name":"array",
+ "type":{
+ "type":"array",
+ "items":"int"
},
- {
- "name":"c",
- "type":"double"
+ "default": [1, 2, 3]
+ },
+ {
+ "name":"myenum",
+ "type":{
+ "type":"enum",
+ "name":"MyEnum",
+ "symbols":["Foo", "Bar", "Baz"]
},
- {
- "name":"map",
- "type":{
- "type":"map",
- "values":"int"
- },
- "default": {
+ "default":"Foo"
+ },
+ {
+ "name":"optional",
+ "type": ["null", "string"],
+ "default": null
+ }
+ ]
+ }
+ "#;
+
+ let schema = Schema::parse_str(schema).unwrap();
+ if let Schema::Record(RecordSchema { name, fields, .. }) =
+ TestBasicStructWithDefaultValues::get_schema()
+ {
+ assert_eq!("TestBasicStructWithDefaultValues", name.fullname(None));
+ use serde_json::json;
+ for field in fields {
+ match field.name.as_str() {
+ "a" => assert_eq!(Some(json!(123_i32)), field.default),
+ "b" => assert_eq!(
+ Some(json!(r#"The default value for 'b'"#.to_owned())),
+ field.default
+ ),
+ "condition" => assert_eq!(Some(json!(true)), field.default),
+ "array" => assert_eq!(Some(json!([1, 2, 3])), field.default),
+ "map" => assert_eq!(
+ Some(json!({
"a": 1,
"b": 2
- }
- },
- {
- "name":"array",
- "type":{
- "type":"array",
- "items":"int"
- },
- "default": [1, 2, 3]
- },
- {
- "name":"myenum",
- "type":{
- "type":"enum",
- "name":"MyEnum",
- "symbols":["Foo", "Bar", "Baz"]
- },
- "default":"Foo"
- },
- {
- "name":"optional",
- "type": ["null", "string"],
- "default": null
- }
- ]
- }
- "#;
-
- let schema = Schema::parse_str(schema).unwrap();
- if let Schema::Record(RecordSchema { name, fields, .. }) =
- TestBasicStructWithDefaultValues::get_schema()
- {
- assert_eq!("TestBasicStructWithDefaultValues", name.fullname(None));
- use serde_json::json;
- for field in fields {
- match field.name.as_str() {
- "a" => assert_eq!(Some(json!(123_i32)), field.default),
- "b" => assert_eq!(
- Some(json!(r#"The default value for 'b'"#.to_owned())),
- field.default
- ),
- "condition" => assert_eq!(Some(json!(true)), field.default),
- "array" => assert_eq!(Some(json!([1, 2, 3])), field.default),
- "map" => assert_eq!(
- Some(json!({
- "a": 1,
- "b": 2
- })),
- field.default
- ),
- "c" => assert_eq!(None, field.default),
- "myenum" => assert_eq!(Some(json!("Foo")), field.default),
- "optional" => assert_eq!(Some(json!(null)), field.default),
- _ => panic!("Unexpected field name"),
- }
+ })),
+ field.default
+ ),
+ "c" => assert_eq!(None, field.default),
+ "myenum" => assert_eq!(Some(json!("Foo")), field.default),
+ "optional" => assert_eq!(Some(json!(null)), field.default),
+ _ => panic!("Unexpected field name"),
}
- } else {
- panic!("TestBasicStructWithDefaultValues schema must be a record schema")
- }
- assert_eq!(schema, TestBasicStructWithDefaultValues::get_schema());
-
- serde_assert(TestBasicStructWithDefaultValues {
- a: 321,
- b: "A custom value for 'b'".to_owned(),
- condition: false,
- c: 987.654,
- map: [("a".to_owned(), 1), ("b".to_owned(), 2)]
- .iter()
- .cloned()
- .collect(),
- array: vec![4, 5, 6],
- myenum: MyEnum::Bar,
- optional: None,
- });
- }
-
- #[test]
- fn avro_3633_test_basic_struct_with_skip_attribute() {
- // Note: If using the skip attribute together with serialization,
- // the serde's skip attribute needs also to be added
-
- #[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
- struct TestBasicStructNoSchema {
- field: bool,
}
+ } else {
+ panic!("TestBasicStructWithDefaultValues schema must be a record schema")
+ }
+ assert_eq!(schema, TestBasicStructWithDefaultValues::get_schema());
+
+ serde_assert(TestBasicStructWithDefaultValues {
+ a: 321,
+ b: "A custom value for 'b'".to_owned(),
+ condition: false,
+ c: 987.654,
+ map: [("a".to_owned(), 1), ("b".to_owned(), 2)]
+ .iter()
+ .cloned()
+ .collect(),
+ array: vec![4, 5, 6],
+ myenum: MyEnum::Bar,
+ optional: None,
+ });
+}
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
- struct TestBasicStructWithSkipAttribute {
- #[avro(skip)]
- #[serde(skip)]
- condition: bool,
- #[avro(skip = false)]
- a: f64,
- #[avro(skip)]
- #[serde(skip)]
- map: HashMap<String, i32>,
- array: Vec<i32>,
- #[avro(skip = true)]
- #[serde(skip)]
- mystruct: TestBasicStructNoSchema,
- b: i32,
- }
+#[test]
+fn avro_3633_test_basic_struct_with_skip_attribute() {
+ // Note: If using the skip attribute together with serialization,
+ // the serde's skip attribute needs also to be added
- let schema = r#"
- {
- "type":"record",
- "name":"TestBasicStructWithSkipAttribute",
- "fields": [
- {
- "name":"a",
- "type":"double"
- },
- {
- "name":"array",
- "type":{
- "type":"array",
- "items":"int"
- }
- },
- {
- "name":"b",
- "type":"int"
- }
- ]
- }
- "#;
-
- let schema = Schema::parse_str(schema).unwrap();
- let derived_schema = TestBasicStructWithSkipAttribute::get_schema();
- if let Schema::Record(RecordSchema { name, fields, .. }) = &derived_schema {
- assert_eq!("TestBasicStructWithSkipAttribute", name.fullname(None));
- for field in fields {
- match field.name.as_str() {
- "condition" => panic!("Unexpected field 'condition'"),
- "mystruct" => panic!("Unexpected field 'mystruct'"),
- "map" => panic!("Unexpected field 'map'"),
- _ => {}
- }
- }
- } else {
- panic!(
- "TestBasicStructWithSkipAttribute schema must be a record schema: {derived_schema:?}"
- )
- }
- assert_eq!(schema, derived_schema);
-
- // Note: If serde's `skip` attribute is used on a field, the field's type
- // needs the trait 'Default' to be implemented, since it is skipping the serialization process.
- // Copied or cloned objects within 'serde_assert()' doesn't "copy" (serialize/deserialze)
- // these fields, so no values are initialized here for skipped fields.
- serde_assert(TestBasicStructWithSkipAttribute {
- condition: bool::default(), // <- skipped
- a: 987.654,
- map: HashMap::default(), // <- skipped
- array: vec![4, 5, 6],
- mystruct: TestBasicStructNoSchema::default(), // <- skipped
- b: 321,
- });
- }
-
- #[test]
- fn avro_3633_test_basic_struct_with_rename_attribute() {
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
- struct TestBasicStructWithRenameAttribute {
- #[avro(rename = "a1")]
- #[serde(rename = "a1")]
- a: bool,
- b: i32,
- #[avro(rename = "c1")]
- #[serde(rename = "c1")]
- c: f32,
- }
+ #[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
+ struct TestBasicStructNoSchema {
+ field: bool,
+ }
- let schema = r#"
- {
- "type":"record",
- "name":"TestBasicStructWithRenameAttribute",
- "fields": [
- {
- "name":"a1",
- "type":"boolean"
- },
- {
- "name":"b",
- "type":"int"
- },
- {
- "name":"c1",
- "type":"float"
- }
- ]
- }
- "#;
-
- let schema = Schema::parse_str(schema).unwrap();
- let derived_schema = TestBasicStructWithRenameAttribute::get_schema();
- if let Schema::Record(RecordSchema { name, fields, .. }) = &derived_schema {
- assert_eq!("TestBasicStructWithRenameAttribute", name.fullname(None));
- for field in fields {
- match field.name.as_str() {
- "a" => panic!("Unexpected field name 'a': must be 'a1'"),
- "c" => panic!("Unexpected field name 'c': must be 'c1'"),
- _ => {}
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ struct TestBasicStructWithSkipAttribute {
+ #[avro(skip)]
+ #[serde(skip)]
+ condition: bool,
+ #[avro(skip = false)]
+ a: f64,
+ #[avro(skip)]
+ #[serde(skip)]
+ map: HashMap<String, i32>,
+ array: Vec<i32>,
+ #[avro(skip = true)]
+ #[serde(skip)]
+ mystruct: TestBasicStructNoSchema,
+ b: i32,
+ }
+
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"TestBasicStructWithSkipAttribute",
+ "fields": [
+ {
+ "name":"a",
+ "type":"double"
+ },
+ {
+ "name":"array",
+ "type":{
+ "type":"array",
+ "items":"int"
}
+ },
+ {
+ "name":"b",
+ "type":"int"
+ }
+ ]
+ }
+ "#;
+
+ let schema = Schema::parse_str(schema).unwrap();
+ let derived_schema = TestBasicStructWithSkipAttribute::get_schema();
+ if let Schema::Record(RecordSchema { name, fields, .. }) = &derived_schema {
+ assert_eq!("TestBasicStructWithSkipAttribute", name.fullname(None));
+ for field in fields {
+ match field.name.as_str() {
+ "condition" => panic!("Unexpected field 'condition'"),
+ "mystruct" => panic!("Unexpected field 'mystruct'"),
+ "map" => panic!("Unexpected field 'map'"),
+ _ => {}
}
- } else {
- panic!(
- "TestBasicStructWithRenameAttribute schema must be a record schema: {derived_schema:?}"
- )
}
- assert_eq!(schema, derived_schema);
-
- serde_assert(TestBasicStructWithRenameAttribute {
- a: true,
- b: 321,
- c: 987.654,
- });
+ } else {
+ panic!(
+ "TestBasicStructWithSkipAttribute schema must be a record schema: {derived_schema:?}"
+ )
}
+ assert_eq!(schema, derived_schema);
+
+ // Note: If serde's `skip` attribute is used on a field, the field's type
+ // needs the trait 'Default' to be implemented, since it is skipping the serialization process.
+ // Copied or cloned objects within 'serde_assert()' doesn't "copy" (serialize/deserialze)
+ // these fields, so no values are initialized here for skipped fields.
+ serde_assert(TestBasicStructWithSkipAttribute {
+ condition: bool::default(), // <- skipped
+ a: 987.654,
+ map: HashMap::default(), // <- skipped
+ array: vec![4, 5, 6],
+ mystruct: TestBasicStructNoSchema::default(), // <- skipped
+ b: 321,
+ });
+}
- #[test]
- fn test_avro_3663_raw_identifier_field_name() {
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
- struct TestRawIdent {
- r#type: bool,
- }
+#[test]
+fn avro_3633_test_basic_struct_with_rename_attribute() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ struct TestBasicStructWithRenameAttribute {
+ #[avro(rename = "a1")]
+ #[serde(rename = "a1")]
+ a: bool,
+ b: i32,
+ #[avro(rename = "c1")]
+ #[serde(rename = "c1")]
+ c: f32,
+ }
- let derived_schema = TestRawIdent::get_schema();
- if let Schema::Record(RecordSchema { fields, .. }) = derived_schema {
- let field = fields.first().expect("TestRawIdent must contain a field");
- assert_eq!(field.name, "type");
- } else {
- panic!("Unexpected schema type for {derived_schema:?}")
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"TestBasicStructWithRenameAttribute",
+ "fields": [
+ {
+ "name":"a1",
+ "type":"boolean"
+ },
+ {
+ "name":"b",
+ "type":"int"
+ },
+ {
+ "name":"c1",
+ "type":"float"
+ }
+ ]
+ }
+ "#;
+
+ let schema = Schema::parse_str(schema).unwrap();
+ let derived_schema = TestBasicStructWithRenameAttribute::get_schema();
+ if let Schema::Record(RecordSchema { name, fields, .. }) = &derived_schema {
+ assert_eq!("TestBasicStructWithRenameAttribute", name.fullname(None));
+ for field in fields {
+ match field.name.as_str() {
+ "a" => panic!("Unexpected field name 'a': must be 'a1'"),
+ "c" => panic!("Unexpected field name 'c': must be 'c1'"),
+ _ => {}
+ }
}
+ } else {
+ panic!(
+ "TestBasicStructWithRenameAttribute schema must be a record schema: {derived_schema:?}"
+ )
}
+ assert_eq!(schema, derived_schema);
- #[test]
- fn avro_3962_fields_documentation() {
- /// Foo docs
- #[derive(AvroSchema)]
- #[allow(dead_code)]
- struct Foo {
- /// a's Rustdoc
- a: i32,
- /// b's Rustdoc
- #[avro(doc = "attribute doc has priority over Rustdoc")]
- b: i32,
- }
+ serde_assert(TestBasicStructWithRenameAttribute {
+ a: true,
+ b: 321,
+ c: 987.654,
+ });
+}
- if let Schema::Record(RecordSchema { fields, .. }) = Foo::get_schema() {
- assert_eq!(fields[0].doc, Some("a's Rustdoc".to_string()));
- assert_eq!(
- fields[1].doc,
- Some("attribute doc has priority over Rustdoc".to_string())
- );
- } else {
- panic!("Unexpected schema type for Foo")
- }
+#[test]
+fn test_avro_3663_raw_identifier_field_name() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ struct TestRawIdent {
+ r#type: bool,
}
- #[test]
- fn avro_rs_247_serde_flatten_support() {
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
- struct Nested {
- a: bool,
- }
+ let derived_schema = TestRawIdent::get_schema();
+ if let Schema::Record(RecordSchema { fields, .. }) = derived_schema {
+ let field = fields.first().expect("TestRawIdent must contain a field");
+ assert_eq!(field.name, "type");
+ } else {
+ panic!("Unexpected schema type for {derived_schema:?}")
+ }
+}
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
- struct Foo {
- #[serde(flatten)]
- #[avro(flatten)]
- nested: Nested,
- b: i32,
- }
+#[test]
+fn avro_3962_fields_documentation() {
+ /// Foo docs
+ #[derive(AvroSchema)]
+ #[allow(dead_code)]
+ struct Foo {
+ /// a's Rustdoc
+ a: i32,
+ /// b's Rustdoc
+ #[avro(doc = "attribute doc has priority over Rustdoc")]
+ b: i32,
+ }
- let schema = r#"
- {
- "type":"record",
- "name":"Foo",
- "fields": [
- {
- "name":"a",
- "type":"boolean"
- },
- {
- "name":"b",
- "type":"int"
- }
- ]
- }
- "#;
+ if let Schema::Record(RecordSchema { fields, .. }) = Foo::get_schema() {
+ assert_eq!(fields[0].doc, Some("a's Rustdoc".to_string()));
+ assert_eq!(
+ fields[1].doc,
+ Some("attribute doc has priority over Rustdoc".to_string())
+ );
+ } else {
+ panic!("Unexpected schema type for Foo")
+ }
+}
- let schema = Schema::parse_str(schema).unwrap();
- assert_eq!(schema, Foo::get_schema());
+#[test]
+fn avro_rs_247_serde_flatten_support() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ struct Nested {
+ a: bool,
+ }
- serde_assert(Foo {
- nested: Nested { a: true },
- b: 321,
- });
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ struct Foo {
+ #[serde(flatten)]
+ #[avro(flatten)]
+ nested: Nested,
+ b: i32,
}
- #[test]
- fn avro_rs_247_serde_nested_flatten_support() {
- use apache_avro::AvroSchema;
- use serde::{Deserialize, Serialize};
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"Foo",
+ "fields": [
+ {
+ "name":"a",
+ "type":"boolean"
+ },
+ {
+ "name":"b",
+ "type":"int"
+ }
+ ]
+ }
+ "#;
- #[derive(AvroSchema, Debug, Clone, PartialEq, Serialize, Deserialize)]
- pub struct NestedFoo {
- one: u32,
- }
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, Foo::get_schema());
- #[derive(AvroSchema, Debug, Clone, PartialEq, Serialize, Deserialize)]
- pub struct Foo {
- #[serde(flatten)]
- #[avro(flatten)]
- nested_foo: NestedFoo,
- }
+ serde_assert(Foo {
+ nested: Nested { a: true },
+ b: 321,
+ });
+}
- #[derive(AvroSchema, Debug, Clone, PartialEq, Serialize, Deserialize)]
- struct Bar {
- foo: Foo,
- two: u32,
- }
+#[test]
+fn avro_rs_247_serde_nested_flatten_support() {
+ use apache_avro::AvroSchema;
+ use serde::{Deserialize, Serialize};
- let schema = r#"
- {
- "type":"record",
- "name":"Bar",
- "fields": [
- {
- "name":"foo",
- "type": {
- "type": "record",
- "name": "Foo",
- "fields": [
- {
- "name": "one",
- "type": "long"
- }
- ]
- }
- },
- {
- "name":"two",
- "type":"long"
- }
- ]
- }
- "#;
+ #[derive(AvroSchema, Debug, Clone, PartialEq, Serialize, Deserialize)]
+ pub struct NestedFoo {
+ one: u32,
+ }
- let schema = Schema::parse_str(schema).unwrap();
- assert_eq!(schema, Bar::get_schema());
+ #[derive(AvroSchema, Debug, Clone, PartialEq, Serialize, Deserialize)]
+ pub struct Foo {
+ #[serde(flatten)]
+ #[avro(flatten)]
+ nested_foo: NestedFoo,
+ }
- serde_assert(Bar {
- foo: Foo {
- nested_foo: NestedFoo { one: 42 },
+ #[derive(AvroSchema, Debug, Clone, PartialEq, Serialize, Deserialize)]
+ struct Bar {
+ foo: Foo,
+ two: u32,
+ }
+
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"Bar",
+ "fields": [
+ {
+ "name":"foo",
+ "type": {
+ "type": "record",
+ "name": "Foo",
+ "fields": [
+ {
+ "name": "one",
+ "type": "long"
+ }
+ ]
+ }
},
- two: 2,
- });
+ {
+ "name":"two",
+ "type":"long"
+ }
+ ]
}
+ "#;
- #[test]
- #[should_panic(expected = "Duplicate field names found")]
- fn avro_rs_247_serde_flatten_support_duplicate_field_name() {
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
- struct Nested {
- a: i32,
- }
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, Bar::get_schema());
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
- struct Foo {
- #[serde(flatten)]
- #[avro(flatten)]
- nested: Nested,
- a: i32,
- }
+ serde_assert(Bar {
+ foo: Foo {
+ nested_foo: NestedFoo { one: 42 },
+ },
+ two: 2,
+ });
+}
- Foo::get_schema();
+#[test]
+#[should_panic(expected = "Duplicate field names found")]
+fn avro_rs_247_serde_flatten_support_duplicate_field_name() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ struct Nested {
+ a: i32,
}
- #[test]
- fn avro_rs_247_serde_flatten_support_with_skip() {
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
- struct Nested {
- a: bool,
- #[serde(skip)]
- #[avro(skip)]
- c: f64,
- }
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ struct Foo {
+ #[serde(flatten)]
+ #[avro(flatten)]
+ nested: Nested,
+ a: i32,
+ }
- #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
- struct Foo {
- #[serde(flatten)]
- #[avro(flatten)]
- nested: Nested,
- b: i32,
- }
+ Foo::get_schema();
+}
- let schema = r#"
- {
- "type":"record",
- "name":"Foo",
- "fields": [
- {
- "name":"a",
- "type":"boolean"
- },
- {
- "name":"b",
- "type":"int"
- }
- ]
- }
- "#;
+#[test]
+fn avro_rs_247_serde_flatten_support_with_skip() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ struct Nested {
+ a: bool,
+ #[serde(skip)]
+ #[avro(skip)]
+ c: f64,
+ }
- let schema = Schema::parse_str(schema).unwrap();
- assert_eq!(schema, Foo::get_schema());
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ struct Foo {
+ #[avro(flatten)]
+ #[serde(flatten)]
+ nested: Nested,
+ b: i32,
+ }
- serde_assert(Foo {
- nested: Nested { a: true, c: 0.0 },
- b: 321,
- });
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"Foo",
+ "fields": [
+ {
+ "name":"a",
+ "type":"boolean"
+ },
+ {
+ "name":"b",
+ "type":"int"
+ }
+ ]
}
+ "#;
+
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, Foo::get_schema());
+
+ serde_assert(Foo {
+ nested: Nested { a: true, c: 0.0 },
+ b: 321,
+ });
}
diff --git a/avro_derive/tests/serde.rs b/avro_derive/tests/serde.rs
new file mode 100644
index 0000000..5e57e78
--- /dev/null
+++ b/avro_derive/tests/serde.rs
@@ -0,0 +1,698 @@
+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_xxx_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_xxx_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",
+ "fields": [
+ {
+ "name":"a",
+ "type":"string"
+ },
+ {
+ "name":"b",
+ "type":"int"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ "#;
+
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, Outer::get_schema());
+
+ serde_assert(Outer {
+ bar: Foo {
+ a: "spam".to_string(),
+ b: 321,
+ },
+ });
+ }
+
+ #[test]
+ fn avro_rs_xxx_rename_all() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ #[serde(rename_all = "UPPERCASE")]
+ #[avro(rename_all = "UPPERCASE")]
+ 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_xxx_rename_all_no_avro() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ #[serde(rename_all = "UPPERCASE")]
+ 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_xxx_from_into() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ #[serde(from = "FooFromInto", into = "FooFromInto")]
+ struct Foo {
+ a: String,
+ b: i32,
+ }
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ struct FooFromInto {
+ a: String,
+ b: i32,
+ }
+
+ impl From<FooFromInto> for Foo {
+ fn from(value: FooFromInto) -> Self {
+ Self {
+ a: value.a,
+ b: value.b,
+ }
+ }
+ }
+
+ impl From<Foo> for FooFromInto {
+ fn from(value: Foo) -> Self {
+ Self {
+ a: value.a,
+ b: value.b,
+ }
+ }
+ }
+
+ 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_xxx_from_into_different() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ #[serde(from = "FooFromInto", into = "FooFromInto")]
+ struct Foo {
+ a: String,
+ b: i32,
+ }
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ struct FooFromInto {
+ a: String,
+ b: i32,
+ c: bool,
+ }
+
+ impl From<FooFromInto> for Foo {
+ fn from(value: FooFromInto) -> Self {
+ Self {
+ a: value.a,
+ b: value.b,
+ }
+ }
+ }
+
+ impl From<Foo> for FooFromInto {
+ fn from(value: Foo) -> Self {
+ Self {
+ a: value.a,
+ b: value.b,
+ c: true,
+ }
+ }
+ }
+
+ 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_err(
+ Foo {
+ a: "spam".to_string(),
+ b: 321,
+ },
+ "Invalid field name c",
+ );
+ }
+}
+
+mod variant_attributes {
+ use super::*;
+
+ #[test]
+ fn avro_rs_xxx_rename_no_avro() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ enum Foo {
+ #[serde(rename = "Three")]
+ One,
+ Two,
+ }
+
+ let schema = r#"
+ {
+ "type":"enum",
+ "name":"Foo",
+ "symbols": [
+ "Three", "Two"
+ ]
+ }
+ "#;
+
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, Foo::get_schema());
+
+ serde_assert(Foo::One);
+ }
+
+ #[test]
+ fn avro_rs_xxx_rename() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ enum Foo {
+ #[serde(rename = "Three")]
+ #[avro(rename = "Three")]
+ One,
+ Two,
+ }
+
+ let schema = r#"
+ {
+ "type":"enum",
+ "name":"Foo",
+ "symbols": [
+ "Three", "Two"
+ ]
+ }
+ "#;
+
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, Foo::get_schema());
+
+ serde_assert(Foo::One);
+ }
+
+ #[test]
+ fn avro_rs_xxx_alias() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ enum Foo {
+ #[serde(rename = "Three", alias = "One")]
+ One,
+ Two,
+ }
+
+ let schema = r#"
+ {
+ "type":"enum",
+ "name":"Foo",
+ "symbols": [
+ "Three", "Two"
+ ]
+ }
+ "#;
+
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, Foo::get_schema());
+
+ serde_assert(Foo::One);
+ }
+
+ // This test does not build as avro-derive does not currently support enums with struct variants
+ // #[test]
+ // fn avro_rs_xxx_rename_all_fields() {
+ // #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ // enum Foo {
+ // #[serde(rename_all_fields = "UPPERCASE")]
+ // #[avro(rename_all = "UPPERCASE")]
+ // Bar {
+ // 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::Bar {
+ // a: "spam".to_string(),
+ // b: 321,
+ // });
+ // }
+
+ #[test]
+ fn avro_rs_xxx_skip() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ enum Foo {
+ #[allow(dead_code)]
+ #[serde(skip)]
+ One,
+ Two,
+ }
+
+ let schema = r#"
+ {
+ "type":"enum",
+ "name":"Foo",
+ "symbols": [
+ "One", "Two"
+ ]
+ }
+ "#;
+
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, Foo::get_schema());
+
+ serde_assert(Foo::Two);
+ }
+}
+
+mod field_attributes {
+ use super::*;
+
+ #[test]
+ fn avro_rs_xxx_rename() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ struct Foo {
+ #[serde(rename = "c")]
+ #[avro(rename = "c")]
+ a: String,
+ b: i32,
+ }
+
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"Foo",
+ "fields": [
+ {
+ "name":"c",
+ "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_xxx_rename_no_avro() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ struct Foo {
+ #[serde(rename = "c")]
+ a: String,
+ b: i32,
+ }
+
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"Foo",
+ "fields": [
+ {
+ "name":"c",
+ "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_xxx_flatten() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ struct Nested {
+ a: bool,
+ }
+
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ struct Foo {
+ #[serde(flatten)]
+ #[avro(flatten)]
+ nested: Nested,
+ b: i32,
+ }
+
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"Foo",
+ "fields": [
+ {
+ "name":"a",
+ "type":"boolean"
+ },
+ {
+ "name":"b",
+ "type":"int"
+ }
+ ]
+ }
+ "#;
+
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, Foo::get_schema());
+
+ serde_assert(Foo {
+ nested: Nested { a: true },
+ b: 321,
+ });
+ }
+
+ #[test]
+ fn avro_rs_xxx_flatten_no_avro() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ struct Nested {
+ a: bool,
+ }
+
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ struct Foo {
+ #[serde(flatten)]
+ nested: Nested,
+ b: i32,
+ }
+
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"Foo",
+ "fields": [
+ {
+ "name":"a",
+ "type":"boolean"
+ },
+ {
+ "name":"b",
+ "type":"int"
+ }
+ ]
+ }
+ "#;
+
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, Foo::get_schema());
+
+ serde_assert(Foo {
+ nested: Nested { a: true },
+ b: 321,
+ });
+ }
+
+ #[test]
+ fn avro_rs_xxx_skip() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ struct Foo {
+ #[serde(skip)]
+ #[avro(skip)]
+ a: String,
+ b: i32,
+ }
+
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"Foo",
+ "fields": [
+ {
+ "name":"b",
+ "type":"int"
+ }
+ ]
+ }
+ "#;
+
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, Foo::get_schema());
+
+ serde_assert(Foo {
+ a: "".to_string(),
+ b: 321,
+ });
+ }
+
+ #[test]
+ fn avro_rs_xxx_skip_no_avro() {
+ #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+ struct Foo {
+ #[serde(skip)]
+ a: String,
+ b: i32,
+ }
+
+ let schema = r#"
+ {
+ "type":"record",
+ "name":"Foo",
+ "fields": [
+ {
+ "name":"b",
+ "type":"int"
+ }
+ ]
+ }
+ "#;
+
+ let schema = Schema::parse_str(schema).unwrap();
+ assert_eq!(schema, Foo::get_schema());
+
+ serde_assert(Foo {
+ a: "".to_string(),
+ b: 321,
+ });
+ }
+}
diff --git a/avro_derive/tests/ui.rs b/avro_derive/tests/ui.rs
new file mode 100644
index 0000000..870c2f9
--- /dev/null
+++ b/avro_derive/tests/ui.rs
@@ -0,0 +1,5 @@
+#[test]
+fn ui() {
+ let t = trybuild::TestCases::new();
+ t.compile_fail("tests/ui/*.rs");
+}
diff --git a/avro_derive/tests/ui/avro_rs_226_skip_serializing.rs b/avro_derive/tests/ui/avro_rs_226_skip_serializing.rs
new file mode 100644
index 0000000..a065cdf
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_226_skip_serializing.rs
@@ -0,0 +1,11 @@
+use apache_avro::AvroSchema;
+
+#[derive(AvroSchema)]
+struct T {
+ x: Option<i8>,
+ y: Option<String>,
+ #[serde(skip_serializing)]
+ z: Option<i8>,
+}
+
+fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_226_skip_serializing.stderr b/avro_derive/tests/ui/avro_rs_226_skip_serializing.stderr
new file mode 100644
index 0000000..93557e4
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_226_skip_serializing.stderr
@@ -0,0 +1,5 @@
+error: `#[serde(skip_serializing)]` and `#[serde(skip_serializing_if)]` require `#[avro(default = "..")]
+ --> tests/ui/avro_rs_226_skip_serializing.rs:7:5
+ |
+7 | #[serde(skip_serializing)]
+ | ^
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
new file mode 100644
index 0000000..f9bba75
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_226_skip_serializing_if.rs
@@ -0,0 +1,11 @@
+use apache_avro::AvroSchema;
+
+#[derive(AvroSchema)]
+struct T {
+ x: Option<i8>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ y: Option<String>,
+ z: Option<i8>,
+}
+
+fn main() {}
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
new file mode 100644
index 0000000..c81af3a
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_226_skip_serializing_if.stderr
@@ -0,0 +1,5 @@
+error: `#[serde(skip_serializing)]` and `#[serde(skip_serializing_if)]` require `#[avro(default = "..")]
+ --> tests/ui/avro_rs_226_skip_serializing_if.rs:6:5
+ |
+6 | #[serde(skip_serializing_if = "Option::is_none")]
+ | ^
diff --git a/avro_derive/tests/ui/avro_rs_xxx_remote.rs b/avro_derive/tests/ui/avro_rs_xxx_remote.rs
new file mode 100644
index 0000000..ea4fa2f
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_xxx_remote.rs
@@ -0,0 +1,16 @@
+use apache_avro::AvroSchema;
+
+#[derive(AvroSchema)]
+struct Foo {
+ a: String,
+ b: i32,
+}
+
+#[derive(AvroSchema)]
+#[serde(remote = "Foo")]
+struct FooRemote {
+ a: String,
+ b: i32,
+}
+
+pub fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_xxx_remote.stderr b/avro_derive/tests/ui/avro_rs_xxx_remote.stderr
new file mode 100644
index 0000000..c60d8cc
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_xxx_remote.stderr
@@ -0,0 +1,5 @@
+error: AvroSchema derive does not support the Serde `remote` attribute
+ --> tests/ui/avro_rs_xxx_remote.rs:10:1
+ |
+10 | #[serde(remote = "Foo")]
+ | ^
diff --git a/avro_derive/tests/ui/avro_rs_xxx_rename_all_fields.rs b/avro_derive/tests/ui/avro_rs_xxx_rename_all_fields.rs
new file mode 100644
index 0000000..540f2d3
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_xxx_rename_all_fields.rs
@@ -0,0 +1,12 @@
+use apache_avro::AvroSchema;
+
+#[derive(AvroSchema)]
+#[serde(rename_all_fields = "UPPERCASE")]
+enum Foo {
+ Bar {
+ a: String,
+ b: i32,
+ }
+}
+
+pub fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_xxx_rename_all_fields.stderr b/avro_derive/tests/ui/avro_rs_xxx_rename_all_fields.stderr
new file mode 100644
index 0000000..d1ff21e
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_xxx_rename_all_fields.stderr
@@ -0,0 +1,5 @@
+error: AvroSchema derive does not work for enums with non unit structs
+ --> tests/ui/avro_rs_xxx_rename_all_fields.rs:5:6
+ |
+5 | enum Foo {
+ | ^^^
diff --git a/avro_derive/tests/ui/avro_rs_xxx_skip_de_serializing.rs b/avro_derive/tests/ui/avro_rs_xxx_skip_de_serializing.rs
new file mode 100644
index 0000000..e95003a
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_xxx_skip_de_serializing.rs
@@ -0,0 +1,11 @@
+use apache_avro::AvroSchema;
+
+#[derive(AvroSchema)]
+struct T {
+ x: Option<i8>,
+ y: Option<String>,
+ #[serde(skip_serializing, skip_deserializing)]
+ z: Option<i8>,
+}
+
+fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_xxx_skip_de_serializing.stderr b/avro_derive/tests/ui/avro_rs_xxx_skip_de_serializing.stderr
new file mode 100644
index 0000000..ba1df1a
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_xxx_skip_de_serializing.stderr
@@ -0,0 +1,5 @@
+error: Use `#[serde(skip)]` instead of `#[serde(skip_serializing, skip_deserializing)]`
+ --> tests/ui/avro_rs_xxx_skip_de_serializing.rs:7:5
+ |
+7 | #[serde(skip_serializing, skip_deserializing)]
+ | ^
diff --git a/avro_derive/tests/ui/avro_rs_xxx_tag struct.rs b/avro_derive/tests/ui/avro_rs_xxx_tag struct.rs
new file mode 100644
index 0000000..1859d4f
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_xxx_tag struct.rs
@@ -0,0 +1,10 @@
+use apache_avro::AvroSchema;
+
+#[derive(AvroSchema)]
+#[serde(tag = "bar")]
+struct Foo {
+ a: String,
+ b: i32,
+}
+
+pub fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_xxx_tag struct.stderr b/avro_derive/tests/ui/avro_rs_xxx_tag struct.stderr
new file mode 100644
index 0000000..cf2ebd6
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_xxx_tag struct.stderr
@@ -0,0 +1,5 @@
+error: AvroSchema derive does not support changing the tagging Serde generates (`tag`, `content`, `untagged`, `variant_identifier`, `field_identifier`)
+ --> tests/ui/avro_rs_xxx_tag struct.rs:4:1
+ |
+4 | #[serde(tag = "bar")]
+ | ^
diff --git a/avro_derive/tests/ui/avro_rs_xxx_tag_content_enum.rs b/avro_derive/tests/ui/avro_rs_xxx_tag_content_enum.rs
new file mode 100644
index 0000000..df1cf3e
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_xxx_tag_content_enum.rs
@@ -0,0 +1,10 @@
+use apache_avro::AvroSchema;
+
+#[derive(AvroSchema)]
+#[serde(tag = "bar", content = "spam")]
+enum Foo {
+ One,
+ Two,
+}
+
+pub fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_xxx_tag_content_enum.stderr b/avro_derive/tests/ui/avro_rs_xxx_tag_content_enum.stderr
new file mode 100644
index 0000000..c1ca250
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_xxx_tag_content_enum.stderr
@@ -0,0 +1,5 @@
+error: AvroSchema derive does not support changing the tagging Serde generates (`tag`, `content`, `untagged`, `variant_identifier`, `field_identifier`)
+ --> tests/ui/avro_rs_xxx_tag_content_enum.rs:4:1
+ |
+4 | #[serde(tag = "bar", content = "spam")]
+ | ^
diff --git a/avro_derive/tests/ui/avro_rs_xxx_tag_enum.rs b/avro_derive/tests/ui/avro_rs_xxx_tag_enum.rs
new file mode 100644
index 0000000..d89bd09
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_xxx_tag_enum.rs
@@ -0,0 +1,10 @@
+use apache_avro::AvroSchema;
+
+#[derive(AvroSchema)]
+#[serde(tag = "bar")]
+enum Foo {
+ One,
+ Two,
+}
+
+pub fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_xxx_tag_enum.stderr b/avro_derive/tests/ui/avro_rs_xxx_tag_enum.stderr
new file mode 100644
index 0000000..9cbf28f
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_xxx_tag_enum.stderr
@@ -0,0 +1,5 @@
+error: AvroSchema derive does not support changing the tagging Serde generates (`tag`, `content`, `untagged`, `variant_identifier`, `field_identifier`)
+ --> tests/ui/avro_rs_xxx_tag_enum.rs:4:1
+ |
+4 | #[serde(tag = "bar")]
+ | ^
diff --git a/avro_derive/tests/ui/avro_rs_xxx_transparent.rs b/avro_derive/tests/ui/avro_rs_xxx_transparent.rs
new file mode 100644
index 0000000..0df31ef
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_xxx_transparent.rs
@@ -0,0 +1,15 @@
+use apache_avro::AvroSchema;
+
+#[derive(AvroSchema)]
+struct Foo {
+ a: String,
+ b: i32,
+}
+
+#[derive(AvroSchema)]
+#[serde(transparent)]
+struct Bar {
+ foo: Foo,
+}
+
+pub fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_xxx_transparent.stderr b/avro_derive/tests/ui/avro_rs_xxx_transparent.stderr
new file mode 100644
index 0000000..39b1e5f
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_xxx_transparent.stderr
@@ -0,0 +1,5 @@
+error: AvroSchema derive does not support Serde transparent
+ --> tests/ui/avro_rs_xxx_transparent.rs:10:1
+ |
+10 | #[serde(transparent)]
+ | ^
diff --git a/avro_derive/tests/ui/avro_rs_xxx_untagged_enum.rs b/avro_derive/tests/ui/avro_rs_xxx_untagged_enum.rs
new file mode 100644
index 0000000..29aa6e9
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_xxx_untagged_enum.rs
@@ -0,0 +1,10 @@
+use apache_avro::AvroSchema;
+
+#[derive(AvroSchema)]
+#[serde(untagged)]
+enum Foo {
+ One,
+ Two,
+}
+
+pub fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_xxx_untagged_enum.stderr b/avro_derive/tests/ui/avro_rs_xxx_untagged_enum.stderr
new file mode 100644
index 0000000..8b6df66
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_xxx_untagged_enum.stderr
@@ -0,0 +1,5 @@
+error: AvroSchema derive does not support changing the tagging Serde generates (`tag`, `content`, `untagged`, `variant_identifier`, `field_identifier`)
+ --> tests/ui/avro_rs_xxx_untagged_enum.rs:4:1
+ |
+4 | #[serde(untagged)]
+ | ^