1use crate::store::schema::resolve_field_name;
4use crate::{AuthError, AuthResult};
5use crate::{FieldMap, FieldValue as Value};
6use indexmap::IndexMap;
7use std::sync::{Arc, LazyLock};
8mod adapter;
9#[cfg(test)]
10mod factory_tests;
11#[cfg(test)]
12mod input_binding_tests;
13pub use adapter::FieldOutputCapabilities;
14mod batch;
15pub(crate) use batch::{
16 project_fields_batches_then, project_fields_then, project_source_fields_batches_then,
17 project_source_fields_then,
18};
19pub use record::AdapterRecord;
20pub(crate) use record::project_adapter_value;
21mod organization;
22pub(crate) use organization::assign_output;
23mod output;
24mod record;
25mod transform;
26mod user_record;
27pub use transform::UserFieldTransform;
28pub(crate) use user_record::USER_FIELDS;
29
30pub type UserFieldValidator = Arc<dyn Fn(Value) -> AuthResult<Value> + Send + Sync>;
32
33pub type UserFieldFactory = Arc<dyn Fn() -> AuthResult<Value> + Send + Sync>;
35
36#[derive(Clone, Debug)]
38pub struct UserFieldReference {
39 pub model: String,
41 pub field: String,
43}
44
45#[derive(Clone, Debug, Default)]
47pub enum UserFieldType {
48 #[default]
50 String,
51 Number,
53 Boolean,
55 Date,
57 Json,
59 StringArray,
61 NumberArray,
63 Enum(Vec<String>),
65}
66
67#[derive(Clone, Default)]
69pub struct FieldTransforms {
70 pub input: Option<UserFieldTransform>,
73 pub output: Option<UserFieldTransform>,
75}
76
77#[derive(Clone, Default)]
79pub struct UserFieldConfig {
80 pub field_type: UserFieldType,
82 pub required: Option<bool>,
84 pub input: Option<bool>,
86 pub returned: Option<bool>,
88 pub field_name: Option<String>,
90 pub references: Option<UserFieldReference>,
92 pub unique: Option<bool>,
94 pub default_value: Option<Value>,
96 pub default_value_fn: Option<UserFieldFactory>,
98 pub on_update: Option<UserFieldFactory>,
100 pub validator: Option<UserFieldValidator>,
102 pub transform: Option<FieldTransforms>,
104}
105
106impl std::fmt::Debug for UserFieldConfig {
107 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 formatter
109 .debug_struct("UserFieldConfig")
110 .field("field_type", &self.field_type)
111 .field("required", &self.required)
112 .field("input", &self.input)
113 .field("returned", &self.returned)
114 .field("field_name", &self.field_name)
115 .finish_non_exhaustive()
116 }
117}
118
119#[derive(Clone, Default)]
121pub struct UserConfig {
122 pub additional_fields: Option<IndexMap<String, UserFieldConfig>>,
124}
125
126impl UserFieldConfig {
127 pub fn input(&self) -> bool {
129 self.input.unwrap_or(true)
130 }
131
132 pub fn returned(&self) -> bool {
134 self.returned.unwrap_or(true)
135 }
136
137 pub fn input_transform(&self) -> Option<&UserFieldTransform> {
139 self.transform
140 .as_ref()
141 .and_then(|transform| transform.input.as_ref())
142 }
143
144 pub fn output_transform(&self) -> Option<&UserFieldTransform> {
146 self.transform
147 .as_ref()
148 .and_then(|transform| transform.output.as_ref())
149 }
150
151 pub(crate) fn normalize_date(&self, value: &mut Value) -> AuthResult<()> {
152 if matches!(self.field_type, UserFieldType::Date)
153 && let Value::String(text) = value
154 {
155 *value = Value::Date(
156 crate::utils::date::parse_adapter_date(text)
157 .map(crate::FieldDate::from)
158 .unwrap_or_else(crate::FieldDate::invalid),
159 );
160 }
161 Ok(())
162 }
163
164 pub(crate) fn default_value(&self) -> AuthResult<Option<Value>> {
165 match &self.default_value_fn {
166 Some(factory) => factory().map(Some),
167 None => Ok(self.default_value.clone()),
168 }
169 }
170}
171
172pub fn is_truthy(value: &Value) -> bool {
174 value.is_truthy()
175}
176
177pub(crate) fn fields_or_empty(
178 fields: &Option<IndexMap<String, UserFieldConfig>>,
179) -> &IndexMap<String, UserFieldConfig> {
180 static EMPTY: LazyLock<IndexMap<String, UserFieldConfig>> = LazyLock::new(IndexMap::new);
181 fields.as_ref().unwrap_or(&EMPTY)
182}
183
184impl UserConfig {
185 pub fn fields(&self) -> &IndexMap<String, UserFieldConfig> {
187 fields_or_empty(&self.additional_fields)
188 }
189
190 pub fn fields_mut(&mut self) -> &mut IndexMap<String, UserFieldConfig> {
192 self.additional_fields.get_or_insert_default()
193 }
194
195 pub fn parse_input(&self, input: &FieldMap, create: bool) -> AuthResult<FieldMap> {
197 let mut parsed = FieldMap::new();
198 for (name, field) in self.fields() {
199 let value = if let Some(value) = input.get(name) {
200 if !field.input() {
201 if create && let Some(default) = field.default_value()? {
202 let _ = parsed.insert(name.clone(), default);
203 continue;
204 }
205 if is_truthy(value) {
206 return Err(AuthError::FieldInput {
207 code: "FIELD_NOT_ALLOWED",
208 message: format!("{name} is not allowed to be set"),
209 });
210 }
211 continue;
212 }
213 if let Some(validate) = &field.validator {
214 Some(
215 validate(value.clone()).map_err(|error| AuthError::FieldInput {
216 code: "VALIDATION_ERROR",
217 message: error.to_string(),
218 })?,
219 )
220 } else if let Some(transform) = field.input_transform() {
221 Some(transform.call_sync(value.clone())?)
222 } else {
223 Some(value.clone())
224 }
225 } else if create {
226 if let Some(default) = field.default_value()? {
227 Some(default)
228 } else if field.required == Some(true) {
229 return Err(AuthError::FieldInput {
230 code: "MISSING_FIELD",
231 message: format!("{name} is required"),
232 });
233 } else {
234 continue;
235 }
236 } else {
237 continue;
238 };
239 if let Some(value) = value.filter(|value| !value.is_undefined()) {
240 let _ = parsed.insert(name.clone(), value);
241 }
242 }
243 Ok(parsed)
244 }
245
246 pub fn parse_provider_input(&self, profile: &FieldMap, create: bool) -> AuthResult<FieldMap> {
248 let allowed = profile
249 .iter()
250 .filter(|(name, _)| self.fields().get(*name).is_some_and(|field| field.input()))
251 .map(|(name, value)| (name.clone(), value.clone()))
252 .collect();
253 self.parse_input(&allowed, create)
254 }
255
256 pub async fn storage_fields(&self, input: FieldMap, create: bool) -> AuthResult<FieldMap> {
258 self.storage_fields_async(input, create, false, |_, _, value| Ok(value))
259 .await
260 }
261
262 async fn storage_fields_async(
263 &self,
264 input: FieldMap,
265 create: bool,
266 preserve_id: bool,
267 bind: impl Fn(&str, &UserFieldConfig, Value) -> AuthResult<Value>,
268 ) -> AuthResult<FieldMap> {
269 let mut output = FieldMap::new();
270 for (name, field) in self.fields() {
271 if preserve_id && name == "id" {
272 continue;
273 }
274 if let Some(value) = field.storage_input(input.get(name), create).await? {
275 let storage = resolve_field_name(field.field_name.as_deref(), name);
276 let value = bind(storage, field, value)?;
277 if !value.is_undefined() {
278 let _ = output.insert(storage.to_owned(), value);
279 }
280 }
281 }
282 Ok(output)
283 }
284}
285
286impl UserFieldConfig {
287 async fn storage_input(
288 &self,
289 input: Option<&Value>,
290 create: bool,
291 ) -> AuthResult<Option<Value>> {
292 let Some(value) = self.storage_value(input, create)? else {
293 return Ok(None);
294 };
295 let value = match self.input_transform() {
296 Some(transform) => transform.call(value).await,
297 None => Ok(value),
298 }?;
299 Ok(Some(value))
301 }
302
303 fn storage_value(&self, input: Option<&Value>, create: bool) -> AuthResult<Option<Value>> {
305 let mut value = input.cloned().unwrap_or_default();
306 if value.is_undefined()
307 && if create {
308 !self.has_storage_default() && self.input_transform().is_none()
309 } else {
310 self.on_update.is_none()
311 }
312 {
313 return Ok(None);
314 }
315 self.normalize_date(&mut value)?;
316 if create
317 && (value.is_undefined() || (self.required == Some(true) && value.is_null()))
318 && self.has_storage_default()
319 && let Some(default) = self.default_value()?
320 {
321 value = default;
322 }
323 if !create
324 && value.is_undefined()
325 && let Some(update) = &self.on_update
326 {
327 value = update()?;
328 }
329 Ok(Some(value))
330 }
331
332 pub(crate) fn has_storage_default(&self) -> bool {
333 self.default_value_fn.is_some()
334 || self
335 .default_value
336 .as_ref()
337 .is_some_and(|value| !value.is_undefined())
338 }
339}