1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// SPDX-License-Identifier: AGPL-3.0-or-later
//! Interfaces for interactions for document-like structs.
use crate::document::error::DocumentError;
use crate::document::{
DocumentId, DocumentView, DocumentViewFields, DocumentViewId, DocumentViewValue,
};
use crate::identity::PublicKey;
use crate::operation::traits::AsOperation;
use crate::operation::{OperationId, OperationValue};
use crate::schema::SchemaId;
/// Trait representing an "document-like" struct.
pub trait AsDocument {
/// Get the document id.
fn id(&self) -> &DocumentId;
/// Get the document view id.
fn view_id(&self) -> &DocumentViewId;
/// Get the document author's public key.
fn author(&self) -> &PublicKey;
/// Get the document schema.
fn schema_id(&self) -> &SchemaId;
/// Get the fields of this document.
fn fields(&self) -> Option<&DocumentViewFields>;
/// Update the view of this document.
fn update_view(&mut self, id: &DocumentViewId, view: Option<&DocumentViewFields>);
/// Returns true if this document has applied an UPDATE operation.
fn is_edited(&self) -> bool {
match self.fields() {
Some(fields) => fields.iter().any(|(_, document_view_value)| {
&DocumentId::new(document_view_value.id()) != self.id()
}),
None => true,
}
}
/// Returns true if this document has processed a DELETE operation.
fn is_deleted(&self) -> bool {
self.fields().is_none()
}
/// The current document view for this document. Returns None if this document
/// has been deleted.
fn view(&self) -> Option<DocumentView> {
self.fields()
.map(|fields| DocumentView::new(self.view_id(), fields))
}
/// Get the value for a field on this document.
fn get(&self, key: &str) -> Option<&OperationValue> {
if let Some(fields) = self.fields() {
return fields.get(key).map(|view_value| view_value.value());
}
None
}
/// Update a documents current view with a single operation.
///
/// For the update to be successful the passed operation must refer to this documents' current
/// view id in it's previous field and must update a field which exists on this document.
fn commit<T: AsOperation>(
&mut self,
operation_id: &OperationId,
operation: &T,
) -> Result<(), DocumentError> {
// Validate operation passed to commit.
if operation.is_create() {
return Err(DocumentError::CommitCreate);
}
if &operation.schema_id() != self.schema_id() {
return Err(DocumentError::InvalidSchemaId(operation_id.to_owned()));
}
// Unwrap as all other operation types contain `previous`.
let previous = operation.previous().unwrap();
if self.is_deleted() {
return Err(DocumentError::UpdateOnDeleted);
}
if self.view_id() != &previous {
return Err(DocumentError::PreviousDoesNotMatch(operation_id.to_owned()));
}
// We performed all validation commit the operation.
self.commit_unchecked(operation_id, operation);
Ok(())
}
/// Commit an new operation to the document without performing any validation.
fn commit_unchecked<T: AsOperation>(&mut self, operation_id: &OperationId, operation: &T) {
let next_fields = match operation.fields() {
// If the operation contains fields it's an UPDATE and so we want to apply the changes
// to the designated fields.
Some(fields) => {
// Get the current document fields, we can unwrap as we checked for deleted
// documents above.
let mut document_fields = self.fields().unwrap().to_owned();
// For every field in the UPDATE operation update the relevant field in the
// current document fields.
for (name, value) in fields.iter() {
let document_field_value = DocumentViewValue::new(operation_id, value);
// We know all the fields are correct for this document as we checked the
// schema id above.
document_fields.insert(name, document_field_value);
}
// Return the updated fields.
Some(document_fields)
}
// If the operation doesn't contain fields this must be a DELETE so we return None as we want to remove the
// current document's fields.
None => None,
};
// Construct the new document view id.
let document_view_id = DocumentViewId::new(&[operation_id.to_owned()]);
// Update the documents' view, edited/deleted state and view id.
self.update_view(&document_view_id, next_fields.as_ref());
}
}