Skip to main content

rouchdb_views/
lib.rs

1/// Design documents and persistent views for RouchDB.
2///
3/// Provides `DesignDocument` types that model CouchDB design documents,
4/// plus a `ViewEngine` for building and querying persistent views using
5/// Rust closures as map/reduce functions.
6mod design_doc;
7mod engine;
8
9pub use design_doc::{DesignDocument, ViewDef};
10pub use engine::{PersistentViewIndex, ViewEngine};
11
12#[cfg(test)]
13mod tests {
14    use super::*;
15    use rouchdb_adapter_memory::MemoryAdapter;
16    use rouchdb_core::adapter::Adapter;
17    use rouchdb_core::document::{BulkDocsOptions, Document};
18    use std::collections::HashMap;
19
20    async fn setup_db() -> MemoryAdapter {
21        let db = MemoryAdapter::new("test");
22        let docs = vec![
23            Document {
24                id: "_design/myapp".into(),
25                rev: None,
26                deleted: false,
27                data: serde_json::json!({
28                    "views": {
29                        "by_type": {
30                            "map": "function(doc) { emit(doc.type, 1); }"
31                        }
32                    },
33                    "filters": {
34                        "users_only": "function(doc) { return doc.type === 'user'; }"
35                    }
36                }),
37                attachments: HashMap::new(),
38            },
39            Document {
40                id: "alice".into(),
41                rev: None,
42                deleted: false,
43                data: serde_json::json!({"type": "user", "name": "Alice"}),
44                attachments: HashMap::new(),
45            },
46            Document {
47                id: "bob".into(),
48                rev: None,
49                deleted: false,
50                data: serde_json::json!({"type": "user", "name": "Bob"}),
51                attachments: HashMap::new(),
52            },
53            Document {
54                id: "order1".into(),
55                rev: None,
56                deleted: false,
57                data: serde_json::json!({"type": "order", "total": 50}),
58                attachments: HashMap::new(),
59            },
60        ];
61        db.bulk_docs(docs, BulkDocsOptions::new()).await.unwrap();
62        db
63    }
64
65    #[tokio::test]
66    async fn design_document_roundtrip() {
67        let db = setup_db().await;
68        let doc = db.get("_design/myapp", Default::default()).await.unwrap();
69        let json = doc.to_json();
70        let ddoc = DesignDocument::from_json(json).unwrap();
71        assert_eq!(ddoc.id, "_design/myapp");
72        assert!(ddoc.views.contains_key("by_type"));
73        assert!(ddoc.filters.contains_key("users_only"));
74
75        // Convert back to JSON
76        let back = ddoc.to_json();
77        assert_eq!(back["_id"], "_design/myapp");
78        assert!(back["views"]["by_type"]["map"].is_string());
79    }
80
81    #[tokio::test]
82    async fn view_engine_with_rust_map() {
83        let db = setup_db().await;
84        let mut engine = ViewEngine::new();
85
86        // Register a Rust-based map function
87        engine.register_map("myapp", "by_type", |doc| {
88            let doc_type = doc.get("type").and_then(|v| v.as_str());
89            if let Some(t) = doc_type {
90                vec![(serde_json::json!(t), serde_json::json!(1))]
91            } else {
92                vec![]
93            }
94        });
95
96        // Build index
97        engine.update_index(&db, "myapp", "by_type").await.unwrap();
98
99        // Query
100        let index = engine.get_index("myapp", "by_type").unwrap();
101        assert_eq!(index.entries.len(), 3); // alice, bob, order1 (not the design doc)
102    }
103}