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))]
40pub fn derive_versioned(input: TokenStream) -> TokenStream {
41 let input = parse_macro_input!(input as DeriveInput);
42
43 let attrs = extract_attributes(&input);
45
46 let name = &input.ident;
47 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
48
49 let version = &attrs.version;
50 let version_key = &attrs.version_key;
51 let data_key = &attrs.data_key;
52
53 let expanded = quote! {
54 impl #impl_generics version_migrate::Versioned for #name #ty_generics #where_clause {
55 const VERSION: &'static str = #version;
56 const VERSION_KEY: &'static str = #version_key;
57 const DATA_KEY: &'static str = #data_key;
58 }
59 };
60
61 TokenStream::from(expanded)
62}
63
64struct VersionedAttributes {
65 version: String,
66 version_key: String,
67 data_key: String,
68}
69
70fn extract_attributes(input: &DeriveInput) -> VersionedAttributes {
71 let mut version = None;
72 let mut version_key = String::from("version");
73 let mut data_key = String::from("data");
74
75 for attr in &input.attrs {
76 if attr.path().is_ident("versioned") {
77 if let Meta::List(meta_list) = &attr.meta {
78 let tokens = meta_list.tokens.to_string();
79 parse_versioned_attrs(&tokens, &mut version, &mut version_key, &mut data_key);
80 }
81 }
82 }
83
84 let version = version.unwrap_or_else(|| {
85 panic!("Missing #[versioned(version = \"x.y.z\")] attribute");
86 });
87
88 if let Err(e) = semver::Version::parse(&version) {
90 panic!("Invalid semantic version '{}': {}", version, e);
91 }
92
93 VersionedAttributes {
94 version,
95 version_key,
96 data_key,
97 }
98}
99
100fn parse_versioned_attrs(
101 tokens: &str,
102 version: &mut Option<String>,
103 version_key: &mut String,
104 data_key: &mut String,
105) {
106 for part in tokens.split(',') {
108 let part = part.trim();
109
110 if let Some(val) = parse_attr_value(part, "version") {
111 *version = Some(val);
112 } else if let Some(val) = parse_attr_value(part, "version_key") {
113 *version_key = val;
114 } else if let Some(val) = parse_attr_value(part, "data_key") {
115 *data_key = val;
116 }
117 }
118}
119
120fn parse_attr_value(token: &str, key: &str) -> Option<String> {
121 let token = token.trim();
122 if let Some(rest) = token.strip_prefix(key) {
123 let rest = rest.trim();
124 if let Some(rest) = rest.strip_prefix('=') {
125 let rest = rest.trim();
126 if rest.starts_with('"') && rest.ends_with('"') {
127 return Some(rest[1..rest.len() - 1].to_string());
128 }
129 }
130 }
131 None
132}