1use 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#[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 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 pub fn name(mut self, name: impl Into<Cow<'a, str>>) -> Self {
55 self.name = name.into();
56 self
57 }
58
59 pub fn author(mut self, author_id: Id) -> Self {
61 self.authors.push(author_id);
62 self
63 }
64
65 pub fn authors(mut self, author_ids: impl IntoIterator<Item = Id>) -> Self {
67 self.authors.extend(author_ids);
68 self
69 }
70
71 pub fn created_at(mut self, timestamp: i64) -> Self {
73 self.created_at = timestamp;
74 self
75 }
76
77 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 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 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 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 pub fn delete_entity(mut self, id: Id) -> Self {
130 self.ops.push(Op::DeleteEntity(DeleteEntity { id }));
131 self
132 }
133
134 pub fn restore_entity(mut self, id: Id) -> Self {
136 self.ops.push(Op::RestoreEntity(RestoreEntity { id }));
137 self
138 }
139
140 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 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 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 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 pub fn delete_relation(mut self, id: Id) -> Self {
215 self.ops.push(Op::DeleteRelation(DeleteRelation { id }));
216 self
217 }
218
219 pub fn restore_relation(mut self, id: Id) -> Self {
221 self.ops.push(Op::RestoreRelation(RestoreRelation { id }));
222 self
223 }
224
225 pub fn op(mut self, op: Op<'a>) -> Self {
231 self.ops.push(op);
232 self
233 }
234
235 pub fn ops(mut self, ops: impl IntoIterator<Item = Op<'a>>) -> Self {
237 self.ops.extend(ops);
238 self
239 }
240
241 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 pub fn op_count(&self) -> usize {
258 self.ops.len()
259 }
260}
261
262#[derive(Debug, Clone, Default)]
264pub struct EntityBuilder<'a> {
265 values: Vec<PropertyValue<'a>>,
266}
267
268impl<'a> EntityBuilder<'a> {
269 pub fn new() -> Self {
271 Self::default()
272 }
273
274 pub fn value(mut self, property: Id, value: Value<'a>) -> Self {
276 self.values.push(PropertyValue { property, value });
277 self
278 }
279
280 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 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 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 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 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 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 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 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 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 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 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 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#[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 pub fn new(id: Id) -> Self {
424 Self {
425 id,
426 set_properties: Vec::new(),
427 unset_values: Vec::new(),
428 }
429 }
430
431 pub fn set(mut self, property: Id, value: Value<'a>) -> Self {
433 self.set_properties.push(PropertyValue { property, value });
434 self
435 }
436
437 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 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 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 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 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 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 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 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 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 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 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 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 pub fn unset(mut self, property: Id, language: UnsetLanguage) -> Self {
571 self.unset_values.push(UnsetValue { property, language });
572 self
573 }
574
575 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 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 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#[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 pub fn new() -> Self {
623 Self::default()
624 }
625
626 pub fn id(mut self, id: Id) -> Self {
628 self.id = Some(id);
629 self
630 }
631
632 pub fn relation_type(mut self, id: Id) -> Self {
634 self.relation_type = Some(id);
635 self
636 }
637
638 pub fn from(mut self, id: Id) -> Self {
640 self.from = Some(id);
641 self
642 }
643
644 pub fn to(mut self, id: Id) -> Self {
646 self.to = Some(id);
647 self
648 }
649
650 pub fn entity(mut self, id: Id) -> Self {
652 self.entity = Some(id);
653 self
654 }
655
656 pub fn position(mut self, pos: impl Into<Cow<'a, str>>) -> Self {
658 self.position = Some(pos.into());
659 self
660 }
661
662 pub fn from_space(mut self, space_id: Id) -> Self {
664 self.from_space = Some(space_id);
665 self
666 }
667
668 pub fn from_version(mut self, version_id: Id) -> Self {
670 self.from_version = Some(version_id);
671 self
672 }
673
674 pub fn to_space(mut self, space_id: Id) -> Self {
676 self.to_space = Some(space_id);
677 self
678 }
679
680 pub fn to_version(mut self, version_id: Id) -> Self {
682 self.to_version = Some(version_id);
683 self
684 }
685
686 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 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 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#[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 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 pub fn set_from_space(mut self, space_id: Id) -> Self {
747 self.from_space = Some(space_id);
748 self
749 }
750
751 pub fn set_from_version(mut self, version_id: Id) -> Self {
753 self.from_version = Some(version_id);
754 self
755 }
756
757 pub fn set_to_space(mut self, space_id: Id) -> Self {
759 self.to_space = Some(space_id);
760 self
761 }
762
763 pub fn set_to_version(mut self, version_id: Id) -> Self {
765 self.to_version = Some(version_id);
766 self
767 }
768
769 pub fn set_position(mut self, pos: impl Into<Cow<'a, str>>) -> Self {
771 self.position = Some(pos.into());
772 self
773 }
774
775 pub fn unset_from_space(mut self) -> Self {
777 self.unset.push(UnsetRelationField::FromSpace);
778 self
779 }
780
781 pub fn unset_from_version(mut self) -> Self {
783 self.unset.push(UnsetRelationField::FromVersion);
784 self
785 }
786
787 pub fn unset_to_space(mut self) -> Self {
789 self.unset.push(UnsetRelationField::ToSpace);
790 self
791 }
792
793 pub fn unset_to_version(mut self) -> Self {
795 self.unset.push(UnsetRelationField::ToVersion);
796 self
797 }
798
799 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}