1use crate::{AuthAttributes, EntityRole, has_field, optional_field};
2use proc_macro2::{Ident, Span, TokenStream};
3use quote::quote;
4use syn::FieldsNamed;
5
6#[derive(Clone)]
8pub enum Insert {
9 Value(TokenStream),
12 Null,
14 Default,
16}
17
18#[must_use]
21pub fn insert_values(role: EntityRole, fields: &FieldsNamed) -> Vec<(Ident, Insert)> {
22 let has = |name: &str| has_field(fields, name);
23 let optional = |name: &str| optional_field(fields, name);
24 let mut values: Vec<(&str, Insert)> = Vec::new();
25 let mut value =
26 |name: &'static str, expr: TokenStream| values.push((name, Insert::Value(expr)));
27 match role {
28 EntityRole::User => {
29 for name in ["email", "name"] {
30 let field = Ident::new(name, Span::call_site());
31 let expression = if optional(name) {
32 quote! { create_user.#field }
33 } else {
34 quote! { create_user.#field.unwrap_or_default() }
35 };
36 value(name, expression);
37 }
38 value("image", quote! { create_user.image });
39 value(
40 "email_verified",
41 quote! { create_user.email_verified.unwrap_or(false) },
42 );
43 value(
44 "created_at",
45 quote! { create_user.created_at.unwrap_or(now) },
46 );
47 value(
48 "updated_at",
49 quote! { create_user.updated_at.unwrap_or(now) },
50 );
51 for name in [
52 "username",
53 "display_username",
54 "role",
55 "is_anonymous",
56 "phone_number",
57 "phone_number_verified",
58 "last_login_method",
59 ] {
60 let field = Ident::new(name, Span::call_site());
61 value(name, quote! { create_user.#field });
62 }
63 for name in ["two_factor_enabled", "banned"] {
66 let field = Ident::new(name, Span::call_site());
67 if optional(name) {
68 value(name, quote! { create_user.#field });
69 } else {
70 value(name, quote! { create_user.#field.unwrap_or(false) });
71 }
72 }
73 value(
74 "metadata",
75 quote! { create_user.metadata.unwrap_or(::serde_json::json!({})) },
76 );
77 values.push(("ban_reason", Insert::Null));
78 values.push(("ban_expires", Insert::Null));
79 }
80 EntityRole::Session => {
81 value("token", quote! { token });
82 value("created_at", quote! { now });
83 value("updated_at", quote! { now });
84 value("active", quote! { true });
85 for name in [
86 "user_id",
87 "expires_at",
88 "ip_address",
89 "user_agent",
90 "impersonated_by",
91 "active_organization_id",
92 "active_team_id",
93 ] {
94 let field = Ident::new(name, Span::call_site());
95 value(name, quote! { create_session.#field });
96 }
97 }
98 EntityRole::Account => {
99 value("created_at", quote! { now });
100 value("updated_at", quote! { now });
101 for name in [
102 "account_id",
103 "provider_id",
104 "user_id",
105 "access_token",
106 "refresh_token",
107 "id_token",
108 "access_token_expires_at",
109 "refresh_token_expires_at",
110 "scope",
111 "password",
112 ] {
113 let field = Ident::new(name, Span::call_site());
114 value(name, quote! { create_account.#field });
115 }
116 }
117 EntityRole::Verification => {
118 value("created_at", quote! { now });
119 value("updated_at", quote! { now });
120 for name in ["identifier", "value", "expires_at"] {
121 let field = Ident::new(name, Span::call_site());
122 value(name, quote! { verification.#field });
123 }
124 }
125 }
126 fields
127 .named
128 .iter()
129 .filter_map(|field| field.ident.clone())
130 .filter(|ident| ident != "id")
131 .map(|ident| {
132 let insert = values
133 .iter()
134 .find(|(name, _)| ident == name && has(name))
135 .map_or(Insert::Default, |(_, insert)| insert.clone());
136 (ident, insert)
137 })
138 .collect()
139}
140
141pub type SetField<'a> = &'a dyn Fn(&Ident, &Insert) -> TokenStream;
145
146#[must_use]
149pub fn update_statements(role: EntityRole, fields: &FieldsNamed, set: SetField<'_>) -> TokenStream {
150 let has = |name: &str| has_field(fields, name);
151 let ident = |name: &str| Ident::new(name, Span::call_site());
152 let some = |name: &str, wrap: fn(&Ident) -> TokenStream| {
154 let field = ident(name);
155 let value = wrap(&field);
156 let assign = set(&field, &Insert::Value(value));
157 quote! { if let ::std::option::Option::Some(#field) = update.#field { #assign } }
158 };
159 let wrapped = |field: &Ident| quote! { ::std::option::Option::Some(#field) };
160 let direct = |field: &Ident| quote! { #field };
161 let mut statements = Vec::new();
162 match role {
163 EntityRole::User => {
164 for name in ["email", "name", "image"] {
165 let wrap = if optional_field(fields, name) {
166 wrapped
167 } else {
168 direct
169 };
170 statements.push(some(name, wrap));
171 }
172 statements.push(some("email_verified", direct));
173 for name in ["username", "display_username", "role"] {
174 if has(name) {
175 statements.push(some(name, wrapped));
176 }
177 }
178 for name in ["two_factor_enabled", "metadata"] {
179 if has(name) {
180 statements.push(some(name, direct));
181 }
182 }
183 if has("banned") && has("ban_reason") && has("ban_expires") {
184 let banned = set(&ident("banned"), &Insert::Value(quote! { banned }));
185 let clear_reason = set(&ident("ban_reason"), &Insert::Null);
186 let clear_expires = set(&ident("ban_expires"), &Insert::Null);
187 let reason = set(
188 &ident("ban_reason"),
189 &Insert::Value(quote! { ::std::option::Option::Some(ban_reason) }),
190 );
191 let expires = set(
192 &ident("ban_expires"),
193 &Insert::Value(quote! { ban_expires }),
194 );
195 statements.push(quote! {
196 if let ::std::option::Option::Some(banned) = update.banned {
197 #banned
198 if !banned {
199 #clear_reason
200 #clear_expires
201 }
202 }
203 if update.banned != ::std::option::Option::Some(false) {
204 if let ::std::option::Option::Some(ban_reason) = update.ban_reason { #reason }
205 if let ::std::option::Option::Some(ban_expires) = update.ban_expires { #expires }
206 }
207 });
208 } else if has("banned") {
209 statements.push(some("banned", direct));
210 }
211 for name in ["is_anonymous", "phone_number_verified"] {
212 if has(name) {
213 statements.push(some(name, wrapped));
214 }
215 }
216 for name in ["phone_number", "last_login_method"] {
217 if has(name) {
218 statements.push(some(name, direct));
219 }
220 }
221 }
222 EntityRole::Account => {
223 for (index, name) in ["access_token", "refresh_token", "id_token"]
224 .into_iter()
225 .enumerate()
226 {
227 let clear = set(&ident(name), &Insert::Null);
228 statements.push(quote! { if update.provider_token_nulls[#index] { #clear } });
229 }
230 for name in [
231 "access_token",
232 "refresh_token",
233 "id_token",
234 "access_token_expires_at",
235 "refresh_token_expires_at",
236 "scope",
237 "password",
238 ] {
239 statements.push(some(name, wrapped));
240 }
241 }
242 EntityRole::Session | EntityRole::Verification => {}
243 }
244 let updated_at = set(&ident("updated_at"), &Insert::Value(quote! { now }));
245 quote! { #(#statements)* #updated_at }
246}
247
248#[must_use]
250pub fn generated_id(attributes: &AuthAttributes, core_root: &TokenStream) -> TokenStream {
251 attributes.id_generator.as_ref().map_or_else(
252 || quote! { #core_root::uuid::Uuid::new_v4().to_string() },
253 |generator| quote! { #generator() },
254 )
255}