alibi_entity_codegen/
accessors.rs1use 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#[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#[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 fn active(&self) -> bool { self.active }
164 }
165 }
166}
167
168#[must_use]
170pub fn auth_account_impl(
171 ident: &Ident,
172 entity_fields: &[EntityField],
173 core_root: &TokenStream,
174) -> TokenStream {
175 let additional_output = additional_output(EntityRole::Account, entity_fields, core_root);
176 quote! {
177 impl #core_root::entity::AuthAccount for #ident {
178 #additional_output
179 fn id(&self) -> ::std::borrow::Cow<'_, str> { ::std::borrow::Cow::Borrowed(&self.id) }
180 fn account_id(&self) -> &str { &self.account_id }
181 fn provider_id(&self) -> &str { &self.provider_id }
182 fn user_id(&self) -> ::std::borrow::Cow<'_, str> { ::std::borrow::Cow::Borrowed(&self.user_id) }
183 fn access_token(&self) -> Option<&str> { self.access_token.as_deref() }
184 fn refresh_token(&self) -> Option<&str> { self.refresh_token.as_deref() }
185 fn id_token(&self) -> Option<&str> { self.id_token.as_deref() }
186 fn access_token_expires_at(&self) -> Option<::chrono::DateTime<::chrono::Utc>> { #core_root::entity::AuthTimestamp::into_utc(self.access_token_expires_at) }
187 fn refresh_token_expires_at(&self) -> Option<::chrono::DateTime<::chrono::Utc>> { #core_root::entity::AuthTimestamp::into_utc(self.refresh_token_expires_at) }
188 fn scope(&self) -> Option<&str> { self.scope.as_deref() }
189 fn password(&self) -> Option<&str> { self.password.as_deref() }
190 fn created_at(&self) -> ::chrono::DateTime<::chrono::Utc> { #core_root::entity::AuthTimestamp::into_utc(self.created_at) }
191 fn updated_at(&self) -> ::chrono::DateTime<::chrono::Utc> { #core_root::entity::AuthTimestamp::into_utc(self.updated_at) }
192 }
193 }
194}
195
196#[must_use]
198pub fn auth_verification_impl(ident: &Ident, core_root: &TokenStream) -> TokenStream {
199 quote! {
200 impl #core_root::entity::AuthVerification for #ident {
201 fn id(&self) -> ::std::borrow::Cow<'_, str> { ::std::borrow::Cow::Borrowed(&self.id) }
202 fn identifier(&self) -> &str { &self.identifier }
203 fn value(&self) -> &str { &self.value }
204 fn expires_at(&self) -> ::chrono::DateTime<::chrono::Utc> { #core_root::entity::AuthTimestamp::into_utc(self.expires_at) }
205 fn created_at(&self) -> ::chrono::DateTime<::chrono::Utc> { #core_root::entity::AuthTimestamp::into_utc(self.created_at) }
206 fn updated_at(&self) -> ::chrono::DateTime<::chrono::Utc> { #core_root::entity::AuthTimestamp::into_utc(self.updated_at) }
207 }
208 }
209}
210
211fn optional_str_getter(fields: &FieldsNamed, name: &str) -> TokenStream {
214 let field = Ident::new(name, Span::call_site());
215 if has_field(fields, name) {
216 quote! { fn #field(&self) -> Option<&str> { self.#field.as_deref() } }
217 } else {
218 quote! { fn #field(&self) -> Option<&str> { None } }
219 }
220}