use super::{ModuleEvent, ModuleEvents};
use crate::{attrs::partition_attributes, utils::FieldsValidator};
use anyhow::{Context, Result};
use syn::{parse_quote, punctuated::Punctuated, Field, Fields, FieldsNamed, Token};
use super::ModuleConfiguration;
pub struct ModuleStruct {
pub is_instantiable: bool,
pub item: syn::ItemStruct,
pub events: ModuleEvents,
pub delegated_fields: Vec<DelegatedField>
}
pub struct DelegatedField {
pub field: syn::Field,
pub delegated_fields: Vec<String>
}
impl DelegatedField {
pub(crate) fn validate(&self, fields: &syn::Fields) -> Result<(), syn::Error> {
let fields = fields
.iter()
.filter_map(|f| f.ident.clone())
.map(|i| i.to_string())
.collect::<Vec<_>>();
let is_valid = self.delegated_fields.iter().find(|f| !fields.contains(f));
if let Some(invalid_ref) = is_valid {
let error_msg = format!("Using non-existing field {}", invalid_ref);
return Err(syn::Error::new_spanned(&self.field, error_msg));
}
Ok(())
}
}
impl TryFrom<syn::Field> for DelegatedField {
type Error = syn::Error;
fn try_from(value: syn::Field) -> std::result::Result<Self, Self::Error> {
let (odra_attrs, other_attrs) = partition_attributes(value.attrs.clone()).unwrap();
let delegated_fields = odra_attrs
.iter()
.flat_map(|attr| attr.using())
.collect::<Vec<_>>();
let field_ident = value.ident.clone().unwrap().to_string();
if delegated_fields.contains(&field_ident) {
return Err(syn::Error::new_spanned(&value, "Self-using is not allowed"));
}
Ok(Self {
field: Field {
attrs: other_attrs,
..value
},
delegated_fields
})
}
}
impl ModuleStruct {
pub fn with_config(mut self, mut config: ModuleConfiguration) -> Result<Self, syn::Error> {
let submodules = self
.item
.fields
.iter()
.filter(|field| field.ident.is_some())
.filter_map(filter_primitives)
.map(|ident| ModuleEvent { ty: ident })
.collect::<Vec<_>>();
let mut mappings = self
.item
.fields
.iter()
.filter(|field| field.ident.is_some())
.filter_map(|f| match &f.ty {
syn::Type::Path(path) => extract_mapping_value_ident_from_path(path).ok(),
_ => None
})
.map(|ty| ModuleEvent { ty })
.collect::<Vec<_>>();
mappings.dedup();
config.events.submodules_events.extend(submodules);
config.events.mappings_events.extend(mappings);
self.events = config.events;
Ok(self)
}
}
impl TryFrom<syn::ItemStruct> for ModuleStruct {
type Error = syn::Error;
fn try_from(value: syn::ItemStruct) -> std::result::Result<Self, Self::Error> {
FieldsValidator::from(&value).result()?;
let (_, other_attrs) = partition_attributes(value.attrs).unwrap();
let named = value
.fields
.clone()
.into_iter()
.map(|field| {
let (_, other_attrs) = partition_attributes(field.attrs).unwrap();
Field {
attrs: other_attrs,
..field
}
})
.collect::<Punctuated<Field, Token![,]>>();
let fields: Fields = Fields::Named(FieldsNamed {
brace_token: Default::default(),
named
});
let delegated_fields = value
.fields
.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<DelegatedField>, syn::Error>>()?;
delegated_fields
.iter()
.try_for_each(|f| f.validate(&fields))?;
Ok(Self {
is_instantiable: true,
item: syn::ItemStruct {
attrs: other_attrs,
fields,
..value
},
events: Default::default(),
delegated_fields
})
}
}
fn extract_mapping_value_ident_from_path(path: &syn::TypePath) -> Result<syn::Type> {
let mut segment = path
.path
.segments
.last()
.cloned()
.context("At least one segment expected")?;
if segment.ident != "Mapping" {
return Err(anyhow::anyhow!(
"Mapping expected but found {}",
segment.ident
));
}
let mut result: Option<syn::Type> = None;
loop {
let args = &segment.arguments;
if args.is_empty() {
break;
}
if let syn::PathArguments::AngleBracketed(args) = args {
match args
.args
.last()
.context("syn::GenericArgument expected but not found")?
{
syn::GenericArgument::Type(syn::Type::Path(path)) => {
result = Some(parse_quote!(#path));
let path = &path.path;
segment = path
.segments
.last()
.cloned()
.context("At least one segment expected")?;
}
other => {
return Err(anyhow::anyhow!(
"syn::TypePath expected but found {:?}",
other
))
}
}
} else {
return Err(anyhow::anyhow!(
"syn::AngleBracketedGenericArguments expected but found {:?}",
args
));
}
}
Ok(result.unwrap())
}
fn filter_primitives(field: &syn::Field) -> Option<syn::Type> {
filter_ident(field, &["Variable", "Mapping", "List", "Sequence"])
}
fn filter_ident(field: &syn::Field, exclusions: &'static [&str]) -> Option<syn::Type> {
match &field.ty {
syn::Type::Path(path) => {
let path = &path.path;
match &path.segments.last() {
Some(segment) => {
if exclusions.contains(&segment.ident.to_string().as_str()) {
return None;
}
Some(field.ty.clone())
}
_ => None
}
}
_ => None
}
}
#[cfg(test)]
mod test {
use quote::ToTokens;
use super::*;
#[test]
fn test() {
let path = syn::parse_str::<syn::TypePath>(
"Mapping<String, Mapping<String, Mapping<u8, a::b::String>>>"
)
.unwrap();
let ident = extract_mapping_value_ident_from_path(&path);
assert_eq!(
ident.unwrap().into_token_stream().to_string(),
"a :: b :: String"
);
let path = syn::parse_str::<syn::TypePath>("String<i32, u8, u16>").unwrap();
let ident = extract_mapping_value_ident_from_path(&path);
assert!(ident.is_err());
let path = syn::parse_str::<syn::TypePath>(
"Mapping<String, Mapping<String, Mapping(u8, String)>>"
)
.unwrap();
let ident = extract_mapping_value_ident_from_path(&path);
assert!(ident.is_err());
let path = syn::parse_str::<syn::TypePath>(
"Mapping<String, Mapping<String, Mapping<fn(usize) -> bool>>>"
)
.unwrap();
let ident = extract_mapping_value_ident_from_path(&path);
assert!(ident.is_err());
}
}