version_migrate_macro/
lib.rs1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, DeriveInput, Meta};
4
5#[proc_macro_derive(Versioned, attributes(versioned))]
25pub fn derive_versioned(input: TokenStream) -> TokenStream {
26 let input = parse_macro_input!(input as DeriveInput);
27
28 let version = extract_version(&input);
30
31 let name = &input.ident;
32 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
33
34 let expanded = quote! {
35 impl #impl_generics version_migrate::Versioned for #name #ty_generics #where_clause {
36 const VERSION: &'static str = #version;
37 }
38 };
39
40 TokenStream::from(expanded)
41}
42
43fn extract_version(input: &DeriveInput) -> String {
44 for attr in &input.attrs {
45 if attr.path().is_ident("versioned") {
46 if let Meta::List(meta_list) = &attr.meta {
47 let nested = meta_list.tokens.to_string();
48 if let Some(version_str) = parse_version_attr(&nested) {
50 if let Err(e) = semver::Version::parse(&version_str) {
52 panic!("Invalid semantic version '{}': {}", version_str, e);
53 }
54 return version_str;
55 }
56 }
57 }
58 }
59 panic!("Missing #[versioned(version = \"x.y.z\")] attribute");
60}
61
62fn parse_version_attr(tokens: &str) -> Option<String> {
63 let tokens = tokens.trim();
65 if let Some(rest) = tokens.strip_prefix("version") {
66 let rest = rest.trim();
67 if let Some(rest) = rest.strip_prefix('=') {
68 let rest = rest.trim();
69 if rest.starts_with('"') && rest.ends_with('"') {
70 return Some(rest[1..rest.len() - 1].to_string());
71 }
72 }
73 }
74 None
75}