1#![warn(missing_docs)]
14
15use proc_macro::TokenStream;
16use quote::quote;
17use syn::{Attribute, Data, DeriveInput, Fields, LitStr, Path, Result, parse_macro_input};
18
19#[proc_macro_derive(SigmfExtension, attributes(sigmf))]
58pub fn derive_sigmf_extension(input: TokenStream) -> TokenStream {
59 let input = parse_macro_input!(input as DeriveInput);
60 expand(input)
61 .unwrap_or_else(syn::Error::into_compile_error)
62 .into()
63}
64
65fn expand(input: DeriveInput) -> Result<proc_macro2::TokenStream> {
66 let mut namespace = None;
67 let mut version = None;
68 let mut scope = None;
69 let mut crate_path: Path = syn::parse_quote!(::sigmf);
70 let mut crate_seen = false;
71
72 for attribute in input
73 .attrs
74 .iter()
75 .filter(|attr| attr.path().is_ident("sigmf"))
76 {
77 attribute.parse_nested_meta(|meta| {
78 if meta.path.is_ident("namespace") {
79 if namespace.is_some() {
80 return Err(meta.error("duplicate sigmf namespace attribute"));
81 }
82 namespace = Some(meta.value()?.parse::<LitStr>()?);
83 } else if meta.path.is_ident("version") {
84 if version.is_some() {
85 return Err(meta.error("duplicate sigmf version attribute"));
86 }
87 version = Some(meta.value()?.parse::<LitStr>()?);
88 } else if meta.path.is_ident("scope") {
89 if scope.is_some() {
90 return Err(meta.error("duplicate sigmf scope attribute"));
91 }
92 scope = Some(meta.value()?.parse::<LitStr>()?);
93 } else if meta.path.is_ident("crate") {
94 if crate_seen {
95 return Err(meta.error("duplicate sigmf crate attribute"));
96 }
97 crate_seen = true;
98 let path = meta.value()?.parse::<LitStr>()?;
99 crate_path = path.parse()?;
100 } else {
101 return Err(meta.error("unsupported sigmf attribute"));
102 }
103 Ok(())
104 })?;
105 }
106
107 let namespace = namespace.ok_or_else(|| {
108 syn::Error::new_spanned(&input.ident, "missing #[sigmf(namespace = \"...\")]")
109 })?;
110 let version = version.ok_or_else(|| {
111 syn::Error::new_spanned(&input.ident, "missing #[sigmf(version = \"...\")]")
112 })?;
113 let scope = scope.ok_or_else(|| {
114 syn::Error::new_spanned(&input.ident, "missing #[sigmf(scope = \"...\")]")
115 })?;
116
117 let namespace_value = namespace.value();
118 if namespace_value == "core"
119 || namespace_value.is_empty()
120 || !namespace_value
121 .bytes()
122 .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-'))
123 {
124 return Err(syn::Error::new_spanned(
125 namespace,
126 "namespace must be a non-empty ASCII identifier and cannot be \"core\"",
127 ));
128 }
129 if version.value().is_empty() {
130 return Err(syn::Error::new_spanned(
131 version,
132 "version must not be empty",
133 ));
134 }
135
136 let scope_variant = match scope.value().as_str() {
137 "global" => quote!(Global),
138 "capture" => quote!(Capture),
139 "annotation" => quote!(Annotation),
140 "collection" => quote!(Collection),
141 _ => {
142 return Err(syn::Error::new_spanned(
143 scope,
144 "scope must be one of global, capture, annotation, or collection",
145 ));
146 }
147 };
148
149 let fields = match &input.data {
150 Data::Struct(data) => match &data.fields {
151 Fields::Named(fields) => &fields.named,
152 _ => {
153 return Err(syn::Error::new_spanned(
154 &input.ident,
155 "SigmfExtension requires a struct with named fields",
156 ));
157 }
158 },
159 _ => {
160 return Err(syn::Error::new_spanned(
161 &input.ident,
162 "SigmfExtension can only be derived for structs",
163 ));
164 }
165 };
166
167 reject_conflicting_container_attributes(&input.attrs)?;
168 let mut names = Vec::<LitStr>::new();
169 let mut seen = std::collections::HashSet::new();
170 for field in fields {
171 let wire_name = serde_wire_name(&field.attrs)?;
172 let wire_value = wire_name.value();
173 let expected_prefix = format!("{namespace_value}:");
174 if !wire_value.starts_with(&expected_prefix) || wire_value.len() == expected_prefix.len() {
175 return Err(syn::Error::new_spanned(
176 wire_name,
177 format!("extension fields must use the {expected_prefix:?} namespace"),
178 ));
179 }
180 if !seen.insert(wire_value.clone()) {
181 return Err(syn::Error::new_spanned(
182 wire_name,
183 "duplicate extension wire field name",
184 ));
185 }
186 names.push(wire_name);
187 }
188
189 let ident = &input.ident;
190 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
191 Ok(quote! {
192 impl #impl_generics #crate_path::extensions::SigmfExtension
193 for #ident #ty_generics #where_clause
194 {
195 const NAMESPACE: &'static str = #namespace_value;
196 const VERSION: &'static str = #version;
197 const SCOPE: #crate_path::extensions::ExtensionScope =
198 #crate_path::extensions::ExtensionScope::#scope_variant;
199 const FIELDS: &'static [&'static str] = &[#(#names),*];
200 }
201 })
202}
203
204fn reject_conflicting_container_attributes(attributes: &[Attribute]) -> Result<()> {
205 for attribute in attributes
206 .iter()
207 .filter(|attr| attr.path().is_ident("serde"))
208 {
209 attribute.parse_nested_meta(|meta| {
210 if [
211 "rename_all",
212 "rename_all_fields",
213 "transparent",
214 "tag",
215 "content",
216 "untagged",
217 "from",
218 "try_from",
219 ]
220 .iter()
221 .any(|unsupported| meta.path.is_ident(unsupported))
222 {
223 return Err(
224 meta.error("this Serde container attribute is not supported by SigmfExtension")
225 );
226 }
227 if meta.input.peek(syn::Token![=]) {
228 let _: syn::Expr = meta.value()?.parse()?;
229 }
230 Ok(())
231 })?;
232 }
233 Ok(())
234}
235
236fn serde_wire_name(attributes: &[Attribute]) -> Result<LitStr> {
237 let mut wire_name = None;
238 for attribute in attributes
239 .iter()
240 .filter(|attr| attr.path().is_ident("serde"))
241 {
242 attribute.parse_nested_meta(|meta| {
243 if meta.path.is_ident("rename") {
244 if wire_name.is_some() {
245 return Err(meta.error("multiple Serde rename attributes are unsupported"));
246 }
247 wire_name = Some(meta.value()?.parse::<LitStr>()?);
248 } else if [
249 "flatten",
250 "with",
251 "serialize_with",
252 "deserialize_with",
253 "skip",
254 "skip_serializing",
255 "skip_deserializing",
256 ]
257 .iter()
258 .any(|unsupported| meta.path.is_ident(unsupported))
259 {
260 return Err(
261 meta.error("this Serde field attribute is not supported by SigmfExtension")
262 );
263 }
264 if meta.input.peek(syn::Token![=]) {
265 let _: syn::Expr = meta.value()?.parse()?;
266 }
267 Ok(())
268 })?;
269 }
270 wire_name.ok_or_else(|| {
271 syn::Error::new(
272 proc_macro2::Span::call_site(),
273 "each extension field requires an explicit #[serde(rename = \"namespace:field\")]",
274 )
275 })
276}
277
278#[cfg(test)]
279mod tests {
280 use super::*;
281
282 fn diagnostic(source: &str) -> String {
283 let input = syn::parse_str::<DeriveInput>(source).unwrap();
284 expand(input).unwrap_err().to_string()
285 }
286
287 #[test]
288 fn reports_missing_and_reserved_extension_attributes() {
289 assert!(diagnostic("struct Missing;").contains("missing #[sigmf(namespace"));
290 let error = diagnostic(
291 r#"
292 #[sigmf(namespace = "core", version = "1.0.0", scope = "global")]
293 struct Reserved {
294 #[serde(rename = "core:value")]
295 value: bool,
296 }
297 "#,
298 );
299 assert!(error.contains("cannot be \"core\""));
300 }
301
302 #[test]
303 fn rejects_invalid_wire_shapes_with_field_diagnostics() {
304 let error = diagnostic(
305 r#"
306 #[sigmf(namespace = "lab", version = "1.0.0", scope = "capture")]
307 struct WrongNamespace {
308 #[serde(rename = "other:value")]
309 value: bool,
310 }
311 "#,
312 );
313 assert!(error.contains("must use the \"lab:\" namespace"));
314
315 let error = diagnostic(
316 r#"
317 #[sigmf(namespace = "lab", version = "1.0.0", scope = "capture")]
318 struct Flattened {
319 #[serde(rename = "lab:value", flatten)]
320 value: bool,
321 }
322 "#,
323 );
324 assert!(error.contains("not supported by SigmfExtension"));
325
326 let error = diagnostic(
327 r#"
328 #[sigmf(namespace = "lab", version = "1.0.0", scope = "capture")]
329 struct Skipped {
330 #[serde(rename = "lab:value", skip)]
331 value: bool,
332 }
333 "#,
334 );
335 assert!(error.contains("not supported by SigmfExtension"));
336 }
337
338 #[test]
339 fn rejects_unsupported_derive_shapes() {
340 let error = diagnostic(
341 r#"
342 #[sigmf(namespace = "lab", version = "1.0.0", scope = "capture")]
343 enum Unsupported {
344 Value,
345 }
346 "#,
347 );
348 assert!(error.contains("can only be derived for structs"));
349
350 let error = diagnostic(
351 r#"
352 #[sigmf(namespace = "lab", namespace = "vendor", version = "1.0.0", scope = "global")]
353 struct DuplicateNamespace {
354 #[serde(rename = "lab:value")]
355 value: bool,
356 }
357 "#,
358 );
359 assert!(error.contains("duplicate sigmf namespace attribute"));
360 }
361}