1use std::convert::TryFrom;
4use std::fmt::{Debug, Display};
5
6use crate::document::error::{DocumentBuilderError, DocumentReducerError};
7use crate::document::traits::AsDocument;
8use crate::document::{DocumentId, DocumentViewFields, DocumentViewId};
9use crate::graph::{Graph, Reducer};
10use crate::hash::HashId;
11use crate::identity::PublicKey;
12use crate::operation::traits::{AsOperation, WithPublicKey};
13use crate::operation::{Operation, OperationId};
14use crate::schema::SchemaId;
15use crate::{Human, WithId};
16
17use super::error::DocumentError;
18
19#[derive(Debug, Clone)]
39pub struct Document {
40 id: DocumentId,
42
43 fields: Option<DocumentViewFields>,
45
46 schema_id: SchemaId,
48
49 view_id: DocumentViewId,
51
52 author: PublicKey,
54}
55
56impl AsDocument for Document {
57 fn id(&self) -> &DocumentId {
59 &self.id
60 }
61
62 fn view_id(&self) -> &DocumentViewId {
64 &self.view_id
65 }
66
67 fn author(&self) -> &PublicKey {
69 &self.author
70 }
71
72 fn schema_id(&self) -> &SchemaId {
74 &self.schema_id
75 }
76
77 fn fields(&self) -> Option<&DocumentViewFields> {
79 self.fields.as_ref()
80 }
81
82 fn update_view(&mut self, id: &DocumentViewId, view: Option<&DocumentViewFields>) {
84 self.view_id = id.to_owned();
85 self.fields = view.cloned();
86 }
87}
88
89impl Display for Document {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 write!(f, "{}", self.id)
92 }
93}
94
95impl Human for Document {
96 fn display(&self) -> String {
97 let offset = yasmf_hash::MAX_YAMF_HASH_SIZE * 2 - 6;
98 format!("<Document {}>", &self.id.as_str()[offset..])
99 }
100}
101
102impl<T> TryFrom<Vec<&T>> for Document
103where
104 T: AsOperation + WithId<OperationId> + WithPublicKey,
105{
106 type Error = DocumentBuilderError;
107
108 fn try_from(operations: Vec<&T>) -> Result<Self, Self::Error> {
109 let document_builder: DocumentBuilder = operations.into();
110 let (document, _) = document_builder.build()?;
111 Ok(document)
112 }
113}
114
115impl<T> TryFrom<&Vec<T>> for Document
116where
117 T: AsOperation + WithId<OperationId> + WithPublicKey,
118{
119 type Error = DocumentBuilderError;
120
121 fn try_from(operations: &Vec<T>) -> Result<Self, Self::Error> {
122 let document_builder: DocumentBuilder = operations.into();
123 let (document, _) = document_builder.build()?;
124 Ok(document)
125 }
126}
127
128#[derive(Debug, Default)]
130struct DocumentReducer {
131 document: Option<Document>,
132}
133
134impl Reducer<(OperationId, Operation, PublicKey)> for DocumentReducer {
136 type Error = DocumentReducerError;
137
138 fn combine(&mut self, value: &(OperationId, Operation, PublicKey)) -> Result<(), Self::Error> {
140 let (operation_id, operation, public_key) = value;
142
143 let document = self.document.clone();
145
146 match document {
147 Some(mut document) => {
149 match document.commit(operation_id, operation) {
150 Ok(_) => Ok(()),
151 Err(err) => match err {
152 DocumentError::PreviousDoesNotMatch(_) => {
153 document.commit_unchecked(operation_id, operation);
161 Ok(())
162 }
163 err => Err(err),
166 },
167 }?;
168 self.document = Some(document);
170 Ok(())
171 }
172 None => {
174 if !operation.is_create() {
176 return Err(DocumentReducerError::FirstOperationNotCreate);
177 }
178
179 let document_fields = DocumentViewFields::new_from_operation_fields(
181 operation_id,
182 &operation.fields().unwrap(),
183 );
184
185 let document = Document {
187 id: operation_id.as_hash().clone().into(),
188 fields: Some(document_fields),
189 schema_id: operation.schema_id(),
190 view_id: DocumentViewId::new(&[operation_id.to_owned()]),
191 author: public_key.to_owned(),
192 };
193
194 self.document = Some(document);
196 Ok(())
197 }
198 }
199 }
200}
201
202type PublishedOperation = (OperationId, Operation, PublicKey);
203type OperationGraph = Graph<OperationId, PublishedOperation>;
204
205#[derive(Debug, Clone)]
207pub struct DocumentBuilder(Vec<(OperationId, Operation, PublicKey)>);
208
209impl DocumentBuilder {
210 pub fn new(operations: Vec<(OperationId, Operation, PublicKey)>) -> Self {
212 Self(operations)
213 }
214
215 pub fn operations(&self) -> &Vec<PublishedOperation> {
217 &self.0
218 }
219
220 pub fn build(&self) -> Result<(Document, Vec<PublishedOperation>), DocumentBuilderError> {
230 let mut graph = self.construct_graph()?;
231 self.reduce_document(&mut graph)
232 }
233
234 pub fn build_to_view_id(
245 &self,
246 document_view_id: DocumentViewId,
247 ) -> Result<(Document, Vec<PublishedOperation>), DocumentBuilderError> {
248 let mut graph = self.construct_graph()?;
249 graph = graph.trim(document_view_id.graph_tips())?;
251 self.reduce_document(&mut graph)
252 }
253
254 fn construct_graph(&self) -> Result<OperationGraph, DocumentBuilderError> {
256 let mut graph = Graph::new();
258
259 let mut create_seen = false;
260
261 for (id, operation, public_key) in &self.0 {
263 if operation.is_create() && create_seen {
265 return Err(DocumentBuilderError::MultipleCreateOperations);
266 };
267
268 if operation.is_create() {
270 create_seen = true;
271 }
272
273 graph.add_node(id, (id.to_owned(), operation.to_owned(), *public_key));
274 }
275
276 for (id, operation, _public_key) in &self.0 {
278 if let Some(previous) = operation.previous() {
279 for previous in previous.iter() {
280 let success = graph.add_link(previous, id);
281 if !success {
282 return Err(DocumentBuilderError::InvalidOperationLink(id.to_owned()));
283 }
284 }
285 }
286 }
287
288 Ok(graph)
289 }
290
291 fn reduce_document(
294 &self,
295 graph: &mut OperationGraph,
296 ) -> Result<(Document, Vec<PublishedOperation>), DocumentBuilderError> {
297 let mut document_reducer = DocumentReducer::default();
302 let graph_data = graph.reduce(&mut document_reducer)?;
303 let graph_tips: Vec<OperationId> = graph_data
304 .current_graph_tips()
305 .iter()
306 .map(|(id, _, _)| id.to_owned())
307 .collect();
308
309 let mut document = document_reducer.document.unwrap();
311
312 document.view_id = DocumentViewId::new(&graph_tips);
316
317 Ok((document, graph_data.sorted()))
318 }
319}
320
321impl<T> From<Vec<&T>> for DocumentBuilder
322where
323 T: AsOperation + WithId<OperationId> + WithPublicKey,
324{
325 fn from(operations: Vec<&T>) -> Self {
326 let operations = operations
327 .into_iter()
328 .map(|operation| {
329 (
330 operation.id().to_owned(),
331 operation.into(),
332 operation.public_key().to_owned(),
333 )
334 })
335 .collect();
336
337 Self(operations)
338 }
339}
340
341impl<T> From<&Vec<T>> for DocumentBuilder
342where
343 T: AsOperation + WithId<OperationId> + WithPublicKey,
344{
345 fn from(operations: &Vec<T>) -> Self {
346 let operations = operations
347 .iter()
348 .map(|operation| {
349 (
350 operation.id().to_owned(),
351 operation.into(),
352 operation.public_key().to_owned(),
353 )
354 })
355 .collect();
356
357 Self(operations)
358 }
359}
360
361#[cfg(test)]
362mod tests {
363 use std::convert::{TryFrom, TryInto};
364
365 use rstest::rstest;
366
367 use crate::document::traits::AsDocument;
368 use crate::document::{
369 Document, DocumentId, DocumentViewFields, DocumentViewId, DocumentViewValue,
370 };
371 use crate::entry::traits::AsEncodedEntry;
372 use crate::identity::KeyPair;
373 use crate::operation::{OperationAction, OperationBuilder, OperationId, OperationValue};
374 use crate::schema::{FieldType, Schema, SchemaId, SchemaName};
375 use crate::test_utils::constants::{self, PRIVATE_KEY};
376 use crate::test_utils::fixtures::{
377 operation, operation_fields, published_operation, random_document_view_id,
378 random_operation_id, schema,
379 };
380 use crate::test_utils::memory_store::helpers::send_to_store;
381 use crate::test_utils::memory_store::{MemoryStore, PublishedOperation};
382 use crate::{Human, WithId};
383
384 use super::DocumentBuilder;
385
386 #[rstest]
387 fn string_representation(#[from(published_operation)] operation: PublishedOperation) {
388 let document: Document = vec![&operation].try_into().unwrap();
389
390 assert_eq!(
391 document.to_string(),
392 "00207f8ffabff270f21098a457b900b4989b7272a6cb637f3c938b06be0a77b708ed"
393 );
394
395 assert_eq!(document.display(), "<Document b708ed>");
397
398 assert_eq!(
400 document.id().as_str(),
401 "00207f8ffabff270f21098a457b900b4989b7272a6cb637f3c938b06be0a77b708ed"
402 );
403 }
404
405 #[rstest]
406 #[tokio::test]
407 async fn resolve_documents(
408 #[with(vec![("name".to_string(), FieldType::String)])] schema: Schema,
409 ) {
410 let panda = KeyPair::from_private_key_str(
411 "ddcafe34db2625af34c8ba3cf35d46e23283d908c9848c8b43d1f5d0fde779ea",
412 )
413 .unwrap();
414
415 let penguin = KeyPair::from_private_key_str(
416 "1c86b2524b48f0ba86103cddc6bdfd87774ab77ab4c0ea989ed0eeab3d28827a",
417 )
418 .unwrap();
419
420 let store = MemoryStore::default();
421
422 let panda_operation_1 = OperationBuilder::new(schema.id())
428 .action(OperationAction::Create)
429 .fields(&[("name", OperationValue::String("Panda Cafe".to_string()))])
430 .build()
431 .unwrap();
432
433 let (panda_entry_1, _) = send_to_store(&store, &panda_operation_1, &schema, &panda)
434 .await
435 .unwrap();
436
437 let panda_operation_2 = OperationBuilder::new(schema.id())
444 .action(OperationAction::Update)
445 .fields(&[("name", OperationValue::String("Panda Cafe!".to_string()))])
446 .previous(&panda_entry_1.hash().into())
447 .build()
448 .unwrap();
449
450 let (panda_entry_2, _) = send_to_store(&store, &panda_operation_2, &schema, &panda)
451 .await
452 .unwrap();
453
454 let penguin_operation_1 = OperationBuilder::new(schema.id())
461 .action(OperationAction::Update)
462 .fields(&[(
463 "name",
464 OperationValue::String("Penguin Cafe!!!".to_string()),
465 )])
466 .previous(&panda_entry_1.hash().into())
467 .build()
468 .unwrap();
469
470 let (penguin_entry_1, _) = send_to_store(&store, &penguin_operation_1, &schema, &penguin)
471 .await
472 .unwrap();
473
474 let penguin_operation_2 = OperationBuilder::new(schema.id())
481 .action(OperationAction::Update)
482 .fields(&[(
483 "name",
484 OperationValue::String("Polar Bear Cafe".to_string()),
485 )])
486 .previous(&DocumentViewId::new(&[
487 penguin_entry_1.hash().into(),
488 panda_entry_2.hash().into(),
489 ]))
490 .build()
491 .unwrap();
492
493 let (penguin_entry_2, _) = send_to_store(&store, &penguin_operation_2, &schema, &penguin)
494 .await
495 .unwrap();
496
497 let penguin_operation_3 = OperationBuilder::new(schema.id())
503 .action(OperationAction::Update)
504 .fields(&[(
505 "name",
506 OperationValue::String("Polar Bear Cafe!!!!!!!!!!".to_string()),
507 )])
508 .previous(&penguin_entry_2.hash().into())
509 .build()
510 .unwrap();
511
512 let (penguin_entry_3, _) = send_to_store(&store, &penguin_operation_3, &schema, &penguin)
513 .await
514 .unwrap();
515
516 let operations = store.operations.lock().unwrap();
517 let operations = operations.values().collect::<Vec<&PublishedOperation>>();
518 let document = Document::try_from(operations.clone());
519
520 assert!(document.is_ok(), "{:#?}", document);
521
522 let document = document.unwrap();
524
525 let mut exp_result = DocumentViewFields::new();
526 exp_result.insert(
527 "name",
528 DocumentViewValue::new(
529 &penguin_entry_3.hash().into(),
530 &OperationValue::String("Polar Bear Cafe!!!!!!!!!!".to_string()),
531 ),
532 );
533
534 let document_id = DocumentId::new(&panda_entry_1.hash().into());
535 let expected_graph_tips: Vec<OperationId> = vec![penguin_entry_3.hash().into()];
536
537 assert_eq!(
538 document.fields().unwrap().get("name"),
539 exp_result.get("name")
540 );
541 assert!(document.is_edited());
542 assert!(!document.is_deleted());
543 assert_eq!(document.author(), &panda.public_key());
544 assert_eq!(document.schema_id(), schema.id());
545 assert_eq!(document.view_id().graph_tips(), expected_graph_tips);
546 assert_eq!(document.id(), &document_id);
547
548 let replica_1: Document = vec![
550 operations[4],
551 operations[3],
552 operations[2],
553 operations[1],
554 operations[0],
555 ]
556 .try_into()
557 .unwrap();
558
559 let replica_2: Document = vec![
560 operations[2],
561 operations[1],
562 operations[0],
563 operations[4],
564 operations[3],
565 ]
566 .try_into()
567 .unwrap();
568
569 assert_eq!(
570 replica_1.fields().unwrap().get("name"),
571 exp_result.get("name")
572 );
573 assert!(replica_1.is_edited());
574 assert!(!replica_1.is_deleted());
575 assert_eq!(replica_1.author(), &panda.public_key());
576 assert_eq!(replica_1.schema_id(), schema.id());
577 assert_eq!(replica_1.view_id().graph_tips(), expected_graph_tips);
578 assert_eq!(replica_1.id(), &document_id);
579
580 assert_eq!(
581 replica_1.fields().unwrap().get("name"),
582 replica_2.fields().unwrap().get("name")
583 );
584 assert_eq!(replica_1.id(), replica_2.id());
585 assert_eq!(
586 replica_1.view_id().graph_tips(),
587 replica_2.view_id().graph_tips(),
588 );
589 }
590
591 #[rstest]
592 fn must_have_create_operation(
593 #[from(published_operation)]
594 #[with(
595 Some(operation_fields(constants::test_fields())),
596 constants::schema(),
597 Some(random_document_view_id())
598 )]
599 update_operation: PublishedOperation,
600 ) {
601 let document: Result<Document, _> = vec![&update_operation].try_into();
602 assert_eq!(
603 document.unwrap_err().to_string(),
604 format!(
605 "operation {} cannot be connected to the document graph",
606 WithId::<OperationId>::id(&update_operation)
607 )
608 );
609 }
610
611 #[rstest]
612 #[tokio::test]
613 async fn incorrect_previous_operations(
614 #[from(published_operation)]
615 #[with(Some(operation_fields(constants::test_fields())), constants::schema())]
616 create_operation: PublishedOperation,
617 #[from(published_operation)]
618 #[with(
619 Some(operation_fields(constants::test_fields())),
620 constants::schema(),
621 Some(random_document_view_id())
622 )]
623 update_operation: PublishedOperation,
624 ) {
625 let document: Result<Document, _> = vec![&create_operation, &update_operation].try_into();
626
627 assert_eq!(
628 document.unwrap_err().to_string(),
629 format!(
630 "operation {} cannot be connected to the document graph",
631 WithId::<OperationId>::id(&update_operation).clone()
632 )
633 );
634 }
635
636 #[rstest]
637 #[tokio::test]
638 async fn operation_schemas_not_matching() {
639 let create_operation = published_operation(
640 Some(operation_fields(constants::test_fields())),
641 constants::schema(),
642 None,
643 KeyPair::from_private_key_str(PRIVATE_KEY).unwrap(),
644 );
645
646 let update_operation = published_operation(
647 Some(operation_fields(vec![
648 ("name", "is_cute".into()),
649 ("type", "bool".into()),
650 ])),
651 Schema::get_system(SchemaId::SchemaFieldDefinition(1))
652 .unwrap()
653 .to_owned(),
654 Some(WithId::<OperationId>::id(&create_operation).clone().into()),
655 KeyPair::from_private_key_str(PRIVATE_KEY).unwrap(),
656 );
657
658 let document: Result<Document, _> = vec![&create_operation, &update_operation].try_into();
659
660 assert_eq!(
661 document.unwrap_err().to_string(),
662 "Could not perform reducer function: Operation 0020b7674a56756183f7d2c6afa20e06041a9a9a30b0aec728e35acf281ecff2b544 does not match the documents schema".to_string()
663 );
664 }
665
666 #[rstest]
667 #[tokio::test]
668 async fn is_deleted(
669 #[from(published_operation)]
670 #[with(Some(operation_fields(constants::test_fields())), constants::schema())]
671 create_operation: PublishedOperation,
672 ) {
673 let delete_operation = published_operation(
674 None,
675 constants::schema(),
676 Some(DocumentViewId::new(&[WithId::<OperationId>::id(
677 &create_operation,
678 )
679 .clone()])),
680 KeyPair::from_private_key_str(PRIVATE_KEY).unwrap(),
681 );
682
683 let document: Document = vec![&create_operation, &delete_operation]
684 .try_into()
685 .unwrap();
686
687 assert!(document.is_deleted());
688 assert!(document.fields().is_none());
689 }
690
691 #[rstest]
692 #[tokio::test]
693 async fn more_than_one_create(
694 #[from(published_operation)] create_operation: PublishedOperation,
695 ) {
696 let document: Result<Document, _> = vec![&create_operation, &create_operation].try_into();
697
698 assert_eq!(
699 document.unwrap_err().to_string(),
700 "multiple CREATE operations found when building operation graph".to_string()
701 );
702 }
703
704 #[rstest]
705 #[tokio::test]
706 async fn fields(#[with(vec![("name".to_string(), FieldType::String)])] schema: Schema) {
707 let mut operations = Vec::new();
708
709 let panda = KeyPair::new().public_key().to_owned();
710 let penguin = KeyPair::new().public_key().to_owned();
711
712 let operation_1_id = random_operation_id();
718 let operation = OperationBuilder::new(schema.id())
719 .action(OperationAction::Create)
720 .fields(&[("name", OperationValue::String("Panda Cafe".to_string()))])
721 .build()
722 .unwrap();
723
724 operations.push((operation_1_id.clone(), operation, panda));
725
726 let operation_2_id = random_operation_id();
733 let operation = OperationBuilder::new(schema.id())
734 .action(OperationAction::Update)
735 .fields(&[("name", OperationValue::String("Panda Cafe!".to_string()))])
736 .previous(&DocumentViewId::new(&[operation_1_id.clone()]))
737 .build()
738 .unwrap();
739
740 operations.push((operation_2_id.clone(), operation, panda));
741
742 let operation_3_id = random_operation_id();
749 let operation = OperationBuilder::new(schema.id())
750 .action(OperationAction::Update)
751 .fields(&[(
752 "name",
753 OperationValue::String("Penguin Cafe!!!".to_string()),
754 )])
755 .previous(&DocumentViewId::new(&[operation_2_id.clone()]))
756 .build()
757 .unwrap();
758
759 operations.push((operation_3_id.clone(), operation, penguin));
760
761 let document_builder = DocumentBuilder::new(operations);
762
763 let (document, _) = document_builder
764 .build_to_view_id(DocumentViewId::new(&[operation_1_id]))
765 .unwrap();
766 assert_eq!(
767 document.fields().unwrap().get("name").unwrap().value(),
768 &OperationValue::String("Panda Cafe".to_string())
769 );
770
771 let (document, _) = document_builder
772 .build_to_view_id(DocumentViewId::new(&[operation_2_id.clone()]))
773 .unwrap();
774 assert_eq!(
775 document.fields().unwrap().get("name").unwrap().value(),
776 &OperationValue::String("Panda Cafe!".to_string())
777 );
778
779 let (document, _) = document_builder
780 .build_to_view_id(DocumentViewId::new(&[operation_3_id.clone()]))
781 .unwrap();
782 assert_eq!(
783 document.fields().unwrap().get("name").unwrap().value(),
784 &OperationValue::String("Penguin Cafe!!!".to_string())
785 );
786
787 let (document, _) = document_builder
788 .build_to_view_id(DocumentViewId::new(&[operation_2_id, operation_3_id]))
789 .unwrap();
790 assert_eq!(
791 document.fields().unwrap().get("name").unwrap().value(),
792 &OperationValue::String("Penguin Cafe!!!".to_string())
793 );
794 }
795
796 #[rstest]
797 #[tokio::test]
798 async fn apply_commit(
799 #[from(published_operation)]
800 #[with(Some(operation_fields(constants::test_fields())), constants::schema())]
801 create_operation: PublishedOperation,
802 ) {
803 let create_view_id =
806 DocumentViewId::new(&[WithId::<OperationId>::id(&create_operation).clone()]);
807
808 let update_operation = operation(
809 Some(operation_fields(vec![("age", OperationValue::Integer(21))])),
810 Some(create_view_id.clone()),
811 constants::schema().id().to_owned(),
812 );
813
814 let update_operation_id = random_operation_id();
815 let update_view_id = DocumentViewId::new(&[update_operation_id.clone()]);
816
817 let delete_operation = operation(
818 None,
819 Some(update_view_id.clone()),
820 constants::schema().id().to_owned(),
821 );
822
823 let delete_operation_id = random_operation_id();
824 let delete_view_id = DocumentViewId::new(&[delete_operation_id.clone()]);
825
826 let mut document: Document = vec![&create_operation].try_into().unwrap();
828
829 assert!(!document.is_edited());
830 assert_eq!(document.view_id(), &create_view_id);
831 assert_eq!(document.get("age").unwrap(), &OperationValue::Integer(28));
832
833 document
835 .commit(&update_operation_id, &update_operation)
836 .unwrap();
837
838 assert!(document.is_edited());
839 assert_eq!(document.view_id(), &update_view_id);
840 assert_eq!(document.get("age").unwrap(), &OperationValue::Integer(21));
841
842 document
844 .commit(&delete_operation_id, &delete_operation)
845 .unwrap();
846
847 assert!(document.is_deleted());
848 assert_eq!(document.view_id(), &delete_view_id);
849 assert_eq!(document.fields(), None);
850 }
851
852 #[rstest]
853 #[tokio::test]
854 async fn validate_commit_operation(
855 #[from(published_operation)]
856 #[with(Some(operation_fields(constants::test_fields())), constants::schema())]
857 create_operation: PublishedOperation,
858 ) {
859 let mut document: Document = vec![&create_operation].try_into().unwrap();
861
862 assert!(document
864 .commit(create_operation.id(), &create_operation)
865 .is_err());
866
867 let create_view_id =
868 DocumentViewId::new(&[WithId::<OperationId>::id(&create_operation).clone()]);
869
870 let schema_name = SchemaName::new("my_wrong_schema").expect("Valid schema name");
871 let update_with_incorrect_schema_id = published_operation(
872 Some(operation_fields(vec![("age", OperationValue::Integer(21))])),
873 schema(
874 vec![("age".into(), FieldType::Integer)],
875 SchemaId::new_application(&schema_name, &random_document_view_id()),
876 "Schema with a wrong id",
877 ),
878 Some(create_view_id.clone()),
879 KeyPair::from_private_key_str(PRIVATE_KEY).unwrap(),
880 );
881
882 assert!(document
884 .commit(
885 update_with_incorrect_schema_id.id(),
886 &update_with_incorrect_schema_id
887 )
888 .is_err());
889
890 let update_not_referring_to_current_view = published_operation(
891 Some(operation_fields(vec![("age", OperationValue::Integer(21))])),
892 constants::schema(),
893 Some(random_document_view_id()),
894 KeyPair::from_private_key_str(PRIVATE_KEY).unwrap(),
895 );
896
897 assert!(document
899 .commit(
900 update_not_referring_to_current_view.id(),
901 &update_not_referring_to_current_view
902 )
903 .is_err());
904
905 let delete_operation = published_operation(
907 None,
908 constants::schema(),
909 Some(create_view_id.clone()),
910 KeyPair::from_private_key_str(PRIVATE_KEY).unwrap(),
911 );
912
913 assert!(document
914 .commit(delete_operation.id(), &delete_operation)
915 .is_ok());
916
917 let delete_view_id =
918 DocumentViewId::new(&[WithId::<OperationId>::id(&delete_operation).clone()]);
919
920 let update_on_a_deleted_document = published_operation(
921 Some(operation_fields(vec![("age", OperationValue::Integer(21))])),
922 constants::schema(),
923 Some(delete_view_id.to_owned()),
924 KeyPair::from_private_key_str(PRIVATE_KEY).unwrap(),
925 );
926
927 assert!(document
929 .commit(
930 update_on_a_deleted_document.id(),
931 &update_on_a_deleted_document
932 )
933 .is_err());
934 }
935}