1
use std::fmt::Debug;
2

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

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

            
12
/// A document with serializable contents.
13
3
#[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
31226
    fn try_from(value: &'a BorrowedDocument<'a>) -> Result<Self, Self::Error> {
32
31226
        Ok(Self {
33
31226
            contents: C::deserialize(&value.contents)?,
34
31226
            header: CollectionHeader::try_from(value.header.clone())?,
35
        })
36
31226
    }
37
}
38

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

            
45
20146
    fn try_from(value: &'a OwnedDocument) -> Result<Self, Self::Error> {
46
20146
        Ok(Self {
47
20146
            contents: C::deserialize(&value.contents)?,
48
20146
            header: CollectionHeader::try_from(value.header.clone())?,
49
        })
50
20146
    }
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
    /// # fn test_fn<C: Connection>(db: C) -> Result<(), Error> {
76
    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
77
    /// if let Some(mut document) = MyCollection::get(42, &db).await? {
78
    ///     // modify the document
79
    ///     document.update(&db).await?;
80
    ///     println!("Updated revision: {:?}", document.header.revision);
81
    /// }
82
    /// # Ok(())
83
    /// # })
84
    /// # }
85
    /// ```
86
4392
    pub async fn update<Cn: Connection>(&mut self, connection: &Cn) -> Result<(), Error> {
87
4392
        let mut doc = self.to_document()?;
88

            
89
5542
        connection.update::<C, _>(&mut doc).await?;
90

            
91
4387
        self.header = CollectionHeader::try_from(doc.header)?;
92

            
93
4387
        Ok(())
94
4392
    }
95

            
96
    /// Modifies `self`, automatically retrying the modification if the document
97
    /// has been updated on the server.
98
    ///
99
    /// ## Data loss warning
100
    ///
101
    /// If you've modified `self` before calling this function and a conflict
102
    /// occurs, all changes to self will be lost when the current document is
103
    /// fetched before retrying the process again. When you use this function,
104
    /// you should limit the edits to the value to within the `modifier`
105
    /// callback.
106
5
    pub async fn modify<Cn: Connection, Modifier: FnMut(&mut Self) + Send + Sync>(
107
5
        &mut self,
108
5
        connection: &Cn,
109
5
        mut modifier: Modifier,
110
5
    ) -> Result<(), Error>
111
5
    where
112
5
        C::Contents: Clone,
113
5
    {
114
5
        let mut is_first_loop = true;
115
        // TODO this should have a retry-limit.
116
5
        loop {
117
5
            // On the first attempt, we want to try sending the update to the
118
5
            // database without fetching new contents. If we receive a conflict,
119
5
            // on future iterations we will first re-load the data.
120
5
            if is_first_loop {
121
5
                is_first_loop = false;
122
5
            } else {
123
                *self = C::get(self.header.id.clone(), connection)
124
                    .await?
125
                    .ok_or_else(|| match DocumentId::new(self.header.id.clone()) {
126
                        Ok(id) => Error::DocumentNotFound(C::collection_name(), Box::new(id)),
127
                        Err(err) => err,
128
                    })?;
129
            }
130
5
            modifier(&mut *self);
131
5
            match self.update(connection).await {
132
                Err(Error::DocumentConflict(..)) => {}
133
5
                other => return other,
134
            }
135
        }
136
5
    }
137

            
138
    /// Removes the document from the collection.
139
    ///
140
    /// ```rust
141
    /// # bonsaidb_core::__doctest_prelude!();
142
    /// # fn test_fn<C: Connection>(db: C) -> Result<(), Error> {
143
    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
144
    /// if let Some(document) = MyCollection::get(42, &db).await? {
145
    ///     document.delete(&db).await?;
146
    /// }
147
    /// # Ok(())
148
    /// # })
149
    /// # }
150
    /// ```
151
416
    pub async fn delete<Cn: Connection>(&self, connection: &Cn) -> Result<(), Error> {
152
416
        connection.collection::<C>().delete(self).await?;
153

            
154
416
        Ok(())
155
416
    }
156

            
157
    /// Converts this value to a serialized `Document`.
158
4392
    pub fn to_document(&self) -> Result<OwnedDocument, Error> {
159
4392
        Ok(OwnedDocument {
160
4392
            contents: Bytes::from(C::serialize(&self.contents)?),
161
4392
            header: Header::try_from(self.header.clone())?,
162
        })
163
4392
    }
164
}
165

            
166
/// Helper functions for a slice of [`OwnedDocument`]s.
167
pub trait OwnedDocuments {
168
    /// Returns a list of deserialized documents.
169
    fn collection_documents<C: SerializedCollection>(
170
        &self,
171
    ) -> Result<Vec<CollectionDocument<C>>, Error>;
172
}
173

            
174
impl OwnedDocuments for [OwnedDocument] {
175
35
    fn collection_documents<C: SerializedCollection>(
176
35
        &self,
177
35
    ) -> Result<Vec<CollectionDocument<C>>, Error> {
178
35
        self.iter().map(CollectionDocument::try_from).collect()
179
35
    }
180
}