jwtiny_derive/
lib.rs

1//! JWTiny Macros
2//!
3//! This crate provides the `#[claims]` attribute macro.
4
5use proc_macro::TokenStream;
6use quote::quote;
7use syn::{DeriveInput, parse_macro_input};
8
9/// Generates standard JWT claim fields and implements `StandardClaims` trait.
10///
11/// Fields included:
12/// - Issuer (`iss`)
13/// - Subject (`sub`)
14/// - Audience (`aud`)
15/// - Expiration (`exp`)
16/// - Not Before (`nbf`)
17/// - Issued At (`iat`)
18/// - JWT ID (`jti`)
19///
20/// And implements the `StandardClaims` trait.
21#[proc_macro_attribute]
22pub fn claims(_args: TokenStream, input: TokenStream) -> TokenStream {
23    let input = parse_macro_input!(input as DeriveInput);
24
25    let struct_name = &input.ident;
26    let vis = &input.vis;
27    let generics = &input.generics;
28
29    // Extract existing fields if it's a struct
30    let existing_fields = if let syn::Data::Struct(syn::DataStruct {
31        fields: syn::Fields::Named(fields),
32        ..
33    }) = &input.data
34    {
35        &fields.named
36    } else {
37        return syn::Error::new_spanned(
38            struct_name,
39            "#[claims] can only be applied to structs with named fields",
40        )
41        .to_compile_error()
42        .into();
43    };
44
45    // Generate the expanded struct with standard claims fields
46    // Always include Debug, Clone, and Deserialize derives
47    let expanded = quote! {
48        #[derive(Debug, Clone, miniserde::Deserialize)]
49        #vis struct #struct_name #generics {
50            #[serde(rename = "iss")]
51            pub issuer: Option<String>,
52            #[serde(rename = "sub")]
53            pub subject: Option<String>,
54            #[serde(rename = "aud")]
55            pub audience: Option<String>,
56            #[serde(rename = "exp")]
57            pub expiration: Option<i64>,
58            #[serde(rename = "nbf")]
59            pub not_before: Option<i64>,
60            #[serde(rename = "iat")]
61            pub issued_at: Option<i64>,
62            #[serde(rename = "jti")]
63            pub jwt_id: Option<String>,
64
65            #existing_fields
66        }
67
68        impl #generics jwtiny::StandardClaims for #struct_name #generics {
69            fn issuer(&self) -> Option<&str> {
70                self.issuer.as_deref()
71            }
72
73            fn subject(&self) -> Option<&str> {
74                self.subject.as_deref()
75            }
76
77            fn audience(&self) -> Option<&str> {
78                self.audience.as_deref()
79            }
80
81            fn expiration(&self) -> Option<i64> {
82                self.expiration
83            }
84
85            fn not_before(&self) -> Option<i64> {
86                self.not_before
87            }
88
89            fn issued_at(&self) -> Option<i64> {
90                self.issued_at
91            }
92
93            fn jwt_id(&self) -> Option<&str> {
94                self.jwt_id.as_deref()
95            }
96        }
97    };
98
99    TokenStream::from(expanded)
100}