Skip to main content

embeddenator_interop/
lib.rs

1//! # embeddenator-interop
2//!
3//! Interoperability layer for Embeddenator: format conversions, FFI, and language bindings.
4//!
5//! Extracted from embeddenator core as part of Phase 2A component decomposition.
6//!
7//! ## Features
8//!
9//! - **Format Conversions**: Convert between JSON, bincode, and text formats
10//! - **FFI Bindings**: C-compatible interface for cross-language integration
11//! - **Python Bindings**: PyO3-based Python API (optional, enable `python` feature)
12//! - **Adapters**: High-level adapters for common integration patterns
13//! - **Kernel Interop**: Backend-agnostic VSA operations for runtime integration
14//!
15//! ## Modules
16//!
17//! - [`formats`]: Format conversion utilities
18//! - [`ffi`]: C FFI bindings (unsafe but documented)
19//! - [`bindings`]: Python bindings (requires `python` feature)
20//! - [`adapters`]: High-level adapter layers
21//! - [`kernel_interop`]: Kernel/runtime interop abstractions
22//!
23//! ## Examples
24//!
25//! ### Format Conversion
26//! ```
27//! use embeddenator_interop::formats::{sparse_vec_to_format, OutputFormat};
28//! use embeddenator_vsa::SparseVec;
29//!
30//! let vec = SparseVec::new();
31//! let json_bytes = sparse_vec_to_format(&vec, OutputFormat::Json).unwrap();
32//! let text_bytes = sparse_vec_to_format(&vec, OutputFormat::Text).unwrap();
33//! ```
34//!
35//! ### File Operations
36//! ```no_run
37//! use embeddenator_interop::adapters::FileAdapter;
38//! use embeddenator_vsa::SparseVec;
39//! use embeddenator_io::CompressionCodec;
40//!
41//! let vec = SparseVec::new();
42//! FileAdapter::save_sparse_vec("vector.bin", &vec).unwrap();
43//! let loaded = FileAdapter::load_sparse_vec("vector.bin").unwrap();
44//! ```
45
46pub mod adapters;
47pub mod ffi;
48pub mod formats;
49pub mod kernel_interop;
50
51#[cfg(feature = "python")]
52pub mod bindings;
53
54// Re-export kernel interop types
55pub use kernel_interop::*;
56
57// Re-export format types
58pub use formats::{FormatError, OutputFormat};
59
60// Re-export adapter types
61pub use adapters::{AutoFormatAdapter, BatchAdapter, EnvelopeAdapter, FileAdapter, StreamAdapter};
62
63// Convenience functions for JSON export/import
64pub fn export_to_json(vec: &embeddenator_vsa::SparseVec) -> Result<String, FormatError> {
65    let bytes = formats::sparse_vec_to_format(vec, OutputFormat::Json)?;
66    String::from_utf8(bytes).map_err(|e| FormatError::SerializationFailed(e.to_string()))
67}
68
69pub fn import_from_json(json: &str) -> Result<embeddenator_vsa::SparseVec, FormatError> {
70    formats::sparse_vec_from_format(json.as_bytes(), OutputFormat::Json)
71}
72
73/// Handle-based interface for C FFI compatibility
74pub struct VSAHandle {
75    vec: embeddenator_vsa::SparseVec,
76}
77
78impl VSAHandle {
79    pub fn from_sparse_vec(vec: embeddenator_vsa::SparseVec) -> Self {
80        Self { vec }
81    }
82
83    pub fn to_sparse_vec(&self) -> embeddenator_vsa::SparseVec {
84        self.vec.clone()
85    }
86
87    pub fn dimensions(&self) -> (usize, usize) {
88        let nnz = self.vec.pos.len() + self.vec.neg.len();
89        (embeddenator_vsa::DIM, nnz)
90    }
91}
92
93#[cfg(test)]
94mod integration_tests {
95    use super::*;
96    use adapters::BatchAdapter;
97    use embeddenator_vsa::{ReversibleVSAConfig, SparseVec};
98    use formats::{sparse_vec_from_format, sparse_vec_to_format};
99
100    #[test]
101    fn test_format_roundtrip() {
102        let vec = SparseVec {
103            pos: vec![1, 2, 3],
104            neg: vec![4, 5],
105        };
106
107        // JSON round-trip
108        let json = sparse_vec_to_format(&vec, OutputFormat::Json).unwrap();
109        let from_json = sparse_vec_from_format(&json, OutputFormat::Json).unwrap();
110        assert_eq!(vec.pos, from_json.pos);
111
112        // Bincode round-trip
113        let bincode = sparse_vec_to_format(&vec, OutputFormat::Bincode).unwrap();
114        let from_bincode = sparse_vec_from_format(&bincode, OutputFormat::Bincode).unwrap();
115        assert_eq!(vec.pos, from_bincode.pos);
116    }
117
118    #[test]
119    fn test_batch_operations() {
120        let config = ReversibleVSAConfig::default();
121        let data = vec![b"test1".as_slice(), b"test2".as_slice()];
122
123        let vectors = BatchAdapter::batch_encode(&data, &config);
124        assert_eq!(vectors.len(), 2);
125
126        let decoded = BatchAdapter::batch_decode(&vectors, &config, 5);
127        assert_eq!(decoded.len(), 2);
128    }
129
130    #[test]
131    fn test_kernel_backend() {
132        let backend = SparseVecBackend;
133        let config = ReversibleVSAConfig::default();
134
135        let data = b"test data";
136        let vec = backend.encode_data(data, &config, None);
137        let decoded = backend.decode_data(&vec, &config, None, data.len());
138
139        // VSA encoding is lossy, so we just verify the decoded data has the correct length
140        assert_eq!(data.len(), decoded.len());
141
142        // Verify operations work
143        let vec2 = backend.encode_data(b"other", &config, None);
144        let _bundled = backend.bundle(&vec, &vec2);
145        let _bound = backend.bind(&vec, &vec2);
146        let similarity = backend.cosine(&vec, &vec2);
147
148        assert!((0.0..=1.0).contains(&similarity));
149    }
150}