couchbase_lite/
document.rs

1use crate::{
2    error::{c4error_init, Error, Result},
3    ffi::{
4        c4doc_getRevisionBody, c4doc_loadRevisionBody, c4doc_release, c4rev_getGeneration,
5        C4Document, C4DocumentFlags, C4Revision, FLSliceResult,
6    },
7};
8use bitflags::bitflags;
9use serde::{de::DeserializeOwned, Serialize};
10use serde_fleece::{to_fl_slice_result_with_encoder, FlEncoderSession};
11use std::{os::raw::c_uint, ptr::NonNull, str};
12use uuid::Uuid;
13
14#[derive(Debug)]
15pub struct Document {
16    id: Box<str>,
17    pub(crate) unsaved_body: Option<FLSliceResult>,
18    pub(crate) inner: Option<C4DocumentOwner>,
19}
20
21bitflags! {
22    #[derive(Debug)]
23    pub struct DocumentFlags: u32 {
24        /// The document's current revision is deleted.
25        const DELETED         = C4DocumentFlags::kDocDeleted.0;
26        /// The document is in conflict.
27        const CONFLICTED      = C4DocumentFlags::kDocConflicted.0;
28        /// The document's current revision has attachments.
29        const HAS_ATTACHMENTS = C4DocumentFlags::kDocHasAttachments.0;
30        /// The document exists (i.e. has revisions.)
31        const EXISTS = C4DocumentFlags::kDocExists.0;
32    }
33}
34
35impl Document {
36    #[inline]
37    pub fn new<T>(data: &T, enc: FlEncoderSession) -> Result<Self>
38    where
39        T: Serialize,
40    {
41        let unsaved_body = Some(to_fl_slice_result_with_encoder(data, enc)?);
42        Ok(Self {
43            inner: None,
44            unsaved_body,
45            id: Uuid::new_v4().hyphenated().to_string().into(),
46        })
47    }
48    #[inline]
49    pub fn new_with_id<S, T>(doc_id: S, data: &T, enc: FlEncoderSession) -> Result<Self>
50    where
51        S: Into<String>,
52        T: Serialize,
53    {
54        let unsaved_body = Some(to_fl_slice_result_with_encoder(data, enc)?);
55        Ok(Self {
56            inner: None,
57            id: doc_id.into().into_boxed_str(),
58            unsaved_body,
59        })
60    }
61    #[inline]
62    pub fn new_with_id_fleece<S: Into<String>>(doc_id: S, fleece_data: FLSliceResult) -> Self {
63        Self {
64            inner: None,
65            id: doc_id.into().into_boxed_str(),
66            unsaved_body: Some(fleece_data),
67        }
68    }
69    /// return the document's ID
70    #[inline]
71    pub fn id(&self) -> &str {
72        &self.id
73    }
74    /// Decode body of document
75    pub fn decode_body<T: DeserializeOwned>(self) -> Result<T> {
76        if let Some(slice) = self.unsaved_body.as_ref().map(FLSliceResult::as_fl_slice) {
77            let x: T = serde_fleece::from_slice(slice.into())?;
78            return Ok(x);
79        }
80        let inner: &C4DocumentOwner = self.inner.as_ref().ok_or_else(|| {
81            Error::LogicError(format!("Document {} have no underlying C4Document", self.id).into())
82        })?;
83        let body = inner.load_body()?;
84        let x: T = serde_fleece::from_slice(body)?;
85        Ok(x)
86    }
87    /// Update internal buffer with data, you need save document
88    /// to database to make this change permanent
89    pub fn update_body<T>(&mut self, data: &T, enc: FlEncoderSession) -> Result<()>
90    where
91        T: Serialize,
92    {
93        let body = to_fl_slice_result_with_encoder(data, enc)?;
94        self.unsaved_body = Some(body);
95        Ok(())
96    }
97
98    /// Returns a document's current sequence in the local database.
99    /// This number increases every time the document is saved, and a more recently saved document
100    /// will have a greater sequence number than one saved earlier, so sequences may be used as an
101    /// abstract 'clock' to tell relative modification times
102    #[inline]
103    pub fn sequence(&self) -> Option<u64> {
104        self.inner
105            .as_ref()
106            .map(|p| unsafe { p.0.as_ref() }.selectedRev.sequence)
107    }
108
109    /// Returns a document's revision ID, which is a short opaque string that's guaranteed to be
110    /// unique to every change made to the document.
111    #[inline]
112    pub fn revision_id(&self) -> Option<&str> {
113        self.inner
114            .as_ref()
115            .map(|p| str::from_utf8(p.revision_id()).ok())
116            .unwrap_or(None)
117    }
118
119    #[inline]
120    pub fn flags(&self) -> Option<DocumentFlags> {
121        self.inner
122            .as_ref()
123            .map(|p| DocumentFlags::from_bits_truncate(p.flags().0))
124    }
125
126    #[inline]
127    pub fn generation(&self) -> c_uint {
128        self.inner
129            .as_ref()
130            .map(|d| C4DocumentOwner::generation(d.revision_id()))
131            .unwrap_or(0)
132    }
133
134    /// Just check `Document::flags` to see if document exists
135    #[inline]
136    pub fn exists(&self) -> bool {
137        self.inner.as_ref().map(|x| x.exists()).unwrap_or(false)
138    }
139
140    pub(crate) fn new_internal<S>(inner: C4DocumentOwner, doc_id: S) -> Self
141    where
142        S: Into<String>,
143    {
144        Self {
145            inner: Some(inner),
146            id: doc_id.into().into_boxed_str(),
147            unsaved_body: None,
148        }
149    }
150    pub(crate) fn replace_c4doc(&mut self, doc: Option<C4DocumentOwner>) {
151        self.inner = doc;
152    }
153}
154
155#[repr(transparent)]
156#[derive(Debug)]
157pub(crate) struct C4DocumentOwner(pub(crate) NonNull<C4Document>);
158
159impl Drop for C4DocumentOwner {
160    fn drop(&mut self) {
161        unsafe { c4doc_release(self.0.as_ptr()) };
162    }
163}
164
165impl C4DocumentOwner {
166    pub(crate) fn exists(&self) -> bool {
167        (self.flags() & C4DocumentFlags::kDocExists) == C4DocumentFlags::kDocExists
168    }
169    fn flags(&self) -> C4DocumentFlags {
170        unsafe { self.0.as_ref().flags }
171    }
172    pub(crate) fn selected_revision(&self) -> &C4Revision {
173        &unsafe { self.0.as_ref() }.selectedRev
174    }
175    pub(crate) fn id(&self) -> Result<&str> {
176        unsafe {
177            self.0
178                .as_ref()
179                .docID
180                .as_fl_slice()
181                .try_into()
182                .map_err(|_| Error::InvalidUtf8)
183        }
184    }
185    pub(crate) fn revision_id(&self) -> &[u8] {
186        unsafe { self.0.as_ref() }.revID.as_fl_slice().into()
187    }
188    pub(crate) fn generation(rev_id: &[u8]) -> c_uint {
189        unsafe { c4rev_getGeneration(rev_id.into()) }
190    }
191    pub(crate) fn load_body(&self) -> Result<&[u8]> {
192        let mut c4err = c4error_init();
193        if unsafe { c4doc_loadRevisionBody(self.0.as_ptr(), &mut c4err) } {
194            Ok(unsafe { c4doc_getRevisionBody(self.0.as_ptr()) }.into())
195        } else {
196            Err(c4err.into())
197        }
198    }
199}