prax_query/
data.rs

1//! Ergonomic data creation utilities.
2//!
3//! This module provides multiple ways to create data for insert/update operations,
4//! from simple struct-based approaches to flexible builders.
5//!
6//! # Approaches
7//!
8//! ## 1. Struct-Based (Recommended for simple creates)
9//!
10//! ```rust,ignore
11//! // Generated struct with required/optional fields
12//! let user = client.user().create(UserCreate {
13//!     email: "bob@example.com".into(),
14//!     name: Some("Bob".into()),
15//!     ..Default::default()
16//! }).exec().await?;
17//! ```
18//!
19//! ## 2. Builder Pattern (For complex creates with relations)
20//!
21//! ```rust,ignore
22//! let user = client.user()
23//!     .create_with(|b| b
24//!         .email("bob@example.com")
25//!         .name("Bob")
26//!         .posts(vec![
27//!             PostCreate { title: "Hello".into(), ..Default::default() }
28//!         ])
29//!     )
30//!     .exec().await?;
31//! ```
32//!
33//! ## 3. Macro (Ultra-concise)
34//!
35//! ```rust,ignore
36//! let user = client.user().create(data! {
37//!     email: "bob@example.com",
38//!     name: "Bob",
39//! }).exec().await?;
40//! ```
41//!
42//! ## 4. From tuples (Quick and dirty)
43//!
44//! ```rust,ignore
45//! // For models with few required fields
46//! let user = client.user().create(("bob@example.com", "Bob")).exec().await?;
47//! ```
48
49use crate::filter::FilterValue;
50use serde::{Deserialize, Serialize};
51use std::collections::HashMap;
52
53/// Trait for types that can be used as create data.
54pub trait CreateData: Send + Sync {
55    /// Get the field values as a map.
56    fn into_fields(self) -> HashMap<String, FieldValue>;
57
58    /// Get the model name.
59    fn model_name() -> &'static str;
60}
61
62/// Trait for types that can be used as update data.
63pub trait UpdateData: Send + Sync {
64    /// Get the field values as a map (only set fields).
65    fn into_fields(self) -> HashMap<String, FieldValue>;
66
67    /// Get the model name.
68    fn model_name() -> &'static str;
69}
70
71/// A field value that can be set in create/update operations.
72#[derive(Debug, Clone, Serialize, Deserialize)]
73#[serde(untagged)]
74pub enum FieldValue {
75    /// Null value.
76    Null,
77    /// Boolean value.
78    Bool(bool),
79    /// Integer value.
80    Int(i64),
81    /// Float value.
82    Float(f64),
83    /// String value.
84    String(String),
85    /// JSON value.
86    Json(serde_json::Value),
87    /// Bytes value.
88    Bytes(Vec<u8>),
89    /// DateTime as ISO string.
90    DateTime(String),
91    /// UUID as string.
92    Uuid(String),
93    /// Array of values.
94    Array(Vec<FieldValue>),
95    /// Nested create data.
96    Nested(Box<DataBuilder>),
97    /// Connect to existing record.
98    Connect(ConnectData),
99    /// Disconnect from related record.
100    Disconnect,
101    /// Set to default value.
102    Default,
103    /// Increment by value.
104    Increment(i64),
105    /// Decrement by value.
106    Decrement(i64),
107    /// Multiply by value.
108    Multiply(f64),
109    /// Divide by value.
110    Divide(f64),
111    /// Append to array.
112    Push(Box<FieldValue>),
113    /// Unset the field.
114    Unset,
115}
116
117impl FieldValue {
118    /// Convert to FilterValue for query operations.
119    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
134// Convenient From implementations
135impl 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/// Data for connecting to an existing record.
199#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct ConnectData {
201    /// Field name to match on.
202    pub field: String,
203    /// Value to match.
204    pub value: Box<FieldValue>,
205}
206
207impl ConnectData {
208    /// Create a connect by ID.
209    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    /// Create a connect by a specific field.
217    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/// A flexible data builder for create/update operations.
226///
227/// This builder allows setting fields dynamically and supports
228/// nested creates, connects, and all update operations.
229#[derive(Debug, Clone, Default, Serialize, Deserialize)]
230pub struct DataBuilder {
231    fields: HashMap<String, FieldValue>,
232}
233
234impl DataBuilder {
235    /// Create a new empty data builder.
236    pub fn new() -> Self {
237        Self::default()
238    }
239
240    /// Set a field value.
241    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    /// Set a field to null.
247    pub fn set_null(mut self, field: impl Into<String>) -> Self {
248        self.fields.insert(field.into(), FieldValue::Null);
249        self
250    }
251
252    /// Set a field to its default value.
253    pub fn set_default(mut self, field: impl Into<String>) -> Self {
254        self.fields.insert(field.into(), FieldValue::Default);
255        self
256    }
257
258    /// Unset a field (for updates).
259    pub fn unset(mut self, field: impl Into<String>) -> Self {
260        self.fields.insert(field.into(), FieldValue::Unset);
261        self
262    }
263
264    /// Increment a numeric field.
265    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    /// Decrement a numeric field.
271    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    /// Multiply a numeric field.
277    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    /// Divide a numeric field.
283    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    /// Push a value to an array field.
289    pub fn push(mut self, field: impl Into<String>, value: impl Into<FieldValue>) -> Self {
290        self.fields.insert(field.into(), FieldValue::Push(Box::new(value.into())));
291        self
292    }
293
294    /// Connect to an existing related record by ID.
295    pub fn connect(mut self, relation: impl Into<String>, id: impl Into<FieldValue>) -> Self {
296        self.fields.insert(relation.into(), FieldValue::Connect(ConnectData::id(id)));
297        self
298    }
299
300    /// Connect to an existing related record by a specific field.
301    pub fn connect_by(
302        mut self,
303        relation: impl Into<String>,
304        field: impl Into<String>,
305        value: impl Into<FieldValue>,
306    ) -> Self {
307        self.fields.insert(
308            relation.into(),
309            FieldValue::Connect(ConnectData::by(field, value)),
310        );
311        self
312    }
313
314    /// Disconnect from a related record.
315    pub fn disconnect(mut self, relation: impl Into<String>) -> Self {
316        self.fields.insert(relation.into(), FieldValue::Disconnect);
317        self
318    }
319
320    /// Create a nested record.
321    pub fn create_nested(mut self, relation: impl Into<String>, data: DataBuilder) -> Self {
322        self.fields.insert(relation.into(), FieldValue::Nested(Box::new(data)));
323        self
324    }
325
326    /// Get the fields map.
327    pub fn into_fields(self) -> HashMap<String, FieldValue> {
328        self.fields
329    }
330
331    /// Check if a field is set.
332    pub fn has(&self, field: &str) -> bool {
333        self.fields.contains_key(field)
334    }
335
336    /// Get a field value.
337    pub fn get(&self, field: &str) -> Option<&FieldValue> {
338        self.fields.get(field)
339    }
340
341    /// Get the number of fields set.
342    pub fn len(&self) -> usize {
343        self.fields.len()
344    }
345
346    /// Check if no fields are set.
347    pub fn is_empty(&self) -> bool {
348        self.fields.is_empty()
349    }
350}
351
352/// Helper trait for converting to DataBuilder.
353pub trait IntoData {
354    /// Convert into a DataBuilder.
355    fn into_data(self) -> DataBuilder;
356}
357
358impl IntoData for DataBuilder {
359    fn into_data(self) -> DataBuilder {
360        self
361    }
362}
363
364impl IntoData for HashMap<String, FieldValue> {
365    fn into_data(self) -> DataBuilder {
366        DataBuilder { fields: self }
367    }
368}
369
370impl IntoData for serde_json::Value {
371    fn into_data(self) -> DataBuilder {
372        match self {
373            serde_json::Value::Object(map) => {
374                let fields = map
375                    .into_iter()
376                    .map(|(k, v)| (k, json_to_field_value(v)))
377                    .collect();
378                DataBuilder { fields }
379            }
380            _ => DataBuilder::new(),
381        }
382    }
383}
384
385fn json_to_field_value(value: serde_json::Value) -> FieldValue {
386    match value {
387        serde_json::Value::Null => FieldValue::Null,
388        serde_json::Value::Bool(b) => FieldValue::Bool(b),
389        serde_json::Value::Number(n) => {
390            if let Some(i) = n.as_i64() {
391                FieldValue::Int(i)
392            } else if let Some(f) = n.as_f64() {
393                FieldValue::Float(f)
394            } else {
395                FieldValue::Json(serde_json::Value::Number(n))
396            }
397        }
398        serde_json::Value::String(s) => FieldValue::String(s),
399        serde_json::Value::Array(arr) => {
400            FieldValue::Array(arr.into_iter().map(json_to_field_value).collect())
401        }
402        serde_json::Value::Object(_) => FieldValue::Json(value),
403    }
404}
405
406/// Macro for concise data creation.
407///
408/// # Examples
409///
410/// ```rust,ignore
411/// use prax_query::data;
412///
413/// // Simple create
414/// let user_data = data! {
415///     email: "bob@example.com",
416///     name: "Bob",
417///     age: 30,
418/// };
419///
420/// // With nested data
421/// let post_data = data! {
422///     title: "Hello World",
423///     author: connect!(id: 1),
424///     tags: ["rust", "orm"],
425/// };
426///
427/// // With optional fields
428/// let update_data = data! {
429///     name: "Robert",
430///     bio: null,
431///     views: increment!(1),
432/// };
433/// ```
434#[macro_export]
435macro_rules! data {
436    // Empty data
437    () => {
438        $crate::data::DataBuilder::new()
439    };
440
441    // Data with fields
442    ($($field:ident : $value:expr),* $(,)?) => {{
443        let mut builder = $crate::data::DataBuilder::new();
444        $(
445            builder = builder.set(stringify!($field), $value);
446        )*
447        builder
448    }};
449}
450
451/// Macro for creating connection data.
452#[macro_export]
453macro_rules! connect {
454    (id: $id:expr) => {
455        $crate::data::FieldValue::Connect($crate::data::ConnectData::id($id))
456    };
457    ($field:ident : $value:expr) => {
458        $crate::data::FieldValue::Connect($crate::data::ConnectData::by(
459            stringify!($field),
460            $value,
461        ))
462    };
463}
464
465/// Macro for increment operations.
466#[macro_export]
467macro_rules! increment {
468    ($value:expr) => {
469        $crate::data::FieldValue::Increment($value)
470    };
471}
472
473/// Macro for decrement operations.
474#[macro_export]
475macro_rules! decrement {
476    ($value:expr) => {
477        $crate::data::FieldValue::Decrement($value)
478    };
479}
480
481/// Batch create helper for creating multiple records.
482#[derive(Debug, Clone, Default)]
483pub struct BatchCreate<T> {
484    items: Vec<T>,
485    skip_duplicates: bool,
486}
487
488impl<T> BatchCreate<T> {
489    /// Create a new batch create.
490    pub fn new(items: Vec<T>) -> Self {
491        Self {
492            items,
493            skip_duplicates: false,
494        }
495    }
496
497    /// Skip duplicate records instead of failing.
498    pub fn skip_duplicates(mut self) -> Self {
499        self.skip_duplicates = true;
500        self
501    }
502
503    /// Get the items.
504    pub fn into_items(self) -> Vec<T> {
505        self.items
506    }
507
508    /// Check if duplicates should be skipped.
509    pub fn should_skip_duplicates(&self) -> bool {
510        self.skip_duplicates
511    }
512
513    /// Get the number of items.
514    pub fn len(&self) -> usize {
515        self.items.len()
516    }
517
518    /// Check if empty.
519    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
530/// Builder for creating records with a fluent API.
531///
532/// This is generated per-model and provides type-safe field setters.
533///
534/// # Example (Generated Code)
535///
536/// ```rust,ignore
537/// // This is what gets generated for a User model:
538/// pub struct UserCreateBuilder {
539///     data: DataBuilder,
540/// }
541///
542/// impl UserCreateBuilder {
543///     pub fn email(self, email: impl Into<String>) -> Self { ... }
544///     pub fn name(self, name: impl Into<String>) -> Self { ... }
545///     pub fn age(self, age: i32) -> Self { ... }
546///     pub fn posts(self, posts: Vec<PostCreate>) -> Self { ... }
547/// }
548/// ```
549pub trait TypedCreateBuilder: Sized {
550    /// The output type.
551    type Output: CreateData;
552
553    /// Build the create data.
554    fn build(self) -> Self::Output;
555}
556
557/// Builder for updating records with a fluent API.
558pub trait TypedUpdateBuilder: Sized {
559    /// The output type.
560    type Output: UpdateData;
561
562    /// Build the update data.
563    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    fn test_field_value_conversions() {
629        let _: FieldValue = true.into();
630        let _: FieldValue = 42_i32.into();
631        let _: FieldValue = 42_i64.into();
632        let _: FieldValue = 3.14_f64.into();
633        let _: FieldValue = "hello".into();
634        let _: FieldValue = String::from("hello").into();
635        let _: FieldValue = Some("optional").into();
636        let _: FieldValue = None::<String>.into();
637        let _: FieldValue = vec!["a", "b"].into();
638    }
639
640    #[test]
641    fn test_batch_create() {
642        let batch: BatchCreate<DataBuilder> = vec![
643            data! { name: "Alice" },
644            data! { name: "Bob" },
645        ].into();
646
647        assert_eq!(batch.len(), 2);
648        assert!(!batch.should_skip_duplicates());
649
650        let batch = batch.skip_duplicates();
651        assert!(batch.should_skip_duplicates());
652    }
653
654    #[test]
655    fn test_json_to_data() {
656        let json = serde_json::json!({
657            "name": "Bob",
658            "age": 30,
659            "active": true,
660            "tags": ["a", "b"]
661        });
662
663        let data: DataBuilder = json.into_data();
664        assert_eq!(data.len(), 4);
665    }
666
667    #[test]
668    fn test_connect_data() {
669        let by_id = ConnectData::id(1);
670        assert_eq!(by_id.field, "id");
671
672        let by_email = ConnectData::by("email", "bob@example.com");
673        assert_eq!(by_email.field, "email");
674    }
675}
676
677