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/// Generate standard JWT claim fields and implement `StandardClaims` trait
10///
11/// Adds the following fields to the struct:
12/// - `issuer` (iss) - principal that issued the JWT
13/// - `subject` (sub) - principal that is the subject of the JWT
14/// - `audience` (aud) - recipients the JWT is intended for
15/// - `expiration` (exp) - expiration time in seconds since Unix epoch
16/// - `not_before` (nbf) - time before which the JWT must not be accepted
17/// - `issued_at` (iat) - time at which the JWT was issued
18/// - `jwt_id` (jti) - unique identifier for the JWT
19#[proc_macro_attribute]
20pub fn claims(_args: TokenStream, input: TokenStream) -> TokenStream {
21    let input = parse_macro_input!(input as DeriveInput);
22
23    let struct_name = &input.ident;
24    let vis = &input.vis;
25    let generics = &input.generics;
26
27    // Extract existing fields if it's a struct
28    let existing_fields = if let syn::Data::Struct(syn::DataStruct {
29        fields: syn::Fields::Named(fields),
30        ..
31    }) = &input.data
32    {
33        &fields.named
34    } else {
35        return syn::Error::new_spanned(
36            struct_name,
37            "#[claims] can only be applied to structs with named fields",
38        )
39        .to_compile_error()
40        .into();
41    };
42
43    // Generate the expanded struct with standard claims fields
44    // Always include Debug, Clone, and Deserialize derives
45    let expanded = quote! {
46        /// Standard JWT claims
47        #[derive(Debug, Clone, miniserde::Deserialize)]
48        #vis struct #struct_name #generics {
49            /// Issuer (iss) - principal that issued the JWT
50            #[serde(rename = "iss")]
51            pub issuer: Option<String>,
52            /// Subject (sub) - principal that is the subject of the JWT
53            #[serde(rename = "sub")]
54            pub subject: Option<String>,
55            /// Audience (aud) - recipients the JWT is intended for
56            #[serde(rename = "aud")]
57            pub audience: Option<String>,
58            /// Expiration (exp) - expiration time in seconds since Unix epoch
59            #[serde(rename = "exp")]
60            pub expiration: Option<i64>,
61            /// Not Before (nbf) - time before which the JWT must not be accepted
62            #[serde(rename = "nbf")]
63            pub not_before: Option<i64>,
64            /// Issued At (iat) - time at which the JWT was issued
65            #[serde(rename = "iat")]
66            pub issued_at: Option<i64>,
67            /// JWT ID (jti) - unique identifier for the JWT
68            #[serde(rename = "jti")]
69            pub jwt_id: Option<String>,
70
71            #existing_fields
72        }
73
74        impl #generics jwtiny::StandardClaims for #struct_name #generics {
75            fn issuer(&self) -> Option<&str> {
76                self.issuer.as_deref()
77            }
78
79            fn subject(&self) -> Option<&str> {
80                self.subject.as_deref()
81            }
82
83            fn audience(&self) -> Option<&str> {
84                self.audience.as_deref()
85            }
86
87            fn expiration(&self) -> Option<i64> {
88                self.expiration
89            }
90
91            fn not_before(&self) -> Option<i64> {
92                self.not_before
93            }
94
95            fn issued_at(&self) -> Option<i64> {
96                self.issued_at
97            }
98
99            fn jwt_id(&self) -> Option<&str> {
100                self.jwt_id.as_deref()
101            }
102        }
103    };
104
105    TokenStream::from(expanded)
106}