1
use std::fmt::Debug;
2

            
3
use arc_bytes::serde::{Bytes, CowBytes};
4

            
5
use crate::{
6
    connection::{AsyncConnection, Connection},
7
    document::{BorrowedDocument, CollectionHeader, DocumentId, Header, OwnedDocument},
8
    schema::SerializedCollection,
9
    Error,
10
};
11

            
12
/// A document with serializable contents.
13
9
#[derive(Clone, Debug, Eq, PartialEq)]
14
pub struct CollectionDocument<C>
15
where
16
    C: SerializedCollection,
17
{
18
    /// The header of the document, which contains the id and `Revision`.
19
    pub header: CollectionHeader<C::PrimaryKey>,
20

            
21
    /// The document's contents.
22
    pub contents: C::Contents,
23
}
24

            
25
impl<'a, C> TryFrom<&'a BorrowedDocument<'a>> for CollectionDocument<C>
26
where
27
    C: SerializedCollection,
28
{
29
    type Error = Error;
30

            
31
42385
    fn try_from(value: &'a BorrowedDocument<'a>) -> Result<Self, Self::Error> {
32
42385
        Ok(Self {
33
42385
            contents: C::deserialize(&value.contents)?,
34
42385
            header: CollectionHeader::try_from(value.header.clone())?,
35
        })
36
42385
    }
37
}
38

            
39
impl<'a, C> TryFrom<&'a OwnedDocument> for CollectionDocument<C>
40
where
41
    C: SerializedCollection,
42
{
43
    type Error = Error;
44

            
45
25644
    fn try_from(value: &'a OwnedDocument) -> Result<Self, Self::Error> {
46
25644
        Ok(Self {
47
25644
            contents: C::deserialize(&value.contents)?,
48
25644
            header: CollectionHeader::try_from(value.header.clone())?,
49
        })
50
25644
    }
51
}
52

            
53
impl<'a, 'b, C> TryFrom<&'b CollectionDocument<C>> for BorrowedDocument<'a>
54
where
55
    C: SerializedCollection,
56
{
57
    type Error = crate::Error;
58

            
59
    fn try_from(value: &'b CollectionDocument<C>) -> Result<Self, Self::Error> {
60
        Ok(Self {
61
            contents: CowBytes::from(C::serialize(&value.contents)?),
62
            header: Header::try_from(value.header.clone())?,
63
        })
64
    }
65
}
66

            
67
impl<C> CollectionDocument<C>
68
where
69
    C: SerializedCollection,
70
{
71
    /// Stores the new value of `contents` in the document.
72
    ///
73
    /// ```rust
74
    /// # bonsaidb_core::__doctest_prelude!();
75
    /// # use bonsaidb_core::connection::Connection;
76
    /// # fn test_fn<C: Connection>(db: C) -> Result<(), Error> {
77
    /// if let Some(mut document) = MyCollection::get(42, &db)? {
78
    ///     // modify the document
79
    ///     document.update(&db)?;
80
    ///     println!("Updated revision: {:?}", document.header.revision);
81
    /// }
82
    /// # Ok(())
83
    /// # }
84
    /// ```
85
55
    pub fn update<Cn: Connection>(&mut self, connection: &Cn) -> Result<(), Error> {
86
55
        let mut doc = self.to_document()?;
87

            
88
55
        connection.update::<C, _>(&mut doc)?;
89

            
90
52
        self.header = CollectionHeader::try_from(doc.header)?;
91

            
92
52
        Ok(())
93
55
    }
94

            
95
    /// Stores the new value of `contents` in the document.
96
    ///
97
    /// ```rust
98
    /// # bonsaidb_core::__doctest_prelude!();
99
    /// # use bonsaidb_core::connection::AsyncConnection;
100
    /// # fn test_fn<C: AsyncConnection>(db: C) -> Result<(), Error> {
101
    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
102
    /// if let Some(mut document) = MyCollection::get_async(42, &db).await? {
103
    ///     // modify the document
104
    ///     document.update_async(&db).await?;
105
    ///     println!("Updated revision: {:?}", document.header.revision);
106
    /// }
107
    /// # Ok(())
108
    /// # })
109
    /// # }
110
    /// ```
111
5048
    pub async fn update_async<Cn: AsyncConnection>(
112
5048
        &mut self,
113
5048
        connection: &Cn,
114
5048
    ) -> Result<(), Error> {
115
5048
        let mut doc = self.to_document()?;
116

            
117
6176
        connection.update::<C, _>(&mut doc).await?;
118

            
119
5043
        self.header = CollectionHeader::try_from(doc.header)?;
120

            
121
5043
        Ok(())
122
5048
    }
123

            
124
    /// Modifies `self`, automatically retrying the modification if the document
125
    /// has been updated on the server.
126
    ///
127
    /// ## Data loss warning
128
    ///
129
    /// If you've modified `self` before calling this function and a conflict
130
    /// occurs, all changes to self will be lost when the current document is
131
    /// fetched before retrying the process again. When you use this function,
132
    /// you should limit the edits to the value to within the `modifier`
133
    /// callback.
134
3
    pub fn modify<Cn: Connection, Modifier: FnMut(&mut Self) + Send + Sync>(
135
3
        &mut self,
136
3
        connection: &Cn,
137
3
        mut modifier: Modifier,
138
3
    ) -> Result<(), Error>
139
3
    where
140
3
        C::Contents: Clone,
141
3
    {
142
3
        let mut is_first_loop = true;
143
        // TODO this should have a retry-limit.
144
3
        loop {
145
3
            // On the first attempt, we want to try sending the update to the
146
3
            // database without fetching new contents. If we receive a conflict,
147
3
            // on future iterations we will first re-load the data.
148
3
            if is_first_loop {
149
3
                is_first_loop = false;
150
3
            } else {
151
                *self =
152
                    C::get(self.header.id.clone(), connection)?.ok_or_else(
153
                        || match DocumentId::new(self.header.id.clone()) {
154
                            Ok(id) => Error::DocumentNotFound(C::collection_name(), Box::new(id)),
155
                            Err(err) => err,
156
                        },
157
                    )?;
158
            }
159
3
            modifier(&mut *self);
160
3
            match self.update(connection) {
161
                Err(Error::DocumentConflict(..)) => {}
162
3
                other => return other,
163
            }
164
        }
165
3
    }
166

            
167
    /// Modifies `self`, automatically retrying the modification if the document
168
    /// has been updated on the server.
169
    ///
170
    /// ## Data loss warning
171
    ///
172
    /// If you've modified `self` before calling this function and a conflict
173
    /// occurs, all changes to self will be lost when the current document is
174
    /// fetched before retrying the process again. When you use this function,
175
    /// you should limit the edits to the value to within the `modifier`
176
    /// callback.
177
5
    pub async fn modify_async<Cn: AsyncConnection, Modifier: FnMut(&mut Self) + Send + Sync>(
178
5
        &mut self,
179
5
        connection: &Cn,
180
5
        mut modifier: Modifier,
181
5
    ) -> Result<(), Error>
182
5
    where
183
5
        C::Contents: Clone,
184
5
    {
185
5
        let mut is_first_loop = true;
186
        // TODO this should have a retry-limit.
187
5
        loop {
188
5
            // On the first attempt, we want to try sending the update to the
189
5
            // database without fetching new contents. If we receive a conflict,
190
5
            // on future iterations we will first re-load the data.
191
5
            if is_first_loop {
192
5
                is_first_loop = false;
193
5
            } else {
194
                *self = C::get_async(self.header.id.clone(), connection)
195
                    .await?
196
                    .ok_or_else(|| match DocumentId::new(self.header.id.clone()) {
197
                        Ok(id) => Error::DocumentNotFound(C::collection_name(), Box::new(id)),
198
                        Err(err) => err,
199
                    })?;
200
            }
201
5
            modifier(&mut *self);
202
5
            match self.update_async(connection).await {
203
                Err(Error::DocumentConflict(..)) => {}
204
5
                other => return other,
205
            }
206
        }
207
5
    }
208

            
209
    /// Removes the document from the collection.
210
    ///
211
    /// ```rust
212
    /// # bonsaidb_core::__doctest_prelude!();
213
    /// # use bonsaidb_core::connection::Connection;
214
    /// # fn test_fn<C: Connection>(db: C) -> Result<(), Error> {
215
    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
216
    /// if let Some(document) = MyCollection::get(42, &db)? {
217
    ///     document.delete(&db)?;
218
    /// }
219
    /// # Ok(())
220
    /// # })
221
    /// # }
222
    /// ```
223
    pub fn delete<Cn: Connection>(&self, connection: &Cn) -> Result<(), Error> {
224
8
        connection.collection::<C>().delete(self)?;
225

            
226
8
        Ok(())
227
8
    }
228

            
229
    /// Removes the document from the collection.
230
    ///
231
    /// ```rust
232
    /// # bonsaidb_core::__doctest_prelude!();
233
    /// # use bonsaidb_core::connection::AsyncConnection;
234
    /// # fn test_fn<C: AsyncConnection>(db: C) -> Result<(), Error> {
235
    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
236
    /// if let Some(document) = MyCollection::get_async(42, &db).await? {
237
    ///     document.delete_async(&db).await?;
238
    /// }
239
    /// # Ok(())
240
    /// # })
241
    /// # }
242
    /// ```
243
452
    pub async fn delete_async<Cn: AsyncConnection>(&self, connection: &Cn) -> Result<(), Error> {
244
452
        connection.collection::<C>().delete(self).await?;
245

            
246
450
        Ok(())
247
452
    }
248

            
249
    /// Converts this value to a serialized `Document`.
250
5103
    pub fn to_document(&self) -> Result<OwnedDocument, Error> {
251
5103
        Ok(OwnedDocument {
252
5103
            contents: Bytes::from(C::serialize(&self.contents)?),
253
5103
            header: Header::try_from(self.header.clone())?,
254
        })
255
5103
    }
256
}
257

            
258
/// Helper functions for a slice of [`OwnedDocument`]s.
259
pub trait OwnedDocuments {
260
    /// Returns a list of deserialized documents.
261
    fn collection_documents<C: SerializedCollection>(
262
        &self,
263
    ) -> Result<Vec<CollectionDocument<C>>, Error>;
264
}
265

            
266
impl OwnedDocuments for [OwnedDocument] {
267
270
    fn collection_documents<C: SerializedCollection>(
268
270
        &self,
269
270
    ) -> Result<Vec<CollectionDocument<C>>, Error> {
270
270
        self.iter().map(CollectionDocument::try_from).collect()
271
270
    }
272
}