Skip to main content

alibi_entity_codegen/
accessors.rs

1use crate::{
2    EntityField, EntityRole, additional_output, has_field, optional_field, secondary_codec,
3};
4use proc_macro2::{Ident, Span, TokenStream};
5use quote::quote;
6use syn::FieldsNamed;
7
8/// `impl AuthUser`: absent plugin fields return their defaults.
9#[expect(
10    clippy::too_many_lines,
11    reason = "Keep the generated AuthUser implementation together as one quoted trait contract"
12)]
13#[must_use]
14pub fn auth_user_impl(
15    ident: &Ident,
16    fields: &FieldsNamed,
17    entity_fields: &[EntityField],
18    secondary: bool,
19    core_root: &TokenStream,
20) -> TokenStream {
21    let has = |name: &str| has_field(fields, name);
22    let optional = |name: &str| optional_field(fields, name);
23    let secondary_codec = if secondary {
24        secondary_codec(fields, core_root)
25    } else {
26        quote! {}
27    };
28    let additional_output = additional_output(EntityRole::User, entity_fields, core_root);
29    let text_getter = |name: &str| {
30        let field = Ident::new(name, Span::call_site());
31        if optional(name) {
32            quote! { fn #field(&self) -> Option<&str> { self.#field.as_deref() } }
33        } else {
34            quote! { fn #field(&self) -> Option<&str> { Some(self.#field.as_str()) } }
35        }
36    };
37    let email_impl = text_getter("email");
38    let name_impl = text_getter("name");
39    let username_impl = optional_str_getter(fields, "username");
40    let display_username_impl = optional_str_getter(fields, "display_username");
41    let two_factor_impl = if has("two_factor_enabled") {
42        if optional("two_factor_enabled") {
43            quote! {
44                fn two_factor_enabled(&self) -> bool { self.two_factor_enabled.unwrap_or(false) }
45                fn two_factor_enabled_value(&self) -> Option<bool> { self.two_factor_enabled }
46            }
47        } else {
48            quote! { fn two_factor_enabled(&self) -> bool { self.two_factor_enabled } }
49        }
50    } else {
51        quote! {
52            fn two_factor_enabled(&self) -> bool { false }
53            fn two_factor_enabled_value(&self) -> Option<bool> { None }
54        }
55    };
56    let role_impl = optional_str_getter(fields, "role");
57    let banned_impl = if has("banned") {
58        if optional("banned") {
59            quote! {
60                fn banned(&self) -> bool { self.banned.unwrap_or(false) }
61                fn banned_value(&self) -> Option<bool> { self.banned }
62            }
63        } else {
64            quote! { fn banned(&self) -> bool { self.banned } }
65        }
66    } else {
67        quote! {
68            fn banned(&self) -> bool { false }
69            fn banned_value(&self) -> Option<bool> { None }
70        }
71    };
72    let ban_reason_impl = optional_str_getter(fields, "ban_reason");
73    let ban_expires_impl = if has("ban_expires") {
74        quote! { fn ban_expires(&self) -> Option<::chrono::DateTime<::chrono::Utc>> { #core_root::entity::AuthTimestamp::into_utc(self.ban_expires) } }
75    } else {
76        quote! { fn ban_expires(&self) -> Option<::chrono::DateTime<::chrono::Utc>> { None } }
77    };
78    let metadata_impl = if has("metadata") {
79        quote! { fn metadata(&self) -> &::serde_json::Value { &self.metadata } }
80    } else {
81        quote! { fn metadata(&self) -> &::serde_json::Value {
82            static EMPTY: ::std::sync::LazyLock<::serde_json::Value> =
83                ::std::sync::LazyLock::new(|| ::serde_json::json!({}));
84            &EMPTY
85        } }
86    };
87    let mut optional_user_getters = Vec::new();
88    for name in ["is_anonymous", "phone_number_verified"] {
89        if has(name) {
90            let field = Ident::new(name, Span::call_site());
91            optional_user_getters.push(quote! { fn #field(&self) -> Option<bool> { self.#field } });
92        }
93    }
94    for name in ["phone_number", "last_login_method"] {
95        if has(name) {
96            let field = Ident::new(name, Span::call_site());
97            optional_user_getters
98                .push(quote! { fn #field(&self) -> Option<&str> { self.#field.as_deref() } });
99        }
100    }
101    quote! {
102        impl #core_root::entity::AuthUser for #ident {
103            #secondary_codec
104            #additional_output
105            fn id(&self) -> ::std::borrow::Cow<'_, str> { ::std::borrow::Cow::Borrowed(&self.id) }
106            #email_impl
107            #name_impl
108            fn email_verified(&self) -> bool { self.email_verified }
109            fn image(&self) -> Option<&str> { self.image.as_deref() }
110            fn created_at(&self) -> ::chrono::DateTime<::chrono::Utc> { #core_root::entity::AuthTimestamp::into_utc(self.created_at) }
111            fn updated_at(&self) -> ::chrono::DateTime<::chrono::Utc> { #core_root::entity::AuthTimestamp::into_utc(self.updated_at) }
112            #username_impl
113            #display_username_impl
114            #two_factor_impl
115            #role_impl
116            #banned_impl
117            #ban_reason_impl
118            #ban_expires_impl
119            #metadata_impl
120            #(#optional_user_getters)*
121        }
122    }
123}
124
125/// `impl AuthSession`: absent plugin fields return their defaults.
126#[must_use]
127pub fn auth_session_impl(
128    ident: &Ident,
129    fields: &FieldsNamed,
130    entity_fields: &[EntityField],
131    secondary: bool,
132    core_root: &TokenStream,
133) -> TokenStream {
134    let has = |name: &str| has_field(fields, name);
135    let secondary_codec = if secondary {
136        secondary_codec(fields, core_root)
137    } else {
138        quote! {}
139    };
140    let additional_output = additional_output(EntityRole::Session, entity_fields, core_root);
141    let impersonated_by_impl = optional_str_getter(fields, "impersonated_by");
142    let active_org_impl = optional_str_getter(fields, "active_organization_id");
143    let active_team_impl = if has("active_team_id") {
144        quote! { fn active_team_id(&self) -> Option<&str> { self.active_team_id.as_deref() } }
145    } else {
146        quote! {}
147    };
148    quote! {
149        impl #core_root::entity::AuthSession for #ident {
150            #secondary_codec
151            #additional_output
152            fn id(&self) -> ::std::borrow::Cow<'_, str> { ::std::borrow::Cow::Borrowed(&self.id) }
153            fn expires_at(&self) -> ::chrono::DateTime<::chrono::Utc> { #core_root::entity::AuthTimestamp::into_utc(self.expires_at) }
154            fn token(&self) -> &str { &self.token }
155            fn created_at(&self) -> ::chrono::DateTime<::chrono::Utc> { #core_root::entity::AuthTimestamp::into_utc(self.created_at) }
156            fn updated_at(&self) -> ::chrono::DateTime<::chrono::Utc> { #core_root::entity::AuthTimestamp::into_utc(self.updated_at) }
157            fn ip_address(&self) -> Option<&str> { self.ip_address.as_deref() }
158            fn user_agent(&self) -> Option<&str> { self.user_agent.as_deref() }
159            fn user_id(&self) -> ::std::borrow::Cow<'_, str> { ::std::borrow::Cow::Borrowed(&self.user_id) }
160            #impersonated_by_impl
161            #active_org_impl
162            #active_team_impl
163        }
164    }
165}
166
167/// `impl AuthAccount`: account fields are all core fields.
168#[must_use]
169pub fn auth_account_impl(
170    ident: &Ident,
171    entity_fields: &[EntityField],
172    core_root: &TokenStream,
173) -> TokenStream {
174    let additional_output = additional_output(EntityRole::Account, entity_fields, core_root);
175    quote! {
176        impl #core_root::entity::AuthAccount for #ident {
177            #additional_output
178            fn id(&self) -> ::std::borrow::Cow<'_, str> { ::std::borrow::Cow::Borrowed(&self.id) }
179            fn account_id(&self) -> &str { &self.account_id }
180            fn provider_id(&self) -> &str { &self.provider_id }
181            fn user_id(&self) -> ::std::borrow::Cow<'_, str> { ::std::borrow::Cow::Borrowed(&self.user_id) }
182            fn access_token(&self) -> Option<&str> { self.access_token.as_deref() }
183            fn refresh_token(&self) -> Option<&str> { self.refresh_token.as_deref() }
184            fn id_token(&self) -> Option<&str> { self.id_token.as_deref() }
185            fn access_token_expires_at(&self) -> Option<::chrono::DateTime<::chrono::Utc>> { #core_root::entity::AuthTimestamp::into_utc(self.access_token_expires_at) }
186            fn refresh_token_expires_at(&self) -> Option<::chrono::DateTime<::chrono::Utc>> { #core_root::entity::AuthTimestamp::into_utc(self.refresh_token_expires_at) }
187            fn scope(&self) -> Option<&str> { self.scope.as_deref() }
188            fn password(&self) -> Option<&str> { self.password.as_deref() }
189            fn created_at(&self) -> ::chrono::DateTime<::chrono::Utc> { #core_root::entity::AuthTimestamp::into_utc(self.created_at) }
190            fn updated_at(&self) -> ::chrono::DateTime<::chrono::Utc> { #core_root::entity::AuthTimestamp::into_utc(self.updated_at) }
191        }
192    }
193}
194
195/// `impl AuthVerification`: verification fields are all core fields.
196#[must_use]
197pub fn auth_verification_impl(ident: &Ident, core_root: &TokenStream) -> TokenStream {
198    quote! {
199        impl #core_root::entity::AuthVerification for #ident {
200            fn id(&self) -> ::std::borrow::Cow<'_, str> { ::std::borrow::Cow::Borrowed(&self.id) }
201            fn identifier(&self) -> &str { &self.identifier }
202            fn value(&self) -> &str { &self.value }
203            fn expires_at(&self) -> ::chrono::DateTime<::chrono::Utc> { #core_root::entity::AuthTimestamp::into_utc(self.expires_at) }
204            fn created_at(&self) -> ::chrono::DateTime<::chrono::Utc> { #core_root::entity::AuthTimestamp::into_utc(self.created_at) }
205            fn updated_at(&self) -> ::chrono::DateTime<::chrono::Utc> { #core_root::entity::AuthTimestamp::into_utc(self.updated_at) }
206        }
207    }
208}
209
210/// `fn name(&self) -> Option<&str>` over an optional text field, or `None` when
211/// the model does not declare the field.
212fn optional_str_getter(fields: &FieldsNamed, name: &str) -> TokenStream {
213    let field = Ident::new(name, Span::call_site());
214    if has_field(fields, name) {
215        quote! { fn #field(&self) -> Option<&str> { self.#field.as_deref() } }
216    } else {
217        quote! { fn #field(&self) -> Option<&str> { None } }
218    }
219}