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
// 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;
use crate::WithId;
/// 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<O>(&mut self, operation: &O) -> Result<(), DocumentError>
where
O: AsOperation + WithId<OperationId>,
{
// Validate operation passed to commit.
if operation.is_create() {
return Err(DocumentError::InvalidOperationType);
}
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.view_id() != &previous {
return Err(DocumentError::PreviousDoesNotMatch(
operation.id().to_owned(),
));
}
if self.is_deleted() {
return Err(DocumentError::UpdateOnDeleted);
}
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());
Ok(())
}
}