use syn::parse::{Parse, ParseStream};
use syn::{parenthesized, Ident, Path, Result};
struct Intermediate {
name: Ident,
body: Vec<Path>,
}
impl Parse for Intermediate {
fn parse(input: ParseStream) -> Result<Self> {
let name: Ident = input.parse().unwrap();
let body;
parenthesized!(body in input);
let body = body
.parse_terminated::<_, syn::token::Comma>(Path::parse)
.unwrap()
.into_iter()
.collect();
Ok(Intermediate { name, body })
}
}
pub enum AuthType {
CallingUser(Ident),
Flag(Path, Option<Ident>),
}
pub struct MacroInput {
pub collection: syn::Path,
pub get: Option<Option<AuthType>>,
pub post: Option<Option<AuthType>>,
pub put: Option<Option<AuthType>>,
pub delete: Option<Option<AuthType>>,
pub image: Option<Option<AuthType>>,
}
impl Parse for MacroInput {
fn parse(attr: ParseStream) -> Result<Self> {
let mut collection = None;
let mut get = None;
let mut post = None;
let mut put = None;
let mut delete = None;
let mut image = None;
let arg_list: syn::punctuated::Punctuated<Intermediate, syn::token::Comma> =
syn::punctuated::Punctuated::parse_terminated(attr).unwrap();
for mut arg in arg_list {
let name = arg.name.to_string().to_lowercase();
let name = name.as_str();
match name {
"collection" => {
assert!(arg.body.len() == 1, "Only one argument in collection allowed");
collection = arg.body.pop();
},
"get" => {
get = match arg.body.len() {
0 => Some(None),
1 => {
let flg = arg.body.pop().unwrap();
Some(Some(AuthType::Flag(flg, None)))
},
2 => {
let resource_id = arg.body.pop().unwrap().get_ident().cloned().unwrap();
let flg = arg.body.pop().unwrap();
if flg.get_ident().is_some() && &flg.get_ident().unwrap().to_string() == "SELF" {
Some(Some(AuthType::CallingUser(resource_id)))
} else {
Some(Some(AuthType::Flag(flg, Some(resource_id))))
}
},
_ => {
panic!("Wrong number of arguments for get");
},
};
},
"post" => {
post = match arg.body.len() {
0 => Some(None),
1 => {
let flg = arg.body.pop().unwrap();
Some(Some(AuthType::Flag(flg, None)))
},
2 => {
let resource_id = arg.body.pop().unwrap().get_ident().cloned().unwrap();
let flg = arg.body.pop().unwrap();
if flg.get_ident().is_some() && &flg.get_ident().unwrap().to_string() == "SELF" {
Some(Some(AuthType::CallingUser(resource_id)))
} else {
Some(Some(AuthType::Flag(flg, Some(resource_id))))
}
},
_ => {
panic!("Wrong number of arguments for get");
},
};
},
"put" => {
put = match arg.body.len() {
0 => Some(None),
1 => {
let flg = arg.body.pop().unwrap();
Some(Some(AuthType::Flag(flg, None)))
},
2 => {
let resource_id = arg.body.pop().unwrap().get_ident().cloned().unwrap();
let flg = arg.body.pop().unwrap();
if flg.get_ident().is_some() && &flg.get_ident().unwrap().to_string() == "SELF" {
Some(Some(AuthType::CallingUser(resource_id)))
} else {
Some(Some(AuthType::Flag(flg, Some(resource_id))))
}
},
_ => {
panic!("Wrong number of arguments for get");
},
};
},
"delete" => {
delete = match arg.body.len() {
0 => Some(None),
1 => {
let flg = arg.body.pop().unwrap();
Some(Some(AuthType::Flag(flg, None)))
},
2 => {
let resource_id = arg.body.pop().unwrap().get_ident().cloned().unwrap();
let flg = arg.body.pop().unwrap();
if flg.get_ident().is_some() && &flg.get_ident().unwrap().to_string() == "SELF" {
Some(Some(AuthType::CallingUser(resource_id)))
} else {
Some(Some(AuthType::Flag(flg, Some(resource_id))))
}
},
_ => {
panic!("Wrong number of arguments for get");
},
};
},
"image" => {
image = Some(parse_auth_type(arg.body));
},
invalid => panic!("Macro input contains unknown keyword: {}, valid keywords are: COLLECTION, GET, POST, PUT, DELETE, IMAGE", invalid),
}
}
Ok(MacroInput {
collection: collection.expect("Collection not found"),
get,
post,
put,
delete,
image,
})
}
}
pub struct Fields {
pub prim: syn::Ident,
pub partition: syn::Ident,
pub path: Option<Vec<syn::Ident>>,
}
pub fn get_field_attributes(mut ast: syn::ItemStruct) -> (syn::ItemStruct, Fields) {
let fields = match &mut ast.fields {
syn::Fields::Named(n) => &mut n.named,
_ => panic!("Model can only be applied to named structs"),
};
let mut prim = None;
let mut partition = None;
let mut path: Option<Vec<syn::Ident>> = None;
for field in fields.iter_mut() {
let mut remaining_attrs = vec![];
let attrs = std::mem::take(&mut field.attrs);
for attr in attrs {
let mut found = false;
for p in &attr.path.segments {
if p.ident == "prim" {
if let Some(_) = prim {
panic!("Only one prim allowed");
}
prim = field.ident.clone();
found = true;
}
if p.ident == "partition" {
if let Some(_) = partition {
panic!("Only one partition allowed");
}
partition = field.ident.clone();
found = true;
}
if p.ident == "path" {
if let Some(v) = &mut path {
v.push(field.ident.clone().unwrap());
} else {
path = Some(vec![field.ident.clone().unwrap()]);
}
found = true;
}
}
if !found {
remaining_attrs.push(attr);
}
}
field.attrs = remaining_attrs;
}
let prim = prim.expect("A model needs to have a prim field, annotate with #[prim]");
let partition =
partition.expect("A model needs to have a partition field, annotate with #[partition]");
let fields = Fields {
prim,
partition,
path,
};
(ast, fields)
}
fn parse_auth_type(mut body: Vec<Path>) -> Option<AuthType> {
match body.len() {
0 => None,
1 => {
let flg = body.pop().unwrap();
Some(AuthType::Flag(flg, None))
},
2 => {
let resource_id = body.pop().unwrap().get_ident().cloned().unwrap();
let flg = body.pop().unwrap();
if flg.get_ident().is_some() && &flg.get_ident().unwrap().to_string() == "SELF" {
Some(AuthType::CallingUser(resource_id))
} else {
Some(AuthType::Flag(flg, Some(resource_id)))
}
},
_ => {
panic!("Wrong number of arguments for get");
},
}
}