1use proc_macro::TokenStream;
43use proc_macro2::Span;
44use quote::quote;
45use syn::{DeriveInput, LitStr, parse_macro_input};
46
47#[proc_macro_derive(TypesecRole, attributes(role))]
51pub fn derive_typesec_role(input: TokenStream) -> TokenStream {
52 let input = parse_macro_input!(input as DeriveInput);
53 match derive_typesec_role_impl(input) {
54 Ok(ts) => ts.into(),
55 Err(e) => e.to_compile_error().into(),
56 }
57}
58
59fn derive_typesec_role_impl(input: DeriveInput) -> Result<proc_macro2::TokenStream, syn::Error> {
60 let struct_name = &input.ident;
61 let struct_name_str = struct_name.to_string().to_lowercase();
62
63 let role_attr = input
65 .attrs
66 .iter()
67 .find(|a| a.path().is_ident("role"))
68 .ok_or_else(|| {
69 syn::Error::new(
70 Span::call_site(),
71 "TypesecRole requires a #[role(permissions = \"...\", resources = \"...\")] attribute",
72 )
73 })?;
74
75 let mut permissions: Vec<String> = Vec::new();
77 let mut resources: Vec<String> = Vec::new();
78
79 role_attr.parse_nested_meta(|meta| {
80 if meta.path.is_ident("permissions") {
81 let value: LitStr = meta.value()?.parse()?;
82 permissions = value
83 .value()
84 .split(',')
85 .map(|s| s.trim().to_owned())
86 .filter(|s| !s.is_empty())
87 .collect();
88 Ok(())
89 } else if meta.path.is_ident("resources") {
90 let value: LitStr = meta.value()?.parse()?;
91 resources = value
92 .value()
93 .split(',')
94 .map(|s| s.trim().to_owned())
95 .filter(|s| !s.is_empty())
96 .collect();
97 Ok(())
98 } else {
99 Err(meta.error("unknown role attribute key (expected 'permissions' or 'resources')"))
100 }
101 })?;
102
103 let perm_lits: Vec<LitStr> = permissions
104 .iter()
105 .map(|p| LitStr::new(p, Span::call_site()))
106 .collect();
107
108 let resource_lits: Vec<LitStr> = resources
109 .iter()
110 .map(|r| LitStr::new(r, Span::call_site()))
111 .collect();
112
113 let name_lit = LitStr::new(&struct_name_str, Span::call_site());
114
115 Ok(quote! {
116 impl typesec_core::role::Role for #struct_name {
117 fn name() -> &'static str {
118 #name_lit
119 }
120 fn permission_names() -> &'static [&'static str] {
121 &[#(#perm_lits),*]
122 }
123 fn resource_patterns() -> &'static [&'static str] {
124 &[#(#resource_lits),*]
125 }
126 }
127 })
128}
129
130#[proc_macro]
145pub fn policy(input: TokenStream) -> TokenStream {
146 match policy_impl(input.into()) {
147 Ok(ts) => ts.into(),
148 Err(e) => e.to_compile_error().into(),
149 }
150}
151
152fn policy_impl(input: proc_macro2::TokenStream) -> Result<proc_macro2::TokenStream, syn::Error> {
153 use syn::{
154 Ident, Token, braced,
155 parse::{Parse, ParseStream},
156 punctuated::Punctuated,
157 };
158
159 struct PolicyParser(Vec<(Ident, Vec<Ident>, Vec<LitStr>)>);
161
162 impl Parse for PolicyParser {
163 fn parse(input: ParseStream) -> syn::Result<Self> {
164 let mut roles = Vec::new();
165
166 while !input.is_empty() {
167 let kw: Ident = input.parse()?;
169 if kw != "role" {
170 return Err(syn::Error::new(kw.span(), "expected `role`"));
171 }
172
173 let name: Ident = input.parse()?;
175
176 let content;
178 braced!(content in input);
179
180 let can_kw: Ident = content.parse()?;
182 if can_kw != "can" {
183 return Err(syn::Error::new(can_kw.span(), "expected `can`"));
184 }
185
186 let perm_content;
188 syn::bracketed!(perm_content in content);
189 let perms: Punctuated<Ident, Token![,]> =
190 perm_content.parse_terminated(Ident::parse, Token![,])?;
191
192 let on_kw: Ident = content.parse()?;
194 if on_kw != "on" {
195 return Err(syn::Error::new(on_kw.span(), "expected `on`"));
196 }
197
198 let res_content;
200 syn::bracketed!(res_content in content);
201 let resources: Punctuated<LitStr, Token![,]> =
202 res_content.parse_terminated(Parse::parse, Token![,])?;
203
204 let _ = content.parse::<Token![;]>();
206
207 roles.push((
208 name,
209 perms.into_iter().collect(),
210 resources.into_iter().collect(),
211 ));
212 }
213
214 Ok(PolicyParser(roles))
215 }
216 }
217
218 let parsed: PolicyParser = syn::parse2(input)?;
219 let mut output = proc_macro2::TokenStream::new();
220
221 for (name, perms, resources) in parsed.0 {
222 let name_str = name.to_string().to_lowercase();
223 let perm_strs: Vec<String> = perms.iter().map(|p| p.to_string()).collect();
224 let perm_lits: Vec<LitStr> = perm_strs
225 .iter()
226 .map(|s| LitStr::new(s, Span::call_site()))
227 .collect();
228
229 let name_lit = LitStr::new(&name_str, Span::call_site());
230
231 output.extend(quote! {
232 #[derive(Debug, Clone, Copy)]
233 pub struct #name;
234
235 impl typesec_core::role::Role for #name {
236 fn name() -> &'static str { #name_lit }
237 fn permission_names() -> &'static [&'static str] { &[#(#perm_lits),*] }
238 fn resource_patterns() -> &'static [&'static str] { &[#(#resources),*] }
239 }
240 });
241 }
242
243 Ok(output)
244}