better_auth_core/user_fields/
adapter.rs1use super::{UserConfig, UserFieldConfig, UserFieldType};
2use crate::AuthResult;
3use crate::store::schema::resolve_field_name;
4use crate::{FieldMap, FieldValue as Value};
5
6#[derive(Clone, Copy, Debug)]
8pub struct FieldOutputCapabilities {
9 pub supports_native_json: bool,
11 pub supports_arrays: bool,
13 pub supports_booleans: bool,
15}
16
17impl FieldOutputCapabilities {
18 pub(crate) const fn json_only(supports_native_json: bool) -> Self {
19 Self {
20 supports_native_json,
21 supports_arrays: true,
22 supports_booleans: true,
23 }
24 }
25}
26
27impl UserConfig {
28 #[doc(hidden)]
30 pub fn adapter_fields(&self, native: &[&str]) -> Self {
31 let mut fields: indexmap::IndexMap<_, _> = self
32 .ordered_fields(native)
33 .into_iter()
34 .map(|(name, field)| (name.to_owned(), field.clone()))
35 .collect();
36 let _ = fields.insert("id".into(), UserFieldConfig::default());
37 Self {
38 additional_fields: Some(fields),
39 }
40 }
41
42 #[doc(hidden)]
44 pub async fn create_adapter_storage_fields<I>(
45 &self,
46 input: FieldMap,
47 generate_id: impl FnMut() -> AuthResult<Option<I>>,
48 bind: impl Fn(&str, &UserFieldConfig, Value) -> AuthResult<Value>,
49 ) -> AuthResult<(FieldMap, Option<I>)> {
50 self.storage_fields_with_adapter_id(input, true, generate_id, bind)
51 .await
52 }
53
54 #[doc(hidden)]
56 pub async fn storage_fields_with_adapter_id<I>(
57 &self,
58 input: FieldMap,
59 create: bool,
60 mut resolve_id: impl FnMut() -> AuthResult<Option<I>>,
61 bind: impl Fn(&str, &UserFieldConfig, Value) -> AuthResult<Value>,
62 ) -> AuthResult<(FieldMap, Option<I>)> {
63 let mut id = None;
64 let output = self
65 .storage_fields_with_bound_id(
66 input,
67 create,
68 || {
69 id = resolve_id()?;
70 Ok(None)
71 },
72 bind,
73 )
74 .await?;
75 Ok((output, id))
76 }
77
78 #[doc(hidden)]
80 pub async fn update_adapter_storage_fields(
81 &self,
82 input: FieldMap,
83 resolve_id: impl FnMut() -> AuthResult<Option<Value>>,
84 bind: impl Fn(&str, &UserFieldConfig, Value) -> AuthResult<Value>,
85 ) -> AuthResult<FieldMap> {
86 self.storage_fields_with_bound_id(input, false, resolve_id, bind)
87 .await
88 }
89
90 #[doc(hidden)]
92 pub async fn storage_fields_with_bound_id(
93 &self,
94 input: FieldMap,
95 create: bool,
96 mut resolve_id: impl FnMut() -> AuthResult<Option<Value>>,
97 bind: impl Fn(&str, &UserFieldConfig, Value) -> AuthResult<Value>,
98 ) -> AuthResult<FieldMap> {
99 let schema = self.adapter_fields(&[]);
100 let mut output = FieldMap::new();
101 for (name, field) in schema.fields() {
102 let value = if name == "id" {
103 resolve_id()?
104 } else {
105 field.storage_input(input.get(name), create).await?
106 };
107 if let Some(value) = value {
108 let storage_name = resolve_field_name(field.field_name.as_deref(), name);
109 let value = if name == "id" {
110 value
111 } else {
112 bind(storage_name, field, value)?
113 };
114 if !value.is_undefined() {
115 let _ = output.insert(storage_name.to_owned(), value);
116 }
117 }
118 }
119 Ok(output)
120 }
121
122 pub(crate) fn ordered_fields(&self, native: &[&str]) -> Vec<(&str, &UserFieldConfig)> {
123 let mut fields: Vec<_> = self
124 .fields()
125 .iter()
126 .map(|(name, field)| (name.as_str(), field))
127 .collect();
128 fields.sort_by_key(|(name, _)| {
130 let native = native
131 .iter()
132 .position(|field| field == name)
133 .unwrap_or(native.len());
134 crate::utils::json::array_index(name)
135 .map_or((true, 0, native), |index| (false, index, 0))
136 });
137 fields
138 }
139
140 pub async fn storage_fields_for_adapter(
142 &self,
143 input: FieldMap,
144 create: bool,
145 supports_native_json: bool,
146 native_json_field: impl Fn(&str) -> bool,
147 ) -> AuthResult<FieldMap> {
148 self.storage_fields_with_binding(input, create, |name, field, value| {
149 field.adapter_input(value, supports_native_json, native_json_field(name))
150 })
151 .await
152 }
153
154 pub async fn storage_fields_with_binding(
156 &self,
157 input: FieldMap,
158 create: bool,
159 bind: impl Fn(&str, &UserFieldConfig, Value) -> AuthResult<Value>,
160 ) -> AuthResult<FieldMap> {
161 self.storage_fields_async(input, create, false, bind).await
162 }
163}
164
165impl UserFieldConfig {
166 pub fn adapter_input(
170 &self,
171 value: Value,
172 supports_native_json: bool,
173 native_json_field: bool,
174 ) -> AuthResult<Value> {
175 if self.references_id() {
176 match (&self.field_type, &value) {
177 (UserFieldType::Boolean, Value::Bool(value)) if !supports_native_json => {
178 return Ok(Value::from(i64::from(*value)));
179 }
180 (UserFieldType::StringArray | UserFieldType::NumberArray, Value::Array(_))
181 | (UserFieldType::Json, Value::Object(_) | Value::Array(_) | Value::Date(_)) => {
182 return json_text(value);
183 }
184 _ => {}
185 }
186 }
187 if !supports_native_json
188 && matches!(self.field_type, UserFieldType::Json)
189 && (value.is_null()
190 || (!native_json_field
191 && (value.is_object() || value.is_array() || matches!(value, Value::Date(_)))))
192 {
193 json_text(value)
194 } else {
195 Ok(value)
196 }
197 }
198
199 pub fn references_id(&self) -> bool {
201 self.references
202 .as_ref()
203 .is_some_and(|reference| reference.field == "id")
204 }
205
206 pub(crate) fn uses_id_output(&self) -> bool {
207 self.references_id() || self.field_name.as_deref() == Some("id")
208 }
209
210 pub async fn adapter_output(
212 &self,
213 value: Value,
214 supports_native_json: bool,
215 ) -> AuthResult<Value> {
216 self.adapter_output_with_capabilities(
217 value,
218 FieldOutputCapabilities::json_only(supports_native_json),
219 )
220 .await
221 }
222
223 pub(crate) async fn adapter_output_with_capabilities(
224 &self,
225 value: Value,
226 capabilities: FieldOutputCapabilities,
227 ) -> AuthResult<Value> {
228 let value = self.prepare_output(value, capabilities.supports_native_json)?;
229 self.adapter_output_from_raw(value, capabilities).await
230 }
231
232 pub(crate) async fn adapter_output_from_raw(
233 &self,
234 mut value: Value,
235 capabilities: FieldOutputCapabilities,
236 ) -> AuthResult<Value> {
237 if let Some(transform) = self.output_transform() {
238 value = transform.call(value).await?;
239 }
240 self.finish_output(value, capabilities)
241 }
242
243 fn prepare_output(&self, value: Value, supports_native_json: bool) -> AuthResult<Value> {
244 if !supports_native_json
245 && self.field_name.as_deref() != Some("id")
246 && matches!(self.field_type, UserFieldType::Json)
247 {
248 match value {
249 Value::Object(_) | Value::Array(_) | Value::Date(_) => json_text(value),
250 value => Ok(value),
251 }
252 } else {
253 Ok(value)
254 }
255 }
256
257 fn finish_output(
258 &self,
259 value: Value,
260 capabilities: FieldOutputCapabilities,
261 ) -> AuthResult<Value> {
262 if self.uses_id_output() {
263 return if value.is_null() || value.is_undefined() {
264 Ok(value)
265 } else {
266 value.display_utf16().map(Value::from)
267 };
268 }
269 match value {
270 Value::String(text)
271 if (!capabilities.supports_native_json
272 && matches!(self.field_type, UserFieldType::Json))
273 || (!capabilities.supports_arrays
274 && matches!(
275 self.field_type,
276 UserFieldType::StringArray | UserFieldType::NumberArray
277 )) =>
278 {
279 revive_json(crate::utils::json::safe_json_parse(&text))
280 }
281 Value::Number(number)
282 if !capabilities.supports_booleans
283 && matches!(self.field_type, UserFieldType::Boolean) =>
284 {
285 Ok(Value::Bool(number == 1.0))
286 }
287 value => Ok(value),
288 }
289 }
290}
291
292fn json_text(value: Value) -> AuthResult<Value> {
293 Ok(value.stringify()?.map(Value::String).unwrap_or_default())
294}
295
296fn revive_json(value: serde_json::Value) -> AuthResult<Value> {
297 match value {
298 serde_json::Value::String(text) => Ok(crate::utils::json::parse_json_date(&text)
299 .map_or_else(|| Value::String(text), Value::from)),
300 serde_json::Value::Array(values) => values
301 .into_iter()
302 .map(revive_json)
303 .collect::<AuthResult<Vec<_>>>()
304 .map(Value::from),
305 serde_json::Value::Object(values) => values
306 .into_iter()
307 .map(|(name, value)| Ok((name, revive_json(value)?)))
308 .collect::<AuthResult<FieldMap>>()
309 .map(Value::from),
310 value => Value::from_json(value),
311 }
312}