extern crate proc_macro;
use std::collections::HashMap;
use inflector::Inflector;
use proc_macro::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream, Result};
use syn::punctuated::Punctuated;
use syn::{braced, parse_macro_input};
use syn::{Error, Expr, ExprLit, Ident, Lit, LitStr, Token, Type, Visibility};
mod kw {
syn::custom_keyword!(alias);
syn::custom_keyword!(brief);
syn::custom_keyword!(rename);
syn::custom_keyword!(base_traits);
syn::custom_keyword!(group_traits);
syn::custom_keyword!(extend);
syn::custom_keyword!(functional);
}
struct PropertyStruct {
functional: bool,
ident: Ident,
variants: Punctuated<PropertyVariant, Token![,]>,
}
impl Parse for PropertyStruct {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
let functional = if lookahead.peek(kw::functional) {
input.parse::<kw::functional>()?;
true
} else {
false
};
let ident = input.parse()?;
let content;
braced!(content in input);
let variants = content.parse_terminated(PropertyVariant::parse)?;
Ok(PropertyStruct {
ident,
functional,
variants,
})
}
}
struct PropertyVariant {
ident: Ident,
ty: Type,
}
impl Parse for PropertyVariant {
fn parse(input: ParseStream) -> Result<Self> {
let ident = input.parse()?;
input.parse::<Token![:]>()?;
let ty = input.parse()?;
Ok(PropertyVariant { ident, ty })
}
}
#[proc_macro]
pub fn complex_property(input: TokenStream) -> TokenStream {
let PropertyStruct {
ident,
functional,
variants,
} = parse_macro_input!(input as PropertyStruct);
let enum_variants = variants.iter().map(|variant| {
let ident = &variant.ident;
let ty = &variant.ty;
let singular_name = format!("{}", ident).to_pascal_case();
let plural_name = format!("{}", ident).to_pascal_case().to_plural();
let singular_ident = Ident::new(&singular_name, ident.span());
let plural_ident = Ident::new(&plural_name, ident.span());
match ty {
Type::TraitObject(_) => {
if functional {
quote! {
#singular_ident(std::boxed::Box<#ty>)
}
} else {
quote! {
#singular_ident(std::boxed::Box<#ty>),
#plural_ident(std::vec::Vec<std::boxed::Box<#ty>>)
}
}
}
_ => {
if functional {
quote! {
#singular_ident(#ty)
}
} else {
quote! {
#singular_ident(#ty),
#plural_ident(std::vec::Vec<#ty>)
}
}
}
}
});
let from_impls = variants.iter().map(|variant| {
let enum_ident = &ident;
let ident = &variant.ident;
let ty = &variant.ty;
let singular_name = format!("{}", ident).to_pascal_case();
let plural_name = format!("{}", ident).to_pascal_case().to_plural();
let singular_ident = Ident::new(&singular_name, ident.span());
let plural_ident = Ident::new(&plural_name, ident.span());
match ty {
Type::TraitObject(_) => {
if functional {
quote! {
impl From<std::boxed::Box<#ty>> for #enum_ident {
fn from(value: std::boxed::Box<#ty>) -> Self {
Self::#singular_ident(value)
}
}
}
} else {
quote! {
impl From<std::boxed::Box<#ty>> for #enum_ident {
fn from(value: std::boxed::Box<#ty>) -> Self {
Self::#singular_ident(value)
}
}
impl From<std::vec::Vec<std::boxed::Box<#ty>>> for #enum_ident {
fn from(value: std::vec::Vec<std::boxed::Box<#ty>>) -> Self {
Self::#plural_ident(value)
}
}
}
}
}
_ => {
if functional {
quote! {
impl std::convert::From<#ty> for #enum_ident {
fn from(value: #ty) -> Self {
Self::#singular_ident(value)
}
}
}
} else {
quote! {
impl std::convert::From<#ty> for #enum_ident {
fn from(value: #ty) -> Self {
Self::#singular_ident(value)
}
}
impl std::convert::From<std::vec::Vec<#ty>> for #enum_ident {
fn from(values: std::vec::Vec<#ty>) -> Self {
Self::#plural_ident(values)
}
}
}
}
}
}
});
let expanded = quote! {
#[derive(std::clone::Clone, std::fmt::Debug, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
pub enum #ident {
None,
#(#enum_variants,)*
}
impl Default for #ident {
fn default() -> Self {
Self::None
}
}
impl ActivitypubProperty for #ident {
fn is_none(&self) -> bool {
match self {
#ident::None => true,
_ => false,
}
}
fn is_some(&self) -> bool {
match self {
#ident::None => false,
_ => true,
}
}
}
#(#from_impls)*
};
expanded.into()
}
struct ActivitypubProperty {
visibility: Visibility,
name: Ident,
functional: bool,
possible_ty: Punctuated<Ident, Token![,]>,
}
impl Parse for ActivitypubProperty {
fn parse(input: ParseStream) -> Result<Self> {
let visibility = input.parse()?;
let name = input.parse()?;
input.parse::<Token![,]>()?;
let functional_expr: Expr = input.parse()?;
input.parse::<Token![,]>()?;
let possible_ty = input.parse_terminated(Ident::parse)?;
let functional = if let Expr::Lit(ExprLit {
lit: Lit::Bool(b), ..
}) = functional_expr
{
b.value
} else {
return Err(Error::new_spanned(
functional_expr,
"expected true or false",
));
};
Ok(ActivitypubProperty {
visibility,
name,
possible_ty,
functional,
})
}
}
#[proc_macro]
pub fn activitypub_property(input: TokenStream) -> TokenStream {
activitypub_property_vec(input)
}
#[proc_macro]
pub fn activitypub_property_vec(input: TokenStream) -> TokenStream {
let ActivitypubProperty {
visibility,
name,
possible_ty,
functional,
} = parse_macro_input!(input as ActivitypubProperty);
let variants = possible_ty.iter().map(|ty| {
let singular_name = format!("{}", ty).to_pascal_case();
let plural_name = format!("{}", ty).to_pascal_case().to_plural();
let singular_ident = Ident::new(&singular_name, ty.span());
let plural_ident = Ident::new(&plural_name, ty.span());
if functional {
quote! {
#singular_ident(Box<#ty>)
}
} else {
quote! {
#singular_ident(Box<#ty>),
#plural_ident(Vec<#ty>)
}
}
});
let constructors = possible_ty.iter().map(|ty| {
let singular_name = format!("{}", ty).to_pascal_case();
let plural_name = format!("{}", ty).to_pascal_case().to_plural();
let singular_ident = Ident::new(&singular_name, ty.span());
let plural_ident = Ident::new(&plural_name, ty.span());
let singular_constructor_name = format!("{}", ty).to_snake_case();
let plural_constructor_name = format!("{}", ty).to_snake_case().to_plural();
let singular_constructor_ident = Ident::new(&singular_constructor_name, ty.span());
let plural_constructor_ident = Ident::new(&plural_constructor_name, ty.span());
if functional {
quote! {
#visibility fn #singular_constructor_ident(value: #ty) -> Self {
Self::#singular_ident(Box::new(value))
}
}
} else {
quote! {
#visibility fn #singular_constructor_ident(value: #ty) -> Self {
Self::#singular_ident(Box::new(value))
}
#visibility fn #plural_constructor_ident(values: Vec<#ty>) -> Self {
Self::#plural_ident(values)
}
}
}
});
let expanded = quote! {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
#visibility enum #name {
None,
#(#variants,)*
}
impl #name {
#(#constructors)*
}
impl Default for #name {
fn default() -> Self {
Self::None
}
}
impl ActivitypubProperty for #name {
fn is_none(&self) -> bool {
self == &Self::None
}
fn is_some(&self) -> bool {
self != &Self::None
}
}
};
expanded.into()
}
#[proc_macro]
pub fn activitypub_property_hashmap(input: TokenStream) -> TokenStream {
let ActivitypubProperty {
visibility,
name,
possible_ty,
functional,
} = parse_macro_input!(input as ActivitypubProperty);
let variants = possible_ty.iter().map(|ty| {
let singular_name = format!("{}", ty).to_pascal_case();
let plural_name = format!("{}", ty).to_pascal_case().to_plural();
let singular_ident = Ident::new(&singular_name, ty.span());
let plural_ident = Ident::new(&plural_name, ty.span());
if functional {
quote! {
#singular_ident(Box<#ty>)
}
} else {
quote! {
#singular_ident(Box<#ty>),
#plural_ident(HashMap<String, #ty>)
}
}
});
let constructors = possible_ty.iter().map(|ty| {
let singular_name = format!("{}", ty).to_pascal_case();
let plural_name = format!("{}", ty).to_pascal_case().to_plural();
let singular_ident = Ident::new(&singular_name, ty.span());
let plural_ident = Ident::new(&plural_name, ty.span());
let singular_constructor_name = format!("{}", ty).to_snake_case();
let plural_constructor_name = format!("{}", ty).to_snake_case().to_plural();
let singular_constructor_ident = Ident::new(&singular_constructor_name, ty.span());
let plural_constructor_ident = Ident::new(&plural_constructor_name, ty.span());
if functional {
quote! {
#visibility fn #singular_constructor_ident(value: #ty) -> Self {
Self::#singular_ident(Box::new(value))
}
}
} else {
quote! {
#visibility fn #singular_constructor_ident(value: #ty) -> Self {
Self::#singular_ident(Box::new(value))
}
#visibility fn #plural_constructor_ident(values: HashMap<String, #ty>) -> Self {
Self::#plural_ident(values)
}
}
}
});
let expanded = quote! {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
#visibility enum #name {
None,
#(#variants,)*
}
impl #name {
#(#constructors)*
}
impl Default for #name {
fn default() -> Self {
Self::None
}
}
impl ActivitypubProperty for #name {
fn is_none(&self) -> bool {
self == &Self::None
}
fn is_some(&self) -> bool {
self != &Self::None
}
}
};
expanded.into()
}
struct ActivitypubStruct {
visibility: Visibility,
ident: Ident,
fields: Punctuated<ActivitypubField, Token![,]>,
brief: ActivitypubField,
}
#[derive(Clone)]
struct ActivitypubField {
visibility: Visibility,
ident: Ident,
alias: Option<LitStr>,
rename: Option<LitStr>,
ty: Type,
}
impl Parse for ActivitypubStruct {
fn parse(input: ParseStream) -> Result<Self> {
let content;
let visibility = input.parse()?;
input.parse::<Token![struct]>()?;
let ident = input.parse()?;
braced!(content in input);
let fields = content.parse_terminated(ActivitypubField::parse)?;
input.parse::<kw::brief>()?;
let brief_ident: Ident = input.parse()?;
let brief = fields
.iter()
.find(|field| field.ident.to_string() == brief_ident.to_string())
.unwrap()
.clone();
Ok(ActivitypubStruct {
visibility,
ident,
fields,
brief,
})
}
}
impl Parse for ActivitypubField {
fn parse(input: ParseStream) -> Result<Self> {
let visibility = input.parse()?;
let ident = input.parse()?;
let lookahead = input.lookahead1();
let (alias, rename) = if lookahead.peek(kw::alias) {
input.parse::<kw::alias>()?;
(Some(input.parse()?), None)
} else if lookahead.peek(kw::rename) {
input.parse::<kw::rename>()?;
(None, Some(input.parse()?))
} else {
(None, None)
};
input.parse::<Token![:]>()?;
Ok(ActivitypubField {
visibility,
ident,
alias,
rename,
ty: input.parse()?,
})
}
}
#[proc_macro]
pub fn activitypub_core(input: TokenStream) -> TokenStream {
let ActivitypubStruct {
visibility,
ident,
fields,
brief,
} = parse_macro_input!(input as ActivitypubStruct);
let struct_fields = fields.iter().map(|field| {
let visibility = &field.visibility;
let ident = &field.ident;
let ty = &field.ty;
if let Some(alias) = &field.alias {
quote! {
#[serde(alias = #alias)]
#visibility #ident: #ty
}
} else if let Some(rename) = &field.rename {
quote! {
#[serde(rename = #rename)]
#visibility #ident: #ty
}
} else {
quote! {
#visibility #ident: #ty
}
}
});
let enum_fields = fields.iter().map(|field| {
let ident = &field.ident;
let ty = &field.ty;
if let Some(alias) = &field.alias {
quote! {
#[serde(alias = #alias, skip_serializing_if = "ActivitypubProperty::is_none", default)]
#ident: #ty
}
} else if let Some(rename) = &field.rename {
quote! {
#[serde(rename = #rename, skip_serializing_if = "ActivitypubProperty::is_none", default)]
#ident: #ty
}
} else {
quote! {
#[serde(skip_serializing_if = "ActivitypubProperty::is_none", default)]
#ident: #ty
}
}
});
let enum_ident_string = format!("{}Enum", ident);
let enum_ident = Ident::new(&enum_ident_string, ident.span());
let builder_ident = Ident::new(&format!("{}Builder", ident), ident.span());
let brief_ident = &brief.ident;
let brief_ty = &brief.ty;
let from_enum_fields = fields.iter().map(|field| {
let ident = &field.ident;
quote! { #ident }
});
let from_enum_fields2 = fields.iter().map(|field| {
let ident = &field.ident;
quote! { #ident }
});
let from_struct_fields = fields.iter().map(|field| {
let ident = &field.ident;
if ident.to_string() != brief_ident.to_string() {
quote! { && struct_value.#ident.is_none() }
} else {
quote! {}
}
});
let from_struct_fields2 = fields.iter().map(|field| {
let ident = &field.ident;
quote! { #ident: struct_value.#ident }
});
let expanded = quote! {
#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize, derive_builder::Builder)]
#[serde(rename_all = "camelCase", from = #enum_ident_string, into = #enum_ident_string)]
#[builder(default, setter(strip_option))]
#visibility struct #ident {
#(#struct_fields,)*
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
#visibility enum #enum_ident {
Brief(String),
#[serde(rename_all = "camelCase")]
Full {
#(#enum_fields,)*
},
}
impl From<#enum_ident> for #ident {
fn from(enum_value: #enum_ident) -> Self {
match enum_value {
#enum_ident::Brief(#brief_ident) => #builder_ident::default()
.#brief_ident(#brief_ty::String(Box::new(#brief_ident)))
.build()
.unwrap(),
#enum_ident::Full {
#(#from_enum_fields,)*
} => {
#ident {
#(#from_enum_fields2,)*
}
},
}
}
}
impl From<#ident> for #enum_ident {
fn from(struct_value: #ident) -> Self {
if struct_value.#brief_ident.is_some()
#(#from_struct_fields)* {
match struct_value.#brief_ident {
#brief_ty::String(#brief_ident) => #enum_ident::Brief(#brief_ident.to_string()),
_ => unreachable!(),
}
} else {
#enum_ident::Full {
#(#from_struct_fields2,)*
}
}
}
}
};
expanded.into()
}
struct ComplexStruct {
base_traits: Punctuated<Ident, Token![,]>,
group_traits: Punctuated<GroupTrait, Token![,]>,
type_groups: Punctuated<TypeGroup, Token![,]>,
}
impl Parse for ComplexStruct {
fn parse(input: ParseStream) -> Result<Self> {
input.parse::<kw::base_traits>()?;
let content;
braced!(content in input);
let base_traits = content.parse_terminated(Ident::parse)?;
input.parse::<kw::group_traits>()?;
let content;
braced!(content in input);
let group_traits = content.parse_terminated(GroupTrait::parse)?;
let type_groups = input.parse_terminated(TypeGroup::parse)?;
Ok(ComplexStruct {
base_traits,
group_traits,
type_groups,
})
}
}
struct GroupTrait {
ident: Ident,
parent: Ident,
}
impl Parse for GroupTrait {
fn parse(input: ParseStream) -> Result<Self> {
let ident = input.parse()?;
input.parse::<Token![:]>()?;
let parent = input.parse()?;
Ok(GroupTrait { ident, parent })
}
}
struct TypeGroup {
ident: Ident,
types: Punctuated<TypeDefinition, Token![,]>,
}
impl Parse for TypeGroup {
fn parse(input: ParseStream) -> Result<Self> {
let ident = input.parse()?;
let content;
braced!(content in input);
let types = content.parse_terminated(TypeDefinition::parse)?;
Ok(TypeGroup { ident, types })
}
}
#[derive(Clone)]
struct TypeDefinition {
ident: Ident,
parent: Option<Ident>,
fields: Punctuated<TypeField, Token![,]>,
}
impl Parse for TypeDefinition {
fn parse(input: ParseStream) -> Result<Self> {
let ident = input.parse()?;
let lookahead = input.lookahead1();
let parent = if lookahead.peek(kw::extend) {
input.parse::<kw::extend>()?;
Some(input.parse()?)
} else {
None
};
let content;
braced!(content in input);
let fields = content.parse_terminated(TypeField::parse)?;
Ok(TypeDefinition {
ident,
parent,
fields,
})
}
}
#[derive(Clone)]
struct TypeField {
ident: Ident,
alias: Option<LitStr>,
rename: Option<LitStr>,
ty: Type,
}
impl Parse for TypeField {
fn parse(input: ParseStream) -> Result<Self> {
let ident = input.parse()?;
let lookahead = input.lookahead1();
let (alias, rename) = if lookahead.peek(kw::alias) {
input.parse::<kw::alias>()?;
(Some(input.parse()?), None)
} else if lookahead.peek(kw::rename) {
input.parse::<kw::rename>()?;
(None, Some(input.parse()?))
} else {
(None, None)
};
input.parse::<Token![:]>()?;
let ty = input.parse()?;
Ok(TypeField {
ident,
alias,
rename,
ty,
})
}
}
#[derive(Clone)]
struct FullTypeDefinition {
ident: Ident,
fields: Vec<TypeField>,
}
#[proc_macro]
pub fn activitypub_complex(input: TokenStream) -> TokenStream {
let ComplexStruct {
base_traits,
group_traits,
type_groups,
} = parse_macro_input!(input as ComplexStruct);
let base_trait_defs = base_traits.iter().map(|base_trait| {
quote! {
#[typetag::serde(tag = "type")]
pub trait #base_trait: std::fmt::Debug {
fn any_ref(&self) -> &dyn std::any::Any;
fn kind(&self) -> &str;
fn box_clone(&self) -> Box<dyn #base_trait>;
}
impl std::clone::Clone for Box<dyn #base_trait> {
fn clone(&self) -> Box<dyn #base_trait> {
self.box_clone()
}
}
}
});
let group_trait_defs = group_traits.iter().map(|group_trait| {
let ident = &group_trait.ident;
quote! {
#[typetag::serde(tag = "type")]
pub trait #ident: std::fmt::Debug {
fn any_ref(&self) -> &dyn std::any::Any;
fn box_clone(&self) -> Box<dyn #ident>;
}
impl std::clone::Clone for Box<dyn #ident> {
fn clone(&self) -> Box<dyn #ident> {
self.box_clone()
}
}
}
});
let mut type_definitions: HashMap<Ident, TypeDefinition> = HashMap::new();
let mut full_type_definitions: HashMap<Ident, FullTypeDefinition> = HashMap::new();
for type_group in type_groups.iter() {
for type_definition in type_group.types.iter() {
type_definitions.insert(type_definition.ident.clone(), type_definition.clone());
}
}
while !type_definitions.is_empty() {
let temp_type_definitions = type_definitions.clone();
let keys = temp_type_definitions.keys();
for key in keys {
let type_definition = type_definitions.get(key).unwrap();
let mut full_type_definition = FullTypeDefinition {
ident: type_definition.ident.clone(),
fields: type_definition
.fields
.iter()
.map(|type_field| type_field.clone())
.collect(),
};
if let Some(parent) = &type_definition.parent {
if let Some(parent_definition) = full_type_definitions.get(parent) {
let parent_fields = parent_definition.fields.clone();
full_type_definition.fields.extend(parent_fields);
full_type_definitions
.insert(full_type_definition.ident.clone(), full_type_definition);
type_definitions.remove(key);
}
} else {
full_type_definitions
.insert(full_type_definition.ident.clone(), full_type_definition);
type_definitions.remove(key);
}
}
}
let group_trait_dyn_impls = type_groups.iter().map(|type_group| {
let ident = &type_group.ident;
let as_methods = type_group.types.iter().map(|type_definition| {
let ident = &type_definition.ident;
let method_name = format!("as_{}", ident).to_snake_case();
let as_method_ident = Ident::new(&method_name, ident.span());
quote! {
pub fn #as_method_ident(&self) -> std::option::Option<&#ident> {
self.downcast_ref::<#ident>()
}
}
});
quote! {
impl dyn #ident {
pub fn downcast_ref<T: std::any::Any>(&self) -> std::option::Option<&T> {
self.any_ref().downcast_ref()
}
#(#as_methods)*
}
}
});
let type_defs = full_type_definitions
.iter()
.map(|(ident, full_type_definition)| {
let struct_fields = full_type_definition.fields.iter().map(|type_field| {
let ident = &type_field.ident;
let ty = &type_field.ty;
if let Some(alias) = &type_field.alias {
quote! {
#[serde(alias = #alias)]
pub #ident: #ty
}
} else if let Some(rename) = &type_field.rename {
quote! {
#[serde(rename = #rename)]
pub #ident: #ty
}
} else {
quote! {
pub #ident: #ty
}
}
});
let enum_fields = full_type_definition.fields.iter().map(|type_field| {
let ident = &type_field.ident;
let ty = &type_field.ty;
if let Some(alias) = &type_field.alias {
quote! {
#[serde(alias = #alias, skip_serializing_if = "ActivitypubProperty::is_none", default)]
#ident: #ty
}
} else if let Some(rename) = &type_field.rename {
quote! {
#[serde(rename = #rename, skip_serializing_if = "ActivitypubProperty::is_none", default)]
#ident: #ty
}
} else {
quote! {
#[serde(skip_serializing_if = "ActivitypubProperty::is_none", default)]
#ident: #ty
}
}
});
let enum_ident_string = format!("{}Enum", ident);
let enum_ident = Ident::new(&enum_ident_string, ident.span());
quote! {
#[derive(std::clone::Clone, std::fmt::Debug, std::default::Default, serde::Deserialize, serde::Serialize, derive_builder::Builder)]
#[serde(rename_all = "camelCase", from = #enum_ident_string, into = #enum_ident_string)]
#[builder(default, setter(into))]
pub struct #ident {
#(#struct_fields,)*
}
#[derive(std::clone::Clone, std::fmt::Debug, serde::Deserialize, serde::Serialize)]
#[serde(untagged)]
pub enum #enum_ident {
Brief(String),
#[serde(rename_all = "camelCase")]
Full {
#(#enum_fields,)*
},
}
}
});
let mut base_to_group_traits: HashMap<Ident, Vec<Ident>> = HashMap::new();
let mut group_to_base_traits: HashMap<Ident, Ident> = HashMap::new();
for group_trait in group_traits.iter() {
group_to_base_traits.insert(group_trait.ident.clone(), group_trait.parent.clone());
if base_to_group_traits.contains_key(&group_trait.parent) {
base_to_group_traits
.get_mut(&group_trait.parent)
.unwrap()
.push(group_trait.ident.clone());
} else {
base_to_group_traits
.insert(group_trait.parent.clone(), vec![group_trait.ident.clone()]);
}
}
let trait_impls = type_groups.iter().map(|type_group| {
let group_ident = &type_group.ident;
let base_ident = group_to_base_traits.get(group_ident).unwrap();
let type_trait_impls = type_group.types.iter().map(|type_definition| {
let ident = &type_definition.ident;
let kind = format!("{}", ident);
quote! {
#[typetag::serde]
impl #base_ident for #ident {
fn any_ref(&self) -> &dyn std::any::Any {
self
}
fn kind(&self) -> &str {
#kind
}
fn box_clone(&self) -> Box<dyn #base_ident> {
Box::new((*self).clone())
}
}
#[typetag::serde]
impl #group_ident for #ident {
fn any_ref(&self) -> &dyn std::any::Any {
self
}
fn box_clone(&self) -> Box<dyn #group_ident> {
Box::new((*self).clone())
}
}
}
});
quote! {
#(#type_trait_impls)*
}
});
let from_impls = full_type_definitions
.iter()
.map(|(ident, full_type_definition)| {
let enum_ident_string = format!("{}Enum", ident);
let enum_ident = Ident::new(&enum_ident_string, ident.span());
let builder_ident = Ident::new(&format!("{}Builder", ident), ident.span());
let brief_ident = match &full_type_definition.ident.to_string()[..] {
"Link" | "Mention" => Ident::new("href", full_type_definition.ident.span()),
_ => Ident::new("id", full_type_definition.ident.span()),
};
let brief = full_type_definition
.fields
.iter()
.find(|type_field| type_field.ident.to_string() == brief_ident.to_string())
.unwrap()
.clone();
let brief_ident = &brief.ident;
let brief_ty = &brief.ty;
let from_enum_fields = full_type_definition.fields.iter().map(|type_field| {
let ident = &type_field.ident;
quote! { #ident }
});
let from_enum_fields2 = full_type_definition.fields.iter().map(|type_field| {
let ident = &type_field.ident;
quote! { #ident }
});
let from_struct_fields = full_type_definition.fields.iter().map(|type_field| {
let ident = &type_field.ident;
if ident.to_string() != brief_ident.to_string() {
quote! { && struct_value.#ident.is_none() }
} else {
quote! {}
}
});
let from_struct_fields2 = full_type_definition.fields.iter().map(|type_field| {
let ident = &type_field.ident;
quote! { #ident: struct_value.#ident }
});
quote! {
impl From<#enum_ident> for #ident {
fn from(enum_value: #enum_ident) -> Self {
match enum_value {
#enum_ident::Brief(#brief_ident) => #builder_ident::default()
.#brief_ident(#brief_ty::String(#brief_ident))
.build()
.unwrap(),
#enum_ident::Full {
#(#from_enum_fields,)*
} => {
#ident {
#(#from_enum_fields2,)*
}
},
}
}
}
impl From<#ident> for #enum_ident {
fn from(struct_value: #ident) -> Self {
if struct_value.#brief_ident.is_some()
#(#from_struct_fields)* {
match struct_value.#brief_ident {
#brief_ty::String(#brief_ident) => #enum_ident::Brief(#brief_ident),
_ => unreachable!(),
}
} else {
#enum_ident::Full {
#(#from_struct_fields2,)*
}
}
}
}
}
});
let expanded = quote! {
#(#base_trait_defs)*
#(#group_trait_defs)*
#(#group_trait_dyn_impls)*
#(#type_defs)*
#(#trait_impls)*
#(#from_impls)*
};
expanded.into()
}