serde_semver_derive/
lib.rs

1use quote::quote;
2use syn::{
3    parse::{Parse, ParseStream},
4    spanned::Spanned as _,
5    Token,
6};
7
8struct UnitStruct {
9    #[allow(dead_code)]
10    vis: syn::Visibility,
11    attrs: Vec<syn::Attribute>,
12    #[allow(dead_code)]
13    struct_token: Token![struct],
14    name: syn::Ident,
15    #[allow(dead_code)]
16    semi_token: Token![;],
17}
18
19impl Parse for UnitStruct {
20    fn parse(input: ParseStream) -> Result<Self, syn::Error> {
21        Ok(UnitStruct {
22            attrs: input.call(syn::Attribute::parse_outer)?,
23            vis: input.parse()?,
24            struct_token: input.parse()?,
25            name: input.parse()?,
26            semi_token: input.parse()?,
27        })
28    }
29}
30
31#[proc_macro_derive(SemverReq, attributes(version))]
32pub fn derive_semver_req(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
33    let input = syn::parse_macro_input!(input as UnitStruct);
34    f_derive_semver_req(input)
35        .unwrap_or_else(|err| err.to_compile_error())
36        .into()
37}
38
39fn f_derive_semver_req(input: UnitStruct) -> Result<proc_macro2::TokenStream, syn::Error> {
40    let UnitStruct { attrs, name, .. } = input;
41
42    let mut attrs_iter = attrs.iter().filter_map(|attr| {
43        let ident = attr.path.get_ident()?;
44        (ident == "version").then(|| attr)
45    });
46
47    let version_attr = attrs_iter.next().ok_or_else(|| {
48        syn::Error::new(
49            name.span(),
50            r#"#[version("X.Y.Z")] attribute must be specified"#,
51        )
52    })?;
53
54    if let Some(dup_attr) = attrs_iter.next() {
55        return Err(syn::Error::new(
56            dup_attr.span(),
57            r#"duplicated version attribute"#,
58        ));
59    }
60
61    let version_lit: syn::LitStr = version_attr.parse_args()?;
62    let version_text = version_lit.value();
63    semver::Version::parse(&version_text)
64        .map_err(|err| syn::Error::new(version_lit.span(), err.to_string()))?;
65
66    let expanded = quote! {
67        impl #name {
68            pub fn version() -> ::serde_semver::semver::Version {
69                ::serde_semver::semver::Version::parse(#version_text).unwrap()
70            }
71        }
72
73        impl ::serde_semver::serde::Serialize for #name {
74            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
75            where
76                S: ::serde_semver::serde::Serializer,
77            {
78                use ::serde_semver::semver::{Version, VersionReq};
79                use ::serde_semver::serde::de::Error;
80                Self::version().serialize(serializer)
81            }
82        }
83
84        impl<'de> ::serde_semver::serde::Deserialize<'de> for #name {
85            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
86            where
87                D: ::serde_semver::serde::Deserializer<'de>,
88            {
89                use ::serde_semver::semver::{Version, VersionReq};
90                use ::serde_semver::serde::de::Error;
91                let input_version = Version::deserialize(deserializer)?;
92                let req = VersionReq::parse(&input_version.to_string()).unwrap();
93                let target_version = Self::version();
94
95                if !req.matches(&target_version) {
96                    return Err(D::Error::custom(
97                        format!(r#"input version {} is not compatible with version {}"#, input_version, target_version)
98                    ));
99                }
100
101                Ok(Self)
102            }
103        }
104    };
105
106    Ok(expanded)
107}