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