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}