helia_dag_json/
lib.rs

1//! # Helia DAG-JSON
2//!
3//! DAG-JSON support for Helia, providing JSON serialization with content addressing
4//! for structured data compatible with the IPFS DAG-JSON specification.
5//!
6//! ## Overview
7//!
8//! This module provides JSON encoding and decoding for IPLD (InterPlanetary Linked Data) with
9//! content addressing. DAG-JSON is useful when you need:
10//!
11//! - **Human-readable storage**: JSON format is easy to debug and inspect
12//! - **Web compatibility**: JSON works seamlessly with browsers and web APIs
13//! - **Interoperability**: Compatible with existing JSON-based systems
14//! - **Text-based data**: Better for configuration files and metadata
15//! - **IPFS compatibility**: Works with go-ipfs and js-ipfs DAG-JSON implementations
16//!
17//! ## Core Concepts
18//!
19//! ### Content Addressing
20//! Each JSON object is serialized and stored with a unique Content Identifier (CID):
21//! - CID is deterministic: same input always produces the same CID
22//! - CID includes codec identifier (0x0129 for DAG-JSON)
23//! - CID provides integrity verification (content tampering is detectable)
24//!
25//! ### DAG-JSON vs DAG-CBOR
26//! | Feature | DAG-JSON | DAG-CBOR |
27//! |---------|----------|----------|
28//! | Format | Text-based | Binary |
29//! | Size | Larger (verbose) | Smaller (compact) |
30//! | Readability | High | Low |
31//! | Parsing Speed | Slower | Faster |
32//! | Use Case | Web, debugging | Performance, storage efficiency |
33//!
34//! Choose DAG-JSON when readability and web compatibility matter more than size.
35//!
36//! ## Usage Examples
37//!
38//! ### Example 1: Basic Object Storage
39//! ```no_run
40//! use rust_helia::create_helia_default;
41//! use helia_dag_json::{DagJson, DagJsonInterface};
42//! use serde::{Deserialize, Serialize};
43//! use std::sync::Arc;
44//!
45//! #[derive(Serialize, Deserialize, PartialEq, Debug)]
46//! struct Person {
47//!     name: String,
48//!     age: u32,
49//!     email: String,
50//! }
51//!
52//! #[tokio::main]
53//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
54//!     let helia = create_helia_default().await?;
55//!     let dag_json = DagJson::new(Arc::new(helia));
56//!     
57//!     let person = Person {
58//!         name: "Alice".to_string(),
59//!         age: 30,
60//!         email: "alice@example.com".to_string(),
61//!     };
62//!     
63//!     // Store the person object
64//!     let cid = dag_json.add(&person, None).await?;
65//!     println!("Stored person at: {}", cid);
66//!     
67//!     // Retrieve the person object
68//!     let retrieved: Person = dag_json.get(&cid, None).await?;
69//!     assert_eq!(person.name, retrieved.name);
70//!     
71//!     Ok(())
72//! }
73//! ```
74//!
75//! ### Example 2: Nested Structures (Configuration Files)
76//! ```no_run
77//! use rust_helia::create_helia_default;
78//! use helia_dag_json::{DagJson, DagJsonInterface};
79//! use serde::{Deserialize, Serialize};
80//! use std::collections::HashMap;
81//! use std::sync::Arc;
82//!
83//! #[derive(Serialize, Deserialize, Debug)]
84//! struct AppConfig {
85//!     version: String,
86//!     features: HashMap<String, bool>,
87//!     endpoints: Vec<String>,
88//! }
89//!
90//! #[tokio::main]
91//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
92//!     let helia = create_helia_default().await?;
93//!     let dag_json = DagJson::new(Arc::new(helia));
94//!     
95//!     let mut features = HashMap::new();
96//!     features.insert("authentication".to_string(), true);
97//!     features.insert("caching".to_string(), false);
98//!     
99//!     let config = AppConfig {
100//!         version: "1.0.0".to_string(),
101//!         features,
102//!         endpoints: vec![
103//!             "https://api.example.com".to_string(),
104//!             "https://backup.example.com".to_string(),
105//!         ],
106//!     };
107//!     
108//!     // Store configuration
109//!     let cid = dag_json.add(&config, None).await?;
110//!     
111//!     // Later: retrieve and use configuration
112//!     let loaded_config: AppConfig = dag_json.get(&cid, None).await?;
113//!     println!("Loaded version: {}", loaded_config.version);
114//!     
115//!     Ok(())
116//! }
117//! ```
118//!
119//! ### Example 3: Pinning Data
120//! ```no_run
121//! use rust_helia::create_helia_default;
122//! use helia_dag_json::{DagJson, DagJsonInterface, AddOptions};
123//! use serde::{Deserialize, Serialize};
124//! use std::sync::Arc;
125//!
126//! #[derive(Serialize, Deserialize, Debug)]
127//! struct ImportantData {
128//!     id: u64,
129//!     content: String,
130//! }
131//!
132//! #[tokio::main]
133//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
134//!     let helia = create_helia_default().await?;
135//!     let dag_json = DagJson::new(Arc::new(helia));
136//!     
137//!     let data = ImportantData {
138//!         id: 12345,
139//!         content: "Critical system data".to_string(),
140//!     };
141//!     
142//!     // Pin to prevent garbage collection
143//!     let options = AddOptions {
144//!         pin: true,
145//!         ..Default::default()
146//!     };
147//!     
148//!     let cid = dag_json.add(&data, Some(options)).await?;
149//!     println!("Pinned data at: {}", cid);
150//!     
151//!     Ok(())
152//! }
153//! ```
154//!
155//! ### Example 4: Primitive Types and Collections
156//! ```no_run
157//! use rust_helia::create_helia_default;
158//! use helia_dag_json::{DagJson, DagJsonInterface};
159//! use std::collections::HashMap;
160//! use std::sync::Arc;
161//!
162//! #[tokio::main]
163//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
164//!     let helia = create_helia_default().await?;
165//!     let dag_json = DagJson::new(Arc::new(helia));
166//!     
167//!     // Store a string
168//!     let text = "Hello, IPFS!".to_string();
169//!     let text_cid = dag_json.add(&text, None).await?;
170//!     
171//!     // Store an array
172//!     let numbers = vec![1, 2, 3, 4, 5];
173//!     let array_cid = dag_json.add(&numbers, None).await?;
174//!     
175//!     // Store a map
176//!     let mut metadata = HashMap::new();
177//!     metadata.insert("author".to_string(), "Alice".to_string());
178//!     metadata.insert("version".to_string(), "1.0".to_string());
179//!     let map_cid = dag_json.add(&metadata, None).await?;
180//!     
181//!     // Retrieve them
182//!     let text_back: String = dag_json.get(&text_cid, None).await?;
183//!     let numbers_back: Vec<i32> = dag_json.get(&array_cid, None).await?;
184//!     let metadata_back: HashMap<String, String> = dag_json.get(&map_cid, None).await?;
185//!     
186//!     Ok(())
187//! }
188//! ```
189//!
190//! ### Example 5: Thread-Safe Concurrent Operations
191//! ```no_run
192//! use rust_helia::create_helia_default;
193//! use helia_dag_json::{DagJson, DagJsonInterface};
194//! use serde::{Deserialize, Serialize};
195//! use std::sync::Arc;
196//!
197//! #[derive(Serialize, Deserialize, Clone, Debug)]
198//! struct Record {
199//!     id: u32,
200//!     data: String,
201//! }
202//!
203//! #[tokio::main]
204//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
205//!     let helia = create_helia_default().await?;
206//!     let dag_json = Arc::new(DagJson::new(Arc::new(helia)));
207//!     
208//!     let mut handles = vec![];
209//!     
210//!     // Spawn multiple tasks to store data concurrently
211//!     for i in 0..10 {
212//!         let dag = dag_json.clone();
213//!         let handle = tokio::spawn(async move {
214//!             let record = Record {
215//!                 id: i,
216//!                 data: format!("Record {}", i),
217//!             };
218//!             dag.add(&record, None).await
219//!         });
220//!         handles.push(handle);
221//!     }
222//!     
223//!     // Wait for all tasks to complete
224//!     for handle in handles {
225//!         let cid = handle.await??;
226//!         println!("Stored record at: {}", cid);
227//!     }
228//!     
229//!     Ok(())
230//! }
231//! ```
232//!
233//! ## Performance Characteristics
234//!
235//! ### Serialization Performance
236//! | Object Size | Serialization Time | Notes |
237//! |-------------|-------------------|-------|
238//! | Small (<1KB) | 15-70µs | Simple objects, few fields |
239//! | Medium (1-10KB) | 70-300µs | Nested structures, arrays |
240//! | Large (>10KB) | 300µs+ | Complex graphs, many fields |
241//!
242//! ### Storage Overhead
243//! - **JSON overhead**: ~30-50% larger than CBOR
244//! - **vs JSON files**: ~5-10% overhead (CID metadata)
245//! - **Readability**: High - can inspect with any JSON viewer
246//!
247//! ### Memory Usage
248//! Objects are serialized in memory before storage:
249//! - Small objects (<10KB): Minimal memory impact
250//! - Large objects (>100KB): Consider streaming or chunking
251//! - Very large data: Use UnixFS for better performance
252//!
253//! ## Error Handling
254//!
255//! The module provides typed errors for common failure scenarios:
256//!
257//! ```no_run
258//! use rust_helia::create_helia_default;
259//! use helia_dag_json::{DagJson, DagJsonInterface, DagJsonError};
260//! use serde::{Deserialize, Serialize};
261//! use std::sync::Arc;
262//!
263//! #[derive(Serialize, Deserialize)]
264//! struct MyData { value: String }
265//!
266//! #[tokio::main]
267//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
268//!     let helia = create_helia_default().await?;
269//!     let dag_json = DagJson::new(Arc::new(helia));
270//!     
271//!     let data = MyData { value: "test".to_string() };
272//!     let cid = dag_json.add(&data, None).await?;
273//!     
274//!     // Handle potential errors
275//!     match dag_json.get::<MyData>(&cid, None).await {
276//!         Ok(retrieved) => println!("Data: {}", retrieved.value),
277//!         Err(DagJsonError::InvalidCodec { codec }) => {
278//!             eprintln!("Wrong codec: expected DAG-JSON, got {}", codec);
279//!         }
280//!         Err(DagJsonError::Json(e)) => {
281//!             eprintln!("JSON parsing failed: {}", e);
282//!         }
283//!         Err(e) => eprintln!("Other error: {}", e),
284//!     }
285//!     
286//!     Ok(())
287//! }
288//! ```
289//!
290//! ## Limitations
291//!
292//! ### Current Constraints
293//! 1. **Object size**: Recommended <10MB per JSON object for optimal performance
294//! 2. **Parsing overhead**: JSON parsing is slower than binary formats (CBOR)
295//! 3. **Storage efficiency**: 30-50% larger than DAG-CBOR
296//! 4. **Floating point**: Limited precision compared to native JSON
297//!
298//! ### When to Use DAG-CBOR Instead
299//! Consider using `helia-dag-cbor` if you need:
300//! - Maximum storage efficiency
301//! - Faster serialization/deserialization
302//! - Binary data support
303//! - High-performance applications
304//!
305//! ### Future Enhancements
306//! - Streaming JSON parsing for very large objects
307//! - Custom serialization options
308//! - Schema validation support
309//!
310//! ## Compatibility
311//!
312//! This implementation is compatible with:
313//! - **IPFS DAG-JSON spec**: Follows the official DAG-JSON specification
314//! - **go-ipfs**: Can read/write data from go-ipfs nodes
315//! - **js-ipfs**: Compatible with JavaScript IPFS implementations
316//! - **RFC 8259**: Follows JSON specification (RFC 8259)
317
318mod dag_json;
319mod errors;
320
321#[cfg(test)]
322mod tests;
323
324use async_trait::async_trait;
325use cid::Cid;
326use serde::{Deserialize, Serialize};
327
328use helia_interface::AbortOptions;
329
330pub use dag_json::*;
331pub use errors::*;
332
333/// Options for adding JSON data
334#[derive(Debug, Clone, Default)]
335pub struct AddOptions {
336    /// Whether to pin the data after adding
337    pub pin: bool,
338    /// Optional abort signal
339    pub abort: Option<AbortOptions>,
340}
341
342/// Options for getting JSON data
343#[derive(Debug, Clone, Default)]
344pub struct GetOptions {
345    /// Optional abort signal
346    pub abort: Option<AbortOptions>,
347}
348
349/// DAG-JSON interface for adding and retrieving JSON-encoded data
350#[async_trait]
351pub trait DagJsonInterface {
352    /// Add a JSON-serializable object to the DAG
353    ///
354    /// # Arguments
355    /// * `obj` - The object to serialize and add
356    /// * `options` - Optional configuration for the add operation
357    ///
358    /// # Returns
359    /// A CID identifying the stored JSON data
360    async fn add<T>(&self, obj: &T, options: Option<AddOptions>) -> Result<Cid, DagJsonError>
361    where
362        T: Serialize + Send + Sync;
363
364    /// Get a JSON object from the DAG by CID
365    ///
366    /// # Arguments
367    /// * `cid` - The CID of the JSON data to retrieve
368    /// * `options` - Optional configuration for the get operation
369    ///
370    /// # Returns
371    /// The deserialized object
372    async fn get<T>(&self, cid: &Cid, options: Option<GetOptions>) -> Result<T, DagJsonError>
373    where
374        T: for<'de> Deserialize<'de> + Send;
375}