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 Ok(Revision {
47 pos,
48 hash: hash.to_string(),
49 })
50 }
51}
52
53impl Ord for Revision {
54 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
55 self.pos
56 .cmp(&other.pos)
57 .then_with(|| self.hash.cmp(&other.hash))
58 }
59}
60
61impl PartialOrd for Revision {
62 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
63 Some(self.cmp(other))
64 }
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct AttachmentMeta {
73 pub content_type: String,
74 pub digest: String,
75 pub length: u64,
76 #[serde(default)]
77 pub stub: bool,
78 #[serde(skip_serializing_if = "Option::is_none")]
79 pub data: Option<Vec<u8>>,
80}
81
82#[derive(Debug, Clone)]
88pub struct Document {
89 pub id: String,
90 pub rev: Option<Revision>,
91 pub deleted: bool,
92 pub data: serde_json::Value,
93 pub attachments: HashMap<String, AttachmentMeta>,
94}
95
96impl Document {
97 pub fn from_json(mut value: serde_json::Value) -> Result<Self> {
102 let obj = value
103 .as_object_mut()
104 .ok_or_else(|| RouchError::BadRequest("document must be a JSON object".into()))?;
105
106 let id = obj
107 .remove("_id")
108 .and_then(|v| v.as_str().map(String::from))
109 .unwrap_or_default();
110
111 let rev = obj
112 .remove("_rev")
113 .and_then(|v| v.as_str().map(String::from))
114 .map(|s| s.parse::<Revision>())
115 .transpose()?;
116
117 let deleted = obj
118 .remove("_deleted")
119 .and_then(|v| v.as_bool())
120 .unwrap_or(false);
121
122 let mut attachments: HashMap<String, AttachmentMeta> = HashMap::new();
123 if let Some(att_val) = obj.remove("_attachments")
124 && let Some(att_obj) = att_val.as_object()
125 {
126 for (name, meta) in att_obj {
127 let mut meta_for_parse = meta.clone();
130 let inline_b64 = if let Some(obj) = meta_for_parse.as_object_mut() {
131 match obj.remove("data") {
132 Some(serde_json::Value::String(s)) => Some(s),
133 Some(other) => {
134 obj.insert("data".to_string(), other);
135 None
136 }
137 None => None,
138 }
139 } else {
140 None
141 };
142
143 if let Ok(mut att) = serde_json::from_value::<AttachmentMeta>(meta_for_parse) {
144 if att.data.is_none()
146 && let Some(ref data_str) = inline_b64
147 {
148 use base64::Engine;
149 if let Ok(bytes) =
150 base64::engine::general_purpose::STANDARD.decode(data_str)
151 {
152 att.length = bytes.len() as u64;
153 att.data = Some(bytes);
154 att.stub = false;
155 }
156 }
157 attachments.insert(name.clone(), att);
158 }
159 }
160 }
161
162 Ok(Document {
163 id,
164 rev,
165 deleted,
166 data: value,
167 attachments,
168 })
169 }
170
171 pub fn to_json(&self) -> serde_json::Value {
173 let mut obj = match &self.data {
174 serde_json::Value::Object(m) => m.clone(),
175 _ => serde_json::Map::new(),
176 };
177
178 obj.insert("_id".into(), serde_json::Value::String(self.id.clone()));
179
180 if let Some(rev) = &self.rev {
181 obj.insert("_rev".into(), serde_json::Value::String(rev.to_string()));
182 }
183
184 if self.deleted {
185 obj.insert("_deleted".into(), serde_json::Value::Bool(true));
186 }
187
188 if !self.attachments.is_empty()
189 && let Ok(att_json) = serde_json::to_value(&self.attachments)
190 {
191 obj.insert("_attachments".into(), att_json);
192 }
193
194 serde_json::Value::Object(obj)
195 }
196}
197
198#[derive(Debug, Clone)]
204pub struct DocMetadata {
205 pub id: String,
206 pub rev_tree: RevTree,
207 pub seq: u64,
208}
209
210#[derive(Debug, Clone, Default)]
215pub struct GetOptions {
216 pub rev: Option<String>,
218 pub conflicts: bool,
220 pub open_revs: Option<OpenRevs>,
222 pub revs: bool,
224 pub revs_info: bool,
226 pub latest: bool,
228 pub attachments: bool,
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct RevInfo {
235 pub rev: String,
236 pub status: String, }
238
239#[derive(Debug, Clone)]
240pub enum OpenRevs {
241 All,
242 Specific(Vec<String>),
243}
244
245#[derive(Debug, Clone, Serialize, Deserialize)]
246pub struct PutResponse {
247 pub ok: bool,
248 pub id: String,
249 pub rev: String,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct DocResult {
254 pub ok: bool,
255 pub id: String,
256 pub rev: Option<String>,
257 pub error: Option<String>,
258 pub reason: Option<String>,
259}
260
261#[derive(Debug, Clone, Default)]
262pub struct BulkDocsOptions {
263 pub new_edits: bool,
266}
267
268impl BulkDocsOptions {
269 pub fn new() -> Self {
270 Self { new_edits: true }
271 }
272
273 pub fn replication() -> Self {
274 Self { new_edits: false }
275 }
276}
277
278#[derive(Debug, Clone, Default)]
279pub struct AllDocsOptions {
280 pub start_key: Option<String>,
281 pub end_key: Option<String>,
282 pub key: Option<String>,
283 pub keys: Option<Vec<String>>,
284 pub include_docs: bool,
285 pub descending: bool,
286 pub skip: u64,
287 pub limit: Option<u64>,
288 pub inclusive_end: bool,
289 pub conflicts: bool,
291 pub update_seq: bool,
293}
294
295impl AllDocsOptions {
296 pub fn new() -> Self {
297 Self {
298 inclusive_end: true,
299 ..Default::default()
300 }
301 }
302}
303
304#[derive(Debug, Clone, Serialize, Deserialize)]
305pub struct AllDocsRow {
306 pub id: String,
307 pub key: String,
308 pub value: AllDocsRowValue,
309 #[serde(skip_serializing_if = "Option::is_none")]
310 pub doc: Option<serde_json::Value>,
311}
312
313#[derive(Debug, Clone, Serialize, Deserialize)]
314pub struct AllDocsRowValue {
315 pub rev: String,
316 #[serde(skip_serializing_if = "Option::is_none")]
317 pub deleted: Option<bool>,
318}
319
320#[derive(Debug, Clone, Serialize, Deserialize)]
321pub struct AllDocsResponse {
322 pub total_rows: u64,
323 pub offset: u64,
324 pub rows: Vec<AllDocsRow>,
325 #[serde(skip_serializing_if = "Option::is_none")]
326 pub update_seq: Option<Seq>,
327}
328
329#[derive(Debug, Clone, Serialize, Deserialize)]
330pub struct DbInfo {
331 pub db_name: String,
332 pub doc_count: u64,
333 pub update_seq: Seq,
334}
335
336#[derive(Debug, Clone, Default)]
341pub struct ChangesOptions {
342 pub since: Seq,
343 pub limit: Option<u64>,
344 pub descending: bool,
345 pub include_docs: bool,
346 pub live: bool,
347 pub doc_ids: Option<Vec<String>>,
348 pub selector: Option<serde_json::Value>,
349 pub conflicts: bool,
351 pub style: ChangesStyle,
354}
355
356#[derive(Debug, Clone, Default, PartialEq, Eq)]
358pub enum ChangesStyle {
359 #[default]
361 MainOnly,
362 AllDocs,
364}
365
366#[derive(Debug, Clone, Serialize, Deserialize)]
367pub struct ChangeEvent {
368 pub seq: Seq,
369 pub id: String,
370 pub changes: Vec<ChangeRev>,
371 #[serde(default)]
372 pub deleted: bool,
373 #[serde(skip_serializing_if = "Option::is_none")]
374 pub doc: Option<serde_json::Value>,
375 #[serde(skip_serializing_if = "Option::is_none")]
377 pub conflicts: Option<Vec<String>>,
378}
379
380#[derive(Debug, Clone, Serialize, Deserialize)]
381pub struct ChangeRev {
382 pub rev: String,
383}
384
385#[derive(Debug, Clone, Serialize, Deserialize)]
386pub struct ChangesResponse {
387 pub results: Vec<ChangeEvent>,
388 pub last_seq: Seq,
389}
390
391#[derive(Debug, Clone, Serialize, Deserialize)]
396pub struct BulkGetItem {
397 pub id: String,
398 pub rev: Option<String>,
399}
400
401#[derive(Debug, Clone, Serialize, Deserialize)]
402pub struct BulkGetResponse {
403 pub results: Vec<BulkGetResult>,
404}
405
406#[derive(Debug, Clone, Serialize, Deserialize)]
407pub struct BulkGetResult {
408 pub id: String,
409 pub docs: Vec<BulkGetDoc>,
410}
411
412#[derive(Debug, Clone, Serialize, Deserialize)]
413pub struct BulkGetDoc {
414 pub ok: Option<serde_json::Value>,
415 pub error: Option<BulkGetError>,
416}
417
418#[derive(Debug, Clone, Serialize, Deserialize)]
419pub struct BulkGetError {
420 pub id: String,
421 pub rev: String,
422 pub error: String,
423 pub reason: String,
424}
425
426#[derive(Debug, Clone, Serialize, Deserialize)]
427pub struct RevsDiffResponse {
428 #[serde(flatten)]
429 pub results: HashMap<String, RevsDiffResult>,
430}
431
432#[derive(Debug, Clone, Serialize, Deserialize)]
433pub struct RevsDiffResult {
434 pub missing: Vec<String>,
435 #[serde(default, skip_serializing_if = "Vec::is_empty")]
436 pub possible_ancestors: Vec<String>,
437}
438
439#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
448#[serde(untagged)]
449pub enum Seq {
450 Num(u64),
451 Str(String),
452}
453
454impl Seq {
455 pub fn zero() -> Self {
457 Seq::Num(0)
458 }
459
460 pub fn as_num(&self) -> u64 {
463 match self {
464 Seq::Num(n) => *n,
465 Seq::Str(s) => s
466 .split('-')
467 .next()
468 .and_then(|n| n.parse().ok())
469 .unwrap_or(0),
470 }
471 }
472
473 pub fn to_query_string(&self) -> String {
475 match self {
476 Seq::Num(n) => n.to_string(),
477 Seq::Str(s) => s.clone(),
478 }
479 }
480}
481
482impl Default for Seq {
483 fn default() -> Self {
484 Seq::Num(0)
485 }
486}
487
488impl From<u64> for Seq {
489 fn from(n: u64) -> Self {
490 Seq::Num(n)
491 }
492}
493
494impl std::fmt::Display for Seq {
495 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
496 match self {
497 Seq::Num(n) => write!(f, "{}", n),
498 Seq::Str(s) => write!(f, "{}", s),
499 }
500 }
501}
502
503#[derive(Debug, Clone, Serialize, Deserialize)]
508pub struct PurgeResponse {
509 pub purge_seq: Option<u64>,
510 pub purged: HashMap<String, Vec<String>>,
511}
512
513#[derive(Debug, Clone, Default, Serialize, Deserialize)]
518pub struct SecurityDocument {
519 #[serde(default)]
520 pub admins: SecurityGroup,
521 #[serde(default)]
522 pub members: SecurityGroup,
523}
524
525#[derive(Debug, Clone, Default, Serialize, Deserialize)]
526pub struct SecurityGroup {
527 #[serde(default)]
528 pub names: Vec<String>,
529 #[serde(default)]
530 pub roles: Vec<String>,
531}
532
533#[derive(Debug, Clone, Default)]
538pub struct GetAttachmentOptions {
539 pub rev: Option<String>,
540}
541
542#[cfg(test)]
547mod tests {
548 use super::*;
549
550 #[test]
551 fn revision_display_and_parse() {
552 let rev = Revision::new(3, "abc123".into());
553 assert_eq!(rev.to_string(), "3-abc123");
554
555 let parsed: Revision = "3-abc123".parse().unwrap();
556 assert_eq!(parsed, rev);
557 }
558
559 #[test]
560 fn revision_ordering() {
561 let r1 = Revision::new(1, "aaa".into());
562 let r2 = Revision::new(2, "aaa".into());
563 let r3 = Revision::new(2, "bbb".into());
564 assert!(r1 < r2);
565 assert!(r2 < r3);
566 }
567
568 #[test]
569 fn invalid_revision() {
570 assert!("nope".parse::<Revision>().is_err());
571 assert!("abc-123".parse::<Revision>().is_err());
572 }
573
574 #[test]
575 fn document_from_json_roundtrip() {
576 let json = serde_json::json!({
577 "_id": "doc1",
578 "_rev": "1-abc",
579 "name": "Alice",
580 "age": 30
581 });
582
583 let doc = Document::from_json(json).unwrap();
584 assert_eq!(doc.id, "doc1");
585 assert_eq!(doc.rev.as_ref().unwrap().to_string(), "1-abc");
586 assert_eq!(doc.data["name"], "Alice");
587 assert!(!doc.data.as_object().unwrap().contains_key("_id"));
588
589 let back = doc.to_json();
590 assert_eq!(back["_id"], "doc1");
591 assert_eq!(back["_rev"], "1-abc");
592 assert_eq!(back["name"], "Alice");
593 }
594
595 #[test]
596 fn document_from_json_minimal() {
597 let json = serde_json::json!({"hello": "world"});
598 let doc = Document::from_json(json).unwrap();
599 assert!(doc.id.is_empty());
600 assert!(doc.rev.is_none());
601 assert!(!doc.deleted);
602 }
603
604 #[test]
605 fn bulk_docs_options_defaults() {
606 let opts = BulkDocsOptions::new();
607 assert!(opts.new_edits);
608
609 let repl = BulkDocsOptions::replication();
610 assert!(!repl.new_edits);
611 }
612
613 #[test]
614 fn to_json_deleted_document() {
615 let doc = Document {
616 id: "doc1".into(),
617 rev: Some(Revision::new(2, "def".into())),
618 deleted: true,
619 data: serde_json::json!({}),
620 attachments: HashMap::new(),
621 };
622 let json = doc.to_json();
623 assert_eq!(json["_deleted"], true);
624 assert_eq!(json["_id"], "doc1");
625 assert_eq!(json["_rev"], "2-def");
626 }
627
628 #[test]
629 fn to_json_with_attachments() {
630 let mut attachments = HashMap::new();
631 attachments.insert(
632 "file.txt".into(),
633 AttachmentMeta {
634 content_type: "text/plain".into(),
635 digest: "md5-abc".into(),
636 length: 100,
637 stub: true,
638 data: None,
639 },
640 );
641 let doc = Document {
642 id: "doc1".into(),
643 rev: None,
644 deleted: false,
645 data: serde_json::json!({"key": "val"}),
646 attachments,
647 };
648 let json = doc.to_json();
649 assert!(json["_attachments"]["file.txt"].is_object());
650 assert_eq!(
651 json["_attachments"]["file.txt"]["content_type"],
652 "text/plain"
653 );
654 }
655
656 #[test]
657 fn to_json_non_object_data() {
658 let doc = Document {
659 id: "doc1".into(),
660 rev: None,
661 deleted: false,
662 data: serde_json::json!("just a string"),
663 attachments: HashMap::new(),
664 };
665 let json = doc.to_json();
666 assert_eq!(json["_id"], "doc1");
667 }
668
669 #[test]
670 fn document_from_json_with_deleted_and_attachments() {
671 let json = serde_json::json!({
672 "_id": "doc1",
673 "_rev": "1-abc",
674 "_deleted": true,
675 "_attachments": {
676 "photo.jpg": {
677 "content_type": "image/jpeg",
678 "digest": "md5-xyz",
679 "length": 500,
680 "stub": true
681 }
682 },
683 "name": "test"
684 });
685 let doc = Document::from_json(json).unwrap();
686 assert!(doc.deleted);
687 assert_eq!(doc.attachments.len(), 1);
688 assert_eq!(doc.attachments["photo.jpg"].content_type, "image/jpeg");
689 }
690
691 #[test]
692 fn document_from_json_not_object() {
693 let json = serde_json::json!("just a string");
694 assert!(Document::from_json(json).is_err());
695 }
696
697 #[test]
698 fn seq_str_as_num() {
699 let seq = Seq::Str("42-g1AAAABXeJzLY".into());
700 assert_eq!(seq.as_num(), 42);
701
702 let seq2 = Seq::Str("not-a-number".into());
703 assert_eq!(seq2.as_num(), 0);
704 }
705
706 #[test]
707 fn seq_to_query_string() {
708 assert_eq!(Seq::Num(5).to_query_string(), "5");
709 let opaque = "13-g1AAAABXeJzLY".to_string();
710 assert_eq!(Seq::Str(opaque.clone()).to_query_string(), opaque);
711 }
712
713 #[test]
714 fn seq_display() {
715 assert_eq!(format!("{}", Seq::Num(42)), "42");
716 assert_eq!(format!("{}", Seq::Str("opaque-seq".into())), "opaque-seq");
717 }
718
719 #[test]
720 fn seq_from_u64() {
721 let seq: Seq = 7u64.into();
722 assert_eq!(seq, Seq::Num(7));
723 }
724}