Skip to main content

jacs_binding_core/
doc_wrapper.rs

1//! `DocumentServiceWrapper` — JSON-in/JSON-out adapter for the unified Document API.
2//!
3//! This module wraps `dyn DocumentService` with FFI-safe methods that
4//! accept and return JSON strings.  Zero business logic — pure marshaling.
5
6use std::sync::Arc;
7
8use crate::{BindingCoreError, BindingResult};
9use jacs::document::DocumentService;
10use jacs::document::types::{CreateOptions, ListFilter, UpdateOptions};
11
12/// Thread-safe, Clone-able FFI wrapper for the unified Document API.
13///
14/// All methods accept JSON strings and return JSON strings, making them
15/// suitable for consumption from Python, Node.js, Go, and other FFI callers.
16#[derive(Clone)]
17pub struct DocumentServiceWrapper {
18    inner: Arc<dyn DocumentService>,
19}
20
21// Compile-time proof of thread safety.
22const _: () = {
23    fn _assert<T: Send + Sync>() {}
24    let _ = _assert::<DocumentServiceWrapper>;
25};
26
27impl DocumentServiceWrapper {
28    /// Create a wrapper from a boxed `DocumentService`.
29    pub fn new(service: Box<dyn DocumentService>) -> Self {
30        Self {
31            inner: Arc::from(service),
32        }
33    }
34
35    /// Create a wrapper from an `Arc<dyn DocumentService>`.
36    pub fn from_arc(service: Arc<dyn DocumentService>) -> Self {
37        Self { inner: service }
38    }
39
40    /// Create a wrapper from an `AgentWrapper` using the filesystem backend.
41    ///
42    /// This is the typical construction path for language bindings:
43    /// load an agent, then create a document service from it.
44    pub fn from_agent_wrapper(wrapper: &crate::AgentWrapper) -> BindingResult<Self> {
45        let agent_arc = wrapper.inner_arc();
46        let service = jacs::document::service_from_agent(agent_arc).map_err(|e| {
47            BindingCoreError::document_failed(format!("Failed to resolve document service: {}", e))
48        })?;
49        Ok(Self::from_arc(service))
50    }
51
52    // =========================================================================
53    // CRUD — JSON-in/JSON-out
54    // =========================================================================
55
56    /// Create a new document. Returns the signed document as JSON.
57    ///
58    /// `options_json` is an optional JSON string of `CreateOptions`.
59    /// If `None`, defaults are used.
60    pub fn create_json(&self, json: &str, options_json: Option<&str>) -> BindingResult<String> {
61        let options: CreateOptions = match options_json {
62            Some(opts) => serde_json::from_str(opts).map_err(|e| {
63                BindingCoreError::invalid_argument(format!("Invalid CreateOptions JSON: {}", e))
64            })?,
65            None => CreateOptions::default(),
66        };
67
68        let doc = self.inner.create(json, options).map_err(|e| {
69            BindingCoreError::document_failed(format!("Document create failed: {}", e))
70        })?;
71
72        serde_json::to_string(&doc.value).map_err(|e| {
73            BindingCoreError::serialization_failed(format!(
74                "Failed to serialize created document: {}",
75                e
76            ))
77        })
78    }
79
80    /// Get a document by key (`id:version`). Returns the document JSON.
81    pub fn get_json(&self, key: &str) -> BindingResult<String> {
82        let doc = self.inner.get(key).map_err(|e| {
83            BindingCoreError::document_failed(format!("Failed to get document '{}': {}", key, e))
84        })?;
85
86        serde_json::to_string(&doc.value).map_err(|e| {
87            BindingCoreError::serialization_failed(format!(
88                "Failed to serialize document '{}': {}",
89                key, e
90            ))
91        })
92    }
93
94    /// Get the latest version of a document. Returns the document JSON.
95    pub fn get_latest_json(&self, document_id: &str) -> BindingResult<String> {
96        let doc = self.inner.get_latest(document_id).map_err(|e| {
97            BindingCoreError::document_failed(format!(
98                "Failed to get latest version of '{}': {}",
99                document_id, e
100            ))
101        })?;
102
103        serde_json::to_string(&doc.value).map_err(|e| {
104            BindingCoreError::serialization_failed(format!(
105                "Failed to serialize document '{}': {}",
106                document_id, e
107            ))
108        })
109    }
110
111    /// Update a document, creating a new signed version. Returns the new version JSON.
112    ///
113    /// `options_json` is an optional JSON string of `UpdateOptions`.
114    pub fn update_json(
115        &self,
116        document_id: &str,
117        new_json: &str,
118        options_json: Option<&str>,
119    ) -> BindingResult<String> {
120        let options: UpdateOptions = match options_json {
121            Some(opts) => serde_json::from_str(opts).map_err(|e| {
122                BindingCoreError::invalid_argument(format!("Invalid UpdateOptions JSON: {}", e))
123            })?,
124            None => UpdateOptions::default(),
125        };
126
127        let doc = self
128            .inner
129            .update(document_id, new_json, options)
130            .map_err(|e| {
131                BindingCoreError::document_failed(format!(
132                    "Failed to update document '{}': {}",
133                    document_id, e
134                ))
135            })?;
136
137        serde_json::to_string(&doc.value).map_err(|e| {
138            BindingCoreError::serialization_failed(format!(
139                "Failed to serialize updated document: {}",
140                e
141            ))
142        })
143    }
144
145    /// Remove (tombstone) a document. Returns the tombstoned document JSON.
146    pub fn remove_json(&self, key: &str) -> BindingResult<String> {
147        let doc = self.inner.remove(key).map_err(|e| {
148            BindingCoreError::document_failed(format!("Failed to remove document '{}': {}", key, e))
149        })?;
150
151        serde_json::to_string(&doc.value).map_err(|e| {
152            BindingCoreError::serialization_failed(format!(
153                "Failed to serialize removed document: {}",
154                e
155            ))
156        })
157    }
158
159    /// List documents with optional filter. Returns JSON array of `DocumentSummary`.
160    ///
161    /// `filter_json` is an optional JSON string of `ListFilter`.
162    pub fn list_json(&self, filter_json: Option<&str>) -> BindingResult<String> {
163        let filter: ListFilter = match filter_json {
164            Some(f) => serde_json::from_str(f).map_err(|e| {
165                BindingCoreError::invalid_argument(format!("Invalid ListFilter JSON: {}", e))
166            })?,
167            None => ListFilter::default(),
168        };
169
170        let summaries = self.inner.list(filter).map_err(|e| {
171            BindingCoreError::document_failed(format!("Failed to list documents: {}", e))
172        })?;
173
174        serde_json::to_string(&summaries).map_err(|e| {
175            BindingCoreError::serialization_failed(format!(
176                "Failed to serialize document list: {}",
177                e
178            ))
179        })
180    }
181
182    // =========================================================================
183    // Search
184    // =========================================================================
185
186    /// Search documents. Returns JSON `SearchResults`.
187    ///
188    /// `query_json` is a JSON string of `SearchQuery`.
189    pub fn search_json(&self, query_json: &str) -> BindingResult<String> {
190        let query: jacs::search::SearchQuery = serde_json::from_str(query_json).map_err(|e| {
191            BindingCoreError::invalid_argument(format!("Invalid SearchQuery JSON: {}", e))
192        })?;
193
194        let results = self.inner.search(query).map_err(|e| {
195            BindingCoreError::document_failed(format!("Document search failed: {}", e))
196        })?;
197
198        serde_json::to_string(&results).map_err(|e| {
199            BindingCoreError::serialization_failed(format!(
200                "Failed to serialize search results: {}",
201                e
202            ))
203        })
204    }
205
206    // =========================================================================
207    // Versions
208    // =========================================================================
209
210    /// Get all versions of a document. Returns JSON array of documents.
211    pub fn versions_json(&self, document_id: &str) -> BindingResult<String> {
212        let docs = self.inner.versions(document_id).map_err(|e| {
213            BindingCoreError::document_failed(format!(
214                "Failed to get versions of '{}': {}",
215                document_id, e
216            ))
217        })?;
218
219        let values: Vec<_> = docs.iter().map(|d| &d.value).collect();
220        serde_json::to_string(&values).map_err(|e| {
221            BindingCoreError::serialization_failed(format!(
222                "Failed to serialize document versions: {}",
223                e
224            ))
225        })
226    }
227
228    /// Diff two document versions. Returns JSON `DocumentDiff`.
229    pub fn diff_json(&self, key_a: &str, key_b: &str) -> BindingResult<String> {
230        let diff = self.inner.diff(key_a, key_b).map_err(|e| {
231            BindingCoreError::document_failed(format!(
232                "Failed to diff '{}' and '{}': {}",
233                key_a, key_b, e
234            ))
235        })?;
236
237        serde_json::to_string(&diff).map_err(|e| {
238            BindingCoreError::serialization_failed(format!("Failed to serialize diff: {}", e))
239        })
240    }
241
242    // =========================================================================
243    // Visibility
244    // =========================================================================
245
246    /// Get the visibility of a document. Returns JSON string (`"public"`, `"private"`, etc.).
247    pub fn visibility_json(&self, key: &str) -> BindingResult<String> {
248        let vis = self.inner.visibility(key).map_err(|e| {
249            BindingCoreError::document_failed(format!(
250                "Failed to get visibility of '{}': {}",
251                key, e
252            ))
253        })?;
254
255        serde_json::to_string(&vis).map_err(|e| {
256            BindingCoreError::serialization_failed(format!("Failed to serialize visibility: {}", e))
257        })
258    }
259
260    /// Set the visibility of a document.
261    ///
262    /// `visibility_json` is a JSON string (e.g., `"public"`, `"private"`,
263    /// `{"restricted":["agent-a"]}`).
264    pub fn set_visibility_json(&self, key: &str, visibility_json: &str) -> BindingResult<()> {
265        let vis: jacs::document::DocumentVisibility = serde_json::from_str(visibility_json)
266            .map_err(|e| {
267                BindingCoreError::invalid_argument(format!(
268                    "Invalid DocumentVisibility JSON: {}",
269                    e
270                ))
271            })?;
272
273        self.inner.set_visibility(key, vis).map_err(|e| {
274            BindingCoreError::document_failed(format!(
275                "Failed to set visibility on '{}': {}",
276                key, e
277            ))
278        })
279    }
280}