1use crate::filter::FilterValue;
50use serde::{Deserialize, Serialize};
51use std::collections::HashMap;
52
53pub trait CreateData: Send + Sync {
55 fn into_fields(self) -> HashMap<String, FieldValue>;
57
58 fn model_name() -> &'static str;
60}
61
62pub trait UpdateData: Send + Sync {
64 fn into_fields(self) -> HashMap<String, FieldValue>;
66
67 fn model_name() -> &'static str;
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
73#[serde(untagged)]
74pub enum FieldValue {
75 Null,
77 Bool(bool),
79 Int(i64),
81 Float(f64),
83 String(String),
85 Json(serde_json::Value),
87 Bytes(Vec<u8>),
89 DateTime(String),
91 Uuid(String),
93 Array(Vec<FieldValue>),
95 Nested(Box<DataBuilder>),
97 Connect(ConnectData),
99 Disconnect,
101 Default,
103 Increment(i64),
105 Decrement(i64),
107 Multiply(f64),
109 Divide(f64),
111 Push(Box<FieldValue>),
113 Unset,
115}
116
117impl FieldValue {
118 pub fn to_filter_value(&self) -> Option<FilterValue> {
120 match self {
121 Self::Null => Some(FilterValue::Null),
122 Self::Bool(b) => Some(FilterValue::Bool(*b)),
123 Self::Int(i) => Some(FilterValue::Int(*i)),
124 Self::Float(f) => Some(FilterValue::Float(*f)),
125 Self::String(s) => Some(FilterValue::String(s.clone())),
126 Self::Json(j) => Some(FilterValue::Json(j.clone())),
127 Self::DateTime(s) => Some(FilterValue::String(s.clone())),
128 Self::Uuid(s) => Some(FilterValue::String(s.clone())),
129 _ => None,
130 }
131 }
132}
133
134impl From<bool> for FieldValue {
136 fn from(v: bool) -> Self {
137 Self::Bool(v)
138 }
139}
140
141impl From<i32> for FieldValue {
142 fn from(v: i32) -> Self {
143 Self::Int(v as i64)
144 }
145}
146
147impl From<i64> for FieldValue {
148 fn from(v: i64) -> Self {
149 Self::Int(v)
150 }
151}
152
153impl From<f32> for FieldValue {
154 fn from(v: f32) -> Self {
155 Self::Float(v as f64)
156 }
157}
158
159impl From<f64> for FieldValue {
160 fn from(v: f64) -> Self {
161 Self::Float(v)
162 }
163}
164
165impl From<String> for FieldValue {
166 fn from(v: String) -> Self {
167 Self::String(v)
168 }
169}
170
171impl From<&str> for FieldValue {
172 fn from(v: &str) -> Self {
173 Self::String(v.to_string())
174 }
175}
176
177impl From<serde_json::Value> for FieldValue {
178 fn from(v: serde_json::Value) -> Self {
179 Self::Json(v)
180 }
181}
182
183impl<T: Into<FieldValue>> From<Option<T>> for FieldValue {
184 fn from(v: Option<T>) -> Self {
185 match v {
186 Some(val) => val.into(),
187 None => Self::Null,
188 }
189 }
190}
191
192impl<T: Into<FieldValue>> From<Vec<T>> for FieldValue {
193 fn from(v: Vec<T>) -> Self {
194 Self::Array(v.into_iter().map(Into::into).collect())
195 }
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct ConnectData {
201 pub field: String,
203 pub value: Box<FieldValue>,
205}
206
207impl ConnectData {
208 pub fn id(id: impl Into<FieldValue>) -> Self {
210 Self {
211 field: "id".to_string(),
212 value: Box::new(id.into()),
213 }
214 }
215
216 pub fn by(field: impl Into<String>, value: impl Into<FieldValue>) -> Self {
218 Self {
219 field: field.into(),
220 value: Box::new(value.into()),
221 }
222 }
223}
224
225#[derive(Debug, Clone, Default, Serialize, Deserialize)]
230pub struct DataBuilder {
231 fields: HashMap<String, FieldValue>,
232}
233
234impl DataBuilder {
235 pub fn new() -> Self {
237 Self::default()
238 }
239
240 pub fn set(mut self, field: impl Into<String>, value: impl Into<FieldValue>) -> Self {
242 self.fields.insert(field.into(), value.into());
243 self
244 }
245
246 pub fn set_null(mut self, field: impl Into<String>) -> Self {
248 self.fields.insert(field.into(), FieldValue::Null);
249 self
250 }
251
252 pub fn set_default(mut self, field: impl Into<String>) -> Self {
254 self.fields.insert(field.into(), FieldValue::Default);
255 self
256 }
257
258 pub fn unset(mut self, field: impl Into<String>) -> Self {
260 self.fields.insert(field.into(), FieldValue::Unset);
261 self
262 }
263
264 pub fn increment(mut self, field: impl Into<String>, by: i64) -> Self {
266 self.fields.insert(field.into(), FieldValue::Increment(by));
267 self
268 }
269
270 pub fn decrement(mut self, field: impl Into<String>, by: i64) -> Self {
272 self.fields.insert(field.into(), FieldValue::Decrement(by));
273 self
274 }
275
276 pub fn multiply(mut self, field: impl Into<String>, by: f64) -> Self {
278 self.fields.insert(field.into(), FieldValue::Multiply(by));
279 self
280 }
281
282 pub fn divide(mut self, field: impl Into<String>, by: f64) -> Self {
284 self.fields.insert(field.into(), FieldValue::Divide(by));
285 self
286 }
287
288 pub fn push(mut self, field: impl Into<String>, value: impl Into<FieldValue>) -> Self {
290 self.fields
291 .insert(field.into(), FieldValue::Push(Box::new(value.into())));
292 self
293 }
294
295 pub fn connect(mut self, relation: impl Into<String>, id: impl Into<FieldValue>) -> Self {
297 self.fields
298 .insert(relation.into(), FieldValue::Connect(ConnectData::id(id)));
299 self
300 }
301
302 pub fn connect_by(
304 mut self,
305 relation: impl Into<String>,
306 field: impl Into<String>,
307 value: impl Into<FieldValue>,
308 ) -> Self {
309 self.fields.insert(
310 relation.into(),
311 FieldValue::Connect(ConnectData::by(field, value)),
312 );
313 self
314 }
315
316 pub fn disconnect(mut self, relation: impl Into<String>) -> Self {
318 self.fields.insert(relation.into(), FieldValue::Disconnect);
319 self
320 }
321
322 pub fn create_nested(mut self, relation: impl Into<String>, data: DataBuilder) -> Self {
324 self.fields
325 .insert(relation.into(), FieldValue::Nested(Box::new(data)));
326 self
327 }
328
329 pub fn into_fields(self) -> HashMap<String, FieldValue> {
331 self.fields
332 }
333
334 pub fn has(&self, field: &str) -> bool {
336 self.fields.contains_key(field)
337 }
338
339 pub fn get(&self, field: &str) -> Option<&FieldValue> {
341 self.fields.get(field)
342 }
343
344 pub fn len(&self) -> usize {
346 self.fields.len()
347 }
348
349 pub fn is_empty(&self) -> bool {
351 self.fields.is_empty()
352 }
353}
354
355pub trait IntoData {
357 fn into_data(self) -> DataBuilder;
359}
360
361impl IntoData for DataBuilder {
362 fn into_data(self) -> DataBuilder {
363 self
364 }
365}
366
367impl IntoData for HashMap<String, FieldValue> {
368 fn into_data(self) -> DataBuilder {
369 DataBuilder { fields: self }
370 }
371}
372
373impl IntoData for serde_json::Value {
374 fn into_data(self) -> DataBuilder {
375 match self {
376 serde_json::Value::Object(map) => {
377 let fields = map
378 .into_iter()
379 .map(|(k, v)| (k, json_to_field_value(v)))
380 .collect();
381 DataBuilder { fields }
382 }
383 _ => DataBuilder::new(),
384 }
385 }
386}
387
388fn json_to_field_value(value: serde_json::Value) -> FieldValue {
389 match value {
390 serde_json::Value::Null => FieldValue::Null,
391 serde_json::Value::Bool(b) => FieldValue::Bool(b),
392 serde_json::Value::Number(n) => {
393 if let Some(i) = n.as_i64() {
394 FieldValue::Int(i)
395 } else if let Some(f) = n.as_f64() {
396 FieldValue::Float(f)
397 } else {
398 FieldValue::Json(serde_json::Value::Number(n))
399 }
400 }
401 serde_json::Value::String(s) => FieldValue::String(s),
402 serde_json::Value::Array(arr) => {
403 FieldValue::Array(arr.into_iter().map(json_to_field_value).collect())
404 }
405 serde_json::Value::Object(_) => FieldValue::Json(value),
406 }
407}
408
409#[macro_export]
438macro_rules! data {
439 () => {
441 $crate::data::DataBuilder::new()
442 };
443
444 ($($field:ident : $value:expr),* $(,)?) => {{
446 let mut builder = $crate::data::DataBuilder::new();
447 $(
448 builder = builder.set(stringify!($field), $value);
449 )*
450 builder
451 }};
452}
453
454#[macro_export]
456macro_rules! connect {
457 (id: $id:expr) => {
458 $crate::data::FieldValue::Connect($crate::data::ConnectData::id($id))
459 };
460 ($field:ident : $value:expr) => {
461 $crate::data::FieldValue::Connect($crate::data::ConnectData::by(stringify!($field), $value))
462 };
463}
464
465#[macro_export]
467macro_rules! increment {
468 ($value:expr) => {
469 $crate::data::FieldValue::Increment($value)
470 };
471}
472
473#[macro_export]
475macro_rules! decrement {
476 ($value:expr) => {
477 $crate::data::FieldValue::Decrement($value)
478 };
479}
480
481#[derive(Debug, Clone, Default)]
483pub struct BatchCreate<T> {
484 items: Vec<T>,
485 skip_duplicates: bool,
486}
487
488impl<T> BatchCreate<T> {
489 pub fn new(items: Vec<T>) -> Self {
491 Self {
492 items,
493 skip_duplicates: false,
494 }
495 }
496
497 pub fn skip_duplicates(mut self) -> Self {
499 self.skip_duplicates = true;
500 self
501 }
502
503 pub fn into_items(self) -> Vec<T> {
505 self.items
506 }
507
508 pub fn should_skip_duplicates(&self) -> bool {
510 self.skip_duplicates
511 }
512
513 pub fn len(&self) -> usize {
515 self.items.len()
516 }
517
518 pub fn is_empty(&self) -> bool {
520 self.items.is_empty()
521 }
522}
523
524impl<T> From<Vec<T>> for BatchCreate<T> {
525 fn from(items: Vec<T>) -> Self {
526 Self::new(items)
527 }
528}
529
530pub trait TypedCreateBuilder: Sized {
550 type Output: CreateData;
552
553 fn build(self) -> Self::Output;
555}
556
557pub trait TypedUpdateBuilder: Sized {
559 type Output: UpdateData;
561
562 fn build(self) -> Self::Output;
564}
565
566#[cfg(test)]
567mod tests {
568 use super::*;
569
570 #[test]
571 fn test_data_builder_basic() {
572 let data = DataBuilder::new()
573 .set("name", "Bob")
574 .set("age", 30)
575 .set("active", true);
576
577 assert_eq!(data.len(), 3);
578 assert!(data.has("name"));
579 assert!(data.has("age"));
580 assert!(data.has("active"));
581 }
582
583 #[test]
584 fn test_data_builder_null_and_default() {
585 let data = DataBuilder::new()
586 .set_null("deleted_at")
587 .set_default("created_at");
588
589 assert!(matches!(data.get("deleted_at"), Some(FieldValue::Null)));
590 assert!(matches!(data.get("created_at"), Some(FieldValue::Default)));
591 }
592
593 #[test]
594 fn test_data_builder_numeric_operations() {
595 let data = DataBuilder::new()
596 .increment("views", 1)
597 .decrement("stock", 5)
598 .multiply("price", 1.1)
599 .divide("score", 2.0);
600
601 assert!(matches!(data.get("views"), Some(FieldValue::Increment(1))));
602 assert!(matches!(data.get("stock"), Some(FieldValue::Decrement(5))));
603 }
604
605 #[test]
606 fn test_data_builder_connect() {
607 let data = DataBuilder::new()
608 .connect("author", 1)
609 .connect_by("category", "slug", "tech");
610
611 assert!(matches!(data.get("author"), Some(FieldValue::Connect(_))));
612 assert!(matches!(data.get("category"), Some(FieldValue::Connect(_))));
613 }
614
615 #[test]
616 fn test_data_macro() {
617 let data = data! {
618 name: "Bob",
619 email: "bob@example.com",
620 age: 30,
621 };
622
623 assert_eq!(data.len(), 3);
624 assert!(matches!(data.get("name"), Some(FieldValue::String(s)) if s == "Bob"));
625 }
626
627 #[test]
628 #[allow(clippy::approx_constant)]
629 fn test_field_value_conversions() {
630 let _: FieldValue = true.into();
631 let _: FieldValue = 42_i32.into();
632 let _: FieldValue = 42_i64.into();
633 let _: FieldValue = 3.14_f64.into();
634 let _: FieldValue = "hello".into();
635 let _: FieldValue = String::from("hello").into();
636 let _: FieldValue = Some("optional").into();
637 let _: FieldValue = None::<String>.into();
638 let _: FieldValue = vec!["a", "b"].into();
639 }
640
641 #[test]
642 fn test_batch_create() {
643 let batch: BatchCreate<DataBuilder> =
644 vec![data! { name: "Alice" }, data! { name: "Bob" }].into();
645
646 assert_eq!(batch.len(), 2);
647 assert!(!batch.should_skip_duplicates());
648
649 let batch = batch.skip_duplicates();
650 assert!(batch.should_skip_duplicates());
651 }
652
653 #[test]
654 fn test_json_to_data() {
655 let json = serde_json::json!({
656 "name": "Bob",
657 "age": 30,
658 "active": true,
659 "tags": ["a", "b"]
660 });
661
662 let data: DataBuilder = json.into_data();
663 assert_eq!(data.len(), 4);
664 }
665
666 #[test]
667 fn test_connect_data() {
668 let by_id = ConnectData::id(1);
669 assert_eq!(by_id.field, "id");
670
671 let by_email = ConnectData::by("email", "bob@example.com");
672 assert_eq!(by_email.field, "email");
673 }
674}