grc_20/model/
builder.rs

1//! Builder API for ergonomic Edit construction.
2//!
3//! Provides a fluent interface for building Edits with operations.
4//!
5//! # Example
6//!
7//! ```rust
8//! use grc_20::model::builder::EditBuilder;
9//! use grc_20::genesis::{properties, relation_types};
10//! use grc_20::Value;
11//! use std::borrow::Cow;
12//!
13//! let edit = EditBuilder::new([1u8; 16])
14//!     .name("Create Alice")
15//!     .author([2u8; 16])
16//!     .create_entity([3u8; 16], |e| e
17//!         .text(properties::name(), "Alice", None)
18//!         .text(properties::description(), "A person", None)
19//!     )
20//!     .build();
21//! ```
22
23use std::borrow::Cow;
24
25use crate::model::{
26    CreateEntity, CreateRelation, DeleteEntity, DeleteRelation,
27    Edit, Id, Op, PropertyValue, RestoreEntity, RestoreRelation, UnsetRelationField,
28    UnsetLanguage, UnsetValue, UpdateEntity, UpdateRelation, Value,
29};
30
31/// Builder for constructing an Edit with operations.
32#[derive(Debug, Clone)]
33pub struct EditBuilder<'a> {
34    id: Id,
35    name: Cow<'a, str>,
36    authors: Vec<Id>,
37    created_at: i64,
38    ops: Vec<Op<'a>>,
39}
40
41impl<'a> EditBuilder<'a> {
42    /// Creates a new EditBuilder with the given edit ID.
43    pub fn new(id: Id) -> Self {
44        Self {
45            id,
46            name: Cow::Borrowed(""),
47            authors: Vec::new(),
48            created_at: 0,
49            ops: Vec::new(),
50        }
51    }
52
53    /// Sets the edit name.
54    pub fn name(mut self, name: impl Into<Cow<'a, str>>) -> Self {
55        self.name = name.into();
56        self
57    }
58
59    /// Adds an author to the edit.
60    pub fn author(mut self, author_id: Id) -> Self {
61        self.authors.push(author_id);
62        self
63    }
64
65    /// Sets multiple authors at once.
66    pub fn authors(mut self, author_ids: impl IntoIterator<Item = Id>) -> Self {
67        self.authors.extend(author_ids);
68        self
69    }
70
71    /// Sets the creation timestamp (microseconds since Unix epoch).
72    pub fn created_at(mut self, timestamp: i64) -> Self {
73        self.created_at = timestamp;
74        self
75    }
76
77    /// Sets the creation timestamp to now.
78    pub fn created_now(mut self) -> Self {
79        use std::time::{SystemTime, UNIX_EPOCH};
80        let micros = SystemTime::now()
81            .duration_since(UNIX_EPOCH)
82            .map(|d| d.as_micros() as i64)
83            .unwrap_or(0);
84        self.created_at = micros;
85        self
86    }
87
88    // =========================================================================
89    // Entity Operations
90    // =========================================================================
91
92    /// Adds a CreateEntity operation using a builder function.
93    pub fn create_entity<F>(mut self, id: Id, f: F) -> Self
94    where
95        F: FnOnce(EntityBuilder<'a>) -> EntityBuilder<'a>,
96    {
97        let builder = f(EntityBuilder::new());
98        self.ops.push(Op::CreateEntity(CreateEntity {
99            id,
100            values: builder.values,
101            context: None,
102        }));
103        self
104    }
105
106    /// Adds a CreateEntity operation with no values.
107    pub fn create_empty_entity(mut self, id: Id) -> Self {
108        self.ops.push(Op::CreateEntity(CreateEntity {
109            id,
110            values: Vec::new(),
111            context: None,
112        }));
113        self
114    }
115
116    /// Adds an UpdateEntity operation using a builder function.
117    pub fn update_entity<F>(mut self, id: Id, f: F) -> Self
118    where
119        F: FnOnce(UpdateEntityBuilder<'a>) -> UpdateEntityBuilder<'a>,
120    {
121        let builder = f(UpdateEntityBuilder::new(id));
122        self.ops.push(Op::UpdateEntity(UpdateEntity {
123            id: builder.id,
124            set_properties: builder.set_properties,
125            unset_values: builder.unset_values,
126            context: None,
127        }));
128        self
129    }
130
131    /// Adds a DeleteEntity operation.
132    pub fn delete_entity(mut self, id: Id) -> Self {
133        self.ops.push(Op::DeleteEntity(DeleteEntity { id, context: None }));
134        self
135    }
136
137    /// Adds a RestoreEntity operation.
138    pub fn restore_entity(mut self, id: Id) -> Self {
139        self.ops.push(Op::RestoreEntity(RestoreEntity { id, context: None }));
140        self
141    }
142
143    // =========================================================================
144    // Relation Operations
145    // =========================================================================
146
147    /// Adds a CreateRelation operation with an explicit ID.
148    pub fn create_relation_simple(
149        mut self,
150        id: Id,
151        from: Id,
152        to: Id,
153        relation_type: Id,
154    ) -> Self {
155        self.ops.push(Op::CreateRelation(CreateRelation {
156            id,
157            relation_type,
158            from,
159            from_is_value_ref: false,
160            to,
161            to_is_value_ref: false,
162            entity: None,
163            position: None,
164            from_space: None,
165            from_version: None,
166            to_space: None,
167            to_version: None,
168            context: None,
169        }));
170        self
171    }
172
173    /// Adds a CreateRelation operation with full control using a builder.
174    pub fn create_relation<F>(mut self, f: F) -> Self
175    where
176        F: FnOnce(RelationBuilder<'a>) -> RelationBuilder<'a>,
177    {
178        let builder = f(RelationBuilder::new());
179        if let Some(relation) = builder.build() {
180            self.ops.push(Op::CreateRelation(relation));
181        }
182        self
183    }
184
185    /// Adds an UpdateRelation operation using a builder function.
186    pub fn update_relation<F>(mut self, id: Id, f: F) -> Self
187    where
188        F: FnOnce(UpdateRelationBuilder<'a>) -> UpdateRelationBuilder<'a>,
189    {
190        let builder = f(UpdateRelationBuilder::new(id));
191        self.ops.push(Op::UpdateRelation(UpdateRelation {
192            id: builder.id,
193            from_space: builder.from_space,
194            from_version: builder.from_version,
195            to_space: builder.to_space,
196            to_version: builder.to_version,
197            position: builder.position,
198            unset: builder.unset,
199            context: None,
200        }));
201        self
202    }
203
204    /// Adds an UpdateRelation operation to only update the position.
205    pub fn update_relation_position(mut self, id: Id, position: Option<Cow<'a, str>>) -> Self {
206        self.ops.push(Op::UpdateRelation(UpdateRelation {
207            id,
208            from_space: None,
209            from_version: None,
210            to_space: None,
211            to_version: None,
212            position,
213            unset: vec![],
214            context: None,
215        }));
216        self
217    }
218
219    /// Adds a DeleteRelation operation.
220    pub fn delete_relation(mut self, id: Id) -> Self {
221        self.ops.push(Op::DeleteRelation(DeleteRelation { id, context: None }));
222        self
223    }
224
225    /// Adds a RestoreRelation operation.
226    pub fn restore_relation(mut self, id: Id) -> Self {
227        self.ops.push(Op::RestoreRelation(RestoreRelation { id, context: None }));
228        self
229    }
230
231    // =========================================================================
232    // Raw Operations
233    // =========================================================================
234
235    /// Adds a raw operation directly.
236    pub fn op(mut self, op: Op<'a>) -> Self {
237        self.ops.push(op);
238        self
239    }
240
241    /// Adds multiple raw operations.
242    pub fn ops(mut self, ops: impl IntoIterator<Item = Op<'a>>) -> Self {
243        self.ops.extend(ops);
244        self
245    }
246
247    // =========================================================================
248    // Build
249    // =========================================================================
250
251    /// Builds the final Edit.
252    pub fn build(self) -> Edit<'a> {
253        Edit {
254            id: self.id,
255            name: self.name,
256            authors: self.authors,
257            created_at: self.created_at,
258                        ops: self.ops,
259        }
260    }
261
262    /// Returns the number of operations added so far.
263    pub fn op_count(&self) -> usize {
264        self.ops.len()
265    }
266}
267
268/// Builder for entity values (used in CreateEntity).
269#[derive(Debug, Clone, Default)]
270pub struct EntityBuilder<'a> {
271    values: Vec<PropertyValue<'a>>,
272}
273
274impl<'a> EntityBuilder<'a> {
275    /// Creates a new empty EntityBuilder.
276    pub fn new() -> Self {
277        Self::default()
278    }
279
280    /// Adds a property value.
281    pub fn value(mut self, property: Id, value: Value<'a>) -> Self {
282        self.values.push(PropertyValue { property, value });
283        self
284    }
285
286    /// Adds a TEXT value.
287    pub fn text(
288        mut self,
289        property: Id,
290        value: impl Into<Cow<'a, str>>,
291        language: Option<Id>,
292    ) -> Self {
293        self.values.push(PropertyValue {
294            property,
295            value: Value::Text {
296                value: value.into(),
297                language,
298            },
299        });
300        self
301    }
302
303    /// Adds an INT64 value.
304    pub fn int64(mut self, property: Id, value: i64, unit: Option<Id>) -> Self {
305        self.values.push(PropertyValue {
306            property,
307            value: Value::Int64 { value, unit },
308        });
309        self
310    }
311
312    /// Adds a FLOAT64 value.
313    pub fn float64(mut self, property: Id, value: f64, unit: Option<Id>) -> Self {
314        self.values.push(PropertyValue {
315            property,
316            value: Value::Float64 { value, unit },
317        });
318        self
319    }
320
321    /// Adds a BOOL value.
322    pub fn bool(mut self, property: Id, value: bool) -> Self {
323        self.values.push(PropertyValue {
324            property,
325            value: Value::Bool(value),
326        });
327        self
328    }
329
330    /// Adds a BYTES value.
331    pub fn bytes(mut self, property: Id, value: impl Into<Cow<'a, [u8]>>) -> Self {
332        self.values.push(PropertyValue {
333            property,
334            value: Value::Bytes(value.into()),
335        });
336        self
337    }
338
339    /// Adds a POINT value (longitude, latitude, optional altitude).
340    pub fn point(mut self, property: Id, lon: f64, lat: f64, alt: Option<f64>) -> Self {
341        self.values.push(PropertyValue {
342            property,
343            value: Value::Point { lon, lat, alt },
344        });
345        self
346    }
347
348    /// Adds a DATE value.
349    ///
350    /// # Arguments
351    /// * `days` - Signed days since Unix epoch (1970-01-01)
352    /// * `offset_min` - Signed UTC offset in minutes (e.g., +330 for +05:30)
353    pub fn date(mut self, property: Id, days: i32, offset_min: i16) -> Self {
354        self.values.push(PropertyValue {
355            property,
356            value: Value::Date { days, offset_min },
357        });
358        self
359    }
360
361    /// Adds a TIME value.
362    ///
363    /// # Arguments
364    /// * `time_us` - Microseconds since midnight (0 to 86,399,999,999)
365    /// * `offset_min` - Signed UTC offset in minutes (e.g., +330 for +05:30)
366    pub fn time(mut self, property: Id, time_us: i64, offset_min: i16) -> Self {
367        self.values.push(PropertyValue {
368            property,
369            value: Value::Time { time_us, offset_min },
370        });
371        self
372    }
373
374    /// Adds a DATETIME value.
375    ///
376    /// # Arguments
377    /// * `epoch_us` - Microseconds since Unix epoch (1970-01-01T00:00:00Z)
378    /// * `offset_min` - Signed UTC offset in minutes (e.g., +330 for +05:30)
379    pub fn datetime(mut self, property: Id, epoch_us: i64, offset_min: i16) -> Self {
380        self.values.push(PropertyValue {
381            property,
382            value: Value::Datetime { epoch_us, offset_min },
383        });
384        self
385    }
386
387    /// Adds a SCHEDULE value (RFC 5545 iCalendar format).
388    pub fn schedule(mut self, property: Id, value: impl Into<Cow<'a, str>>) -> Self {
389        self.values.push(PropertyValue {
390            property,
391            value: Value::Schedule(value.into()),
392        });
393        self
394    }
395
396    /// Adds a DECIMAL value.
397    pub fn decimal(
398        mut self,
399        property: Id,
400        exponent: i32,
401        mantissa: crate::model::DecimalMantissa<'a>,
402        unit: Option<Id>,
403    ) -> Self {
404        self.values.push(PropertyValue {
405            property,
406            value: Value::Decimal { exponent, mantissa, unit },
407        });
408        self
409    }
410
411    /// Adds an EMBEDDING value.
412    pub fn embedding(
413        mut self,
414        property: Id,
415        sub_type: crate::model::EmbeddingSubType,
416        dims: usize,
417        data: impl Into<Cow<'a, [u8]>>,
418    ) -> Self {
419        self.values.push(PropertyValue {
420            property,
421            value: Value::Embedding {
422                sub_type,
423                dims,
424                data: data.into(),
425            },
426        });
427        self
428    }
429}
430
431/// Builder for UpdateEntity operations.
432#[derive(Debug, Clone)]
433pub struct UpdateEntityBuilder<'a> {
434    id: Id,
435    set_properties: Vec<PropertyValue<'a>>,
436    unset_values: Vec<UnsetValue>,
437}
438
439impl<'a> UpdateEntityBuilder<'a> {
440    /// Creates a new UpdateEntityBuilder for the given entity ID.
441    pub fn new(id: Id) -> Self {
442        Self {
443            id,
444            set_properties: Vec::new(),
445            unset_values: Vec::new(),
446        }
447    }
448
449    /// Sets a property value.
450    pub fn set(mut self, property: Id, value: Value<'a>) -> Self {
451        self.set_properties.push(PropertyValue { property, value });
452        self
453    }
454
455    /// Sets a TEXT value.
456    pub fn set_text(
457        mut self,
458        property: Id,
459        value: impl Into<Cow<'a, str>>,
460        language: Option<Id>,
461    ) -> Self {
462        self.set_properties.push(PropertyValue {
463            property,
464            value: Value::Text {
465                value: value.into(),
466                language,
467            },
468        });
469        self
470    }
471
472    /// Sets an INT64 value.
473    pub fn set_int64(mut self, property: Id, value: i64, unit: Option<Id>) -> Self {
474        self.set_properties.push(PropertyValue {
475            property,
476            value: Value::Int64 { value, unit },
477        });
478        self
479    }
480
481    /// Sets a FLOAT64 value.
482    pub fn set_float64(mut self, property: Id, value: f64, unit: Option<Id>) -> Self {
483        self.set_properties.push(PropertyValue {
484            property,
485            value: Value::Float64 { value, unit },
486        });
487        self
488    }
489
490    /// Sets a BOOL value.
491    pub fn set_bool(mut self, property: Id, value: bool) -> Self {
492        self.set_properties.push(PropertyValue {
493            property,
494            value: Value::Bool(value),
495        });
496        self
497    }
498
499    /// Sets a POINT value.
500    pub fn set_point(mut self, property: Id, lon: f64, lat: f64, alt: Option<f64>) -> Self {
501        self.set_properties.push(PropertyValue {
502            property,
503            value: Value::Point { lon, lat, alt },
504        });
505        self
506    }
507
508    /// Sets a DATE value.
509    ///
510    /// # Arguments
511    /// * `days` - Signed days since Unix epoch (1970-01-01)
512    /// * `offset_min` - Signed UTC offset in minutes (e.g., +330 for +05:30)
513    pub fn set_date(mut self, property: Id, days: i32, offset_min: i16) -> Self {
514        self.set_properties.push(PropertyValue {
515            property,
516            value: Value::Date { days, offset_min },
517        });
518        self
519    }
520
521    /// Sets a TIME value.
522    ///
523    /// # Arguments
524    /// * `time_us` - Microseconds since midnight (0 to 86,399,999,999)
525    /// * `offset_min` - Signed UTC offset in minutes (e.g., +330 for +05:30)
526    pub fn set_time(mut self, property: Id, time_us: i64, offset_min: i16) -> Self {
527        self.set_properties.push(PropertyValue {
528            property,
529            value: Value::Time { time_us, offset_min },
530        });
531        self
532    }
533
534    /// Sets a DATETIME value.
535    ///
536    /// # Arguments
537    /// * `epoch_us` - Microseconds since Unix epoch (1970-01-01T00:00:00Z)
538    /// * `offset_min` - Signed UTC offset in minutes (e.g., +330 for +05:30)
539    pub fn set_datetime(mut self, property: Id, epoch_us: i64, offset_min: i16) -> Self {
540        self.set_properties.push(PropertyValue {
541            property,
542            value: Value::Datetime { epoch_us, offset_min },
543        });
544        self
545    }
546
547    /// Sets a SCHEDULE value.
548    pub fn set_schedule(mut self, property: Id, value: impl Into<Cow<'a, str>>) -> Self {
549        self.set_properties.push(PropertyValue {
550            property,
551            value: Value::Schedule(value.into()),
552        });
553        self
554    }
555
556    /// Sets a BYTES value.
557    pub fn set_bytes(mut self, property: Id, value: impl Into<Cow<'a, [u8]>>) -> Self {
558        self.set_properties.push(PropertyValue {
559            property,
560            value: Value::Bytes(value.into()),
561        });
562        self
563    }
564
565    /// Sets a DECIMAL value.
566    pub fn set_decimal(
567        mut self,
568        property: Id,
569        exponent: i32,
570        mantissa: crate::model::DecimalMantissa<'a>,
571        unit: Option<Id>,
572    ) -> Self {
573        self.set_properties.push(PropertyValue {
574            property,
575            value: Value::Decimal { exponent, mantissa, unit },
576        });
577        self
578    }
579
580    /// Sets an EMBEDDING value.
581    pub fn set_embedding(
582        mut self,
583        property: Id,
584        sub_type: crate::model::EmbeddingSubType,
585        dims: usize,
586        data: impl Into<Cow<'a, [u8]>>,
587    ) -> Self {
588        self.set_properties.push(PropertyValue {
589            property,
590            value: Value::Embedding {
591                sub_type,
592                dims,
593                data: data.into(),
594            },
595        });
596        self
597    }
598
599    /// Unsets a specific property+language combination.
600    pub fn unset(mut self, property: Id, language: UnsetLanguage) -> Self {
601        self.unset_values.push(UnsetValue { property, language });
602        self
603    }
604
605    /// Unsets all values for a property (all languages).
606    pub fn unset_all(mut self, property: Id) -> Self {
607        self.unset_values.push(UnsetValue {
608            property,
609            language: UnsetLanguage::All,
610        });
611        self
612    }
613
614    /// Unsets the English value for a property.
615    pub fn unset_english(mut self, property: Id) -> Self {
616        self.unset_values.push(UnsetValue {
617            property,
618            language: UnsetLanguage::English,
619        });
620        self
621    }
622
623    /// Unsets a specific language for a property.
624    pub fn unset_language(mut self, property: Id, language: Id) -> Self {
625        self.unset_values.push(UnsetValue {
626            property,
627            language: UnsetLanguage::Specific(language),
628        });
629        self
630    }
631}
632
633/// Builder for CreateRelation operations with full control.
634#[derive(Debug, Clone, Default)]
635pub struct RelationBuilder<'a> {
636    id: Option<Id>,
637    relation_type: Option<Id>,
638    from: Option<Id>,
639    from_is_value_ref: bool,
640    to: Option<Id>,
641    to_is_value_ref: bool,
642    entity: Option<Id>,
643    position: Option<Cow<'a, str>>,
644    from_space: Option<Id>,
645    from_version: Option<Id>,
646    to_space: Option<Id>,
647    to_version: Option<Id>,
648}
649
650impl<'a> RelationBuilder<'a> {
651    /// Creates a new empty RelationBuilder.
652    pub fn new() -> Self {
653        Self::default()
654    }
655
656    /// Sets the relation ID.
657    pub fn id(mut self, id: Id) -> Self {
658        self.id = Some(id);
659        self
660    }
661
662    /// Sets the relation type.
663    pub fn relation_type(mut self, id: Id) -> Self {
664        self.relation_type = Some(id);
665        self
666    }
667
668    /// Sets the source entity.
669    pub fn from(mut self, id: Id) -> Self {
670        self.from = Some(id);
671        self
672    }
673
674    /// Sets the target entity.
675    pub fn to(mut self, id: Id) -> Self {
676        self.to = Some(id);
677        self
678    }
679
680    /// Sets an explicit reified entity ID.
681    pub fn entity(mut self, id: Id) -> Self {
682        self.entity = Some(id);
683        self
684    }
685
686    /// Sets the position string for ordering.
687    pub fn position(mut self, pos: impl Into<Cow<'a, str>>) -> Self {
688        self.position = Some(pos.into());
689        self
690    }
691
692    /// Sets the from_space pin.
693    pub fn from_space(mut self, space_id: Id) -> Self {
694        self.from_space = Some(space_id);
695        self
696    }
697
698    /// Sets the from_version pin.
699    pub fn from_version(mut self, version_id: Id) -> Self {
700        self.from_version = Some(version_id);
701        self
702    }
703
704    /// Sets the to_space pin.
705    pub fn to_space(mut self, space_id: Id) -> Self {
706        self.to_space = Some(space_id);
707        self
708    }
709
710    /// Sets the to_version pin.
711    pub fn to_version(mut self, version_id: Id) -> Self {
712        self.to_version = Some(version_id);
713        self
714    }
715
716    /// Builds the CreateRelation, returning None if required fields are missing.
717    pub fn build(self) -> Option<CreateRelation<'a>> {
718        Some(CreateRelation {
719            id: self.id?,
720            relation_type: self.relation_type?,
721            from: self.from?,
722            from_is_value_ref: self.from_is_value_ref,
723            to: self.to?,
724            to_is_value_ref: self.to_is_value_ref,
725            entity: self.entity,
726            position: self.position,
727            from_space: self.from_space,
728            from_version: self.from_version,
729            to_space: self.to_space,
730            to_version: self.to_version,
731            context: None,
732        })
733    }
734
735    /// Sets `from` as a value ref ID (inline encoding).
736    pub fn from_value_ref(mut self, id: Id) -> Self {
737        self.from = Some(id);
738        self.from_is_value_ref = true;
739        self
740    }
741
742    /// Sets `to` as a value ref ID (inline encoding).
743    pub fn to_value_ref(mut self, id: Id) -> Self {
744        self.to = Some(id);
745        self.to_is_value_ref = true;
746        self
747    }
748}
749
750/// Builder for UpdateRelation operations.
751#[derive(Debug, Clone)]
752pub struct UpdateRelationBuilder<'a> {
753    id: Id,
754    from_space: Option<Id>,
755    from_version: Option<Id>,
756    to_space: Option<Id>,
757    to_version: Option<Id>,
758    position: Option<Cow<'a, str>>,
759    unset: Vec<UnsetRelationField>,
760}
761
762impl<'a> UpdateRelationBuilder<'a> {
763    /// Creates a new UpdateRelationBuilder for the given relation ID.
764    pub fn new(id: Id) -> Self {
765        Self {
766            id,
767            from_space: None,
768            from_version: None,
769            to_space: None,
770            to_version: None,
771            position: None,
772            unset: Vec::new(),
773        }
774    }
775
776    /// Sets the from_space pin.
777    pub fn set_from_space(mut self, space_id: Id) -> Self {
778        self.from_space = Some(space_id);
779        self
780    }
781
782    /// Sets the from_version pin.
783    pub fn set_from_version(mut self, version_id: Id) -> Self {
784        self.from_version = Some(version_id);
785        self
786    }
787
788    /// Sets the to_space pin.
789    pub fn set_to_space(mut self, space_id: Id) -> Self {
790        self.to_space = Some(space_id);
791        self
792    }
793
794    /// Sets the to_version pin.
795    pub fn set_to_version(mut self, version_id: Id) -> Self {
796        self.to_version = Some(version_id);
797        self
798    }
799
800    /// Sets the position for ordering.
801    pub fn set_position(mut self, pos: impl Into<Cow<'a, str>>) -> Self {
802        self.position = Some(pos.into());
803        self
804    }
805
806    /// Unsets the from_space pin.
807    pub fn unset_from_space(mut self) -> Self {
808        self.unset.push(UnsetRelationField::FromSpace);
809        self
810    }
811
812    /// Unsets the from_version pin.
813    pub fn unset_from_version(mut self) -> Self {
814        self.unset.push(UnsetRelationField::FromVersion);
815        self
816    }
817
818    /// Unsets the to_space pin.
819    pub fn unset_to_space(mut self) -> Self {
820        self.unset.push(UnsetRelationField::ToSpace);
821        self
822    }
823
824    /// Unsets the to_version pin.
825    pub fn unset_to_version(mut self) -> Self {
826        self.unset.push(UnsetRelationField::ToVersion);
827        self
828    }
829
830    /// Unsets the position.
831    pub fn unset_position(mut self) -> Self {
832        self.unset.push(UnsetRelationField::Position);
833        self
834    }
835}
836
837#[cfg(test)]
838mod tests {
839    use super::*;
840
841    #[test]
842    fn test_edit_builder_basic() {
843        let edit_id = [1u8; 16];
844        let author_id = [2u8; 16];
845        let entity_id = [3u8; 16];
846        let prop_id = [4u8; 16];
847
848        let edit = EditBuilder::new(edit_id)
849            .name("Test Edit")
850            .author(author_id)
851            .created_at(1234567890)
852            .create_entity(entity_id, |e| {
853                e.text(prop_id, "Hello", None)
854                    .int64([5u8; 16], 42, None)
855            })
856            .build();
857
858        assert_eq!(edit.id, edit_id);
859        assert_eq!(edit.name, "Test Edit");
860        assert_eq!(edit.authors, vec![author_id]);
861        assert_eq!(edit.created_at, 1234567890);
862        assert_eq!(edit.ops.len(), 1);
863
864        match &edit.ops[0] {
865            Op::CreateEntity(ce) => {
866                assert_eq!(ce.id, entity_id);
867                assert_eq!(ce.values.len(), 2);
868            }
869            _ => panic!("Expected CreateEntity"),
870        }
871    }
872
873    #[test]
874    fn test_edit_builder_relations() {
875        let edit = EditBuilder::new([1u8; 16])
876            .create_relation_simple([5u8; 16], [2u8; 16], [3u8; 16], [4u8; 16])
877            .create_relation_simple([6u8; 16], [2u8; 16], [3u8; 16], [4u8; 16])
878            .build();
879
880        assert_eq!(edit.ops.len(), 2);
881
882        match &edit.ops[0] {
883            Op::CreateRelation(cr) => {
884                assert_eq!(cr.id, [5u8; 16]);
885            }
886            _ => panic!("Expected CreateRelation"),
887        }
888
889        match &edit.ops[1] {
890            Op::CreateRelation(cr) => {
891                assert_eq!(cr.id, [6u8; 16]);
892            }
893            _ => panic!("Expected CreateRelation"),
894        }
895    }
896
897    #[test]
898    fn test_update_entity_builder() {
899        let entity_id = [1u8; 16];
900        let prop_id = [2u8; 16];
901
902        let edit = EditBuilder::new([0u8; 16])
903            .update_entity(entity_id, |u| {
904                u.set_text(prop_id, "New value", None)
905                    .unset_all([3u8; 16])
906            })
907            .build();
908
909        assert_eq!(edit.ops.len(), 1);
910
911        match &edit.ops[0] {
912            Op::UpdateEntity(ue) => {
913                assert_eq!(ue.id, entity_id);
914                assert_eq!(ue.set_properties.len(), 1);
915                assert_eq!(ue.unset_values.len(), 1);
916            }
917            _ => panic!("Expected UpdateEntity"),
918        }
919    }
920
921    #[test]
922    fn test_relation_builder_full() {
923        let edit = EditBuilder::new([0u8; 16])
924            .create_relation(|r| {
925                r.id([1u8; 16])
926                    .from([2u8; 16])
927                    .to([3u8; 16])
928                    .relation_type([4u8; 16])
929                    .entity([5u8; 16])
930                    .position("aaa")
931                    .from_space([6u8; 16])
932            })
933            .build();
934
935        assert_eq!(edit.ops.len(), 1);
936
937        match &edit.ops[0] {
938            Op::CreateRelation(cr) => {
939                assert_eq!(cr.id, [1u8; 16]);
940                assert_eq!(cr.entity, Some([5u8; 16]));
941                assert_eq!(cr.position.as_deref(), Some("aaa"));
942                assert_eq!(cr.from_space, Some([6u8; 16]));
943            }
944            _ => panic!("Expected CreateRelation"),
945        }
946    }
947
948    #[test]
949    fn test_entity_builder_all_types() {
950        let edit = EditBuilder::new([0u8; 16])
951            .create_entity([1u8; 16], |e| {
952                e.text([2u8; 16], "text", None)
953                    .int64([3u8; 16], 123, None)
954                    .float64([4u8; 16], 3.14, None)
955                    .bool([5u8; 16], true)
956                    .point([6u8; 16], -74.0060, 40.7128, None)
957                    .date([7u8; 16], 19738, 0) // 2024-01-15 UTC
958                    .schedule([8u8; 16], "BEGIN:VEVENT\r\nDTSTART:20240315T090000Z\r\nEND:VEVENT")
959                    .bytes([9u8; 16], vec![1, 2, 3, 4])
960            })
961            .build();
962
963        match &edit.ops[0] {
964            Op::CreateEntity(ce) => {
965                assert_eq!(ce.values.len(), 8);
966            }
967            _ => panic!("Expected CreateEntity"),
968        }
969    }
970}