···101101102102 for meta in bundles {
103103 // Filter by bundle range if specified
104104- if let Some(ref target_set) = target_bundles {
105105- if !target_set.contains(&meta.bundle_number) {
104104+ if let Some(ref target_set) = target_bundles
105105+ && !target_set.contains(&meta.bundle_number) {
106106 continue;
107107- }
108107 }
109108110109 let embedded_meta = manager.get_embedded_metadata(meta.bundle_number)?;
···239238 let total_bytes = bytes_atomic.fetch_add(info.old_size, Ordering::Relaxed) + info.old_size;
240239241240 // Only update progress bar periodically to reduce lock contention
242242- if current_count % update_interval == 0 || current_count == 1 {
241241+ if current_count.is_multiple_of(update_interval) || current_count == 1 {
243242 let prog = progress_arc.lock().unwrap();
244243 prog.set_with_bytes(current_count, total_bytes);
245244 }
···372371 size_change,
373372 size_diff as f64 / total_old_size as f64 * 100.0
374373 );
374374+ let old_ratio_str = format!("{:.3}x", old_ratio);
375375+ let new_ratio_str = format!("{:.3}x", new_ratio);
376376+ let ratio_diff_str = format!("{:+.3}x", ratio_diff);
375377 eprintln!(
376378 "Ratio {:<13} {:<13} {}",
377377- format!("{:.3}x", old_ratio),
378378- format!("{:.3}x", new_ratio),
379379- format!("{:+.3}x", ratio_diff)
379379+ old_ratio_str,
380380+ new_ratio_str,
381381+ ratio_diff_str
380382 );
381383 let avg_change = size_diff / success as i64;
382384 let avg_change_str = if avg_change >= 0 {
···482484483485 // Check if it's a skippable frame
484486 let magic = u32::from_le_bytes([header[0], header[1], header[2], header[3]]);
485485- if magic < 0x184D2A50 || magic > 0x184D2A5F {
487487+ if !(0x184D2A50..=0x184D2A5F).contains(&magic) {
486488 return Ok(0); // No metadata frame
487489 }
488490
+1-1
src/cli/cmd_op.rs
···406406 };
407407408408 for (i, op) in result.operations.iter().enumerate() {
409409- let cid_matches = op.cid.as_ref().map_or(false, |c| c == &cid);
409409+ let cid_matches = op.cid.as_ref() == Some(&cid);
410410411411 if cid_matches {
412412 let global_pos =
+1-5
src/cli/cmd_query.rs
···177177178178 // Track bundle count separately since callback gives increment, not total
179179 let bundle_count = Arc::new(Mutex::new(0usize));
180180- let pb_arc = if let Some(ref pb) = pb {
181181- Some(Arc::new(Mutex::new(pb)))
182182- } else {
183183- None
184184- };
180180+ let pb_arc = pb.as_ref().map(|pb| Arc::new(Mutex::new(pb)));
185181186182 let stats = processor.process(
187183 &bundle_numbers,
+3-3
src/cli/cmd_rollback.rs
···33use clap::Args;
44use plcbundle::BundleManager;
55use std::io::{self, Write};
66-use std::path::PathBuf;
66+use std::path::{Path, PathBuf};
7788#[derive(Args)]
99#[command(
···106106 bail!("cannot use both --to and --last together");
107107 }
108108109109- if super::utils::is_repository_empty(&manager) {
109109+ if super::utils::is_repository_empty(manager) {
110110 bail!("no bundles to rollback");
111111 }
112112···194194 })
195195}
196196197197-fn display_rollback_plan(dir: &PathBuf, plan: &RollbackPlan) -> Result<()> {
197197+fn display_rollback_plan(dir: &Path, plan: &RollbackPlan) -> Result<()> {
198198 println!("╔════════════════════════════════════════════════════════════════╗");
199199 println!("║ ROLLBACK PLAN ║");
200200 println!("╚════════════════════════════════════════════════════════════════╝\n");
+1-1
src/cli/cmd_server.rs
···155155 let callback = Box::new(move |current: u32, _total: u32, bytes_processed: u64, _total_bytes: u64| {
156156 let pb = progress_clone.lock().unwrap();
157157 pb.set_with_bytes(current as usize, bytes_processed);
158158- if verbose && current % 100 == 0 {
158158+ if verbose && current.is_multiple_of(100) {
159159 log::debug!("[DIDResolver] Index progress: {}/{} bundles", current, _total);
160160 }
161161 }) as Box<dyn Fn(u32, u32, u64, u64) + Send + Sync>;
+1-1
src/cli/cmd_stats.rs
···328328 .max()
329329 .cloned();
330330331331- let time_span_days = if let (Some(ref earliest), Some(ref latest)) = (earliest_time.as_ref(), latest_time.as_ref()) {
331331+ let time_span_days = if let (Some(earliest), Some(latest)) = (earliest_time.as_ref(), latest_time.as_ref()) {
332332 if let (Ok(e), Ok(l)) = (
333333 chrono::DateTime::parse_from_rfc3339(earliest),
334334 chrono::DateTime::parse_from_rfc3339(latest),
+8-10
src/cli/cmd_status.rs
···11use anyhow::Result;
22use clap::Parser;
33use plcbundle::*;
44-use std::path::PathBuf;
44+use std::path::{Path, PathBuf};
5566use super::utils;
77···6363fn print_human_status(
6464 manager: &BundleManager,
6565 index: &Index,
6666- dir: &PathBuf,
6666+ dir: &Path,
6767 detailed: bool
6868) -> Result<()> {
6969 let dir_display = utils::display_path(dir);
···187187 suggestions.push("Build DID index for fast lookups: plcbundle index build");
188188 }
189189190190- if let Ok(mempool_stats) = manager.get_mempool_stats() {
191191- if mempool_stats.count >= constants::BUNDLE_SIZE {
190190+ if let Ok(mempool_stats) = manager.get_mempool_stats()
191191+ && mempool_stats.count >= constants::BUNDLE_SIZE {
192192 suggestions.push("Mempool ready to create new bundle: plcbundle sync");
193193- }
194193 }
195194196195 // Add general hints
···231230fn print_json_status(
232231 manager: &BundleManager,
233232 index: &Index,
234234- dir: &PathBuf,
233233+ dir: &Path,
235234) -> Result<()> {
236235 use serde_json::json;
237236···290289 if !is_empty && !did_index_exists {
291290 health.push("did_index_not_built");
292291 }
293293- if let Ok(mempool_stats) = manager.get_mempool_stats() {
294294- if mempool_stats.count >= constants::BUNDLE_SIZE {
292292+ if let Ok(mempool_stats) = manager.get_mempool_stats()
293293+ && mempool_stats.count >= constants::BUNDLE_SIZE {
295294 health.push("mempool_ready_to_bundle");
296296- }
297295 }
298296 status["health"] = json!(health);
299297···302300 Ok(())
303301}
304302305305-fn check_did_index_exists(dir: &PathBuf) -> bool {
303303+fn check_did_index_exists(dir: &Path) -> bool {
306304 let did_index_dir = dir.join(constants::DID_INDEX_DIR).join(constants::DID_INDEX_SHARDS);
307305 did_index_dir.exists() && did_index_dir.is_dir()
308306}
+4-6
src/cli/cmd_verify.rs
···349349 drop(job_tx); // Close sender, workers will finish
350350351351 // Collect results and update progress in real-time
352352- let mut results: Vec<(usize, u32, VerifyResult)> = Vec::new();
353353- results.reserve(bundles.len());
352352+ let mut results: Vec<(usize, u32, VerifyResult)> = Vec::with_capacity(bundles.len());
354353355354 let mut verified_count = 0;
356355 let mut error_count = 0;
···404403 }
405404406405 // Store first error for summary
407407- if first_error.is_none() {
408408- if let Some(first_err) = errors.first() {
406406+ if first_error.is_none()
407407+ && let Some(first_err) = errors.first() {
409408 first_error = Some(anyhow::anyhow!("{}", first_err));
410410- }
411409 }
412410 } else {
413411 verified_count += 1;
···617615 let bundles_per_sec = total as f64 / elapsed.as_secs_f64();
618616 eprintln!(" Throughput: {:.1} bundles/sec", bundles_per_sec);
619617620620- let avg_time = elapsed / total as u32;
618618+ let avg_time = elapsed / total;
621619 eprintln!(" Avg/bundle: {:?}", avg_time);
622620 }
623621
+2-3
src/cli/progress.rs
···247247 // Don't set message - progress bar template will show pos/len without extra message
248248249249 // Finish progress bar when stage 2 completes (256/256)
250250- if current == 256 {
251251- if let Some(pb) = stage2_pb_guard.take() {
250250+ if current == 256
251251+ && let Some(pb) = stage2_pb_guard.take() {
252252 pb.finish();
253253- }
254253 }
255254 }
256255 } else {
+1-1
src/cli/utils.rs
···206206 let mut stat: libc::statvfs = std::mem::zeroed();
207207 if libc::statvfs(c_path.as_ptr(), &mut stat) == 0 {
208208 // Calculate free space: available blocks * block size
209209- let free_bytes = stat.f_bavail as u64 * stat.f_frsize as u64;
209209+ let free_bytes = stat.f_bavail as u64 * stat.f_frsize;
210210 Some(free_bytes)
211211 } else {
212212 None
+46-56
src/did_index.rs
···2020const DIDINDEX_MAGIC: &[u8; 4] = b"PLCD";
2121const DIDINDEX_VERSION: u32 = 4;
22222323+// Type aliases to simplify complex signatures for Clippy
2424+type ShardEntries = HashMap<u8, Vec<(String, OpLocation)>>;
2525+type ShardProcessResult = Result<(ShardEntries, u64, u64)>;
2626+2327// ============================================================================
2428// OpLocation - Packed 32-bit global position with nullified flag
2529//
···253257 fn add(&mut self, identifier: String, loc: OpLocation) {
254258 self.entries
255259 .entry(identifier)
256256- .or_insert_with(Vec::new)
260260+ .or_default()
257261 .push(loc);
258262 }
259263260264 fn merge(&mut self, other: HashMap<String, Vec<OpLocation>>) {
261265 for (id, locs) in other {
262262- self.entries.entry(id).or_insert_with(Vec::new).extend(locs);
266266+ self.entries.entry(id).or_default().extend(locs);
263267 }
264268 }
265269···513517 anyhow::bail!("DID index has zero entries.");
514518 }
515519516516- let seed = seed.unwrap_or_else(|| unix_timestamp());
520520+ let seed = seed.unwrap_or_else(unix_timestamp);
517521 let mut attempts = 0usize;
518522 let mut results = Vec::with_capacity(count);
519523···716720 fn process_bundle_for_index(
717721 bundle_dir: &PathBuf,
718722 bundle_num: u32,
719719- ) -> Result<(HashMap<u8, Vec<(String, OpLocation)>>, u64, u64)> {
723723+ ) -> ShardProcessResult {
720724 use std::fs::File;
721725 use std::io::BufReader;
722726···744748 fn process_bundle_lines<R: std::io::BufRead>(
745749 bundle_num: u32,
746750 reader: R,
747747- ) -> Result<(HashMap<u8, Vec<(String, OpLocation)>>, u64, u64)> {
751751+ ) -> ShardProcessResult {
748752 use sonic_rs::JsonValueTrait;
749753750754 let mut shard_entries: HashMap<u8, Vec<(String, OpLocation)>> = HashMap::new();
···766770 total_operations += 1;
767771768772 // Parse only the fields we need (did and nullified)
769769- if let Ok(value) = sonic_rs::from_str::<sonic_rs::Value>(&line) {
770770- if let Some(did) = value.get("did").and_then(|v| v.as_str()) {
771771- let nullified = value.get("nullified")
772772- .and_then(|v| v.as_bool())
773773- .unwrap_or(false);
773773+ if let Ok(value) = sonic_rs::from_str::<sonic_rs::Value>(&line)
774774+ && let Some(did) = value.get("did").and_then(|v| v.as_str()) {
775775+ let nullified = value.get("nullified")
776776+ .and_then(|v| v.as_bool())
777777+ .unwrap_or(false);
774778775779 // Calculate shard directly from DID bytes (no String allocation)
776780 if let Some(shard_num) = calculate_shard_from_did(did) {
···778782 let identifier = &did[DID_PREFIX.len()..DID_PREFIX.len() + DID_IDENTIFIER_LEN];
779783 let identifier = identifier.to_string();
780784781781- let global_pos = crate::constants::bundle_position_to_global(bundle_num as u32, position as usize) as u32;
785785+ let global_pos = crate::constants::bundle_position_to_global(bundle_num, position as usize) as u32;
782786 let loc = OpLocation::new(global_pos, nullified);
783787784788 shard_entries
785789 .entry(shard_num)
786786- .or_insert_with(Vec::new)
790790+ .or_default()
787791 .push((identifier, loc));
788792 }
789793 }
790790- }
791794792795 position += 1;
793796 if position >= constants::BUNDLE_SIZE as u16 {
···858861 for entry in fs::read_dir(shard_dir)? {
859862 let entry = entry?;
860863 let path = entry.path();
861861- if let Some(ext) = path.extension() {
862862- if ext == "tmp" {
863863- fs::remove_file(&path)?;
864864- cleaned += 1;
865865- }
864864+ if path.extension().is_some_and(|ext| ext == "tmp") {
865865+ fs::remove_file(&path)?;
866866+ cleaned += 1;
866867 }
867868 }
868869 if cleaned > 0 {
···933934 let mut total_flush_time = std::time::Duration::ZERO;
934935935936 // Metrics tracking (aggregated every N bundles)
936936- let metrics_interval = 100; // Log metrics every 100 bundles
937937+ let metrics_interval = 100u32; // Log metrics every 100 bundles
937938 let mut metrics_start = Instant::now();
938939 let mut metrics_ops = 0u64;
939940 let mut metrics_bytes = 0u64;
···947948 let batch_end = (batch_start + batch_size).min(bundle_numbers.len());
948949 let batch: Vec<u32> = bundle_numbers[batch_start..batch_end].to_vec();
949950950950- let batch_results: Vec<Result<(HashMap<u8, Vec<(String, OpLocation)>>, u64, u64)>> = if use_parallel {
951951+ let batch_results: Vec<ShardProcessResult> = if use_parallel {
951952 // Process batch in parallel
952953 batch.par_iter()
953954 .map(|&bundle_num| Self::process_bundle_for_index(bundle_dir, bundle_num))
···996997 // Merge batch entries into main shard_entries
997998 for (shard_num, mut entries) in batch_entries.drain() {
998999 shard_entries.entry(shard_num)
999999- .or_insert_with(Vec::new)
10001000+ .or_default()
10001001 .append(&mut entries);
10011002 }
10021003···1050105110511052 log::info!(
10521053 "[DID Index] Metrics (bundles {}..{}): {} ops, {:.1} MB | {:.1} ops/sec, {:.1} MB/sec",
10531053- last_bundle_in_batch.saturating_sub(metrics_interval as u32 - 1).max(1),
10541054+ last_bundle_in_batch.saturating_sub(metrics_interval - 1).max(1),
10541055 last_bundle_in_batch,
10551056 metrics_ops,
10561057 metrics_bytes as f64 / 1_000_000.0,
···1271127212721273 valid_dids += 1;
12731274 let shard_num = self.calculate_shard(&identifier);
12741274- let global_pos = crate::constants::bundle_position_to_global(bundle_num as u32, position as usize) as u32;
12751275+ let global_pos = crate::constants::bundle_position_to_global(bundle_num, position) as u32;
12751276 let loc = OpLocation::new(global_pos, *nullified);
1276127712771278 shard_ops
12781279 .entry(shard_num)
12791279- .or_insert_with(HashMap::new)
12801280+ .or_default()
12801281 .entry(identifier)
12811281- .or_insert_with(Vec::new)
12821282+ .or_default()
12821283 .push(loc);
12831284 }
12841285···2049205020502051 impl Drop for TempFileGuard {
20512052 fn drop(&mut self) {
20522052- if self.path.exists() {
20532053- if let Err(e) = fs::remove_file(&self.path) {
20542054- log::warn!("[DID Index] Failed to remove temp file {}: {}", self.path.display(), e);
20552055- }
20532053+ if self.path.exists() && let Err(e) = fs::remove_file(&self.path) {
20542054+ log::warn!("[DID Index] Failed to remove temp file {}: {}", self.path.display(), e);
20562055 }
20572056 }
20582057 }
···24562455 let start = Instant::now();
2457245624582457 // Ensure shard directory exists
24592459- if let Some(parent) = target.parent() {
24602460- if !parent.exists() {
24612461- fs::create_dir_all(parent)?;
24622462- }
24582458+ if let Some(parent) = target.parent() && !parent.exists() {
24592459+ fs::create_dir_all(parent)?;
24632460 }
2464246124652462 if builder.entries.is_empty() {
24662466- fs::write(target, &[])?;
24632463+ fs::write(target, [])?;
24672464 return Ok(());
24682465 }
24692466···27402737 for entry in fs::read_dir(&self.shard_dir)? {
27412738 let entry = entry?;
27422739 let path = entry.path();
27432743- if let Some(ext) = path.extension() {
27442744- if ext == "tmp" {
27452745- fs::remove_file(&path)?;
27462746- cleaned += 1;
27472747- }
27402740+ if let Some(ext) = path.extension() && ext == "tmp" {
27412741+ fs::remove_file(&path)?;
27422742+ cleaned += 1;
27482743 }
27492744 }
27502745 if cleaned > 0 {
···28902885 let exists = seg.get("exists").and_then(|v| v.as_bool()).unwrap_or(false);
28912886 if !exists {
28922887 missing_delta_segments += 1;
28932893- if let Some(start) = seg.get("bundle_start").and_then(|v| v.as_u64()).map(|v| v as u32) {
28942894- if let Some(end) = seg.get("bundle_end").and_then(|v| v.as_u64()).map(|v| v as u32) {
28882888+ if let Some(start) = seg.get("bundle_start").and_then(|v| v.as_u64()).map(|v| v as u32)
28892889+ && let Some(end) = seg.get("bundle_end").and_then(|v| v.as_u64()).map(|v| v as u32) {
28952890 for bundle_num in start..=end {
28962891 missing_segment_bundles.insert(bundle_num);
28972892 }
28982893 }
28992899- }
29002894 }
29012895 }
29022896 }
29032897 }
2904289829052905- let missing_bundles = if index_last_bundle < last_bundle_in_repo {
29062906- last_bundle_in_repo - index_last_bundle
29072907- } else {
29082908- 0
29092909- };
28992899+ let missing_bundles = last_bundle_in_repo.saturating_sub(index_last_bundle);
2910290029112901 let needs_rebuild = index_last_bundle < last_bundle_in_repo
29122902 || missing_base_shards > 0
···33503340 // Test creating OpLocation with nullified = false
33513341 let loc = OpLocation::new(0, false);
33523342 assert_eq!(loc.global_position(), 0);
33533353- assert_eq!(loc.nullified(), false);
33433343+ assert!(!loc.nullified());
33543344 assert_eq!(loc.bundle(), 1);
33553345 assert_eq!(loc.position(), 0);
3356334633573347 // Test creating OpLocation with nullified = true
33583348 let loc = OpLocation::new(0, true);
33593349 assert_eq!(loc.global_position(), 0);
33603360- assert_eq!(loc.nullified(), true);
33503350+ assert!(loc.nullified());
3361335133623352 // Test with various global positions
33633353 let loc = OpLocation::new(42, false);
33643354 assert_eq!(loc.global_position(), 42);
33653365- assert_eq!(loc.nullified(), false);
33553355+ assert!(!loc.nullified());
3366335633673357 let loc = OpLocation::new(10000, false);
33683358 assert_eq!(loc.global_position(), 10000);
···3371336133723362 let loc = OpLocation::new(10500, true);
33733363 assert_eq!(loc.global_position(), 10500);
33743374- assert_eq!(loc.nullified(), true);
33643364+ assert!(loc.nullified());
33753365 assert_eq!(loc.bundle(), 2);
33763366 assert_eq!(loc.position(), 500);
33773367 }
···3412340234133403 assert_eq!(loc1.global_position(), loc2.global_position());
34143404 assert_ne!(loc1.nullified(), loc2.nullified());
34153415- assert_eq!(loc1.nullified(), false);
34163416- assert_eq!(loc2.nullified(), true);
34053405+ assert!(!loc1.nullified());
34063406+ assert!(loc2.nullified());
34173407 }
3418340834193409 #[test]
···34313421 let loc2 = OpLocation::from_u32(u32_val);
34323422 assert_eq!(loc1.global_position(), loc2.global_position());
34333423 assert_eq!(loc1.nullified(), loc2.nullified());
34343434- assert_eq!(loc2.nullified(), true);
34243424+ assert!(loc2.nullified());
34353425 }
3436342634373427 #[test]
···34603450 // Test maximum u32 value (will overflow bundle/position but should not panic)
34613451 let loc = OpLocation::new(u32::MAX >> 1, false);
34623452 assert_eq!(loc.global_position(), u32::MAX >> 1);
34633463- assert_eq!(loc.nullified(), false);
34533453+ assert!(!loc.nullified());
3464345434653455 // Test with nullified flag set on max value
34663456 let loc = OpLocation::new(u32::MAX >> 1, true);
34673467- assert_eq!(loc.nullified(), true);
34573457+ assert!(loc.nullified());
34683458 }
3469345934703460 #[test]
+96-10
src/ffi.rs
···113113// BundleManager lifecycle
114114// ============================================================================
115115116116+/// # Safety
117117+///
118118+/// The caller must ensure `bundle_dir` is a valid, NUL-terminated C string
119119+/// pointer. The returned pointer is owned by the caller and must be freed
120120+/// with `bundle_manager_free` when no longer needed. Passing a null or
121121+/// invalid pointer is undefined behavior.
116122#[unsafe(no_mangle)]
117123pub unsafe extern "C" fn bundle_manager_new(bundle_dir: *const c_char) -> *mut CBundleManager {
118124 let bundle_dir = unsafe {
···133139 }
134140}
135141142142+/// # Safety
143143+///
144144+/// The caller must ensure `manager` is a pointer previously returned by
145145+/// `bundle_manager_new` and not already freed. Passing invalid or dangling
146146+/// pointers is undefined behavior.
136147#[unsafe(no_mangle)]
137148pub unsafe extern "C" fn bundle_manager_free(manager: *mut CBundleManager) {
138149 if !manager.is_null() {
···146157// Smart Loading
147158// ============================================================================
148159160160+/// # Safety
161161+///
162162+/// The `manager` pointer must be valid. `options` may be NULL to use defaults.
163163+/// `out_result` must be a valid writable pointer to a `CLoadResult` struct.
164164+/// Strings passed to this API must be NUL-terminated. Violating these
165165+/// requirements is undefined behavior.
149166#[unsafe(no_mangle)]
150167pub unsafe extern "C" fn bundle_manager_load_bundle(
151168 manager: *const CBundleManager,
···198215// Batch Operations
199216// ============================================================================
200217218218+/// # Safety
219219+///
220220+/// The `manager` pointer must be valid. `requests` must point to `count`
221221+/// valid `COperationRequest` items. `out_operations` and `out_count` must
222222+/// be valid writable pointers. Returned `COperation` arrays must be freed
223223+/// by the caller using `bundle_manager_free_operations` if applicable.
201224#[unsafe(no_mangle)]
202225pub unsafe extern "C" fn bundle_manager_get_operations_batch(
203226 manager: *const CBundleManager,
···244267// DID Operations
245268// ============================================================================
246269270270+/// # Safety
271271+///
272272+/// The `manager` pointer must be valid. `did` must be a valid NUL-terminated
273273+/// C string. `out_operations` and `out_count` must be valid writable pointers
274274+/// to receive results. Returned memory ownership rules are documented in the
275275+/// API and must be followed by the caller.
247276#[unsafe(no_mangle)]
248277pub unsafe extern "C" fn bundle_manager_get_did_operations(
249278 manager: *const CBundleManager,
···281310 }
282311}
283312313313+/// # Safety
314314+///
315315+/// `manager` must be valid. `dids` must point to `did_count` valid
316316+/// NUL-terminated C string pointers. `callback` will be called from this
317317+/// function and must be a valid function pointer. The caller must ensure the
318318+/// callback and its environment remain valid for the duration of the call.
284319#[unsafe(no_mangle)]
285320pub unsafe extern "C" fn bundle_manager_batch_resolve_dids(
286321 manager: *const CBundleManager,
···328363// Query
329364// ============================================================================
330365366366+/// # Safety
367367+///
368368+/// `manager` must be valid. `query_str` must be a valid NUL-terminated C
369369+/// string. `out_operations` and `out_count` must be valid writable pointers.
370370+/// The caller is responsible for freeing any returned memory according to the
371371+/// API's ownership rules.
331372#[unsafe(no_mangle)]
332373pub unsafe extern "C" fn bundle_manager_query(
333374 manager: *const CBundleManager,
···388429// Verification
389430// ============================================================================
390431432432+/// # Safety
433433+///
434434+/// `manager` must be a valid pointer. `out_result` must be a valid writable
435435+/// pointer to `CVerifyResult`. Passing invalid or null pointers is undefined
436436+/// behavior.
391437#[unsafe(no_mangle)]
392438pub unsafe extern "C" fn bundle_manager_verify_bundle(
393439 manager: *const CBundleManager,
···432478 }
433479}
434480481481+/// # Safety
482482+///
483483+/// `manager` must be a valid pointer. Calling this function with invalid
484484+/// pointers is undefined behavior.
435485#[unsafe(no_mangle)]
436486pub unsafe extern "C" fn bundle_manager_verify_chain(
437487 manager: *const CBundleManager,
···466516// Info
467517// ============================================================================
468518519519+/// # Safety
520520+///
521521+/// `manager` must be valid. `out_info` must be a valid writable pointer to
522522+/// `CBundleInfo`. Any returned string pointers must be freed by the caller as
523523+/// documented by the API.
469524#[unsafe(no_mangle)]
470525pub unsafe extern "C" fn bundle_manager_get_bundle_info(
471526 manager: *const CBundleManager,
···507562// Cache Management
508563// ============================================================================
509564565565+/// # Safety
566566+///
567567+/// `manager` must be valid. `bundle_nums` must point to `count` valid u32
568568+/// values. Passing invalid pointers is undefined behavior.
510569#[unsafe(no_mangle)]
511570pub unsafe extern "C" fn bundle_manager_prefetch_bundles(
512571 manager: *const CBundleManager,
···526585 }
527586}
528587588588+/// # Safety
589589+///
590590+/// `manager` must be valid. Passing invalid pointers is undefined behavior.
529591#[unsafe(no_mangle)]
530592pub unsafe extern "C" fn bundle_manager_warm_up(
531593 manager: *const CBundleManager,
···556618 }
557619}
558620621621+/// # Safety
622622+///
623623+/// `manager` must be a valid pointer previously returned by
624624+/// `bundle_manager_new`.
559625#[unsafe(no_mangle)]
560626pub unsafe extern "C" fn bundle_manager_clear_caches(manager: *const CBundleManager) -> i32 {
561627 if manager.is_null() {
···571637// DID Index
572638// ============================================================================
573639640640+/// # Safety
641641+///
642642+/// `manager` must be valid. `out_stats` must be a valid writable pointer if
643643+/// provided. The caller must ensure `progress_callback` is a valid function
644644+/// pointer if passed.
574645#[unsafe(no_mangle)]
575646pub unsafe extern "C" fn bundle_manager_rebuild_did_index(
576647 manager: *const CBundleManager,
···606677 }
607678}
608679680680+/// # Safety
681681+///
682682+/// `manager` must be valid. `out_stats` must be a valid writable pointer.
609683#[unsafe(no_mangle)]
610684pub unsafe extern "C" fn bundle_manager_get_did_index_stats(
611685 manager: *const CBundleManager,
···636710// Observability
637711// ============================================================================
638712713713+/// # Safety
714714+///
715715+/// `manager` must be valid. `out_stats` must be a valid writable pointer.
639716#[unsafe(no_mangle)]
640717pub unsafe extern "C" fn bundle_manager_get_stats(
641718 manager: *const CBundleManager,
···703780 }
704781}
705782783783+/// # Safety
784784+///
785785+/// `s` must be a pointer previously returned by this API that is safe to
786786+/// free. Passing invalid pointers is undefined behavior.
706787#[unsafe(no_mangle)]
707788pub unsafe extern "C" fn bundle_manager_free_string(s: *mut c_char) {
708789 if !s.is_null() {
···712793 }
713794}
714795796796+/// # Safety
797797+///
798798+/// `op` must be a pointer previously returned by this API and safe to free.
715799#[unsafe(no_mangle)]
716800pub unsafe extern "C" fn bundle_manager_free_operation(op: *mut COperation) {
717801 if !op.is_null() {
···722806 }
723807}
724808809809+/// # Safety
810810+///
811811+/// `ops` must point to an array of `count` `COperation` previously returned
812812+/// by this API and safe to free.
725813#[unsafe(no_mangle)]
726814pub unsafe extern "C" fn bundle_manager_free_operations(ops: *mut COperation, count: usize) {
727815 if !ops.is_null() {
···744832pub type ExportCallback =
745833 extern "C" fn(data: *const c_char, len: usize, user_data: *mut std::ffi::c_void) -> i32;
746834835835+/// # Safety
836836+///
837837+/// `manager` must be valid. `spec` must point to a valid `CExportSpec` and
838838+/// `callback` must be a valid function pointer. `out_stats` must be a valid
839839+/// writable pointer if provided.
747840#[unsafe(no_mangle)]
748841pub unsafe extern "C" fn bundle_manager_export(
749842 manager: *const CBundleManager,
···790883 };
791884 bundle_numbers
792885 .into_iter()
793793- .filter_map(|num| {
794794- if let Some(meta) = index.get_bundle(num) {
795795- if meta.end_time >= after_ts {
796796- Some(num)
797797- } else {
798798- None
799799- }
800800- } else {
801801- Some(num)
802802- }
886886+ .filter(|num| match index.get_bundle(*num) {
887887+ Some(meta) => meta.end_time >= after_ts,
888888+ None => true,
803889 })
804890 .collect()
805891 } else {
+18-23
src/index.rs
···220220 .unwrap_or("");
221221222222 // Match pattern: NNNNNN.jsonl.zst (16 chars: 6 digits + 10 chars for .jsonl.zst)
223223- if filename.ends_with(".jsonl.zst") && filename.len() == 16 {
224224- if let Ok(bundle_num) = filename[0..6].parse::<u32>() {
225225- bundle_files.push((bundle_num, path));
226226- }
223223+ if filename.ends_with(".jsonl.zst") && filename.len() == 16
224224+ && let Ok(bundle_num) = filename[0..6].parse::<u32>() {
225225+ bundle_files.push((bundle_num, path));
227226 }
228227 }
229228···244243 }
245244246245 // Validate no gaps in bundle sequence
247247- for i in 0..bundle_files.len() {
246246+ for (i, (actual, _)) in bundle_files.iter().enumerate() {
248247 let expected = (i + 1) as u32;
249249- let actual = bundle_files[i].0;
250250- if actual != expected {
248248+ if *actual != expected {
251249 anyhow::bail!(
252250 "Gap detected in bundle files: expected {:06}.jsonl.zst, found {:06}.jsonl.zst",
253251 expected,
254254- actual
252252+ *actual
255253 );
256254 }
257255 }
···288286 let current_count = count_atomic.fetch_add(1, Ordering::Relaxed) + 1;
289287290288 // Update progress periodically
291291- if current_count % update_interval == 0 || current_count == 1 || current_count == bundle_count {
292292- if let Ok(cb_guard) = progress_cb_arc.lock() {
293293- if let Some(ref cb) = *cb_guard {
294294- cb(current_count, bundle_count, bytes_processed, total_bytes);
295295- }
296296- }
289289+ if (current_count.is_multiple_of(update_interval) || current_count == 1 || current_count == bundle_count)
290290+ && let Ok(cb_guard) = progress_cb_arc.lock()
291291+ && let Some(ref cb) = *cb_guard {
292292+ cb(current_count, bundle_count, bytes_processed, total_bytes);
297293 }
298294299295 // Extract embedded metadata from bundle file
···311307 // Verify origin matches
312308 {
313309 let origin_guard = detected_origin.lock().unwrap();
314314- if let Some(ref expected_origin) = *origin_guard {
315315- if embedded.origin != *expected_origin {
316316- anyhow::bail!(
317317- "Bundle {:06}: origin mismatch (expected '{}', got '{}')",
318318- bundle_num,
319319- expected_origin,
320320- embedded.origin
321321- );
322322- }
310310+ if let Some(ref expected_origin) = *origin_guard
311311+ && embedded.origin != *expected_origin {
312312+ anyhow::bail!(
313313+ "Bundle {:06}: origin mismatch (expected '{}', got '{}')",
314314+ bundle_num,
315315+ expected_origin,
316316+ embedded.origin
317317+ );
323318 }
324319 }
325320
···257257fn parse_retry_after(response: &reqwest::Response) -> Duration {
258258 const MAX_RETRY_SECONDS: u64 = 60;
259259260260- if let Some(retry_after_header) = response.headers().get("retry-after") {
261261- if let Ok(retry_after_str) = retry_after_header.to_str() {
262262- // Try parsing as seconds (integer) - most common format
263263- if let Ok(seconds) = retry_after_str.parse::<u64>() {
264264- // Cap at maximum wait time
265265- return Duration::from_secs(seconds.min(MAX_RETRY_SECONDS));
266266- }
260260+ if let Some(retry_after_header) = response.headers().get("retry-after")
261261+ && let Ok(retry_after_str) = retry_after_header.to_str()
262262+ {
263263+ // Try parsing as seconds (integer) - most common format
264264+ if let Ok(seconds) = retry_after_str.parse::<u64>() {
265265+ // Cap at maximum wait time
266266+ return Duration::from_secs(seconds.min(MAX_RETRY_SECONDS));
267267+ }
267268268268- // Try parsing as HTTP date (RFC 7231)
269269- // httpdate::parse_http_date returns a SystemTime
270270- if let Ok(http_time) = httpdate::parse_http_date(retry_after_str) {
271271- if let Ok(duration) = http_time.duration_since(std::time::SystemTime::now()) {
272272- // Cap at maximum wait time
273273- return duration.min(Duration::from_secs(MAX_RETRY_SECONDS));
274274- }
275275- }
269269+ // Try parsing as HTTP date (RFC 7231)
270270+ // httpdate::parse_http_date returns a SystemTime
271271+ if let Ok(http_time) = httpdate::parse_http_date(retry_after_str)
272272+ && let Ok(duration) = http_time.duration_since(std::time::SystemTime::now())
273273+ {
274274+ // Cap at maximum wait time
275275+ return duration.min(Duration::from_secs(MAX_RETRY_SECONDS));
276276 }
277277 }
278278···280280 Duration::from_secs(MAX_RETRY_SECONDS)
281281}
282282283283-#[cfg(test)]
284284-mod tests {
285285- use super::*;
286286-287287- #[tokio::test]
288288- async fn test_plc_client_new() {
289289- let client = PLCClient::new("https://plc.directory").unwrap();
290290- // Verify client was created successfully
291291- assert!(client.base_url.contains("plc.directory"));
292292- }
293293-294294- #[tokio::test]
295295- async fn test_plc_client_new_with_trailing_slash() {
296296- let client = PLCClient::new("https://plc.directory/").unwrap();
297297- // URL should be stored as-is (no normalization in PLCClient)
298298- assert!(client.base_url.contains("plc.directory"));
299299- }
300300-}
301283302284/// Simple token bucket rate limiter
303285/// Prevents burst requests by starting with 0 permits and refilling at steady rate
···346328 }
347329}
348330331331+#[cfg(test)]
332332+mod tests {
333333+ use super::*;
334334+335335+ #[tokio::test]
336336+ async fn test_plc_client_new() {
337337+ let client = PLCClient::new("https://plc.directory").unwrap();
338338+ // Verify client was created successfully
339339+ assert!(client.base_url.contains("plc.directory"));
340340+ }
341341+342342+ #[tokio::test]
343343+ async fn test_plc_client_new_with_trailing_slash() {
344344+ let client = PLCClient::new("https://plc.directory/").unwrap();
345345+ // URL should be stored as-is (no normalization in PLCClient)
346346+ assert!(client.base_url.contains("plc.directory"));
347347+ }
348348+}
349349+
+4-4
src/resolver.rs
···154154 }
155155156156 // Handle legacy handle format
157157- if let Some(handle) = op_data.get("handle").and_then(|v| v.as_str()) {
158158- if state.also_known_as.is_empty() {
159159- state.also_known_as = vec![format!("at://{}", handle)];
160160- }
157157+ if let Some(handle) = op_data.get("handle").and_then(|v| v.as_str())
158158+ && state.also_known_as.is_empty()
159159+ {
160160+ state.also_known_as = vec![format!("at://{}", handle)];
161161 }
162162163163 // Update services
+7-13
src/runtime.rs
···9797 self.trigger_shutdown();
98989999 // Always abort resolver tasks immediately - they're just keep-alive pings
100100- if let Some(resolver_tasks) = resolver_tasks {
101101- if !resolver_tasks.is_empty() {
102102- resolver_tasks.abort_all();
103103- while let Some(result) = resolver_tasks.join_next().await {
104104- if let Err(e) = result {
105105- if !e.is_cancelled() {
106106- eprintln!("Resolver task error: {}", e);
107107- }
108108- }
100100+ if let Some(resolver_tasks) = resolver_tasks && !resolver_tasks.is_empty() {
101101+ resolver_tasks.abort_all();
102102+ while let Some(result) = resolver_tasks.join_next().await {
103103+ if let Err(e) = result && !e.is_cancelled() {
104104+ eprintln!("Resolver task error: {}", e);
109105 }
110106 }
111107 }
···118114 background_tasks.abort_all();
119115 // Wait briefly for aborted tasks to finish
120116 while let Some(result) = background_tasks.join_next().await {
121121- if let Err(e) = result {
122122- if !e.is_cancelled() {
123123- eprintln!("Background task error: {}", e);
124124- }
117117+ if let Err(e) = result && !e.is_cancelled() {
118118+ eprintln!("Background task error: {}", e);
125119 }
126120 }
127121 } else {
+4-6
src/server/handle_root.rs
···2323 let mut response = String::new();
24242525 // ASCII art banner
2626- response.push_str("\n");
2626+ response.push('\n');
2727 response.push_str(&crate::server::get_ascii_art_banner(&state.config.version));
2828 response.push_str(&format!(" {} server\n\n", constants::BINARY_NAME));
2929 response.push_str("*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*\n");
···3434 response.push_str("| any time. Do not use this for production systems. |\n");
3535 response.push_str("| Please wait for the 1.0 release. |\n");
3636 response.push_str("|________________________________________________________________|\n");
3737- response.push_str("\n");
3737+ response.push('\n');
3838 response.push_str("What is PLC Bundle?\n");
3939 response.push_str("━━━━━━━━━━━━━━━━━━━━\n");
4040 response.push_str("plcbundle archives AT Protocol's DID PLC Directory operations into\n");
···8282 }
8383 }
84848585- if state.config.sync_mode {
8686- if let Ok(mempool_stats) = state.manager.get_mempool_stats() {
8585+ if state.config.sync_mode && let Ok(mempool_stats) = state.manager.get_mempool_stats() {
8786 response.push_str("\nMempool\n");
8887 response.push_str("━━━━━━━\n");
8988 response.push_str(&format!(
···123122 } else {
124123 response.push_str(" (empty)\n");
125124 }
126126- }
127125 }
128126129127 if state.config.enable_resolver {
···164162 ));
165163 }
166164 }
167167- response.push_str("\n");
165165+ response.push('\n');
168166 }
169167170168 response.push_str("Server Stats\n");
+1-3
src/server/handle_status.rs
···6666 response["bundles"]["total_operations"] = json!(total_ops);
6767 }
68686969- if state.config.sync_mode {
7070- if let Ok(mempool_stats) = state.manager.get_mempool_stats() {
6969+ if state.config.sync_mode && let Ok(mempool_stats) = state.manager.get_mempool_stats() {
7170 response["mempool"] = json!({
7271 "count": mempool_stats.count,
7372 "target_bundle": mempool_stats.target_bundle,
···7675 "bundle_size": constants::BUNDLE_SIZE,
7776 "operations_needed": constants::BUNDLE_SIZE - mempool_stats.count,
7877 });
7979- }
8078 }
81798280 // DID Index stats (get_stats is fast, but we should still avoid holding lock in async context)
+2-5
src/server/mod.rs
···9292/// Returns the ASCII art banner as a string
9393#[cfg(feature = "server")]
9494pub fn get_ascii_art_banner(_version: &str) -> String {
9595- format!(
9696- r#"
9595+ r#"
9796 ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠄⠀⡀⠀⠀⠀⠀⠀⠀⢀⠀⠀⡀⠀⢀⠀⢀⡀⣤⡢⣤⡤⡀⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
9897⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡄⡄⠐⡀⠈⣀⠀⡠⡠⠀⣢⣆⢌⡾⢙⠺⢽⠾⡋⣻⡷⡫⢵⣭⢦⣴⠦⠀⢠⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
9998⠀⠀⠀⠀⠀⠀⠀⠀⢠⣤⣽⣥⡈⠧⣂⢧⢾⠕⠞⠡⠊⠁⣐⠉⠀⠉⢍⠀⠉⠌⡉⠀⠂⠁⠱⠉⠁⢝⠻⠎⣬⢌⡌⣬⣡⣀⣢⣄⡄⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀
···116115⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠂⠂⠏⠿⢻⣥⡪⢽⣳⣳⣥⡶⣫⣍⢐⣥⣻⣾⡻⣅⢭⡴⢭⣿⠕⣧⡭⣞⣻⣣⣻⢿⠟⠛⠙⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
117116⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠄⠋⠫⠯⣍⢻⣿⣿⣷⣕⣵⣹⣽⣿⣷⣇⡏⣿⡿⣍⡝⠵⠯⠁⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
118117⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠠⠁⠋⢣⠓⡍⣫⠹⣿⣿⣷⡿⠯⠺⠁⠁⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
119119-⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠋⢀⠋⢈⡿⠿⠁⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
120120-"#
121121- )
118118+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠋⢀⠋⢈⡿⠿⠁⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀"#.to_string()
122119}
+2-2
src/server/startup.rs
···589589 use crate::server::get_ascii_art_banner;
590590591591 // Print ASCII art banner
592592- eprint!(
593593- "{}\n",
592592+ eprintln!(
593593+ "{}",
594594 get_ascii_art_banner(env!("CARGO_PKG_VERSION"))
595595 );
596596 eprintln!("{} HTTP server started", constants::BINARY_NAME);
+16-34
src/server/utils.rs
···55555656 // Must not have invalid characters
5757 for c in input.chars() {
5858- if !((c >= 'a' && c <= 'z')
5959- || (c >= 'A' && c <= 'Z')
6060- || (c >= '0' && c <= '9')
6161- || c == '.'
6262- || c == '-')
6363- {
5858+ if !(c.is_ascii_lowercase() || c.is_ascii_uppercase() || c.is_ascii_digit() || c == '.' || c == '-') {
6459 return false;
6560 }
6661 }
···121116122117/// Extract base URL from request headers and URI
123118pub fn extract_base_url(headers: &HeaderMap, uri: &Uri) -> String {
124124- if let Some(host) = headers.get("host") {
125125- if let Ok(host_str) = host.to_str() {
119119+ if let Some(host_str) = headers.get("host").and_then(|h| h.to_str().ok()) {
126120 // Check if request is HTTPS (from X-Forwarded-Proto or X-Forwarded-Ssl)
127127- let scheme = if headers
121121+ let is_https = headers
128122 .get("x-forwarded-proto")
129123 .and_then(|v| v.to_str().ok())
130124 .map(|s| s == "https")
131125 .unwrap_or(false)
132132- {
133133- "https"
134134- } else if headers
135135- .get("x-forwarded-ssl")
136136- .and_then(|v| v.to_str().ok())
137137- .map(|s| s == "on")
138138- .unwrap_or(false)
139139- {
140140- "https"
141141- } else {
142142- "http"
143143- };
126126+ || headers
127127+ .get("x-forwarded-ssl")
128128+ .and_then(|v| v.to_str().ok())
129129+ .map(|s| s == "on")
130130+ .unwrap_or(false);
131131+132132+ let scheme = if is_https { "https" } else { "http" };
144133 return format!("{}://{}", scheme, host_str);
145145- }
146134 }
147135148136 if let Some(authority) = uri.authority() {
···170158171159 // Simple parser: "60s", "5m", "1h"
172160 let s = s.trim();
173173- if s.ends_with('s') {
174174- let secs: u64 = s[..s.len() - 1]
175175- .parse()
176176- .context("Invalid duration format")?;
161161+ if let Some(stripped) = s.strip_suffix('s') {
162162+ let secs: u64 = stripped.parse().context("Invalid duration format")?;
177163 Ok(Duration::from_secs(secs))
178178- } else if s.ends_with('m') {
179179- let mins: u64 = s[..s.len() - 1]
180180- .parse()
181181- .context("Invalid duration format")?;
164164+ } else if let Some(stripped) = s.strip_suffix('m') {
165165+ let mins: u64 = stripped.parse().context("Invalid duration format")?;
182166 Ok(Duration::from_secs(mins * 60))
183183- } else if s.ends_with('h') {
184184- let hours: u64 = s[..s.len() - 1]
185185- .parse()
186186- .context("Invalid duration format")?;
167167+ } else if let Some(stripped) = s.strip_suffix('h') {
168168+ let hours: u64 = stripped.parse().context("Invalid duration format")?;
187169 Ok(Duration::from_secs(hours * 3600))
188170 } else {
189171 // Try parsing as seconds
+6-8
src/server/websocket.rs
···8383 let start_bundle_idx = (start_bundle_num - 1) as usize;
84848585 if start_bundle_idx < bundles.len() {
8686- for i in start_bundle_idx..bundles.len() {
8787- let bundle_num = bundles[i].bundle_number;
8686+ for (i, bundle) in bundles.iter().enumerate().skip(start_bundle_idx) {
8787+ let bundle_num = bundle.bundle_number;
8888 let skip_until = if i == start_bundle_idx {
8989 start_position
9090 } else {
···197197 streamed += 1;
198198199199 // Send ping every 1000 operations
200200- if streamed % 1000 == 0 {
201201- if sender.send(Message::Ping(Bytes::new())).await.is_err() {
202202- break;
203203- }
200200+ if streamed % 1000 == 0 && sender.send(Message::Ping(Bytes::new())).await.is_err() {
201201+ break;
204202 }
205203 }
206204···227225 return Ok(());
228226 }
229227230230- for i in *last_seen_count..mempool_ops.len() {
228228+ for (i, op) in mempool_ops.iter().enumerate().skip(*last_seen_count) {
231229 let record_num = bundle_record_base + i as u64;
232230 if record_num < start_cursor {
233231 continue;
234232 }
235233236234 // Send operation as JSON
237237- let json = match sonic_rs::to_string(&mempool_ops[i]) {
235235+ let json = match sonic_rs::to_string(op) {
238236 Ok(j) => j,
239237 Err(_) => continue, // Skip invalid operations
240238 };
+32-33
src/sync.rs
···11+12// Sync module - PLC directory synchronization
23use crate::constants;
34use crate::operations::Operation;
···1920 nullified: Option<Value>,
2021 #[serde(rename = "createdAt")]
2122 created_at: String,
2323+2424+2225 #[serde(skip)]
2326 pub raw_json: Option<String>,
2427}
25282629impl From<PLCOperation> for Operation {
2730 fn from(plc: PLCOperation) -> Self {
2828- let is_nullified = plc.nullified.as_ref().map_or(false, |v| {
2929- v.as_bool().unwrap_or(false) || v.as_str().map_or(false, |s| !s.is_empty())
3131+ let is_nullified = plc.nullified.as_ref().is_some_and(|v| {
3232+ v.as_bool().unwrap_or(false) || v.as_str().is_some_and(|s| !s.is_empty())
3033 });
31343235 Self {
···7275 operations.retain(|op| {
7376 op.cid
7477 .as_ref()
7575- .map_or(true, |cid| !prev_boundary.contains(cid))
7878+ .is_none_or(|cid| !prev_boundary.contains(cid))
7679 });
77807881 operations
···9497 total_duration_ms: u64,
9598 fetch_requests: usize,
9699 did_index_compacted: bool,
100100+97101 unique_dids: u32,
98102 size_bytes: u64,
99103 },
···156160pub trait SyncLogger: Send + Sync {
157161 fn on_sync_start(&self, interval: Duration);
158162163163+ #[allow(clippy::too_many_arguments)]
159164 fn on_bundle_created(
160165 &self,
161166 bundle_num: u32,
···171176 size_bytes: u64,
172177 );
173178179179+ // Allow the sync logger to accept multiple arguments for detailed bundle info
180180+ // (Removed workaround method; use allow attribute on trait method instead)
181181+182182+174183 fn on_caught_up(
175184 &self,
176185 next_bundle: u32,
···238247 }
239248}
240249241241-242250impl SyncLogger for SyncLoggerImpl {
243251 fn as_any(&self) -> &dyn Any {
244252 self
···246254247255 fn on_sync_start(&self, interval: Duration) {
248256 eprintln!("[Sync] Starting initial sync...");
249249- if let Some(verbose) = &self.verbose {
250250- if *verbose.lock().unwrap() {
251251- eprintln!("[Sync] Sync loop interval: {:?}", interval);
252252- }
257257+ if let Some(verbose) = &self.verbose && *verbose.lock().unwrap() {
258258+ eprintln!("[Sync] Sync loop interval: {:?}", interval);
253259 }
254260 }
255261262262+ #[allow(clippy::too_many_arguments)]
256263 fn on_bundle_created(
257264 &self,
258265 bundle_num: u32,
···345352 client: PLCClient,
346353 config: SyncConfig,
347354 logger: Option<Box<dyn SyncLogger>>,
355355+ #[allow(clippy::type_complexity)]
348356 event_callback: Option<Box<dyn Fn(&SyncEvent) + Send + Sync>>,
349357}
350358···471479472480 loop {
473481 // Check for shutdown if configured
474474- if let Some(ref shutdown_rx) = self.config.shutdown_rx {
475475- if *shutdown_rx.borrow() {
482482+ if let Some(ref shutdown_rx) = self.config.shutdown_rx
483483+ && *shutdown_rx.borrow() {
476484 break;
477485 }
478478- }
479486480487 // Get stats before sync to track compaction
481488 let stats_before = self.manager.get_did_index_stats();
···519526 self.show_compaction_if_needed(did_index_compacted, delta_segments_before, index_ms);
520527521528 // Check if we've reached the limit
522522- if let Some(max) = max_bundles {
523523- if synced >= max {
524524- break;
525525- }
529529+ if let Some(max) = max_bundles && synced >= max {
530530+ break;
526531 }
527532 }
528533 Ok(crate::manager::SyncResult::CaughtUp {
···586591587592 loop {
588593 // Check for shutdown before starting sync
589589- if let Some(ref shutdown_rx) = self.config.shutdown_rx {
590590- if *shutdown_rx.borrow() {
591591- if self.config.verbose {
592592- eprintln!("[Sync] Shutdown requested, stopping...");
593593- }
594594- break;
594594+ if let Some(ref shutdown_rx) = self.config.shutdown_rx && *shutdown_rx.borrow() {
595595+ if self.config.verbose {
596596+ eprintln!("[Sync] Shutdown requested, stopping...");
595597 }
598598+ break;
596599 }
597600598601 // Update DID index on every bundle (now fast with delta segments)
···658661 }
659662660663 // Check for shutdown before sleeping
661661- if let Some(ref shutdown_rx) = self.config.shutdown_rx {
662662- if *shutdown_rx.borrow() {
663663- if self.config.verbose {
664664- eprintln!("[Sync] Shutdown requested, stopping...");
665665- }
666666- break;
664664+ if let Some(ref shutdown_rx) = self.config.shutdown_rx && *shutdown_rx.borrow() {
665665+ if self.config.verbose {
666666+ eprintln!("[Sync] Shutdown requested, stopping...");
667667 }
668668+ break;
668669 }
669670670671 // During initial sync, sleep briefly (500ms) to avoid hammering the API
···697698 fetch_duration_ms,
698699 }) => {
699700 // Check for shutdown
700700- if let Some(ref shutdown_rx) = self.config.shutdown_rx {
701701- if *shutdown_rx.borrow() {
702702- if self.config.verbose {
703703- eprintln!("[Sync] Shutdown requested, stopping...");
704704- }
705705- break;
701701+ if let Some(ref shutdown_rx) = self.config.shutdown_rx && *shutdown_rx.borrow() {
702702+ if self.config.verbose {
703703+ eprintln!("[Sync] Shutdown requested, stopping...");
706704 }
705705+ break;
707706 }
708707709708 // Caught up to the end of the chain
···3333 // Query DID operations and resolve DID
3434 let did = "did:plc:aaaaaaaaaaaaaaaaaaaaaaaa";
3535 let did_ops = manager.get_did_operations(did, false, false)?;
3636- assert!(did_ops.operations.len() >= 1);
3636+ assert!(!did_ops.operations.is_empty());
37373838 let resolved = manager.resolve_did(did)?;
3939 assert_eq!(resolved.document.id, did);
···7575 let arc_mgr = Arc::new(manager.clone_for_arc());
76767777 // Range iterator over bundle 1 should yield 10 operations
7878- let mut range_iter = arc_mgr.get_operations_range(1, 1, None);
7878+ let range_iter = arc_mgr.get_operations_range(1, 1, None);
7979 let mut count = 0usize;
8080- while let Some(res) = range_iter.next() {
8080+ for res in range_iter {
8181 let op = res?;
8282 assert!(op.did.starts_with("did:plc:"));
8383 count += 1;
···9393 count: Some(5),
9494 after_timestamp: None,
9595 };
9696- let mut export_iter = plcbundle::ExportIterator::new(Arc::clone(&arc_mgr), spec);
9696+ let export_iter = plcbundle::ExportIterator::new(Arc::clone(&arc_mgr), spec);
9797 let mut exported = Vec::new();
9898- while let Some(item) = export_iter.next() {
9898+ for item in export_iter {
9999 let s = item?;
100100 exported.push(s);
101101 }
···130130 // Verify we can query DID operations from the newly built index
131131 let first_did = "did:plc:aaaaaaaaaaaaaaaaaaaaaaaa";
132132 let did_ops2 = manager2.get_did_operations(first_did, false, false)?;
133133- assert!(did_ops2.operations.len() >= 1);
133133+ assert!(!did_ops2.operations.is_empty());
134134135135 Ok(())
136136}