1use std::collections::HashMap;
2use std::fmt;
3use std::str::FromStr;
4
5use serde::{Deserialize, Serialize};
6
7use crate::error::{Result, RouchError};
8use crate::rev_tree::RevTree;
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
19pub struct Revision {
20 pub pos: u64,
21 pub hash: String,
22}
23
24impl Revision {
25 pub fn new(pos: u64, hash: String) -> Self {
26 Self { pos, hash }
27 }
28}
29
30impl fmt::Display for Revision {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 write!(f, "{}-{}", self.pos, self.hash)
33 }
34}
35
36impl FromStr for Revision {
37 type Err = RouchError;
38
39 fn from_str(s: &str) -> Result<Self> {
40 let (pos_str, hash) = s
41 .split_once('-')
42 .ok_or_else(|| RouchError::InvalidRev(s.to_string()))?;
43 let pos: u64 = pos_str
44 .parse()
45 .map_err(|_| RouchError::InvalidRev(s.to_string()))?;
46 if hash.is_empty() {
47 return Err(RouchError::InvalidRev(s.to_string()));
48 }
49 Ok(Revision {
50 pos,
51 hash: hash.to_string(),
52 })
53 }
54}
55
56impl Ord for Revision {
57 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
58 self.pos
59 .cmp(&other.pos)
60 .then_with(|| self.hash.cmp(&other.hash))
61 }
62}
63
64impl PartialOrd for Revision {
65 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
66 Some(self.cmp(other))
67 }
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct AttachmentMeta {
76 pub content_type: String,
77 pub digest: String,
78 pub length: u64,
79 #[serde(default)]
80 pub stub: bool,
81 #[serde(skip_serializing_if = "Option::is_none")]
82 pub data: Option<Vec<u8>>,
83}
84
85#[derive(Debug, Clone)]
91pub struct Document {
92 pub id: String,
93 pub rev: Option<Revision>,
94 pub deleted: bool,
95 pub data: serde_json::Value,
96 pub attachments: HashMap<String, AttachmentMeta>,
97}
98
99impl Document {
100 pub fn from_json(mut value: serde_json::Value) -> Result<Self> {
105 let obj = value
106 .as_object_mut()
107 .ok_or_else(|| RouchError::BadRequest("document must be a JSON object".into()))?;
108
109 let id = obj
110 .remove("_id")
111 .and_then(|v| v.as_str().map(String::from))
112 .unwrap_or_default();
113
114 let rev = obj
115 .remove("_rev")
116 .and_then(|v| v.as_str().map(String::from))
117 .map(|s| s.parse::<Revision>())
118 .transpose()?;
119
120 let deleted = obj
121 .remove("_deleted")
122 .and_then(|v| v.as_bool())
123 .unwrap_or(false);
124
125 let mut attachments: HashMap<String, AttachmentMeta> = HashMap::new();
126 if let Some(att_val) = obj.remove("_attachments")
127 && let Some(att_obj) = att_val.as_object()
128 {
129 for (name, meta) in att_obj {
130 let mut meta_for_parse = meta.clone();
133 let inline_b64 = if let Some(obj) = meta_for_parse.as_object_mut() {
134 match obj.remove("data") {
135 Some(serde_json::Value::String(s)) => Some(s),
136 Some(other) => {
137 obj.insert("data".to_string(), other);
138 None
139 }
140 None => None,
141 }
142 } else {
143 None
144 };
145
146 if let Ok(mut att) = serde_json::from_value::<AttachmentMeta>(meta_for_parse) {
147 if att.data.is_none()
149 && let Some(ref data_str) = inline_b64
150 {
151 use base64::Engine;
152 if let Ok(bytes) =
153 base64::engine::general_purpose::STANDARD.decode(data_str)
154 {
155 att.length = bytes.len() as u64;
156 att.data = Some(bytes);
157 att.stub = false;
158 }
159 }
160 attachments.insert(name.clone(), att);
161 }
162 }
163 }
164
165 Ok(Document {
166 id,
167 rev,
168 deleted,
169 data: value,
170 attachments,
171 })
172 }
173
174 pub fn to_json(&self) -> serde_json::Value {
176 let mut obj = match &self.data {
177 serde_json::Value::Object(m) => m.clone(),
178 _ => serde_json::Map::new(),
179 };
180
181 obj.insert("_id".into(), serde_json::Value::String(self.id.clone()));
182
183 if let Some(rev) = &self.rev {
184 obj.insert("_rev".into(), serde_json::Value::String(rev.to_string()));
185 }
186
187 if self.deleted {
188 obj.insert("_deleted".into(), serde_json::Value::Bool(true));
189 }
190
191 if !self.attachments.is_empty() {
192 use base64::Engine;
193 let mut att_map = serde_json::Map::new();
194 for (name, att) in &self.attachments {
195 if let Ok(serde_json::Value::Object(mut m)) = serde_json::to_value(att) {
196 if let Some(bytes) = &att.data {
199 m.insert(
200 "data".into(),
201 serde_json::Value::String(
202 base64::engine::general_purpose::STANDARD.encode(bytes),
203 ),
204 );
205 m.insert("stub".into(), serde_json::Value::Bool(false));
206 }
207 att_map.insert(name.clone(), serde_json::Value::Object(m));
208 }
209 }
210 obj.insert("_attachments".into(), serde_json::Value::Object(att_map));
211 }
212
213 serde_json::Value::Object(obj)
214 }
215}
216
217#[derive(Debug, Clone)]
223pub struct DocMetadata {
224 pub id: String,
225 pub rev_tree: RevTree,
226 pub seq: u64,
227}
228
229#[derive(Debug, Clone, Default)]
234pub struct GetOptions {
235 pub rev: Option<String>,
237 pub conflicts: bool,
239 pub open_revs: Option<OpenRevs>,
241 pub revs: bool,
243 pub revs_info: bool,
245 pub latest: bool,
247 pub attachments: bool,
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct RevInfo {
254 pub rev: String,
255 pub status: String, }
257
258#[derive(Debug, Clone)]
259pub enum OpenRevs {
260 All,
261 Specific(Vec<String>),
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
265pub struct PutResponse {
266 pub ok: bool,
267 pub id: String,
268 pub rev: String,
269}
270
271#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct DocResult {
273 pub ok: bool,
274 pub id: String,
275 pub rev: Option<String>,
276 pub error: Option<String>,
277 pub reason: Option<String>,
278}
279
280#[derive(Debug, Clone, Default)]
281pub struct BulkDocsOptions {
282 pub new_edits: bool,
285}
286
287impl BulkDocsOptions {
288 pub fn new() -> Self {
289 Self { new_edits: true }
290 }
291
292 pub fn replication() -> Self {
293 Self { new_edits: false }
294 }
295}
296
297#[derive(Debug, Clone, Default)]
298pub struct AllDocsOptions {
299 pub start_key: Option<String>,
300 pub end_key: Option<String>,
301 pub key: Option<String>,
302 pub keys: Option<Vec<String>>,
303 pub include_docs: bool,
304 pub descending: bool,
305 pub skip: u64,
306 pub limit: Option<u64>,
307 pub inclusive_end: bool,
308 pub conflicts: bool,
310 pub update_seq: bool,
312}
313
314impl AllDocsOptions {
315 pub fn new() -> Self {
316 Self {
317 inclusive_end: true,
318 ..Default::default()
319 }
320 }
321}
322
323#[derive(Debug, Clone, Serialize, Deserialize)]
324pub struct AllDocsRow {
325 pub id: String,
326 pub key: String,
327 pub value: AllDocsRowValue,
328 #[serde(skip_serializing_if = "Option::is_none")]
329 pub doc: Option<serde_json::Value>,
330}
331
332#[derive(Debug, Clone, Serialize, Deserialize)]
333pub struct AllDocsRowValue {
334 pub rev: String,
335 #[serde(skip_serializing_if = "Option::is_none")]
336 pub deleted: Option<bool>,
337}
338
339#[derive(Debug, Clone, Serialize, Deserialize)]
340pub struct AllDocsResponse {
341 pub total_rows: u64,
342 pub offset: u64,
343 pub rows: Vec<AllDocsRow>,
344 #[serde(skip_serializing_if = "Option::is_none")]
345 pub update_seq: Option<Seq>,
346}
347
348#[derive(Debug, Clone, Serialize, Deserialize)]
349pub struct DbInfo {
350 pub db_name: String,
351 pub doc_count: u64,
352 #[serde(default)]
353 pub doc_del_count: u64,
354 pub update_seq: Seq,
355}
356
357#[derive(Debug, Clone, Default)]
362pub struct ChangesOptions {
363 pub since: Seq,
364 pub limit: Option<u64>,
365 pub descending: bool,
366 pub include_docs: bool,
367 pub live: bool,
368 pub doc_ids: Option<Vec<String>>,
369 pub selector: Option<serde_json::Value>,
370 pub conflicts: bool,
372 pub style: ChangesStyle,
375}
376
377#[derive(Debug, Clone, Default, PartialEq, Eq)]
379pub enum ChangesStyle {
380 #[default]
382 MainOnly,
383 AllDocs,
385}
386
387#[derive(Debug, Clone, Serialize, Deserialize)]
388pub struct ChangeEvent {
389 pub seq: Seq,
390 pub id: String,
391 pub changes: Vec<ChangeRev>,
392 #[serde(default)]
393 pub deleted: bool,
394 #[serde(skip_serializing_if = "Option::is_none")]
395 pub doc: Option<serde_json::Value>,
396 #[serde(skip_serializing_if = "Option::is_none")]
398 pub conflicts: Option<Vec<String>>,
399}
400
401#[derive(Debug, Clone, Serialize, Deserialize)]
402pub struct ChangeRev {
403 pub rev: String,
404}
405
406#[derive(Debug, Clone, Serialize, Deserialize)]
407pub struct ChangesResponse {
408 pub results: Vec<ChangeEvent>,
409 pub last_seq: Seq,
410}
411
412#[derive(Debug, Clone, Serialize, Deserialize)]
417pub struct BulkGetItem {
418 pub id: String,
419 pub rev: Option<String>,
420}
421
422#[derive(Debug, Clone, Serialize, Deserialize)]
423pub struct BulkGetResponse {
424 pub results: Vec<BulkGetResult>,
425}
426
427#[derive(Debug, Clone, Serialize, Deserialize)]
428pub struct BulkGetResult {
429 pub id: String,
430 pub docs: Vec<BulkGetDoc>,
431}
432
433#[derive(Debug, Clone, Serialize, Deserialize)]
434pub struct BulkGetDoc {
435 pub ok: Option<serde_json::Value>,
436 pub error: Option<BulkGetError>,
437}
438
439#[derive(Debug, Clone, Serialize, Deserialize)]
440pub struct BulkGetError {
441 pub id: String,
442 pub rev: String,
443 pub error: String,
444 pub reason: String,
445}
446
447#[derive(Debug, Clone, Serialize, Deserialize)]
448pub struct RevsDiffResponse {
449 #[serde(flatten)]
450 pub results: HashMap<String, RevsDiffResult>,
451}
452
453#[derive(Debug, Clone, Serialize, Deserialize)]
454pub struct RevsDiffResult {
455 pub missing: Vec<String>,
456 #[serde(default, skip_serializing_if = "Vec::is_empty")]
457 pub possible_ancestors: Vec<String>,
458}
459
460#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
469#[serde(untagged)]
470pub enum Seq {
471 Num(u64),
472 Str(String),
473}
474
475impl Seq {
476 pub fn zero() -> Self {
478 Seq::Num(0)
479 }
480
481 pub fn as_num(&self) -> u64 {
484 match self {
485 Seq::Num(n) => *n,
486 Seq::Str(s) => s
487 .split('-')
488 .next()
489 .and_then(|n| n.parse().ok())
490 .unwrap_or(0),
491 }
492 }
493
494 pub fn to_query_string(&self) -> String {
496 match self {
497 Seq::Num(n) => n.to_string(),
498 Seq::Str(s) => s.clone(),
499 }
500 }
501}
502
503impl Default for Seq {
504 fn default() -> Self {
505 Seq::Num(0)
506 }
507}
508
509impl From<u64> for Seq {
510 fn from(n: u64) -> Self {
511 Seq::Num(n)
512 }
513}
514
515impl std::fmt::Display for Seq {
516 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
517 match self {
518 Seq::Num(n) => write!(f, "{}", n),
519 Seq::Str(s) => write!(f, "{}", s),
520 }
521 }
522}
523
524#[derive(Debug, Clone, Serialize, Deserialize)]
529pub struct PurgeResponse {
530 pub purge_seq: Option<u64>,
531 pub purged: HashMap<String, Vec<String>>,
532}
533
534#[derive(Debug, Clone, Default, Serialize, Deserialize)]
539pub struct SecurityDocument {
540 #[serde(default)]
541 pub admins: SecurityGroup,
542 #[serde(default)]
543 pub members: SecurityGroup,
544 #[serde(flatten, default)]
547 pub extra: serde_json::Map<String, serde_json::Value>,
548}
549
550#[derive(Debug, Clone, Default, Serialize, Deserialize)]
551pub struct SecurityGroup {
552 #[serde(default)]
553 pub names: Vec<String>,
554 #[serde(default)]
555 pub roles: Vec<String>,
556}
557
558#[derive(Debug, Clone, Default)]
563pub struct GetAttachmentOptions {
564 pub rev: Option<String>,
565}
566
567#[cfg(test)]
572mod tests {
573 use super::*;
574
575 #[test]
576 fn revision_display_and_parse() {
577 let rev = Revision::new(3, "abc123".into());
578 assert_eq!(rev.to_string(), "3-abc123");
579
580 let parsed: Revision = "3-abc123".parse().unwrap();
581 assert_eq!(parsed, rev);
582 }
583
584 #[test]
585 fn revision_ordering() {
586 let r1 = Revision::new(1, "aaa".into());
587 let r2 = Revision::new(2, "aaa".into());
588 let r3 = Revision::new(2, "bbb".into());
589 assert!(r1 < r2);
590 assert!(r2 < r3);
591 }
592
593 #[test]
594 fn invalid_revision() {
595 assert!("nope".parse::<Revision>().is_err());
596 assert!("abc-123".parse::<Revision>().is_err());
597 }
598
599 #[test]
600 fn revision_rejects_empty_hash() {
601 assert!("3-".parse::<Revision>().is_err());
603 assert!("1-".parse::<Revision>().is_err());
604 }
605
606 #[test]
607 fn to_json_inline_attachment_is_base64() {
608 let mut attachments = HashMap::new();
609 attachments.insert(
610 "hi.txt".into(),
611 AttachmentMeta {
612 content_type: "text/plain".into(),
613 digest: "md5-abc".into(),
614 length: 3,
615 stub: false,
616 data: Some(b"hi!".to_vec()),
617 },
618 );
619 let doc = Document {
620 id: "doc1".into(),
621 rev: None,
622 deleted: false,
623 data: serde_json::json!({}),
624 attachments,
625 };
626 let json = doc.to_json();
627 assert_eq!(json["_attachments"]["hi.txt"]["data"], "aGkh");
629 assert_eq!(json["_attachments"]["hi.txt"]["stub"], false);
630 }
631
632 #[test]
633 fn document_from_json_roundtrip() {
634 let json = serde_json::json!({
635 "_id": "doc1",
636 "_rev": "1-abc",
637 "name": "Alice",
638 "age": 30
639 });
640
641 let doc = Document::from_json(json).unwrap();
642 assert_eq!(doc.id, "doc1");
643 assert_eq!(doc.rev.as_ref().unwrap().to_string(), "1-abc");
644 assert_eq!(doc.data["name"], "Alice");
645 assert!(!doc.data.as_object().unwrap().contains_key("_id"));
646
647 let back = doc.to_json();
648 assert_eq!(back["_id"], "doc1");
649 assert_eq!(back["_rev"], "1-abc");
650 assert_eq!(back["name"], "Alice");
651 }
652
653 #[test]
654 fn document_from_json_minimal() {
655 let json = serde_json::json!({"hello": "world"});
656 let doc = Document::from_json(json).unwrap();
657 assert!(doc.id.is_empty());
658 assert!(doc.rev.is_none());
659 assert!(!doc.deleted);
660 }
661
662 #[test]
663 fn bulk_docs_options_defaults() {
664 let opts = BulkDocsOptions::new();
665 assert!(opts.new_edits);
666
667 let repl = BulkDocsOptions::replication();
668 assert!(!repl.new_edits);
669 }
670
671 #[test]
672 fn to_json_deleted_document() {
673 let doc = Document {
674 id: "doc1".into(),
675 rev: Some(Revision::new(2, "def".into())),
676 deleted: true,
677 data: serde_json::json!({}),
678 attachments: HashMap::new(),
679 };
680 let json = doc.to_json();
681 assert_eq!(json["_deleted"], true);
682 assert_eq!(json["_id"], "doc1");
683 assert_eq!(json["_rev"], "2-def");
684 }
685
686 #[test]
687 fn to_json_with_attachments() {
688 let mut attachments = HashMap::new();
689 attachments.insert(
690 "file.txt".into(),
691 AttachmentMeta {
692 content_type: "text/plain".into(),
693 digest: "md5-abc".into(),
694 length: 100,
695 stub: true,
696 data: None,
697 },
698 );
699 let doc = Document {
700 id: "doc1".into(),
701 rev: None,
702 deleted: false,
703 data: serde_json::json!({"key": "val"}),
704 attachments,
705 };
706 let json = doc.to_json();
707 assert!(json["_attachments"]["file.txt"].is_object());
708 assert_eq!(
709 json["_attachments"]["file.txt"]["content_type"],
710 "text/plain"
711 );
712 }
713
714 #[test]
715 fn to_json_non_object_data() {
716 let doc = Document {
717 id: "doc1".into(),
718 rev: None,
719 deleted: false,
720 data: serde_json::json!("just a string"),
721 attachments: HashMap::new(),
722 };
723 let json = doc.to_json();
724 assert_eq!(json["_id"], "doc1");
725 }
726
727 #[test]
728 fn document_from_json_with_deleted_and_attachments() {
729 let json = serde_json::json!({
730 "_id": "doc1",
731 "_rev": "1-abc",
732 "_deleted": true,
733 "_attachments": {
734 "photo.jpg": {
735 "content_type": "image/jpeg",
736 "digest": "md5-xyz",
737 "length": 500,
738 "stub": true
739 }
740 },
741 "name": "test"
742 });
743 let doc = Document::from_json(json).unwrap();
744 assert!(doc.deleted);
745 assert_eq!(doc.attachments.len(), 1);
746 assert_eq!(doc.attachments["photo.jpg"].content_type, "image/jpeg");
747 }
748
749 #[test]
750 fn document_from_json_not_object() {
751 let json = serde_json::json!("just a string");
752 assert!(Document::from_json(json).is_err());
753 }
754
755 #[test]
756 fn seq_str_as_num() {
757 let seq = Seq::Str("42-g1AAAABXeJzLY".into());
758 assert_eq!(seq.as_num(), 42);
759
760 let seq2 = Seq::Str("not-a-number".into());
761 assert_eq!(seq2.as_num(), 0);
762 }
763
764 #[test]
765 fn seq_to_query_string() {
766 assert_eq!(Seq::Num(5).to_query_string(), "5");
767 let opaque = "13-g1AAAABXeJzLY".to_string();
768 assert_eq!(Seq::Str(opaque.clone()).to_query_string(), opaque);
769 }
770
771 #[test]
772 fn seq_display() {
773 assert_eq!(format!("{}", Seq::Num(42)), "42");
774 assert_eq!(format!("{}", Seq::Str("opaque-seq".into())), "opaque-seq");
775 }
776
777 #[test]
778 fn seq_from_u64() {
779 let seq: Seq = 7u64.into();
780 assert_eq!(seq, Seq::Num(7));
781 }
782}