https://github.com/apache/avro/blob/63c58f3b29d20ba94a6c68d9e4c09816d448bf34/lang/rust/avro_derive/src/lib.rs

Created Diff never expires
0 removals
491 lines
13 additions
503 lines
// Licensed to the Apache Software Foundation (ASF) under one
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
// with the License. You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing,
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// specific language governing permissions and limitations
// under the License.
// under the License.


extern crate darling;
extern crate darling;


use darling::FromAttributes;
use darling::FromAttributes;
use proc_macro2::{Span, TokenStream};
use proc_macro2::{Span, TokenStream};
use quote::quote;
use quote::quote;


use syn::{
use syn::{
parse_macro_input, spanned::Spanned, AttrStyle, Attribute, DeriveInput, Error, Type, TypePath,
parse_macro_input, spanned::Spanned, AttrStyle, Attribute, DeriveInput, Error, Type, TypePath,
};
};


#[derive(FromAttributes)]
#[derive(FromAttributes)]
#[darling(attributes(avro))]
#[darling(attributes(avro))]
struct FieldOptions {
struct FieldOptions {
#[darling(default)]
#[darling(default)]
doc: Option<String>,
doc: Option<String>,
#[darling(default)]
#[darling(default)]
default: Option<String>,
default: Option<String>,
#[darling(default)]
rename: Option<String>,
#[darling(default)]
skip: Option<bool>,
}
}


#[derive(FromAttributes)]
#[derive(FromAttributes)]
#[darling(attributes(avro))]
#[darling(attributes(avro))]
struct NamedTypeOptions {
struct NamedTypeOptions {
#[darling(default)]
#[darling(default)]
namespace: Option<String>,
namespace: Option<String>,
#[darling(default)]
#[darling(default)]
doc: Option<String>,
doc: Option<String>,
#[darling(multiple)]
#[darling(multiple)]
alias: Vec<String>,
alias: Vec<String>,
}
}


#[proc_macro_derive(AvroSchema, attributes(avro))]
#[proc_macro_derive(AvroSchema, attributes(avro))]
// Templated from Serde
// Templated from Serde
pub fn proc_macro_derive_avro_schema(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
pub fn proc_macro_derive_avro_schema(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let mut input = parse_macro_input!(input as DeriveInput);
let mut input = parse_macro_input!(input as DeriveInput);
derive_avro_schema(&mut input)
derive_avro_schema(&mut input)
.unwrap_or_else(to_compile_errors)
.unwrap_or_else(to_compile_errors)
.into()
.into()
}
}


fn derive_avro_schema(input: &mut DeriveInput) -> Result<TokenStream, Vec<syn::Error>> {
fn derive_avro_schema(input: &mut DeriveInput) -> Result<TokenStream, Vec<syn::Error>> {
let named_type_options =
let named_type_options =
NamedTypeOptions::from_attributes(&input.attrs[..]).map_err(darling_to_syn)?;
NamedTypeOptions::from_attributes(&input.attrs[..]).map_err(darling_to_syn)?;
let full_schema_name = vec![named_type_options.namespace, Some(input.ident.to_string())]
let full_schema_name = vec![named_type_options.namespace, Some(input.ident.to_string())]
.into_iter()
.into_iter()
.flatten()
.flatten()
.collect::<Vec<String>>()
.collect::<Vec<String>>()
.join(".");
.join(".");
let schema_def = match &input.data {
let schema_def = match &input.data {
syn::Data::Struct(s) => get_data_struct_schema_def(
syn::Data::Struct(s) => get_data_struct_schema_def(
&full_schema_name,
&full_schema_name,
named_type_options
named_type_options
.doc
.doc
.or_else(|| extract_outer_doc(&input.attrs)),
.or_else(|| extract_outer_doc(&input.attrs)),
named_type_options.alias,
named_type_options.alias,
s,
s,
input.ident.span(),
input.ident.span(),
)?,
)?,
syn::Data::Enum(e) => get_data_enum_schema_def(
syn::Data::Enum(e) => get_data_enum_schema_def(
&full_schema_name,
&full_schema_name,
named_type_options
named_type_options
.doc
.doc
.or_else(|| extract_outer_doc(&input.attrs)),
.or_else(|| extract_outer_doc(&input.attrs)),
named_type_options.alias,
named_type_options.alias,
e,
e,
input.ident.span(),
input.ident.span(),
)?,
)?,
_ => {
_ => {
return Err(vec![Error::new(
return Err(vec![Error::new(
input.ident.span(),
input.ident.span(),
"AvroSchema derive only works for structs and simple enums ",
"AvroSchema derive only works for structs and simple enums ",
)])
)])
}
}
};
};


let ident = &input.ident;
let ident = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
Ok(quote! {
Ok(quote! {
impl #impl_generics apache_avro::schema::derive::AvroSchemaComponent for #ident #ty_generics #where_clause {
impl #impl_generics apache_avro::schema::derive::AvroSchemaComponent for #ident #ty_generics #where_clause {
fn get_schema_in_ctxt(named_schemas: &mut std::collections::HashMap<apache_avro::schema::Name, apache_avro::schema::Schema>, enclosing_namespace: &Option<String>) -> apache_avro::schema::Schema {
fn get_schema_in_ctxt(named_schemas: &mut std::collections::HashMap<apache_avro::schema::Name, apache_avro::schema::Schema>, enclosing_namespace: &Option<String>) -> apache_avro::schema::Schema {
let name = apache_avro::schema::Name::new(#full_schema_name).expect(&format!("Unable to parse schema name {}", #full_schema_name)[..]).fully_qualified_name(enclosing_namespace);
let name = apache_avro::schema::Name::new(#full_schema_name).expect(&format!("Unable to parse schema name {}", #full_schema_name)[..]).fully_qualified_name(enclosing_namespace);
let enclosing_namespace = &name.namespace;
let enclosing_namespace = &name.namespace;
if named_schemas.contains_key(&name) {
if named_schemas.contains_key(&name) {
apache_avro::schema::Schema::Ref{name: name.clone()}
apache_avro::schema::Schema::Ref{name: name.clone()}
} else {
} else {
named_schemas.insert(name.clone(), apache_avro::schema::Schema::Ref{name: name.clone()});
named_schemas.insert(name.clone(), apache_avro::schema::Schema::Ref{name: name.clone()});
#schema_def
#schema_def
}
}
}
}
}
}
})
})
}
}


fn get_data_struct_schema_def(
fn get_data_struct_schema_def(
full_schema_name: &str,
full_schema_name: &str,
record_doc: Option<String>,
record_doc: Option<String>,
aliases: Vec<String>,
aliases: Vec<String>,
s: &syn::DataStruct,
s: &syn::DataStruct,
error_span: Span,
error_span: Span,
) -> Result<TokenStream, Vec<syn::Error>> {
) -> Result<TokenStream, Vec<syn::Error>> {
let mut record_field_exprs = vec![];
let mut record_field_exprs = vec![];
match s.fields {
match s.fields {
syn::Fields::Named(ref a) => {
syn::Fields::Named(ref a) => {
for (position, field) in a.named.iter().enumerate() {
for (position, field) in a.named.iter().enumerate() {
let name = field.ident.as_ref().unwrap().to_string(); // we know everything has a name
let mut name = field.ident.as_ref().unwrap().to_string(); // we know everything has a name
let field_attrs =
let field_attrs =
FieldOptions::from_attributes(&field.attrs[..]).map_err(darling_to_syn)?;
FieldOptions::from_attributes(&field.attrs[..]).map_err(darling_to_syn)?;
let doc = preserve_optional(field_attrs.doc);
let doc = preserve_optional(field_attrs.doc);
let rename = field_attrs.rename;
if rename.is_some() {
name = rename.unwrap();
}
let skip = field_attrs.skip;
if skip.is_some() && skip.unwrap() {
continue;
}
let default_value = match field_attrs.default {
let default_value = match field_attrs.default {
Some(default_value) => {
Some(default_value) => {
let _: serde_json::Value = serde_json::from_str(&default_value[..])
let _: serde_json::Value = serde_json::from_str(&default_value[..])
.map_err(|e| {
.map_err(|e| {
vec![Error::new(
vec![Error::new(
field.ident.span(),
field.ident.span(),
format!("Invalid avro default json: \n{}", e),
format!("Invalid avro default json: \n{}", e),
)]
)]
})?;
})?;
quote! {
quote! {
Some(serde_json::from_str(#default_value).expect(format!("Invalid JSON: {:?}", #default_value).as_str()))
Some(serde_json::from_str(#default_value).expect(format!("Invalid JSON: {:?}", #default_value).as_str()))
}
}
}
}
None => quote! { None },
None => quote! { None },
};
};
let schema_expr = type_to_schema_expr(&field.ty)?;
let schema_expr = type_to_schema_expr(&field.ty)?;
let position = position;
let position = position;
record_field_exprs.push(quote! {
record_field_exprs.push(quote! {
apache_avro::schema::RecordField {
apache_avro::schema::RecordField {
name: #name.to_string(),
name: #name.to_string(),
doc: #doc,
doc: #doc,
default: #default_value,
default: #default_value,
schema: #schema_expr,
schema: #schema_expr,
order: apache_avro::schema::RecordFieldOrder::Ascending,
order: apache_avro::schema::RecordFieldOrder::Ascending,
position: #position,
position: #position,
custom_attributes: Default::default(),
custom_attributes: Default::default(),
}
}
});
});
}
}
}
}
syn::Fields::Unnamed(_) => {
syn::Fields::Unnamed(_) => {
return Err(vec![Error::new(
return Err(vec![Error::new(
error_span,
error_span,
"AvroSchema derive does not work for tuple structs",
"AvroSchema derive does not work for tuple structs",
)])
)])
}
}
syn::Fields::Unit => {
syn::Fields::Unit => {
return Err(vec![Error::new(
return Err(vec![Error::new(
error_span,
error_span,
"AvroSchema derive does not work for unit structs",
"AvroSchema derive does not work for unit structs",
)])
)])
}
}
}
}
let record_doc = preserve_optional(record_doc);
let record_doc = preserve_optional(record_doc);
let record_aliases = preserve_vec(aliases);
let record_aliases = preserve_vec(aliases);
Ok(quote! {
Ok(quote! {
let schema_fields = vec![#(#record_field_exprs),*];
let schema_fields = vec![#(#record_field_exprs),*];
let name = apache_avro::schema::Name::new(#full_schema_name).expect(&format!("Unable to parse struct name for schema {}", #full_schema_name)[..]);
let name = apache_avro::schema::Name::new(#full_schema_name).expect(&format!("Unable to parse struct name for schema {}", #full_schema_name)[..]);
let lookup: std::collections::BTreeMap<String, usize> = schema_fields
let lookup: std::collections::BTreeMap<String, usize> = schema_fields
.iter()
.iter()
.map(|field| (field.name.to_owned(), field.position))
.map(|field| (field.name.to_owned(), field.position))
.collect();
.collect();
apache_avro::schema::Schema::Record {
apache_avro::schema::Schema::Record {
name,
name,
aliases: #record_aliases,
aliases: #record_aliases,
doc: #record_doc,
doc: #record_doc,
fields: schema_fields,
fields: schema_fields,
lookup,
lookup,
attributes: Default::default(),
attributes: Default::default(),
}
}
})
})
}
}


fn get_data_enum_schema_def(
fn get_data_enum_schema_def(
full_schema_name: &str,
full_schema_name: &str,
doc: Option<String>,
doc: Option<String>,
aliases: Vec<String>,
aliases: Vec<String>,
e: &syn::DataEnum,
e: &syn::DataEnum,
error_span: Span,
error_span: Span,
) -> Result<TokenStream, Vec<syn::Error>> {
) -> Result<TokenStream, Vec<syn::Error>> {
let doc = preserve_optional(doc);
let doc = preserve_optional(doc);
let enum_aliases = preserve_vec(aliases);
let enum_aliases = preserve_vec(aliases);
if e.variants.iter().all(|v| syn::Fields::Unit == v.fields) {
if e.variants.iter().all(|v| syn::Fields::Unit == v.fields) {
let symbols: Vec<String> = e
let symbols: Vec<String> = e
.variants
.variants
.iter()
.iter()
.map(|variant| variant.ident.to_string())
.map(|variant| variant.ident.to_string())
.collect();
.collect();
Ok(quote! {
Ok(quote! {
apache_avro::schema::Schema::Enum {
apache_avro::schema::Schema::Enum {
name: apache_avro::schema::Name::new(#full_schema_name).expect(&format!("Unable to parse enum name for schema {}", #full_schema_name)[..]),
name: apache_avro::schema::Name::new(#full_schema_name).expect(&format!("Unable to parse enum name for schema {}", #full_schema_name)[..]),
aliases: #enum_aliases,
aliases: #enum_aliases,
doc: #doc,
doc: #doc,
symbols: vec![#(#symbols.to_owned()),*],
symbols: vec![#(#symbols.to_owned()),*],
attributes: Default::default(),
attributes: Default::default(),
}
}
})
})
} else {
} else {
Err(vec![Error::new(
Err(vec![Error::new(
error_span,
error_span,
"AvroSchema derive does not work for enums with non unit structs",
"AvroSchema derive does not work for enums with non unit structs",
)])
)])
}
}
}
}


/// Takes in the Tokens of a type and returns the tokens of an expression with return type `Schema`
/// Takes in the Tokens of a type and returns the tokens of an expression with return type `Schema`
fn type_to_schema_expr(ty: &Type) -> Result<TokenStream, Vec<syn::Error>> {
fn type_to_schema_expr(ty: &Type) -> Result<TokenStream, Vec<syn::Error>> {
if let Type::Path(p) = ty {
if let Type::Path(p) = ty {
let type_string = p.path.segments.last().unwrap().ident.to_string();
let type_string = p.path.segments.last().unwrap().ident.to_string();


let schema = match &type_string[..] {
let schema = match &type_string[..] {
"bool" => quote! {apache_avro::schema::Schema::Boolean},
"bool" => quote! {apache_avro::schema::Schema::Boolean},
"i8" | "i16" | "i32" | "u8" | "u16" => quote! {apache_avro::schema::Schema::Int},
"i8" | "i16" | "i32" | "u8" | "u16" => quote! {apache_avro::schema::Schema::Int},
"i64" => quote! {apache_avro::schema::Schema::Long},
"i64" => quote! {apache_avro::schema::Schema::Long},
"f32" => quote! {apache_avro::schema::Schema::Float},
"f32" => quote! {apache_avro::schema::Schema::Float},
"f64" => quote! {apache_avro::schema::Schema::Double},
"f64" => quote! {apache_avro::schema::Schema::Double},
"String" | "str" => quote! {apache_avro::schema::Schema::String},
"String" | "str" => quote! {apache_avro::schema::Schema::String},
"char" => {
"char" => {
return Err(vec![syn::Error::new_spanned(
return Err(vec![syn::Error::new_spanned(
ty,
ty,
"AvroSchema: Cannot guarantee successful deserialization of this type",
"AvroSchema: Cannot guarantee successful deserialization of this type",
)])
)])
}
}
"u64" => {
"u64" => {
return Err(vec![syn::Error::new_spanned(
return Err(vec![syn::Error::new_spanned(
ty,
ty,
"Cannot guarantee successful serialization of this type due to overflow concerns",
"Cannot guarantee successful serialization of this type due to overflow concerns",
)])
)])
} // Can't guarantee serialization type
} // Can't guarantee serialization type
_ => {
_ => {
// Fails when the type does not implement AvroSchemaComponent directly
// Fails when the type does not implement AvroSchemaComponent directly
// TODO check and error report with something like https://docs.rs/quote/1.0.15/quote/macro.quote_spanned.html#example
// TODO check and error report with something like https://docs.rs/quote/1.0.15/quote/macro.quote_spanned.html#example
type_path_schema_expr(p)
type_path_schema_expr(p)
}
}
};
};
Ok(schema)
Ok(schema)
} else if let Type::Array(ta) = ty {
} else if let Type::Array(ta) = ty {
let inner_schema_expr = type_to_schema_expr(&ta.elem)?;
let inner_schema_expr = type_to_schema_expr(&ta.elem)?;
Ok(quote! {apache_avro::schema::Schema::Array(Box::new(#inner_schema_expr))})
Ok(quote! {apache_avro::schema::Schema::Array(Box::new(#inner_schema_expr))})
} else if let Type::Reference(tr) = ty {
} else if let Type::Reference(tr) = ty {
type_to_schema_expr(&tr.elem)
type_to_schema_expr(&tr.elem)
} else {
} else {
Err(vec![syn::Error::new_spanned(
Err(vec![syn::Error::new_spanned(
ty,
ty,
format!("Unable to generate schema for type: {:?}", ty),
format!("Unable to generate schema for type: {:?}", ty),
)])
)])
}
}
}
}


/// Generates the schema def expression for fully qualified type paths using the associated function
/// Generates the schema def expression for fully qualified type paths using the associated function
/// - `A -> <A as apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt()`
/// - `A -> <A as apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt()`
/// - `A<T> -> <A<T> as apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt()`
/// - `A<T> -> <A<T> as apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt()`
fn type_path_schema_expr(p: &TypePath) -> TokenStream {
fn type_path_schema_expr(p: &TypePath) -> TokenStream {
quote! {<#p as apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas, enclosing_namespace)}
quote! {<#p as apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas, enclosing_namespace)}
}
}


/// Stolen from serde
/// Stolen from serde
fn to_compile_errors(errors: Vec<syn::Error>) -> proc_macro2::TokenStream {
fn to_compile_errors(errors: Vec<syn::Error>) -> proc_macro2::TokenStream {
let compile_errors = errors.iter().map(syn::Error::to_compile_error);
let compile_errors = errors.iter().map(syn::Error::to_compile_error);
quote!(#(#compile_errors)*)
quote!(#(#compile_errors)*)
}
}


fn extract_outer_doc(attributes: &[Attribute]) -> Option<String> {
fn extract_outer_doc(attributes: &[Attribute]) -> Option<String> {
let doc = attributes
let doc = attributes
.iter()
.iter()
.filter(|attr| attr.style == AttrStyle::Outer && attr.path.is_ident("doc"))
.filter(|attr| attr.style == AttrStyle::Outer && attr.path.is_ident("doc"))
.map(|attr| {
.map(|attr| {
let mut tokens = attr.tokens.clone().into_iter();
let mut tokens = attr.tokens.clone().into_iter();
tokens.next(); // skip the Punct
tokens.next(); // skip the Punct
let to_trim: &[char] = &['"', ' '];
let to_trim: &[char] = &['"', ' '];
tokens
tokens
.next() // use the Literal
.next() // use the Literal
.unwrap()
.unwrap()
.to_string()
.to_string()
.trim_matches(to_trim)
.trim_matches(to_trim)
.to_string()
.to_string()
})
})
.collect::<Vec<String>>()
.collect::<Vec<String>>()
.join("\n");
.join("\n");
if doc.is_empty() {
if doc.is_empty() {
None
None
} else {
} else {
Some(doc)
Some(doc)
}
}
}
}


fn preserve_optional(op: Option<impl quote::ToTokens>) -> TokenStream {
fn preserve_optional(op: Option<impl quote::ToTokens>) -> TokenStream {
match op {
match op {
Some(tt) => quote! {Some(#tt.into())},
Some(tt) => quote! {Some(#tt.into())},
None => quote! {None},
None => quote! {None},
}
}
}
}


fn preserve_vec(op: Vec<impl quote::ToTokens>) -> TokenStream {
fn preserve_vec(op: Vec<impl quote::ToTokens>) -> TokenStream {
let items: Vec<TokenStream> = op.iter().map(|tt| quote! {#tt.into()}).collect();
let items: Vec<TokenStream> = op.iter().map(|tt| quote! {#tt.into()}).collect();
if items.is_empty() {
if items.is_empty() {
quote! {None}
quote! {None}
} else {
} else {
quote! {Some(vec![#(#items),*])}
quote! {Some(vec![#(#items),*])}
}
}
}
}


fn darling_to_syn(e: darling::Error) -> Vec<syn::Error> {
fn darling_to_syn(e: darling::Error) -> Vec<syn::Error> {
let msg = format!("{}", e);
let msg = format!("{}", e);
let token_errors = e.write_errors();
let token_errors = e.write_errors();
vec![syn::Error::new(token_errors.span(), msg)]
vec![syn::Error::new(token_errors.span(), msg)]
}
}


#[cfg(test)]
#[cfg(test)]
mod tests {
mod tests {
use super::*;
use super::*;
#[test]
#[test]
fn basic_case() {
fn basic_case() {
let test_struct = quote! {
let test_struct = quote! {
struct A {
struct A {
a: i32,
a: i32,
b: String
b: String
}
}
};
};


match syn::parse2::<DeriveInput>(test_struct) {
match syn::parse2::<DeriveInput>(test_struct) {
Ok(mut input) => {
Ok(mut input) => {
assert!(derive_avro_schema(&mut input).is_ok())
assert!(derive_avro_schema(&mut input).is_ok())
}
}
Err(error) => panic!(
Err(error) => panic!(
"Failed to parse as derive input when it should be able to. Error: {:?}",
"Failed to parse as derive input when it should be able to. Error: {:?}",
error
error
),
),
};
};
}
}


#[test]
#[test]
fn tuple_struct_unsupported() {
fn tuple_struct_unsupported() {
let test_tuple_struct = quote! {
let test_tuple_struct = quote! {
struct B (i32, String);
struct B (i32, String);
};
};


match syn::parse2::<DeriveInput>(test_tuple_struct) {
match syn::parse2::<DeriveInput>(test_tuple_struct) {
Ok(mut input) => {
Ok(mut input) => {
assert!(derive_avro_schema(&mut input).is_err())
assert!(derive_avro_schema(&mut input).is_err())
}
}
Err(error) => panic!(
Err(error) => panic!(
"Failed to parse as derive input when it should be able to. Error: {:?}",
"Failed to parse as derive input when it should be able to. Error: {:?}",
error
error
),
),
};
};
}
}


#[test]
#[test]
fn unit_struct_unsupported() {
fn unit_struct_unsupported() {
let test_tuple_struct = quote! {
let test_tuple_struct = quote! {
struct AbsoluteUnit;
struct AbsoluteUnit;
};
};


match syn::parse2::<DeriveInput>(test_tuple_struct) {
match syn::parse2::<DeriveInput>(test_tuple_struct) {
Ok(mut input) => {
Ok(mut input) => {
assert!(derive_avro_schema(&mut input).is_err())
assert!(derive_avro_schema(&mut input).is_err())
}
}
Err(error) => panic!(
Err(error) => panic!(
"Failed to parse as derive input when it should be able to. Error: {:?}",
"Failed to parse as derive input when it should be able to. Error: {:?}",
error
error
),
),
};
};
}
}


#[test]
#[test]
fn struct_with_optional() {
fn struct_with_optional() {
let struct_with_optional = quote! {
let struct_with_optional = quote! {
struct Test4 {
struct Test4 {
a : Option<i32>
a : Option<i32>
}
}
};
};
match syn::parse2::<DeriveInput>(struct_with_optional) {
match syn::parse2::<DeriveInput>(struct_with_optional) {
Ok(mut input) => {
Ok(mut input) => {
assert!(derive_avro_schema(&mut input).is_ok())
assert!(derive_avro_schema(&mut input).is_ok())
}
}
Err(error) => panic!(
Err(error) => panic!(
"Failed to parse as derive input when it should be able to. Error: {:?}",
"Failed to parse as derive input when it should be able to. Error: {:?}",
error
error
),
),
};
};
}
}


#[test]
#[test]
fn test_basic_enum() {
fn test_basic_enum() {
let basic_enum = quote! {
let basic_enum = quote! {
enum Basic {
enum Basic {
A,
A,
B,
B,
C,
C,
D
D
}
}
};
};
match syn::parse2::<DeriveInput>(basic_enum) {
match syn::parse2::<DeriveInput>(basic_enum) {
Ok(mut input) => {
Ok(mut input) => {
assert!(derive_avro_schema(&mut input).is_ok())
assert!(derive_avro_schema(&mut input).is_ok())
}
}
Err(error) => panic!(
Err(error) => panic!(
"Failed to parse as derive input when it should be able to. Error: {:?}",
"Failed to parse as derive input when it should be able to. Error: {:?}",
error
error
),
),
};
};
}
}


#[test]
#[test]
fn test_non_basic_enum() {
fn test_non_basic_enum() {
let non_basic_enum = quote! {
let non_basic_enum = quote! {
enum Basic {
enum Basic {
A(i32),
A(i32),
B,
B,
C,
C,
D
D
}
}
};
};
match syn::parse2::<DeriveInput>(non_basic_enum) {
match syn::parse2::<DeriveInput>(non_basic_enum) {
Ok(mut input) => {
Ok(mut input) => {
assert!(derive_avro_schema(&mut input).is_err())
assert!(derive_avro_schema(&mut input).is_err())
}
}
Err(error) => panic!(
Err(error) => panic!(
"Failed to parse as derive input when it should be able to. Error: {:?}",
"Failed to parse as derive input when it should be able to. Error: {:?}",
error
error
),
),
};
};
}
}


#[test]
#[test]
fn test_namespace() {
fn test_namespace() {
let test_struct = quote! {
let test_struct = quote! {
#[avro(namespace = "namespace.testing")]
#[avro(namespace = "namespace.testing")]
struct A {
struct A {
a: i32,
a: i32,
b: String
b: String
}
}
};
};


match syn::parse2::<DeriveInput>(test_struct) {
match syn::parse2::<DeriveInput>(test_struct) {
Ok(mut input) => {
Ok(mut input) => {
assert!(derive_avro_schema(&mut input).is_ok());
assert!(derive_avro_schema(&mut input).is_ok());
assert!(derive_avro_schema(&mut input)
assert!(derive_avro_schema(&mut input)
.unwrap()
.unwrap()
.to_string()
.to_string()
.contains("namespace.testing"))
.contains("namespace.testing"))
}
}
Err(error) => panic!(
Err(error) => panic!(
"Failed to parse as derive input when it should be able to. Error: {:?}",
"Failed to parse as derive input when it should be able to. Error: {:?}",
error
error
),
),
};
};
}
}


#[test]
#[test]
fn test_reference() {
fn test_reference() {
let test_reference_struct = quote! {
let test_reference_struct = quote! {
struct A<'a> {
struct A<'a> {
a: &'a Vec<i32>,
a: &'a Vec<i32>,
b: &'static str
b: &'static str
}
}
};
};


match syn::parse2::<DeriveInput>(test_reference_struct) {
match syn::parse2::<DeriveInput>(test_reference_struct) {
Ok(mut input) => {
Ok(mut input) => {
assert!(derive_avro_schema(&mut input).is_ok())
assert!(derive_avro_schema(&mut input).is_ok())
}
}
Err(error) => panic!(
Err(error) => panic!(
"Failed to parse as derive input when it should be able to. Error: {:?}",
"Failed to parse as derive input when it should be able to. Error: {:?}",
error
error
),
),
};
};
}
}


#[test]
#[test]
fn test_trait_cast() {
fn test_trait_cast() {
assert_eq!(type_path_schema_expr(&syn::parse2::<TypePath>(quote!{i32}).unwrap()).to_string(), quote!{<i32 as apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas, enclosing_namespace)}.to_string());
assert_eq!(type_path_schema_expr(&syn::parse2::<TypePath>(quote!{i32}).unwrap()).to_string(), quote!{<i32 as apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas, enclosing_namespace)}.to_string());
assert_eq!(type_path_schema_expr(&syn::parse2::<TypePath>(quote!{Vec<T>}).unwrap()).to_string(), quote!{<Vec<T> as apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas, enclosing_namespace)}.to_string());
assert_eq!(type_path_schema_expr(&syn::parse2::<TypePath>(quote!{Vec<T>}).unwrap()).to_string(), quote!{<Vec<T> as apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas, enclosing_namespace)}.to_string());
assert_eq!(type_path_schema_expr(&syn::parse2::<TypePath>(quote!{AnyType}).unwrap()).to_string(), quote!{<AnyType as apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas, enclosing_namespace)}.to_string());
assert_eq!(type_path_schema_expr(&syn::parse2::<TypePath>(quote!{AnyType}).unwrap()).to_string(), quote!{<AnyType as apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas, enclosing_namespace)}.to_string());
}
}
}
}