1use proc_macro::TokenStream;
46use proc_macro2::TokenStream as TokenStream2;
47use quote::quote;
48use syn::{parse_macro_input, Data, DeriveInput, Error, Fields, Ident, LitStr, Result};
49
50#[proc_macro_derive(Resource, attributes(resource))]
69pub fn derive_resource(input: TokenStream) -> TokenStream {
70 let input = parse_macro_input!(input as DeriveInput);
71 match derive_resource_impl(input) {
72 Ok(tokens) => tokens.into(),
73 Err(err) => err.to_compile_error().into(),
74 }
75}
76
77#[proc_macro_derive(Subject, attributes(subject))]
96pub fn derive_subject(input: TokenStream) -> TokenStream {
97 let input = parse_macro_input!(input as DeriveInput);
98 match derive_subject_impl(input) {
99 Ok(tokens) => tokens.into(),
100 Err(err) => err.to_compile_error().into(),
101 }
102}
103
104fn derive_resource_impl(input: DeriveInput) -> Result<TokenStream2> {
105 let name = &input.ident;
106 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
107
108 let resource_type = parse_type_attr(&input, "resource")?.ok_or_else(|| {
110 Error::new_spanned(&input, "missing #[resource(type = \"...\")] attribute")
111 })?;
112
113 let id_field = find_id_field(&input.data, "resource")?;
115
116 Ok(quote! {
117 impl #impl_generics ::inferadb::Resource for #name #ty_generics #where_clause {
118 fn resource_type() -> &'static str {
119 #resource_type
120 }
121
122 fn resource_id(&self) -> &str {
123 &self.#id_field
124 }
125 }
126 })
127}
128
129fn derive_subject_impl(input: DeriveInput) -> Result<TokenStream2> {
130 let name = &input.ident;
131 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
132
133 let subject_type = parse_type_attr(&input, "subject")?.ok_or_else(|| {
135 Error::new_spanned(&input, "missing #[subject(type = \"...\")] attribute")
136 })?;
137
138 let id_field = find_id_field(&input.data, "subject")?;
140
141 Ok(quote! {
142 impl #impl_generics ::inferadb::Subject for #name #ty_generics #where_clause {
143 fn subject_type() -> &'static str {
144 #subject_type
145 }
146
147 fn subject_id(&self) -> &str {
148 &self.#id_field
149 }
150 }
151 })
152}
153
154fn parse_type_attr(input: &DeriveInput, attr_name: &str) -> Result<Option<String>> {
156 for attr in &input.attrs {
157 if !attr.path().is_ident(attr_name) {
158 continue;
159 }
160
161 let mut type_value = None;
162 attr.parse_nested_meta(|meta| {
163 if meta.path.is_ident("type") {
164 let value: LitStr = meta.value()?.parse()?;
165 type_value = Some(value.value());
166 }
167 Ok(())
168 })?;
169
170 if type_value.is_some() {
171 return Ok(type_value);
172 }
173 }
174 Ok(None)
175}
176
177fn find_id_field(data: &Data, attr_name: &str) -> Result<Ident> {
179 let fields = match data {
180 Data::Struct(data) => match &data.fields {
181 Fields::Named(fields) => &fields.named,
182 Fields::Unnamed(_) => {
183 return Err(Error::new(
184 proc_macro2::Span::call_site(),
185 "tuple structs are not supported",
186 ))
187 }
188 Fields::Unit => {
189 return Err(Error::new(
190 proc_macro2::Span::call_site(),
191 "unit structs are not supported",
192 ))
193 }
194 },
195 Data::Enum(_) => {
196 return Err(Error::new(
197 proc_macro2::Span::call_site(),
198 "enums are not supported",
199 ))
200 }
201 Data::Union(_) => {
202 return Err(Error::new(
203 proc_macro2::Span::call_site(),
204 "unions are not supported",
205 ))
206 }
207 };
208
209 for field in fields {
210 for attr in &field.attrs {
211 if !attr.path().is_ident(attr_name) {
212 continue;
213 }
214
215 let mut is_id_field = false;
217 let _ = attr.parse_nested_meta(|meta| {
218 if meta.path.is_ident("id") {
219 is_id_field = true;
220 }
221 Ok(())
222 });
223
224 if is_id_field {
225 return field
226 .ident
227 .clone()
228 .ok_or_else(|| Error::new_spanned(field, "expected named field"));
229 }
230 }
231 }
232
233 Err(Error::new(
234 proc_macro2::Span::call_site(),
235 format!("no field marked with #[{}(id)]", attr_name),
236 ))
237}
238
239#[cfg(test)]
240mod tests {
241 }