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 for name in [
85 "user_id",
86 "expires_at",
87 "ip_address",
88 "user_agent",
89 "impersonated_by",
90 "active_organization_id",
91 "active_team_id",
92 ] {
93 let field = Ident::new(name, Span::call_site());
94 value(name, quote! { create_session.#field });
95 }
96 }
97 EntityRole::Account => {
98 value("created_at", quote! { now });
99 value("updated_at", quote! { now });
100 for name in [
101 "account_id",
102 "provider_id",
103 "user_id",
104 "access_token",
105 "refresh_token",
106 "id_token",
107 "access_token_expires_at",
108 "refresh_token_expires_at",
109 "scope",
110 "password",
111 ] {
112 let field = Ident::new(name, Span::call_site());
113 value(name, quote! { create_account.#field });
114 }
115 }
116 EntityRole::Verification => {
117 value("created_at", quote! { now });
118 value("updated_at", quote! { now });
119 for name in ["identifier", "value", "expires_at"] {
120 let field = Ident::new(name, Span::call_site());
121 value(name, quote! { verification.#field });
122 }
123 }
124 }
125 fields
126 .named
127 .iter()
128 .filter_map(|field| field.ident.clone())
129 .filter(|ident| ident != "id")
130 .map(|ident| {
131 let insert = values
132 .iter()
133 .find(|(name, _)| ident == name && has(name))
134 .map_or(Insert::Default, |(_, insert)| insert.clone());
135 (ident, insert)
136 })
137 .collect()
138}
139
140pub type SetField<'a> = &'a dyn Fn(&Ident, &Insert) -> TokenStream;
144
145#[must_use]
148pub fn update_statements(role: EntityRole, fields: &FieldsNamed, set: SetField<'_>) -> TokenStream {
149 let has = |name: &str| has_field(fields, name);
150 let ident = |name: &str| Ident::new(name, Span::call_site());
151 let some = |name: &str, wrap: fn(&Ident) -> TokenStream| {
153 let field = ident(name);
154 let value = wrap(&field);
155 let assign = set(&field, &Insert::Value(value));
156 quote! { if let ::std::option::Option::Some(#field) = update.#field { #assign } }
157 };
158 let wrapped = |field: &Ident| quote! { ::std::option::Option::Some(#field) };
159 let direct = |field: &Ident| quote! { #field };
160 let mut statements = Vec::new();
161 match role {
162 EntityRole::User => {
163 for name in ["email", "name", "image"] {
164 let wrap = if optional_field(fields, name) {
165 wrapped
166 } else {
167 direct
168 };
169 statements.push(some(name, wrap));
170 }
171 statements.push(some("email_verified", direct));
172 for name in ["username", "display_username", "role"] {
173 if has(name) {
174 statements.push(some(name, wrapped));
175 }
176 }
177 for name in ["two_factor_enabled", "metadata"] {
178 if has(name) {
179 statements.push(some(name, direct));
180 }
181 }
182 if has("banned") && has("ban_reason") && has("ban_expires") {
183 let banned = set(&ident("banned"), &Insert::Value(quote! { banned }));
184 let clear_reason = set(&ident("ban_reason"), &Insert::Null);
185 let clear_expires = set(&ident("ban_expires"), &Insert::Null);
186 let reason = set(
187 &ident("ban_reason"),
188 &Insert::Value(quote! { ::std::option::Option::Some(ban_reason) }),
189 );
190 let expires = set(
191 &ident("ban_expires"),
192 &Insert::Value(quote! { ban_expires }),
193 );
194 statements.push(quote! {
195 if let ::std::option::Option::Some(banned) = update.banned {
196 #banned
197 if !banned {
198 #clear_reason
199 #clear_expires
200 }
201 }
202 if update.banned != ::std::option::Option::Some(false) {
203 if let ::std::option::Option::Some(ban_reason) = update.ban_reason { #reason }
204 if let ::std::option::Option::Some(ban_expires) = update.ban_expires { #expires }
205 }
206 });
207 } else if has("banned") {
208 statements.push(some("banned", direct));
209 }
210 for name in ["is_anonymous", "phone_number_verified"] {
211 if has(name) {
212 statements.push(some(name, wrapped));
213 }
214 }
215 for name in ["phone_number", "last_login_method"] {
216 if has(name) {
217 statements.push(some(name, direct));
218 }
219 }
220 }
221 EntityRole::Account => {
222 for (index, name) in ["access_token", "refresh_token", "id_token"]
223 .into_iter()
224 .enumerate()
225 {
226 let clear = set(&ident(name), &Insert::Null);
227 statements.push(quote! { if update.provider_token_nulls[#index] { #clear } });
228 }
229 for name in [
230 "access_token",
231 "refresh_token",
232 "id_token",
233 "access_token_expires_at",
234 "refresh_token_expires_at",
235 "scope",
236 "password",
237 ] {
238 statements.push(some(name, wrapped));
239 }
240 }
241 EntityRole::Session | EntityRole::Verification => {}
242 }
243 let updated_at = set(&ident("updated_at"), &Insert::Value(quote! { now }));
244 quote! { #(#statements)* #updated_at }
245}
246
247#[must_use]
249pub fn generated_id(attributes: &AuthAttributes, core_root: &TokenStream) -> TokenStream {
250 attributes.id_generator.as_ref().map_or_else(
251 || quote! { #core_root::uuid::Uuid::new_v4().to_string() },
252 |generator| quote! { #generator() },
253 )
254}