1
use std::{fmt::Debug, ops::Deref};
2

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

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

            
12
/// A document with serializable contents.
13
1
#[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: Header,
20

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

            
25
impl<C> AsRef<Header> for CollectionDocument<C>
26
where
27
    C: SerializedCollection,
28
{
29
297
    fn as_ref(&self) -> &Header {
30
297
        &self.header
31
297
    }
32
}
33

            
34
impl<C> Deref for CollectionDocument<C>
35
where
36
    C: SerializedCollection,
37
{
38
    type Target = Header;
39

            
40
27305
    fn deref(&self) -> &Self::Target {
41
27305
        &self.header
42
27305
    }
43
}
44

            
45
impl<'a, C> TryFrom<&'a BorrowedDocument<'a>> for CollectionDocument<C>
46
where
47
    C: SerializedCollection,
48
{
49
    type Error = Error;
50

            
51
26201
    fn try_from(value: &'a BorrowedDocument<'a>) -> Result<Self, Self::Error> {
52
26201
        Ok(Self {
53
26201
            contents: C::deserialize(&value.contents)?,
54
26201
            header: value.header.clone(),
55
        })
56
26201
    }
57
}
58

            
59
impl<'a, C> TryFrom<&'a OwnedDocument> for CollectionDocument<C>
60
where
61
    C: SerializedCollection,
62
{
63
    type Error = Error;
64

            
65
15291
    fn try_from(value: &'a OwnedDocument) -> Result<Self, Self::Error> {
66
15291
        Ok(Self {
67
15291
            contents: C::deserialize(&value.contents)?,
68
15291
            header: value.header.clone(),
69
        })
70
15291
    }
71
}
72

            
73
impl<'a, 'b, C> TryFrom<&'b CollectionDocument<C>> for BorrowedDocument<'a>
74
where
75
    C: SerializedCollection,
76
{
77
    type Error = crate::Error;
78

            
79
    fn try_from(value: &'b CollectionDocument<C>) -> Result<Self, Self::Error> {
80
        Ok(Self {
81
            contents: CowBytes::from(C::serialize(&value.contents)?),
82
            header: value.header.clone(),
83
        })
84
    }
85
}
86

            
87
impl<C> CollectionDocument<C>
88
where
89
    C: SerializedCollection,
90
{
91
    /// Stores the new value of `contents` in the document.
92
3399
    pub async fn update<Cn: Connection>(&mut self, connection: &Cn) -> Result<(), Error> {
93
3399
        let mut doc = self.to_document()?;
94

            
95
4553
        connection.update::<C, _>(&mut doc).await?;
96

            
97
3391
        self.header = doc.header;
98
3391

            
99
3391
        Ok(())
100
3399
    }
101

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

            
138
    /// Removes the document from the collection.
139
297
    pub async fn delete<Cn: Connection>(&self, connection: &Cn) -> Result<(), Error> {
140
297
        connection.collection::<C>().delete(self).await?;
141

            
142
297
        Ok(())
143
297
    }
144

            
145
    /// Converts this value to a serialized `Document`.
146
3399
    pub fn to_document(&self) -> Result<OwnedDocument, Error> {
147
3399
        Ok(OwnedDocument {
148
3399
            contents: Bytes::from(C::serialize(&self.contents)?),
149
3399
            header: self.header.clone(),
150
        })
151
3399
    }
152
}
153

            
154
/// Helper functions for a slice of [`OwnedDocument`]s.
155
pub trait OwnedDocuments {
156
    /// Returns a list of deserialized documents.
157
    fn collection_documents<C: SerializedCollection>(
158
        &self,
159
    ) -> Result<Vec<CollectionDocument<C>>, Error>;
160
}
161

            
162
impl OwnedDocuments for [OwnedDocument] {
163
20
    fn collection_documents<C: SerializedCollection>(
164
20
        &self,
165
20
    ) -> Result<Vec<CollectionDocument<C>>, Error> {
166
20
        self.iter().map(CollectionDocument::try_from).collect()
167
20
    }
168
}