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 attachments: HashMap<String, AttachmentMeta> = obj
123 .remove("_attachments")
124 .map(|v| serde_json::from_value(v).unwrap_or_default())
125 .unwrap_or_default();
126
127 Ok(Document {
128 id,
129 rev,
130 deleted,
131 data: value,
132 attachments,
133 })
134 }
135
136 pub fn to_json(&self) -> serde_json::Value {
138 let mut obj = match &self.data {
139 serde_json::Value::Object(m) => m.clone(),
140 _ => serde_json::Map::new(),
141 };
142
143 obj.insert("_id".into(), serde_json::Value::String(self.id.clone()));
144
145 if let Some(rev) = &self.rev {
146 obj.insert("_rev".into(), serde_json::Value::String(rev.to_string()));
147 }
148
149 if self.deleted {
150 obj.insert("_deleted".into(), serde_json::Value::Bool(true));
151 }
152
153 if !self.attachments.is_empty() {
154 obj.insert(
155 "_attachments".into(),
156 serde_json::to_value(&self.attachments).unwrap(),
157 );
158 }
159
160 serde_json::Value::Object(obj)
161 }
162}
163
164#[derive(Debug, Clone)]
170pub struct DocMetadata {
171 pub id: String,
172 pub rev_tree: RevTree,
173 pub seq: u64,
174}
175
176#[derive(Debug, Clone, Default)]
181pub struct GetOptions {
182 pub rev: Option<String>,
184 pub conflicts: bool,
186 pub open_revs: Option<OpenRevs>,
188 pub revs: bool,
190}
191
192#[derive(Debug, Clone)]
193pub enum OpenRevs {
194 All,
195 Specific(Vec<String>),
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
199pub struct PutResponse {
200 pub ok: bool,
201 pub id: String,
202 pub rev: String,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
206pub struct DocResult {
207 pub ok: bool,
208 pub id: String,
209 pub rev: Option<String>,
210 pub error: Option<String>,
211 pub reason: Option<String>,
212}
213
214#[derive(Debug, Clone, Default)]
215pub struct BulkDocsOptions {
216 pub new_edits: bool,
219}
220
221impl BulkDocsOptions {
222 pub fn new() -> Self {
223 Self { new_edits: true }
224 }
225
226 pub fn replication() -> Self {
227 Self { new_edits: false }
228 }
229}
230
231#[derive(Debug, Clone, Default)]
232pub struct AllDocsOptions {
233 pub start_key: Option<String>,
234 pub end_key: Option<String>,
235 pub key: Option<String>,
236 pub keys: Option<Vec<String>>,
237 pub include_docs: bool,
238 pub descending: bool,
239 pub skip: u64,
240 pub limit: Option<u64>,
241 pub inclusive_end: bool,
242}
243
244impl AllDocsOptions {
245 pub fn new() -> Self {
246 Self {
247 inclusive_end: true,
248 ..Default::default()
249 }
250 }
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize)]
254pub struct AllDocsRow {
255 pub id: String,
256 pub key: String,
257 pub value: AllDocsRowValue,
258 #[serde(skip_serializing_if = "Option::is_none")]
259 pub doc: Option<serde_json::Value>,
260}
261
262#[derive(Debug, Clone, Serialize, Deserialize)]
263pub struct AllDocsRowValue {
264 pub rev: String,
265 #[serde(skip_serializing_if = "Option::is_none")]
266 pub deleted: Option<bool>,
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize)]
270pub struct AllDocsResponse {
271 pub total_rows: u64,
272 pub offset: u64,
273 pub rows: Vec<AllDocsRow>,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
277pub struct DbInfo {
278 pub db_name: String,
279 pub doc_count: u64,
280 pub update_seq: Seq,
281}
282
283#[derive(Debug, Clone, Default)]
288pub struct ChangesOptions {
289 pub since: Seq,
290 pub limit: Option<u64>,
291 pub descending: bool,
292 pub include_docs: bool,
293 pub live: bool,
294 pub doc_ids: Option<Vec<String>>,
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize)]
298pub struct ChangeEvent {
299 pub seq: Seq,
300 pub id: String,
301 pub changes: Vec<ChangeRev>,
302 #[serde(default)]
303 pub deleted: bool,
304 #[serde(skip_serializing_if = "Option::is_none")]
305 pub doc: Option<serde_json::Value>,
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize)]
309pub struct ChangeRev {
310 pub rev: String,
311}
312
313#[derive(Debug, Clone, Serialize, Deserialize)]
314pub struct ChangesResponse {
315 pub results: Vec<ChangeEvent>,
316 pub last_seq: Seq,
317}
318
319#[derive(Debug, Clone, Serialize, Deserialize)]
324pub struct BulkGetItem {
325 pub id: String,
326 pub rev: Option<String>,
327}
328
329#[derive(Debug, Clone, Serialize, Deserialize)]
330pub struct BulkGetResponse {
331 pub results: Vec<BulkGetResult>,
332}
333
334#[derive(Debug, Clone, Serialize, Deserialize)]
335pub struct BulkGetResult {
336 pub id: String,
337 pub docs: Vec<BulkGetDoc>,
338}
339
340#[derive(Debug, Clone, Serialize, Deserialize)]
341pub struct BulkGetDoc {
342 pub ok: Option<serde_json::Value>,
343 pub error: Option<BulkGetError>,
344}
345
346#[derive(Debug, Clone, Serialize, Deserialize)]
347pub struct BulkGetError {
348 pub id: String,
349 pub rev: String,
350 pub error: String,
351 pub reason: String,
352}
353
354#[derive(Debug, Clone, Serialize, Deserialize)]
355pub struct RevsDiffResponse {
356 #[serde(flatten)]
357 pub results: HashMap<String, RevsDiffResult>,
358}
359
360#[derive(Debug, Clone, Serialize, Deserialize)]
361pub struct RevsDiffResult {
362 pub missing: Vec<String>,
363 #[serde(default, skip_serializing_if = "Vec::is_empty")]
364 pub possible_ancestors: Vec<String>,
365}
366
367#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
376#[serde(untagged)]
377pub enum Seq {
378 Num(u64),
379 Str(String),
380}
381
382impl Seq {
383 pub fn zero() -> Self {
385 Seq::Num(0)
386 }
387
388 pub fn as_num(&self) -> u64 {
391 match self {
392 Seq::Num(n) => *n,
393 Seq::Str(s) => s
394 .split('-')
395 .next()
396 .and_then(|n| n.parse().ok())
397 .unwrap_or(0),
398 }
399 }
400
401 pub fn to_query_string(&self) -> String {
403 match self {
404 Seq::Num(n) => n.to_string(),
405 Seq::Str(s) => s.clone(),
406 }
407 }
408}
409
410impl Default for Seq {
411 fn default() -> Self {
412 Seq::Num(0)
413 }
414}
415
416impl From<u64> for Seq {
417 fn from(n: u64) -> Self {
418 Seq::Num(n)
419 }
420}
421
422impl std::fmt::Display for Seq {
423 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
424 match self {
425 Seq::Num(n) => write!(f, "{}", n),
426 Seq::Str(s) => write!(f, "{}", s),
427 }
428 }
429}
430
431#[derive(Debug, Clone, Default)]
436pub struct GetAttachmentOptions {
437 pub rev: Option<String>,
438}
439
440#[cfg(test)]
445mod tests {
446 use super::*;
447
448 #[test]
449 fn revision_display_and_parse() {
450 let rev = Revision::new(3, "abc123".into());
451 assert_eq!(rev.to_string(), "3-abc123");
452
453 let parsed: Revision = "3-abc123".parse().unwrap();
454 assert_eq!(parsed, rev);
455 }
456
457 #[test]
458 fn revision_ordering() {
459 let r1 = Revision::new(1, "aaa".into());
460 let r2 = Revision::new(2, "aaa".into());
461 let r3 = Revision::new(2, "bbb".into());
462 assert!(r1 < r2);
463 assert!(r2 < r3);
464 }
465
466 #[test]
467 fn invalid_revision() {
468 assert!("nope".parse::<Revision>().is_err());
469 assert!("abc-123".parse::<Revision>().is_err());
470 }
471
472 #[test]
473 fn document_from_json_roundtrip() {
474 let json = serde_json::json!({
475 "_id": "doc1",
476 "_rev": "1-abc",
477 "name": "Alice",
478 "age": 30
479 });
480
481 let doc = Document::from_json(json).unwrap();
482 assert_eq!(doc.id, "doc1");
483 assert_eq!(doc.rev.as_ref().unwrap().to_string(), "1-abc");
484 assert_eq!(doc.data["name"], "Alice");
485 assert!(!doc.data.as_object().unwrap().contains_key("_id"));
486
487 let back = doc.to_json();
488 assert_eq!(back["_id"], "doc1");
489 assert_eq!(back["_rev"], "1-abc");
490 assert_eq!(back["name"], "Alice");
491 }
492
493 #[test]
494 fn document_from_json_minimal() {
495 let json = serde_json::json!({"hello": "world"});
496 let doc = Document::from_json(json).unwrap();
497 assert!(doc.id.is_empty());
498 assert!(doc.rev.is_none());
499 assert!(!doc.deleted);
500 }
501
502 #[test]
503 fn bulk_docs_options_defaults() {
504 let opts = BulkDocsOptions::new();
505 assert!(opts.new_edits);
506
507 let repl = BulkDocsOptions::replication();
508 assert!(!repl.new_edits);
509 }
510
511 #[test]
512 fn to_json_deleted_document() {
513 let doc = Document {
514 id: "doc1".into(),
515 rev: Some(Revision::new(2, "def".into())),
516 deleted: true,
517 data: serde_json::json!({}),
518 attachments: HashMap::new(),
519 };
520 let json = doc.to_json();
521 assert_eq!(json["_deleted"], true);
522 assert_eq!(json["_id"], "doc1");
523 assert_eq!(json["_rev"], "2-def");
524 }
525
526 #[test]
527 fn to_json_with_attachments() {
528 let mut attachments = HashMap::new();
529 attachments.insert("file.txt".into(), AttachmentMeta {
530 content_type: "text/plain".into(),
531 digest: "md5-abc".into(),
532 length: 100,
533 stub: true,
534 data: None,
535 });
536 let doc = Document {
537 id: "doc1".into(),
538 rev: None,
539 deleted: false,
540 data: serde_json::json!({"key": "val"}),
541 attachments,
542 };
543 let json = doc.to_json();
544 assert!(json["_attachments"]["file.txt"].is_object());
545 assert_eq!(json["_attachments"]["file.txt"]["content_type"], "text/plain");
546 }
547
548 #[test]
549 fn to_json_non_object_data() {
550 let doc = Document {
551 id: "doc1".into(),
552 rev: None,
553 deleted: false,
554 data: serde_json::json!("just a string"),
555 attachments: HashMap::new(),
556 };
557 let json = doc.to_json();
558 assert_eq!(json["_id"], "doc1");
559 }
560
561 #[test]
562 fn document_from_json_with_deleted_and_attachments() {
563 let json = serde_json::json!({
564 "_id": "doc1",
565 "_rev": "1-abc",
566 "_deleted": true,
567 "_attachments": {
568 "photo.jpg": {
569 "content_type": "image/jpeg",
570 "digest": "md5-xyz",
571 "length": 500,
572 "stub": true
573 }
574 },
575 "name": "test"
576 });
577 let doc = Document::from_json(json).unwrap();
578 assert!(doc.deleted);
579 assert_eq!(doc.attachments.len(), 1);
580 assert_eq!(doc.attachments["photo.jpg"].content_type, "image/jpeg");
581 }
582
583 #[test]
584 fn document_from_json_not_object() {
585 let json = serde_json::json!("just a string");
586 assert!(Document::from_json(json).is_err());
587 }
588
589 #[test]
590 fn seq_str_as_num() {
591 let seq = Seq::Str("42-g1AAAABXeJzLY".into());
592 assert_eq!(seq.as_num(), 42);
593
594 let seq2 = Seq::Str("not-a-number".into());
595 assert_eq!(seq2.as_num(), 0);
596 }
597
598 #[test]
599 fn seq_to_query_string() {
600 assert_eq!(Seq::Num(5).to_query_string(), "5");
601 let opaque = "13-g1AAAABXeJzLY".to_string();
602 assert_eq!(Seq::Str(opaque.clone()).to_query_string(), opaque);
603 }
604
605 #[test]
606 fn seq_display() {
607 assert_eq!(format!("{}", Seq::Num(42)), "42");
608 assert_eq!(format!("{}", Seq::Str("opaque-seq".into())), "opaque-seq");
609 }
610
611 #[test]
612 fn seq_from_u64() {
613 let seq: Seq = 7u64.into();
614 assert_eq!(seq, Seq::Num(7));
615 }
616}