use crate::case::RenameRule;
use darling::{FromAttributes, FromMeta};
use proc_macro2::Span;
use serde_json::Value;
use syn::{AttrStyle, Attribute, Expr, Ident, Path, spanned::Spanned};
mod avro;
mod serde;
#[derive(Debug, PartialEq)]
pub enum Repr {
Enum,
BareUnion { untagged: bool },
UnionOfRecords,
RecordTagContent { tag: String, content: String },
RecordInternallyTagged { tag: String },
}
impl Repr {
fn from_avro_and_serde(
avro: Option<avro::Repr>,
tag: Option<String>,
content: Option<String>,
untagged: bool,
span: Span,
) -> Result<Option<Self>, syn::Error> {
match avro {
Some(avro::Repr::Enum) => {
if tag.is_some() || content.is_some() || untagged {
Err(syn::Error::new(
span,
r#"AvroSchema: `#[avro(repr = "enum")]` is incompatible with `#[serde(tag = "..")]`, `#[serde(content = "..")]`, and `#[serde(untagged)]`"#,
))
} else {
Ok(Some(Self::Enum))
}
}
Some(avro::Repr::BareUnion) => {
if tag.is_some() || content.is_some() {
Err(syn::Error::new(
span,
r#"AvroSchema: `#[avro(repr = "bare_union")]` is incompatible with `#[serde(tag = "..")]` and `#[serde(content = "..")]`"#,
))
} else {
Ok(Some(Self::BareUnion { untagged }))
}
}
Some(avro::Repr::UnionOfRecords) => {
if tag.is_some() || content.is_some() || untagged {
Err(syn::Error::new(
span,
r#"AvroSchema: `#[avro(repr = "union_of_records")]` is incompatible with `#[serde(tag = "..")]`, `#[serde(content = "..")]`, and `#[serde(untagged)]`"#,
))
} else {
Ok(Some(Self::UnionOfRecords))
}
}
Some(avro::Repr::RecordTagContent) => {
if let Some(tag) = tag
&& let Some(content) = content
&& !untagged
{
Ok(Some(Self::RecordTagContent { tag, content }))
} else {
Err(syn::Error::new(
span,
r#"AvroSchema: `#[avro(repr = "record_tag_content")]` requires `#[serde(tag = "..", content = "..")]` and is incompatible with `#[serde(untagged)]`"#,
))
}
}
Some(avro::Repr::RecordInternallyTagged) => {
if let Some(tag) = tag
&& content.is_none()
&& !untagged
{
Ok(Some(Self::RecordInternallyTagged { tag }))
} else {
Err(syn::Error::new(
span,
r#"AvroSchema: `#[avro(repr = "record_internally_tagged")]` requires `#[serde(tag = "..")]` and is incompatible with `#[serde(content = "..")]` and `#[serde(untagged)]`"#,
))
}
}
None => match (tag, content, untagged) {
(Some(tag), Some(content), false) => {
Ok(Some(Self::RecordTagContent { tag, content }))
}
(Some(tag), None, false) => Ok(Some(Self::RecordInternallyTagged { tag })),
(None, None, true) => Ok(Some(Self::BareUnion { untagged: true })),
(None, None, false) => Ok(None),
_ => Err(syn::Error::new(
span,
"AvroSchema: incompatible Serde tagging attributes",
)),
},
}
}
}
#[derive(Default)]
pub struct NamedTypeOptions {
pub name: String,
pub doc: Option<String>,
pub aliases: Vec<String>,
pub rename_all: RenameRule,
pub rename_all_fields: RenameRule,
pub transparent: bool,
pub default: Option<Value>,
pub repr: Option<Repr>,
}
impl NamedTypeOptions {
pub fn new(
ident: &Ident,
attributes: &[Attribute],
span: Span,
) -> Result<Self, Vec<syn::Error>> {
let avro =
avro::ContainerAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
let serde =
serde::ContainerAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
avro.deprecated(span);
let mut errors = Vec::new();
if serde.variant_identifier || serde.field_identifier {
errors.push(syn::Error::new(
span,
"AvroSchema: `#[serde(variant_identifier)]` and `#[serde(field_identifier)]` are not supported",
));
}
if serde.rename_all.deserialize != serde.rename_all.serialize {
errors.push(syn::Error::new(
span,
r#"AvroSchema: rename rules for serializing and deserializing must match (`rename_all(serialize = "..", deserialize = "..")`)"#
));
}
if serde.rename_all_fields.deserialize != serde.rename_all_fields.serialize {
errors.push(syn::Error::new(
span,
r#"AvroSchema: rename rules for serializing and deserializing must match (`rename_all_fields(serialize = "..", deserialize = "..")`)"#
));
}
if avro.name.is_some() && avro.name != serde.rename {
errors.push(syn::Error::new(
span,
r#"AvroSchema: #[avro(name = "..")] must match #[serde(rename = "..")] and it's deprecated. Please use only `#[serde(rename = "..")]`"#,
));
}
if avro.rename_all != RenameRule::None && serde.rename_all.serialize != avro.rename_all {
errors.push(syn::Error::new(
span,
r#"AvroSchema: #[avro(rename_all = "..")] must match #[serde(rename_all = "..")] and it's deprecated. Please use only `#[serde(rename_all = "..")]`"#,
));
}
if serde.transparent
&& (serde.rename.is_some()
|| avro.name.is_some()
|| avro.namespace.is_some()
|| avro.doc.is_some()
|| avro.default.is_some()
|| !avro.alias.is_empty()
|| avro.repr.is_some()
|| avro.rename_all != RenameRule::None
|| serde.rename_all.serialize != RenameRule::None
|| serde.rename_all.deserialize != RenameRule::None
|| serde.rename_all_fields.serialize != RenameRule::None
|| serde.rename_all_fields.deserialize != RenameRule::None
|| serde.untagged
|| serde.tag.is_some()
|| serde.content.is_some())
{
errors.push(syn::Error::new(
span,
"AvroSchema: #[serde(transparent)] is incompatible with all other attributes",
));
}
let repr = match Repr::from_avro_and_serde(
avro.repr,
serde.tag,
serde.content,
serde.untagged,
span,
) {
Ok(repr) => repr,
Err(err) => {
errors.push(err);
None
}
};
let default = if let Some(default_value) = avro.default {
match serde_json::from_str(default_value.as_str()) {
Ok(value) => Some(value),
Err(err) => {
errors.push(syn::Error::new(
ident.span(),
format!("Invalid Avro `default` JSON: \n{err}"),
));
None
}
}
} else {
None
};
if !errors.is_empty() {
return Err(errors);
}
let name = serde.rename.unwrap_or(ident.to_string());
let full_schema_name = vec![avro.namespace, Some(name)]
.into_iter()
.flatten()
.collect::<Vec<String>>()
.join(".");
let doc = avro.doc.or_else(|| extract_rustdoc(attributes));
Ok(Self {
name: full_schema_name,
doc,
aliases: avro.alias,
rename_all: serde.rename_all.serialize,
rename_all_fields: serde.rename_all_fields.serialize,
transparent: serde.transparent,
default,
repr,
})
}
}
#[derive(Debug, PartialEq, Default, Clone)]
pub enum With {
#[default]
Trait,
Serde(Path),
Expr(Expr),
}
impl With {
fn from_avro_and_serde(
avro: &avro::With,
serde: Option<&String>,
span: Span,
) -> Result<Self, syn::Error> {
match &avro {
avro::With::Trait => Ok(Self::Trait),
avro::With::Serde => {
if let Some(serde) = serde {
let path = Path::from_string(serde).map_err(|err| {
syn::Error::new(
span,
format!(
r#"AvroSchema: Expected a path for `#[serde(with = "..")]`: {err:?}"#
),
)
})?;
Ok(Self::Serde(path))
} else {
Err(syn::Error::new(
span,
r#"`#[avro(with)]` requires `#[serde(with = "some_module")]` or provide a function to call `#[avro(with = some_fn)]`"#,
))
}
}
avro::With::Expr(expr) => Ok(Self::Expr(expr.clone())),
}
}
}
pub struct VariantOptions {
pub aliases: Vec<String>,
pub doc: Option<String>,
pub rename: Option<String>,
pub rename_all: RenameRule,
pub skip: bool,
pub with: With,
}
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)?;
avro.deprecated(span);
let mut errors = Vec::new();
if serde.other || serde.untagged {
errors.push(syn::Error::new(
span,
"AvroSchema: `#[serde(other)]` and `#[serde(untagged)]` are not supported on variants",
));
}
if serde.rename_all.deserialize != serde.rename_all.serialize {
errors.push(syn::Error::new(
span,
r#"AvroSchema: rename rules for serializing and deserializing must match (`rename_all(serialize = "..", deserialize = "..")`)"#
));
}
if avro.rename.is_some() && serde.rename != avro.rename {
errors.push(syn::Error::new(
span,
r#"`#[avro(rename = "..")]` must match `#[serde(rename = "..")]`, it's also deprecated. Please use only `#[serde(rename = "..")]`"#
));
}
let with = match With::from_avro_and_serde(&avro.with, serde.with.as_ref(), span) {
Ok(with) => with,
Err(error) => {
errors.push(error);
With::Trait
}
};
if !errors.is_empty() {
return Err(errors);
}
let doc = avro.doc.or_else(|| extract_rustdoc(attributes));
Ok(Self {
aliases: serde.alias,
doc,
rename: serde.rename,
rename_all: serde.rename_all.serialize,
skip: serde.skip || (serde.skip_serializing && serde.skip_deserializing),
with,
})
}
pub fn only_skip_rename_and_alias_can_be_set(&self) -> bool {
self.doc.is_none() && self.rename_all == RenameRule::None && self.with == With::Trait
}
}
#[derive(Debug, PartialEq, Default)]
pub enum FieldDefault {
#[default]
Trait,
Disabled,
Value(Value),
}
impl FromMeta for FieldDefault {
fn from_string(value: &str) -> darling::Result<Self> {
Ok(Self::Value(serde_json::from_str(value).map_err(|e| {
darling::Error::custom(format!("Failed to parse field default: {e:?}"))
})?))
}
fn from_bool(value: bool) -> darling::Result<Self> {
if value {
Err(darling::Error::custom(
"Expected `false` or a JSON string, got `true`",
))
} else {
Ok(Self::Disabled)
}
}
}
#[derive(Default)]
pub struct FieldOptions {
pub doc: Option<String>,
pub default: FieldDefault,
pub alias: Vec<String>,
pub rename: Option<String>,
pub skip: bool,
pub flatten: bool,
pub with: With,
}
impl FieldOptions {
pub fn new(attributes: &[Attribute], span: Span) -> Result<Self, Vec<syn::Error>> {
let mut avro =
avro::FieldAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
let mut serde =
serde::FieldAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
avro.alias.sort();
serde.alias.sort();
avro.deprecated(span);
let mut errors = Vec::new();
if avro.skip && !(serde.skip || (serde.skip_serializing && serde.skip_deserializing)) {
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)]`"
));
}
if avro.rename.is_some() && serde.rename != avro.rename {
errors.push(syn::Error::new(
span,
r#"`#[avro(rename = "..")]` must match `#[serde(rename = "..")]`, it's also deprecated. Please use only `#[serde(rename = "..")]`"#
));
}
if !avro.alias.is_empty() && serde.alias != avro.alias {
errors.push(syn::Error::new(
span,
r#"`#[avro(alias = "..")]` must match `#[serde(alias = "..")]`, it's also deprecated. Please use only `#[serde(alias = "..")]`"#
));
}
let with = match With::from_avro_and_serde(&avro.with, serde.with.as_ref(), span) {
Ok(with) => with,
Err(error) => {
errors.push(error);
With::Trait
}
};
if with != With::Trait && avro.default == FieldDefault::Trait {
avro.default = FieldDefault::Disabled;
}
if ((serde.skip_serializing && !serde.skip_deserializing)
|| serde.skip_serializing_if.is_some())
&& avro.default == FieldDefault::Disabled
{
errors.push(syn::Error::new(
span,
"`#[serde(skip_serializing)]` and `#[serde(skip_serializing_if)]` are incompatible with `#[avro(default = false)]`"
));
}
if !errors.is_empty() {
return Err(errors);
}
let doc = avro.doc.or_else(|| extract_rustdoc(attributes));
Ok(Self {
doc,
default: avro.default,
alias: serde.alias,
rename: serde.rename,
skip: serde.skip || (serde.skip_serializing && serde.skip_deserializing),
flatten: serde.flatten,
with,
})
}
}
fn extract_rustdoc(attributes: &[Attribute]) -> Option<String> {
let doc = attributes
.iter()
.filter(|attr| attr.style == AttrStyle::Outer && attr.path().is_ident("doc"))
.filter_map(|attr| {
let name_value = attr.meta.require_name_value();
match name_value {
Ok(name_value) => match &name_value.value {
syn::Expr::Lit(expr_lit) => match expr_lit.lit {
syn::Lit::Str(ref lit_str) => Some(lit_str.value().trim().to_string()),
_ => None,
},
_ => None,
},
Err(_) => None,
}
})
.collect::<Vec<String>>()
.join("\n");
if doc.is_empty() { None } else { Some(doc) }
}
fn darling_to_syn(e: darling::Error) -> Vec<syn::Error> {
let msg = format!("{e}");
let token_errors = e.write_errors();
vec![syn::Error::new(token_errors.span(), msg)]
}
#[cfg(nightly)]
fn warn(span: Span, message: &str, help: &str) {
proc_macro::Diagnostic::spanned(span.unwrap(), proc_macro::Level::Warning, message)
.help(help)
.emit();
}
#[cfg(not(nightly))]
fn warn(_span: Span, _message: &str, _help: &str) {}