use proc_macro2::TokenStream;
use quote::quote;
use syn::{DeriveInput, Fields, LitStr};
pub fn derive(input: TokenStream) -> syn::Result<TokenStream> {
let input: DeriveInput = syn::parse2(input)?;
let name = &input.ident;
let fields = match &input.data {
syn::Data::Struct(s) => match &s.fields {
Fields::Named(named) => &named.named,
_ => {
return Err(syn::Error::new_spanned(
&input,
"TypeBridgeRelation requires a struct with named fields",
));
}
},
_ => {
return Err(syn::Error::new_spanned(
&input,
"TypeBridgeRelation can only be derived for structs",
));
}
};
let relation_attrs = parse_relation_attrs(&input.attrs)?;
let type_name = relation_attrs.name;
let is_abstract = relation_attrs.is_abstract;
let parent_type = relation_attrs.parent_type;
let type_doc = relation_attrs.doc;
let type_meta = relation_attrs.meta;
let mut has_iid = false;
let mut role_fields: Vec<RoleField> = Vec::new();
let mut attr_fields: Vec<AttrField> = Vec::new();
for field in fields {
let ident = field
.ident
.as_ref()
.ok_or_else(|| syn::Error::new_spanned(field, "Expected named field"))?;
if ident == "iid" {
has_iid = true;
continue;
}
if let Some(role_attrs) = parse_role_attrs(&field.attrs)? {
role_fields.push(RoleField {
ident: ident.clone(),
role_name: role_attrs.role_name,
player_type: role_attrs.player_type,
doc: role_attrs.doc,
meta: role_attrs.meta,
});
continue;
}
let field_attrs = parse_field_attrs(&field.attrs)?;
let (is_optional, inner_ty) = unwrap_option_type(&field.ty);
attr_fields.push(AttrField {
ident: ident.clone(),
inner_ty: inner_ty.clone(),
is_optional,
is_key: field_attrs.is_key,
is_unique: field_attrs.is_unique,
card_min: field_attrs.card_min,
card_max: field_attrs.card_max,
doc: field_attrs.doc,
meta: field_attrs.meta,
});
}
if !has_iid {
return Err(syn::Error::new_spanned(
&input,
"TypeBridgeRelation requires a field `iid: Option<String>`",
));
}
if role_fields.is_empty() {
return Err(syn::Error::new_spanned(
&input,
"TypeBridgeRelation requires at least one #[role(...)] field",
));
}
let owned_attrs = attr_fields.iter().map(|f| {
let ty = &f.inner_ty;
let annots_tokens = build_annotations(f.is_key, f.is_unique, f.card_min, f.card_max);
let (doc_tokens, meta_tokens) = doc_meta_literal_tokens(&f.doc, &f.meta);
quote! {
type_bridge_orm::OwnedAttributeInfo {
attr_name: <#ty as type_bridge_orm::TypeBridgeAttribute>::ATTR_NAME,
value_type: <#ty as type_bridge_orm::TypeBridgeAttribute>::VALUE_TYPE_ENUM,
annotations: #annots_tokens,
doc: #doc_tokens,
meta: #meta_tokens,
}
}
});
let n_attrs = attr_fields.len();
let role_infos = role_fields.iter().map(|r| {
let role_name = &r.role_name;
let player_type = &r.player_type;
let (doc_tokens, meta_tokens) = doc_meta_literal_tokens(&r.doc, &r.meta);
quote! {
type_bridge_orm::RoleInfo {
role_name: #role_name,
player_type_name: #player_type,
doc: #doc_tokens,
meta: #meta_tokens,
}
}
});
let n_roles = role_fields.len();
let to_values = attr_fields.iter().map(|f| {
let ident = &f.ident;
let ty = &f.inner_ty;
if f.is_optional {
quote! {
if let Some(ref val) = self.#ident {
values.push((
<#ty as type_bridge_orm::TypeBridgeAttribute>::ATTR_NAME,
<#ty as type_bridge_orm::TypeBridgeAttribute>::to_value(val),
));
}
}
} else {
quote! {
values.push((
<#ty as type_bridge_orm::TypeBridgeAttribute>::ATTR_NAME,
<#ty as type_bridge_orm::TypeBridgeAttribute>::to_value(&self.#ident),
));
}
}
});
let role_refs = role_fields.iter().map(|r| {
let ident = &r.ident;
quote! {
self.#ident.clone()
}
});
let from_doc_attrs = attr_fields.iter().map(|f| {
let ident = &f.ident;
let ty = &f.inner_ty;
let type_name_str = &type_name;
if f.is_optional {
quote! {
let #ident = {
let attr_name = <#ty as type_bridge_orm::TypeBridgeAttribute>::ATTR_NAME;
let value_type = <#ty as type_bridge_orm::TypeBridgeAttribute>::VALUE_TYPE;
match doc.get(attr_name) {
Some(json_val) => {
let attr_val = type_bridge_orm::AttributeValue::from_json(json_val, value_type)
.ok_or_else(|| type_bridge_orm::OrmError::Hydration {
type_name: #type_name_str.to_string(),
message: format!("cannot parse attribute '{}' as {}", attr_name, value_type),
})?;
Some(<#ty as type_bridge_orm::TypeBridgeAttribute>::from_value(&attr_val)
.ok_or_else(|| type_bridge_orm::OrmError::Hydration {
type_name: #type_name_str.to_string(),
message: format!("type mismatch for attribute '{}'", attr_name),
})?)
}
None => None,
}
};
}
} else {
quote! {
let #ident = {
let attr_name = <#ty as type_bridge_orm::TypeBridgeAttribute>::ATTR_NAME;
let value_type = <#ty as type_bridge_orm::TypeBridgeAttribute>::VALUE_TYPE;
let json_val = doc.get(attr_name).ok_or_else(|| {
type_bridge_orm::OrmError::Hydration {
type_name: #type_name_str.to_string(),
message: format!("missing required attribute '{}'", attr_name),
}
})?;
let attr_val = type_bridge_orm::AttributeValue::from_json(json_val, value_type)
.ok_or_else(|| type_bridge_orm::OrmError::Hydration {
type_name: #type_name_str.to_string(),
message: format!("cannot parse attribute '{}' as {}", attr_name, value_type),
})?;
<#ty as type_bridge_orm::TypeBridgeAttribute>::from_value(&attr_val)
.ok_or_else(|| type_bridge_orm::OrmError::Hydration {
type_name: #type_name_str.to_string(),
message: format!("type mismatch for attribute '{}'", attr_name),
})?
};
}
}
});
let default_role_fields = role_fields.iter().map(|r| {
let ident = &r.ident;
let role_name = &r.role_name;
let player_type = &r.player_type;
quote! {
#ident: type_bridge_orm::RolePlayerRef {
role: #role_name,
entity_type_name: #player_type,
iid: None,
key: None,
}
}
});
let attr_idents: Vec<_> = attr_fields.iter().map(|f| &f.ident).collect();
let is_abstract_tokens = if is_abstract {
quote! { const IS_ABSTRACT: bool = true; }
} else {
quote! {}
};
let parent_type_tokens = match &parent_type {
Some(p) => quote! { const PARENT_TYPE: Option<&'static str> = Some(#p); },
None => quote! {},
};
let doc_tokens = match &type_doc {
Some(text) => quote! { const DOC: Option<&'static str> = Some(#text); },
None => quote! {},
};
let meta_tokens = if type_meta.is_empty() {
quote! {}
} else {
let pairs = type_meta
.iter()
.map(|(key, value)| quote! { (#key, #value) });
quote! { const META: &'static [(&'static str, &'static str)] = &[ #(#pairs),* ]; }
};
let fields_struct_name = syn::Ident::new(&format!("{}Fields", name), name.span());
let fields_struct_attr_fields = attr_fields.iter().map(|f| {
let ident = &f.ident;
let ty = &f.inner_ty;
quote! {
pub #ident: type_bridge_orm::FieldRef<#ty>
}
});
let fields_struct_role_fields = role_fields.iter().map(|r| {
let ident = &r.ident;
quote! {
pub #ident: type_bridge_orm::RoleRef
}
});
let fields_struct_attr_init = attr_fields.iter().map(|f| {
let ident = &f.ident;
let ty = &f.inner_ty;
quote! {
#ident: type_bridge_orm::FieldRef::<#ty>::new(
<#ty as type_bridge_orm::TypeBridgeAttribute>::ATTR_NAME
)
}
});
let fields_struct_role_init = role_fields.iter().map(|r| {
let ident = &r.ident;
let role_name = &r.role_name;
quote! {
#ident: type_bridge_orm::RoleRef::new(#role_name)
}
});
Ok(quote! {
pub struct #fields_struct_name {
#(#fields_struct_attr_fields,)*
#(#fields_struct_role_fields),*
}
impl #name {
pub fn fields() -> #fields_struct_name {
#fields_struct_name {
#(#fields_struct_attr_init,)*
#(#fields_struct_role_init),*
}
}
}
impl type_bridge_orm::TypeBridgeRelation for #name {
const TYPE_NAME: &'static str = #type_name;
#is_abstract_tokens
#parent_type_tokens
#doc_tokens
#meta_tokens
fn owned_attributes() -> &'static [type_bridge_orm::OwnedAttributeInfo] {
static ATTRS: [type_bridge_orm::OwnedAttributeInfo; #n_attrs] = [
#(#owned_attrs),*
];
&ATTRS
}
fn role_info() -> &'static [type_bridge_orm::RoleInfo] {
static ROLES: [type_bridge_orm::RoleInfo; #n_roles] = [
#(#role_infos),*
];
&ROLES
}
fn iid(&self) -> Option<&str> {
self.iid.as_deref()
}
fn set_iid(&mut self, iid: String) {
self.iid = Some(iid);
}
fn to_attribute_values(&self) -> Vec<(&'static str, type_bridge_orm::AttributeValue)> {
let mut values = Vec::new();
#(#to_values)*
values
}
fn to_role_player_refs(&self) -> Vec<type_bridge_orm::RolePlayerRef> {
vec![#(#role_refs),*]
}
fn from_document(
doc: &serde_json::Map<String, serde_json::Value>,
) -> type_bridge_orm::Result<Self> {
#(#from_doc_attrs)*
Ok(Self {
iid: None,
#(#default_role_fields,)*
#(#attr_idents),*
})
}
}
})
}
struct RoleField {
ident: syn::Ident,
role_name: String,
player_type: String,
doc: Option<String>,
meta: Vec<(String, String)>,
}
struct AttrField {
ident: syn::Ident,
inner_ty: syn::Type,
is_optional: bool,
is_key: bool,
is_unique: bool,
card_min: Option<u32>,
card_max: Option<Option<u32>>,
doc: Option<String>,
meta: Vec<(String, String)>,
}
struct RoleAttrs {
role_name: String,
player_type: String,
doc: Option<String>,
meta: Vec<(String, String)>,
}
struct FieldAttrs {
is_key: bool,
is_unique: bool,
card_min: Option<u32>,
card_max: Option<Option<u32>>,
doc: Option<String>,
meta: Vec<(String, String)>,
}
struct RelationDeriveAttrs {
name: String,
is_abstract: bool,
parent_type: Option<String>,
doc: Option<String>,
meta: Vec<(String, String)>,
}
fn parse_relation_attrs(attrs: &[syn::Attribute]) -> syn::Result<RelationDeriveAttrs> {
for attr in attrs {
if !attr.path().is_ident("relation") {
continue;
}
let mut relation_name: Option<String> = None;
let mut is_abstract = false;
let mut parent_type: Option<String> = None;
let mut doc: Option<String> = None;
let mut meta_pairs: Vec<(String, String)> = Vec::new();
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("name") {
let value: LitStr = meta.value()?.parse()?;
relation_name = Some(value.value());
Ok(())
} else if meta.path.is_ident("r#abstract") || meta.path.is_ident("abstract") {
is_abstract = true;
Ok(())
} else if meta.path.is_ident("extends") {
let value: LitStr = meta.value()?.parse()?;
parent_type = Some(value.value());
Ok(())
} else if meta.path.is_ident("doc") {
let value: LitStr = meta.value()?.parse()?;
doc = Some(value.value());
Ok(())
} else if meta.path.is_ident("meta") {
let content;
syn::parenthesized!(content in meta.input);
let key: LitStr = content.parse()?;
content.parse::<syn::Token![,]>()?;
let value: LitStr = content.parse()?;
meta_pairs.push((key.value(), value.value()));
Ok(())
} else {
Err(meta.error("expected `name`, `abstract`, `extends`, `doc`, or `meta`"))
}
})?;
if let Some(name) = relation_name {
return Ok(RelationDeriveAttrs {
name,
is_abstract,
parent_type,
doc,
meta: meta_pairs,
});
}
}
Err(syn::Error::new(
proc_macro2::Span::call_site(),
"Missing `#[relation(name = \"...\")]`",
))
}
fn parse_role_attrs(attrs: &[syn::Attribute]) -> syn::Result<Option<RoleAttrs>> {
for attr in attrs {
if !attr.path().is_ident("role") {
continue;
}
let mut role_name: Option<String> = None;
let mut player_type: Option<String> = None;
let mut doc: Option<String> = None;
let mut meta_pairs: Vec<(String, String)> = Vec::new();
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("name") {
let value: LitStr = meta.value()?.parse()?;
role_name = Some(value.value());
Ok(())
} else if meta.path.is_ident("player_type") {
let value: LitStr = meta.value()?.parse()?;
player_type = Some(value.value());
Ok(())
} else if meta.path.is_ident("doc") {
let value: LitStr = meta.value()?.parse()?;
doc = Some(value.value());
Ok(())
} else if meta.path.is_ident("meta") {
let content;
syn::parenthesized!(content in meta.input);
let key: LitStr = content.parse()?;
content.parse::<syn::Token![,]>()?;
let value: LitStr = content.parse()?;
meta_pairs.push((key.value(), value.value()));
Ok(())
} else {
Err(meta.error("expected `name`, `player_type`, `doc`, or `meta`"))
}
})?;
let role_name = role_name
.ok_or_else(|| syn::Error::new_spanned(attr, "Missing `name` in #[role(...)]"))?;
let player_type = player_type.ok_or_else(|| {
syn::Error::new_spanned(attr, "Missing `player_type` in #[role(...)]")
})?;
return Ok(Some(RoleAttrs {
role_name,
player_type,
doc,
meta: meta_pairs,
}));
}
Ok(None)
}
fn parse_field_attrs(attrs: &[syn::Attribute]) -> syn::Result<FieldAttrs> {
let mut is_key = false;
let mut is_unique = false;
let mut card_min: Option<u32> = None;
let mut card_max: Option<Option<u32>> = None;
let mut doc: Option<String> = None;
let mut meta_pairs: Vec<(String, String)> = Vec::new();
for attr in attrs {
if !attr.path().is_ident("field") {
continue;
}
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("key") {
is_key = true;
Ok(())
} else if meta.path.is_ident("unique") {
is_unique = true;
Ok(())
} else if meta.path.is_ident("card_min") {
let value: syn::LitInt = meta.value()?.parse()?;
card_min = Some(value.base10_parse()?);
Ok(())
} else if meta.path.is_ident("card_max") {
let value: syn::LitInt = meta.value()?.parse()?;
card_max = Some(Some(value.base10_parse()?));
Ok(())
} else if meta.path.is_ident("doc") {
let value: LitStr = meta.value()?.parse()?;
doc = Some(value.value());
Ok(())
} else if meta.path.is_ident("meta") {
let content;
syn::parenthesized!(content in meta.input);
let key: LitStr = content.parse()?;
content.parse::<syn::Token![,]>()?;
let value: LitStr = content.parse()?;
meta_pairs.push((key.value(), value.value()));
Ok(())
} else {
Err(meta
.error("expected `key`, `unique`, `card_min`, `card_max`, `doc`, or `meta`"))
}
})?;
}
if card_min.is_some() && card_max.is_none() {
card_max = Some(None);
}
Ok(FieldAttrs {
is_key,
is_unique,
card_min,
card_max,
doc,
meta: meta_pairs,
})
}
fn doc_meta_literal_tokens(
doc: &Option<String>,
meta: &[(String, String)],
) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
let doc_tokens = match doc {
Some(text) => quote! { Some(#text) },
None => quote! { None },
};
let pairs = meta.iter().map(|(key, value)| quote! { (#key, #value) });
let meta_tokens = quote! { &[ #(#pairs),* ] };
(doc_tokens, meta_tokens)
}
fn build_annotations(
is_key: bool,
is_unique: bool,
card_min: Option<u32>,
card_max: Option<Option<u32>>,
) -> proc_macro2::TokenStream {
let mut annots = Vec::new();
if is_key {
annots.push(quote! { type_bridge_orm::Annotation::Key });
}
if is_unique {
annots.push(quote! { type_bridge_orm::Annotation::Unique });
}
if let Some(min) = card_min {
let max_tokens = match card_max {
Some(Some(m)) => quote! { Some(#m) },
_ => quote! { None },
};
annots.push(quote! { type_bridge_orm::Annotation::Card(#min, #max_tokens) });
}
if annots.is_empty() {
quote! { &[] }
} else {
quote! { &[#(#annots),*] }
}
}
fn unwrap_option_type(ty: &syn::Type) -> (bool, &syn::Type) {
if let syn::Type::Path(type_path) = ty
&& let Some(last) = type_path.path.segments.last()
&& last.ident == "Option"
&& let syn::PathArguments::AngleBracketed(args) = &last.arguments
&& let Some(syn::GenericArgument::Type(inner)) = args.args.first()
{
return (true, inner);
}
(false, ty)
}