couchbase_lite/
document.rs1use 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 const DELETED = C4DocumentFlags::kDocDeleted.0;
26 const CONFLICTED = C4DocumentFlags::kDocConflicted.0;
28 const HAS_ATTACHMENTS = C4DocumentFlags::kDocHasAttachments.0;
30 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 #[inline]
71 pub fn id(&self) -> &str {
72 &self.id
73 }
74 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 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 #[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 #[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 #[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}