High-performance implementation of plcbundle written in Rust
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

use sonic_rs for JSON handling

+127 -105
+1
src/cli/cmd_compare.rs
··· 2 2 use anyhow::{Context, Result, bail}; 3 3 use clap::{Args, ValueHint}; 4 4 use plcbundle::{BundleManager, constants, remote}; 5 + use sonic_rs::JsonValueTrait; 5 6 use std::collections::HashMap; 6 7 use std::path::PathBuf; 7 8 use tokio::runtime::Runtime;
+9 -7
src/cli/cmd_did.rs
··· 2 2 use anyhow::{Context, Result}; 3 3 use clap::{Args, Subcommand, ValueHint}; 4 4 use plcbundle::{BundleManager, DIDLookupStats, DIDLookupTimings}; 5 + use sonic_rs::{JsonContainerTrait, JsonValueTrait}; 5 6 use std::path::PathBuf; 6 7 7 8 #[derive(Args)] ··· 1001 1002 println!("{}", values.join(separator)); 1002 1003 1003 1004 if verbose && !owl.nullified { 1004 - if let Some(op_data) = owl.operation.operation.as_object() { 1005 - if let Some(op_type) = op_data.get("type").and_then(|v| v.as_str()) { 1006 - eprintln!(" type: {}", op_type); 1007 - } 1008 - if let Some(handle) = op_data.get("handle").and_then(|v| v.as_str()) { 1009 - eprintln!(" handle: {}", handle); 1010 - } else if let Some(aka) = op_data.get("alsoKnownAs").and_then(|v| v.as_array()) { 1005 + let op_val = &owl.operation.operation; 1006 + if let Some(op_type) = op_val.get("type").and_then(|v| v.as_str()) { 1007 + eprintln!(" type: {}", op_type); 1008 + } 1009 + if let Some(handle) = op_val.get("handle").and_then(|v| v.as_str()) { 1010 + eprintln!(" handle: {}", handle); 1011 + } else { 1012 + if let Some(aka) = op_val.get("alsoKnownAs").and_then(|v| v.as_array()) { 1011 1013 if let Some(aka_str) = aka.first().and_then(|v| v.as_str()) { 1012 1014 let handle = aka_str.strip_prefix("at://").unwrap_or(aka_str); 1013 1015 eprintln!(" handle: {}", handle);
+45 -67
src/cli/cmd_inspect.rs
··· 2 2 use anyhow::Result; 3 3 use chrono::DateTime; 4 4 use clap::{Args, ValueHint}; 5 - use plcbundle::constants; 6 5 use plcbundle::format::{format_bytes, format_duration_verbose, format_number}; 7 6 use plcbundle::{BundleManager, LoadOptions, Operation}; 8 7 use serde::Serialize; 9 - use serde_json::Value; 8 + use sonic_rs::{JsonContainerTrait, JsonValueTrait}; 10 9 use std::collections::HashMap; 11 10 use std::path::PathBuf; 12 11 ··· 189 188 let manager = super::utils::create_manager(dir.clone(), false, false)?; 190 189 191 190 // Resolve target to bundle number or file path 192 - let (bundle_num, file_path) = resolve_target(&cmd.target, &dir)?; 191 + let (bundle_num, file_path) = super::utils::resolve_bundle_target(&manager, &cmd.target, &dir)?; 193 192 194 - // Get file size - use bundle metadata if available, otherwise read from filesystem 193 + // Get file size - always use bundle metadata if available, otherwise read from filesystem 195 194 let file_size = if let Some(num) = bundle_num { 196 195 // Use bundle metadata from index (avoids direct file access per RULES.md) 197 196 manager 198 197 .get_bundle_metadata(num)? 199 198 .map(|meta| meta.compressed_size) 200 - .unwrap_or_else(|| { 201 - // Fallback to filesystem if metadata not available 202 - std::fs::metadata(&file_path).map(|m| m.len()).unwrap_or(0) 203 - }) 199 + .unwrap_or(0) 204 200 } else { 205 - // For arbitrary file paths, we still need filesystem access 206 - // TODO: Add method to load from arbitrary path 207 - anyhow::bail!("Loading from arbitrary paths not yet implemented"); 201 + // For arbitrary file paths, we still need filesystem access - this should be refactored 202 + // to use a manager method for loading from arbitrary paths in the future if supported. 203 + // For now, it will return an error as per `resolve_bundle_target`. 204 + anyhow::bail!("Loading from arbitrary paths not yet implemented. Please specify a bundle number."); 208 205 }; 209 206 210 207 if !cmd.json { ··· 371 368 max_op_size = max_op_size.max(op_size); 372 369 373 370 // Parse operation for detailed analysis 374 - if let Value::Object(ref map) = op.operation { 375 - // Operation type 376 - if let Some(Value::String(op_type)) = map.get("type") { 377 - *operation_types.entry(op_type.clone()).or_insert(0) += 1; 378 - } 371 + let op_val = &op.operation; 372 + // Operation type 373 + if let Some(op_type) = op_val.get("type").and_then(|v| v.as_str()) { 374 + *operation_types.entry(op_type.to_string()).or_insert(0) += 1; 375 + } 379 376 380 - // Pattern analysis (if not skipped) 381 - if !cmd.skip_patterns { 382 - // Handle analysis 383 - if let Some(Value::Array(aka)) = map.get("alsoKnownAs") { 384 - for item in aka { 385 - if let Value::String(aka_str) = item { 386 - if aka_str.starts_with("at://") { 387 - total_handles += 1; 377 + // Pattern analysis (if not skipped) 378 + if !cmd.skip_patterns { 379 + // Handle analysis 380 + if let Some(aka) = op_val.get("alsoKnownAs").and_then(|v| v.as_array()) { 381 + for item in aka.iter() { 382 + if let Some(aka_str) = item.as_str() { 383 + if aka_str.starts_with("at://") { 384 + total_handles += 1; 388 385 389 - // Extract domain 390 - let handle = aka_str.strip_prefix("at://").unwrap_or(""); 391 - let handle = handle.split('/').next().unwrap_or(""); 386 + // Extract domain 387 + let handle = aka_str.strip_prefix("at://").unwrap_or(""); 388 + let handle = handle.split('/').next().unwrap_or(""); 392 389 393 - // Count domain (TLD) 394 - let parts: Vec<&str> = handle.split('.').collect(); 395 - if parts.len() >= 2 { 396 - let domain = format!( 397 - "{}.{}", 398 - parts[parts.len() - 2], 399 - parts[parts.len() - 1] 400 - ); 401 - *domain_counts.entry(domain).or_insert(0) += 1; 402 - } 390 + // Count domain (TLD) 391 + let parts: Vec<&str> = handle.split('.').collect(); 392 + if parts.len() >= 2 { 393 + let domain = format!( 394 + "{}.{}", 395 + parts[parts.len() - 2], 396 + parts[parts.len() - 1] 397 + ); 398 + *domain_counts.entry(domain).or_insert(0) += 1; 399 + } 403 400 404 - // Check for invalid patterns 405 - if handle.contains('_') { 406 - invalid_handles += 1; 407 - } 401 + // Check for invalid patterns 402 + if handle.contains('_') { 403 + invalid_handles += 1; 408 404 } 409 405 } 410 406 } 411 407 } 408 + } 412 409 413 - // Service analysis 414 - if let Some(Value::Object(services)) = map.get("services") { 415 - total_services += services.len(); 410 + // Service analysis 411 + if let Some(services) = op_val.get("services").and_then(|v| v.as_object()) { 412 + total_services += services.len(); 416 413 417 - // Extract PDS endpoints 418 - if let Some(Value::Object(pds)) = services.get("atproto_pds") { 419 - if let Some(Value::String(endpoint)) = pds.get("endpoint") { 414 + // Extract PDS endpoints 415 + if let Some(pds_val) = op_val.get("services").and_then(|v| v.get("atproto_pds")) { 416 + if let Some(pds) = pds_val.as_object() { 417 + if let Some(endpoint) = pds_val.get("endpoint").and_then(|v| v.as_str()) { 420 418 // Normalize endpoint 421 419 let endpoint = endpoint 422 420 .strip_prefix("https://") ··· 921 919 922 920 Ok(()) 923 921 } 924 - 925 - fn resolve_target(target: &str, dir: &PathBuf) -> Result<(Option<u32>, PathBuf)> { 926 - // Try to parse as bundle number 927 - if let Ok(num) = target.parse::<u32>() { 928 - let path = constants::bundle_path(dir, num); 929 - if path.exists() { 930 - return Ok((Some(num), path)); 931 - } else { 932 - anyhow::bail!("Bundle {} not found in repository", num); 933 - } 934 - } 935 - 936 - // Otherwise treat as file path 937 - let path = PathBuf::from(target); 938 - if path.exists() { 939 - Ok((None, path)) 940 - } else { 941 - anyhow::bail!("File not found: {}", target) 942 - } 943 - }
+3 -4
src/cli/cmd_op.rs
··· 1 1 use anyhow::Result; 2 2 use clap::{Args, Subcommand}; 3 3 use plcbundle::{LoadOptions, constants}; 4 + use sonic_rs::{JsonContainerTrait, JsonValueTrait}; 4 5 use std::path::PathBuf; 5 6 use std::time::Instant; 6 7 ··· 299 300 let pds = op 300 301 .operation 301 302 .get("services") 302 - .and_then(|v| v.as_object()) 303 - .and_then(|services| services.get("atproto_pds")) 304 - .and_then(|v| v.as_object()) 305 - .and_then(|pds| pds.get("endpoint")) 303 + .and_then(|services_val| services_val.get("atproto_pds")) 304 + .and_then(|pds_val| pds_val.get("endpoint")) 306 305 .and_then(|v| v.as_str()); 307 306 308 307 // Display formatted output
+3 -7
src/cli/cmd_stats.rs
··· 1 1 use anyhow::Result; 2 2 use clap::{Args, ValueEnum}; 3 3 use plcbundle::BundleManager; 4 - use serde_json::Value; 4 + use sonic_rs::JsonValueTrait; 5 5 use std::collections::HashMap; 6 6 use std::path::PathBuf; 7 7 ··· 231 231 nullified_operations += 1; 232 232 } 233 233 234 - if let Value::Object(ref map) = op.operation { 235 - if let Some(Value::String(op_type)) = map.get("type") { 236 - *operation_types.entry(op_type.clone()).or_insert(0) += 1; 237 - } else { 238 - *operation_types.entry("unknown".to_string()).or_insert(0) += 1; 239 - } 234 + if let Some(op_type) = op.operation.get("type").and_then(|v| v.as_str()) { 235 + *operation_types.entry(op_type.to_string()).or_insert(0) += 1; 240 236 } else { 241 237 *operation_types.entry("unknown".to_string()).or_insert(0) += 1; 242 238 }
+22
src/cli/utils.rs
··· 152 152 create_manager(dir, cmd.verbose(), cmd.quiet()) 153 153 } 154 154 155 + /// Resolve a target string (bundle number or path) into a bundle number and canonical path. 156 + /// This utility ensures that file existence checks for bundles are done through the BundleManager. 157 + pub fn resolve_bundle_target( 158 + manager: &BundleManager, 159 + target: &str, 160 + repo_dir: &PathBuf, 161 + ) -> Result<(Option<u32>, PathBuf)> { 162 + // Try to parse as bundle number 163 + if let Ok(num) = target.parse::<u32>() { 164 + let path = plcbundle::constants::bundle_path(repo_dir, num); 165 + // Check if bundle exists via BundleManager's index 166 + if manager.get_bundle_metadata(num)?.is_some() { 167 + Ok((Some(num), path)) 168 + } else { 169 + anyhow::bail!("Bundle {} not found in repository index", num); 170 + } 171 + } else { 172 + // Otherwise treat as file path. For now, this is an error as direct file access is disallowed. 173 + anyhow::bail!("Loading from arbitrary paths not yet implemented. Please specify a bundle number."); 174 + } 175 + } 176 + 155 177 /// Get all bundle metadata from the repository 156 178 /// This is more efficient than iterating through bundle numbers 157 179 pub fn get_all_bundle_metadata(manager: &BundleManager) -> Vec<plcbundle::index::BundleMetadata> {
+1
src/ffi.rs
··· 1 1 use crate::constants; 2 2 use crate::manager::*; 3 3 use crate::operations::*; 4 + use sonic_rs::JsonValueTrait; 4 5 use std::ffi::{CStr, CString}; 5 6 use std::os::raw::c_char; 6 7 use std::path::PathBuf;
+20 -1
src/manager.rs
··· 5 5 use crate::operations::{Operation, OperationFilter, OperationRequest, OperationWithLocation}; 6 6 use crate::options::QueryMode; 7 7 use crate::{cache, did_index, handle_resolver, mempool, verification}; 8 - use anyhow::Result; 8 + use anyhow::{Context, Result}; 9 9 use chrono::{DateTime, Utc}; 10 10 use std::collections::{HashMap, HashSet}; 11 11 use std::io::Write; 12 12 use std::path::PathBuf; 13 13 use std::sync::{Arc, Mutex, RwLock}; 14 + use std::fs::File; 14 15 15 16 /// Result of a sync_next_bundle operation 16 17 #[derive(Debug, Clone)] ··· 3169 3170 let failed_count = failed.load(Ordering::SeqCst); 3170 3171 3171 3172 Ok((downloaded_count, failed_count)) 3173 + } 3174 + 3175 + /// Deletes a bundle file from the repository. 3176 + /// 3177 + /// This method removes a bundle file from the repository directory. 3178 + /// 3179 + /// # Arguments 3180 + /// * `bundle_num` - The number of the bundle to delete. 3181 + /// 3182 + /// # Returns 3183 + /// A `Result` indicating whether the operation was successful. 3184 + pub fn delete_bundle_file(&self, bundle_num: u32) -> Result<()> { 3185 + let bundle_path = constants::bundle_path(&self.directory, bundle_num); 3186 + if bundle_path.exists() { 3187 + std::fs::remove_file(bundle_path)?; 3188 + } 3189 + self.cache.remove(bundle_num); 3190 + Ok(()) 3172 3191 } 3173 3192 } 3174 3193
+3 -2
src/operations.rs
··· 1 1 // src/operations.rs 2 2 use serde::{Deserialize, Serialize}; 3 + use sonic_rs::{self, Value}; 3 4 4 5 /// PLC Operation 5 6 /// ··· 8 9 pub struct Operation { 9 10 pub did: String, 10 11 #[serde(alias = "operation")] 11 - pub operation: serde_json::Value, 12 + pub operation: Value, 12 13 #[serde(default)] 13 14 pub cid: Option<String>, 14 15 #[serde(default)] ··· 16 17 #[serde(rename = "createdAt", alias = "created_at")] 17 18 pub created_at: String, 18 19 #[serde(flatten)] 19 - pub extra: serde_json::Value, 20 + pub extra: Value, 20 21 21 22 /// CRITICAL: Raw JSON bytes as received from source 22 23 ///
+6 -4
src/resolver.rs
··· 2 2 use crate::operations::Operation; 3 3 use anyhow::Result; 4 4 use serde::{Deserialize, Serialize}; 5 + use sonic_rs::{JsonContainerTrait, JsonValueTrait, Value}; 5 6 use std::collections::HashMap; 6 7 7 8 // ============================================================================ ··· 117 118 } 118 119 119 120 /// Apply a single operation to the state 120 - fn apply_operation_to_state(state: &mut DIDState, op_data: &serde_json::Value) { 121 + fn apply_operation_to_state(state: &mut DIDState, op_data: &Value) { 121 122 // Update rotation keys 122 123 if let Some(rot_keys) = op_data.get("rotationKeys").and_then(|v| v.as_array()) { 123 124 state.rotation_keys = rot_keys ··· 133 134 { 134 135 state.verification_methods = vm 135 136 .iter() 136 - .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string()))) 137 + .filter_map(|(k, v)| v.as_str().map(|s| (k.to_string(), s.to_string()))) 137 138 .collect(); 138 139 } 139 140 ··· 167 168 let service_type = v.get("type")?.as_str()?.to_string(); 168 169 let endpoint = v.get("endpoint")?.as_str()?.to_string(); 169 170 Some(( 170 - k.clone(), 171 + k.to_string(), 171 172 ServiceDefinition { 172 173 service_type, 173 174 endpoint: normalize_service_endpoint(&endpoint), ··· 334 335 #[derive(Debug, Clone, Serialize, Deserialize)] 335 336 pub struct AuditLogEntry { 336 337 pub did: String, 337 - pub operation: serde_json::Value, 338 + #[serde(skip)] 339 + pub operation: Value, 338 340 pub cid: Option<String>, 339 341 #[serde(skip_serializing_if = "Option::is_none")] 340 342 pub nullified: Option<bool>,
+14 -13
src/sync.rs
··· 4 4 use crate::plc_client::PLCClient; 5 5 use anyhow::Result; 6 6 use serde::Deserialize; 7 + use sonic_rs::{JsonValueTrait, Value}; 7 8 use std::any::Any; 8 9 use std::collections::HashSet; 9 10 use std::sync::{Arc, Mutex}; ··· 12 13 #[derive(Debug, Deserialize)] 13 14 pub struct PLCOperation { 14 15 did: String, 15 - operation: serde_json::Value, 16 + operation: Value, 16 17 cid: String, 17 18 #[serde(default)] 18 - nullified: Option<serde_json::Value>, 19 + nullified: Option<Value>, 19 20 #[serde(rename = "createdAt")] 20 21 created_at: String, 21 22 #[serde(skip)] ··· 34 35 cid: Some(plc.cid), 35 36 nullified: is_nullified, 36 37 created_at: plc.created_at, 37 - extra: serde_json::Value::Null, 38 + extra: sonic_rs::from_str("null").unwrap_or_else(|_| Value::new()), 38 39 raw_json: plc.raw_json, 39 40 } 40 41 } ··· 897 898 let ops = vec![ 898 899 Operation { 899 900 did: "did:plc:1".into(), 900 - operation: serde_json::Value::Null, 901 + operation: sonic_rs::from_str("null").unwrap_or_else(|_| Value::new()), 901 902 cid: Some("cid1".into()), 902 903 nullified: false, 903 904 created_at: "2024-01-01T00:00:00Z".into(), 904 - extra: serde_json::Value::Null, 905 + extra: sonic_rs::from_str("null").unwrap_or_else(|_| Value::new()), 905 906 raw_json: None, 906 907 }, 907 908 Operation { 908 909 did: "did:plc:2".into(), 909 - operation: serde_json::Value::Null, 910 + operation: sonic_rs::from_str("null").unwrap_or_else(|_| Value::new()), 910 911 cid: Some("cid2".into()), 911 912 nullified: false, 912 913 created_at: "2024-01-01T00:00:01Z".into(), 913 - extra: serde_json::Value::Null, 914 + extra: sonic_rs::from_str("null").unwrap_or_else(|_| Value::new()), 914 915 raw_json: None, 915 916 }, 916 917 Operation { 917 918 did: "did:plc:3".into(), 918 - operation: serde_json::Value::Null, 919 + operation: sonic_rs::from_str("null").unwrap_or_else(|_| Value::new()), 919 920 cid: Some("cid3".into()), 920 921 nullified: false, 921 922 created_at: "2024-01-01T00:00:01Z".into(), // Same time as cid2 922 - extra: serde_json::Value::Null, 923 + extra: sonic_rs::from_str("null").unwrap_or_else(|_| Value::new()), 923 924 raw_json: None, 924 925 }, 925 926 ]; ··· 938 939 let ops = vec![ 939 940 Operation { 940 941 did: "did:plc:1".into(), 941 - operation: serde_json::Value::Null, 942 + operation: sonic_rs::from_str("null").unwrap_or_else(|_| Value::new()), 942 943 cid: Some("cid1".into()), // Duplicate 943 944 nullified: false, 944 945 created_at: "2024-01-01T00:00:00Z".into(), 945 - extra: serde_json::Value::Null, 946 + extra: sonic_rs::from_str("null").unwrap_or_else(|_| Value::new()), 946 947 raw_json: None, 947 948 }, 948 949 Operation { 949 950 did: "did:plc:2".into(), 950 - operation: serde_json::Value::Null, 951 + operation: sonic_rs::from_str("null").unwrap_or_else(|_| Value::new()), 951 952 cid: Some("cid2".into()), // New 952 953 nullified: false, 953 954 created_at: "2024-01-01T00:00:01Z".into(), 954 - extra: serde_json::Value::Null, 955 + extra: sonic_rs::from_str("null").unwrap_or_else(|_| Value::new()), 955 956 raw_json: None, 956 957 }, 957 958 ];