Skip to main content

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 in RFC 3339 format.
349    ///
350    /// # Arguments
351    /// * `value` - RFC 3339 date string (e.g., "2024-01-15" or "2024-01-15+05:30")
352    pub fn date(mut self, property: Id, value: impl Into<Cow<'a, str>>) -> Self {
353        self.values.push(PropertyValue {
354            property,
355            value: Value::Date(value.into()),
356        });
357        self
358    }
359
360    /// Adds a TIME value in RFC 3339 format.
361    ///
362    /// # Arguments
363    /// * `value` - RFC 3339 time string (e.g., "14:30:45.123456Z" or "14:30:45+05:30")
364    pub fn time(mut self, property: Id, value: impl Into<Cow<'a, str>>) -> Self {
365        self.values.push(PropertyValue {
366            property,
367            value: Value::Time(value.into()),
368        });
369        self
370    }
371
372    /// Adds a DATETIME value in RFC 3339 format.
373    ///
374    /// # Arguments
375    /// * `value` - RFC 3339 datetime string (e.g., "2024-01-15T14:30:45.123456Z")
376    pub fn datetime(mut self, property: Id, value: impl Into<Cow<'a, str>>) -> Self {
377        self.values.push(PropertyValue {
378            property,
379            value: Value::Datetime(value.into()),
380        });
381        self
382    }
383
384    /// Adds a SCHEDULE value (RFC 5545 iCalendar format).
385    pub fn schedule(mut self, property: Id, value: impl Into<Cow<'a, str>>) -> Self {
386        self.values.push(PropertyValue {
387            property,
388            value: Value::Schedule(value.into()),
389        });
390        self
391    }
392
393    /// Adds a DECIMAL value.
394    pub fn decimal(
395        mut self,
396        property: Id,
397        exponent: i32,
398        mantissa: crate::model::DecimalMantissa<'a>,
399        unit: Option<Id>,
400    ) -> Self {
401        self.values.push(PropertyValue {
402            property,
403            value: Value::Decimal { exponent, mantissa, unit },
404        });
405        self
406    }
407
408    /// Adds an EMBEDDING value.
409    pub fn embedding(
410        mut self,
411        property: Id,
412        sub_type: crate::model::EmbeddingSubType,
413        dims: usize,
414        data: impl Into<Cow<'a, [u8]>>,
415    ) -> Self {
416        self.values.push(PropertyValue {
417            property,
418            value: Value::Embedding {
419                sub_type,
420                dims,
421                data: data.into(),
422            },
423        });
424        self
425    }
426}
427
428/// Builder for UpdateEntity operations.
429#[derive(Debug, Clone)]
430pub struct UpdateEntityBuilder<'a> {
431    id: Id,
432    set_properties: Vec<PropertyValue<'a>>,
433    unset_values: Vec<UnsetValue>,
434}
435
436impl<'a> UpdateEntityBuilder<'a> {
437    /// Creates a new UpdateEntityBuilder for the given entity ID.
438    pub fn new(id: Id) -> Self {
439        Self {
440            id,
441            set_properties: Vec::new(),
442            unset_values: Vec::new(),
443        }
444    }
445
446    /// Sets a property value.
447    pub fn set(mut self, property: Id, value: Value<'a>) -> Self {
448        self.set_properties.push(PropertyValue { property, value });
449        self
450    }
451
452    /// Sets a TEXT value.
453    pub fn set_text(
454        mut self,
455        property: Id,
456        value: impl Into<Cow<'a, str>>,
457        language: Option<Id>,
458    ) -> Self {
459        self.set_properties.push(PropertyValue {
460            property,
461            value: Value::Text {
462                value: value.into(),
463                language,
464            },
465        });
466        self
467    }
468
469    /// Sets an INT64 value.
470    pub fn set_int64(mut self, property: Id, value: i64, unit: Option<Id>) -> Self {
471        self.set_properties.push(PropertyValue {
472            property,
473            value: Value::Int64 { value, unit },
474        });
475        self
476    }
477
478    /// Sets a FLOAT64 value.
479    pub fn set_float64(mut self, property: Id, value: f64, unit: Option<Id>) -> Self {
480        self.set_properties.push(PropertyValue {
481            property,
482            value: Value::Float64 { value, unit },
483        });
484        self
485    }
486
487    /// Sets a BOOL value.
488    pub fn set_bool(mut self, property: Id, value: bool) -> Self {
489        self.set_properties.push(PropertyValue {
490            property,
491            value: Value::Bool(value),
492        });
493        self
494    }
495
496    /// Sets a POINT value.
497    pub fn set_point(mut self, property: Id, lon: f64, lat: f64, alt: Option<f64>) -> Self {
498        self.set_properties.push(PropertyValue {
499            property,
500            value: Value::Point { lon, lat, alt },
501        });
502        self
503    }
504
505    /// Sets a DATE value in RFC 3339 format.
506    ///
507    /// # Arguments
508    /// * `value` - RFC 3339 date string (e.g., "2024-01-15" or "2024-01-15+05:30")
509    pub fn set_date(mut self, property: Id, value: impl Into<Cow<'a, str>>) -> Self {
510        self.set_properties.push(PropertyValue {
511            property,
512            value: Value::Date(value.into()),
513        });
514        self
515    }
516
517    /// Sets a TIME value in RFC 3339 format.
518    ///
519    /// # Arguments
520    /// * `value` - RFC 3339 time string (e.g., "14:30:45.123456Z" or "14:30:45+05:30")
521    pub fn set_time(mut self, property: Id, value: impl Into<Cow<'a, str>>) -> Self {
522        self.set_properties.push(PropertyValue {
523            property,
524            value: Value::Time(value.into()),
525        });
526        self
527    }
528
529    /// Sets a DATETIME value in RFC 3339 format.
530    ///
531    /// # Arguments
532    /// * `value` - RFC 3339 datetime string (e.g., "2024-01-15T14:30:45.123456Z")
533    pub fn set_datetime(mut self, property: Id, value: impl Into<Cow<'a, str>>) -> Self {
534        self.set_properties.push(PropertyValue {
535            property,
536            value: Value::Datetime(value.into()),
537        });
538        self
539    }
540
541    /// Sets a SCHEDULE value.
542    pub fn set_schedule(mut self, property: Id, value: impl Into<Cow<'a, str>>) -> Self {
543        self.set_properties.push(PropertyValue {
544            property,
545            value: Value::Schedule(value.into()),
546        });
547        self
548    }
549
550    /// Sets a BYTES value.
551    pub fn set_bytes(mut self, property: Id, value: impl Into<Cow<'a, [u8]>>) -> Self {
552        self.set_properties.push(PropertyValue {
553            property,
554            value: Value::Bytes(value.into()),
555        });
556        self
557    }
558
559    /// Sets a DECIMAL value.
560    pub fn set_decimal(
561        mut self,
562        property: Id,
563        exponent: i32,
564        mantissa: crate::model::DecimalMantissa<'a>,
565        unit: Option<Id>,
566    ) -> Self {
567        self.set_properties.push(PropertyValue {
568            property,
569            value: Value::Decimal { exponent, mantissa, unit },
570        });
571        self
572    }
573
574    /// Sets an EMBEDDING value.
575    pub fn set_embedding(
576        mut self,
577        property: Id,
578        sub_type: crate::model::EmbeddingSubType,
579        dims: usize,
580        data: impl Into<Cow<'a, [u8]>>,
581    ) -> Self {
582        self.set_properties.push(PropertyValue {
583            property,
584            value: Value::Embedding {
585                sub_type,
586                dims,
587                data: data.into(),
588            },
589        });
590        self
591    }
592
593    /// Unsets a specific property+language combination.
594    pub fn unset(mut self, property: Id, language: UnsetLanguage) -> Self {
595        self.unset_values.push(UnsetValue { property, language });
596        self
597    }
598
599    /// Unsets all values for a property (all languages).
600    pub fn unset_all(mut self, property: Id) -> Self {
601        self.unset_values.push(UnsetValue {
602            property,
603            language: UnsetLanguage::All,
604        });
605        self
606    }
607
608    /// Unsets the English value for a property.
609    pub fn unset_english(mut self, property: Id) -> Self {
610        self.unset_values.push(UnsetValue {
611            property,
612            language: UnsetLanguage::English,
613        });
614        self
615    }
616
617    /// Unsets a specific language for a property.
618    pub fn unset_language(mut self, property: Id, language: Id) -> Self {
619        self.unset_values.push(UnsetValue {
620            property,
621            language: UnsetLanguage::Specific(language),
622        });
623        self
624    }
625}
626
627/// Builder for CreateRelation operations with full control.
628#[derive(Debug, Clone, Default)]
629pub struct RelationBuilder<'a> {
630    id: Option<Id>,
631    relation_type: Option<Id>,
632    from: Option<Id>,
633    from_is_value_ref: bool,
634    to: Option<Id>,
635    to_is_value_ref: bool,
636    entity: Option<Id>,
637    position: Option<Cow<'a, str>>,
638    from_space: Option<Id>,
639    from_version: Option<Id>,
640    to_space: Option<Id>,
641    to_version: Option<Id>,
642}
643
644impl<'a> RelationBuilder<'a> {
645    /// Creates a new empty RelationBuilder.
646    pub fn new() -> Self {
647        Self::default()
648    }
649
650    /// Sets the relation ID.
651    pub fn id(mut self, id: Id) -> Self {
652        self.id = Some(id);
653        self
654    }
655
656    /// Sets the relation type.
657    pub fn relation_type(mut self, id: Id) -> Self {
658        self.relation_type = Some(id);
659        self
660    }
661
662    /// Sets the source entity.
663    pub fn from(mut self, id: Id) -> Self {
664        self.from = Some(id);
665        self
666    }
667
668    /// Sets the target entity.
669    pub fn to(mut self, id: Id) -> Self {
670        self.to = Some(id);
671        self
672    }
673
674    /// Sets an explicit reified entity ID.
675    pub fn entity(mut self, id: Id) -> Self {
676        self.entity = Some(id);
677        self
678    }
679
680    /// Sets the position string for ordering.
681    pub fn position(mut self, pos: impl Into<Cow<'a, str>>) -> Self {
682        self.position = Some(pos.into());
683        self
684    }
685
686    /// Sets the from_space pin.
687    pub fn from_space(mut self, space_id: Id) -> Self {
688        self.from_space = Some(space_id);
689        self
690    }
691
692    /// Sets the from_version pin.
693    pub fn from_version(mut self, version_id: Id) -> Self {
694        self.from_version = Some(version_id);
695        self
696    }
697
698    /// Sets the to_space pin.
699    pub fn to_space(mut self, space_id: Id) -> Self {
700        self.to_space = Some(space_id);
701        self
702    }
703
704    /// Sets the to_version pin.
705    pub fn to_version(mut self, version_id: Id) -> Self {
706        self.to_version = Some(version_id);
707        self
708    }
709
710    /// Builds the CreateRelation, returning None if required fields are missing.
711    pub fn build(self) -> Option<CreateRelation<'a>> {
712        Some(CreateRelation {
713            id: self.id?,
714            relation_type: self.relation_type?,
715            from: self.from?,
716            from_is_value_ref: self.from_is_value_ref,
717            to: self.to?,
718            to_is_value_ref: self.to_is_value_ref,
719            entity: self.entity,
720            position: self.position,
721            from_space: self.from_space,
722            from_version: self.from_version,
723            to_space: self.to_space,
724            to_version: self.to_version,
725            context: None,
726        })
727    }
728
729    /// Sets `from` as a value ref ID (inline encoding).
730    pub fn from_value_ref(mut self, id: Id) -> Self {
731        self.from = Some(id);
732        self.from_is_value_ref = true;
733        self
734    }
735
736    /// Sets `to` as a value ref ID (inline encoding).
737    pub fn to_value_ref(mut self, id: Id) -> Self {
738        self.to = Some(id);
739        self.to_is_value_ref = true;
740        self
741    }
742}
743
744/// Builder for UpdateRelation operations.
745#[derive(Debug, Clone)]
746pub struct UpdateRelationBuilder<'a> {
747    id: Id,
748    from_space: Option<Id>,
749    from_version: Option<Id>,
750    to_space: Option<Id>,
751    to_version: Option<Id>,
752    position: Option<Cow<'a, str>>,
753    unset: Vec<UnsetRelationField>,
754}
755
756impl<'a> UpdateRelationBuilder<'a> {
757    /// Creates a new UpdateRelationBuilder for the given relation ID.
758    pub fn new(id: Id) -> Self {
759        Self {
760            id,
761            from_space: None,
762            from_version: None,
763            to_space: None,
764            to_version: None,
765            position: None,
766            unset: Vec::new(),
767        }
768    }
769
770    /// Sets the from_space pin.
771    pub fn set_from_space(mut self, space_id: Id) -> Self {
772        self.from_space = Some(space_id);
773        self
774    }
775
776    /// Sets the from_version pin.
777    pub fn set_from_version(mut self, version_id: Id) -> Self {
778        self.from_version = Some(version_id);
779        self
780    }
781
782    /// Sets the to_space pin.
783    pub fn set_to_space(mut self, space_id: Id) -> Self {
784        self.to_space = Some(space_id);
785        self
786    }
787
788    /// Sets the to_version pin.
789    pub fn set_to_version(mut self, version_id: Id) -> Self {
790        self.to_version = Some(version_id);
791        self
792    }
793
794    /// Sets the position for ordering.
795    pub fn set_position(mut self, pos: impl Into<Cow<'a, str>>) -> Self {
796        self.position = Some(pos.into());
797        self
798    }
799
800    /// Unsets the from_space pin.
801    pub fn unset_from_space(mut self) -> Self {
802        self.unset.push(UnsetRelationField::FromSpace);
803        self
804    }
805
806    /// Unsets the from_version pin.
807    pub fn unset_from_version(mut self) -> Self {
808        self.unset.push(UnsetRelationField::FromVersion);
809        self
810    }
811
812    /// Unsets the to_space pin.
813    pub fn unset_to_space(mut self) -> Self {
814        self.unset.push(UnsetRelationField::ToSpace);
815        self
816    }
817
818    /// Unsets the to_version pin.
819    pub fn unset_to_version(mut self) -> Self {
820        self.unset.push(UnsetRelationField::ToVersion);
821        self
822    }
823
824    /// Unsets the position.
825    pub fn unset_position(mut self) -> Self {
826        self.unset.push(UnsetRelationField::Position);
827        self
828    }
829}
830
831#[cfg(test)]
832mod tests {
833    use super::*;
834
835    #[test]
836    fn test_edit_builder_basic() {
837        let edit_id = [1u8; 16];
838        let author_id = [2u8; 16];
839        let entity_id = [3u8; 16];
840        let prop_id = [4u8; 16];
841
842        let edit = EditBuilder::new(edit_id)
843            .name("Test Edit")
844            .author(author_id)
845            .created_at(1234567890)
846            .create_entity(entity_id, |e| {
847                e.text(prop_id, "Hello", None)
848                    .int64([5u8; 16], 42, None)
849            })
850            .build();
851
852        assert_eq!(edit.id, edit_id);
853        assert_eq!(edit.name, "Test Edit");
854        assert_eq!(edit.authors, vec![author_id]);
855        assert_eq!(edit.created_at, 1234567890);
856        assert_eq!(edit.ops.len(), 1);
857
858        match &edit.ops[0] {
859            Op::CreateEntity(ce) => {
860                assert_eq!(ce.id, entity_id);
861                assert_eq!(ce.values.len(), 2);
862            }
863            _ => panic!("Expected CreateEntity"),
864        }
865    }
866
867    #[test]
868    fn test_edit_builder_relations() {
869        let edit = EditBuilder::new([1u8; 16])
870            .create_relation_simple([5u8; 16], [2u8; 16], [3u8; 16], [4u8; 16])
871            .create_relation_simple([6u8; 16], [2u8; 16], [3u8; 16], [4u8; 16])
872            .build();
873
874        assert_eq!(edit.ops.len(), 2);
875
876        match &edit.ops[0] {
877            Op::CreateRelation(cr) => {
878                assert_eq!(cr.id, [5u8; 16]);
879            }
880            _ => panic!("Expected CreateRelation"),
881        }
882
883        match &edit.ops[1] {
884            Op::CreateRelation(cr) => {
885                assert_eq!(cr.id, [6u8; 16]);
886            }
887            _ => panic!("Expected CreateRelation"),
888        }
889    }
890
891    #[test]
892    fn test_update_entity_builder() {
893        let entity_id = [1u8; 16];
894        let prop_id = [2u8; 16];
895
896        let edit = EditBuilder::new([0u8; 16])
897            .update_entity(entity_id, |u| {
898                u.set_text(prop_id, "New value", None)
899                    .unset_all([3u8; 16])
900            })
901            .build();
902
903        assert_eq!(edit.ops.len(), 1);
904
905        match &edit.ops[0] {
906            Op::UpdateEntity(ue) => {
907                assert_eq!(ue.id, entity_id);
908                assert_eq!(ue.set_properties.len(), 1);
909                assert_eq!(ue.unset_values.len(), 1);
910            }
911            _ => panic!("Expected UpdateEntity"),
912        }
913    }
914
915    #[test]
916    fn test_relation_builder_full() {
917        let edit = EditBuilder::new([0u8; 16])
918            .create_relation(|r| {
919                r.id([1u8; 16])
920                    .from([2u8; 16])
921                    .to([3u8; 16])
922                    .relation_type([4u8; 16])
923                    .entity([5u8; 16])
924                    .position("aaa")
925                    .from_space([6u8; 16])
926            })
927            .build();
928
929        assert_eq!(edit.ops.len(), 1);
930
931        match &edit.ops[0] {
932            Op::CreateRelation(cr) => {
933                assert_eq!(cr.id, [1u8; 16]);
934                assert_eq!(cr.entity, Some([5u8; 16]));
935                assert_eq!(cr.position.as_deref(), Some("aaa"));
936                assert_eq!(cr.from_space, Some([6u8; 16]));
937            }
938            _ => panic!("Expected CreateRelation"),
939        }
940    }
941
942    #[test]
943    fn test_entity_builder_all_types() {
944        let edit = EditBuilder::new([0u8; 16])
945            .create_entity([1u8; 16], |e| {
946                e.text([2u8; 16], "text", None)
947                    .int64([3u8; 16], 123, None)
948                    .float64([4u8; 16], 3.14, None)
949                    .bool([5u8; 16], true)
950                    .point([6u8; 16], -74.0060, 40.7128, None)
951                    .date([7u8; 16], "2024-01-15Z") // RFC 3339 date
952                    .time([10u8; 16], "14:30:00Z") // RFC 3339 time
953                    .datetime([11u8; 16], "2024-01-15T14:30:00Z") // RFC 3339 datetime
954                    .schedule([8u8; 16], "BEGIN:VEVENT\r\nDTSTART:20240315T090000Z\r\nEND:VEVENT")
955                    .bytes([9u8; 16], vec![1, 2, 3, 4])
956            })
957            .build();
958
959        match &edit.ops[0] {
960            Op::CreateEntity(ce) => {
961                assert_eq!(ce.values.len(), 10);
962            }
963            _ => panic!("Expected CreateEntity"),
964        }
965    }
966}