diff --git a/crates/presswerk-app/src/pages/add_printer.rs b/crates/presswerk-app/src/pages/add_printer.rs index d7a7303..03231ba 100644 --- a/crates/presswerk-app/src/pages/add_printer.rs +++ b/crates/presswerk-app/src/pages/add_printer.rs @@ -165,8 +165,7 @@ pub fn AddPrinter() -> Element { /// Probe a printer by attempting Get-Printer-Attributes. async fn probe_printer(uri: &str) -> Result<(), String> { - let client = presswerk_print::ipp_client::IppClient::new(uri) - .map_err(|e| e.to_string())?; + let client = presswerk_print::ipp_client::IppClient::new(uri).map_err(|e| e.to_string())?; client .get_printer_attributes() .await diff --git a/crates/presswerk-app/src/services/app_services.rs b/crates/presswerk-app/src/services/app_services.rs index 6e3fe12..fcb9193 100644 --- a/crates/presswerk-app/src/services/app_services.rs +++ b/crates/presswerk-app/src/services/app_services.rs @@ -269,8 +269,7 @@ impl AppServices { Ok(remote_id) => { info!(job_id = %job_id, remote_id, "print job accepted"); if let Ok(queue) = services.job_queue.lock() { - let _ = - queue.update_status(&job_id, JobStatus::Completed, None); + let _ = queue.update_status(&job_id, JobStatus::Completed, None); } services.audit("print_completed", &hash, true, None); } @@ -278,11 +277,7 @@ impl AppServices { error!(job_id = %job_id, error = %e, "print job failed"); let msg = e.to_string(); if let Ok(queue) = services.job_queue.lock() { - let _ = queue.update_status( - &job_id, - JobStatus::Failed, - Some(&msg), - ); + let _ = queue.update_status(&job_id, JobStatus::Failed, Some(&msg)); } services.audit("print_failed", &hash, false, Some(&msg)); } diff --git a/crates/presswerk-bridge/src/android/mod.rs b/crates/presswerk-bridge/src/android/mod.rs index b4d9fa9..9809d3b 100644 --- a/crates/presswerk-bridge/src/android/mod.rs +++ b/crates/presswerk-bridge/src/android/mod.rs @@ -1025,7 +1025,6 @@ fn get_authority(env: &mut JNIEnv<'_>, activity: &JObject<'_>) -> Result Ok(format!("{pkg}.fileprovider")) } - // --------------------------------------------------------------------------- // Stub implementations for connection types not yet wired to Android APIs // --------------------------------------------------------------------------- @@ -1095,7 +1094,12 @@ impl NativeThunderboltPrint for AndroidBridge { Err(PresswerkError::PlatformUnavailable) } - fn print_thunderbolt(&self, _device_id: &str, _document: &[u8], _mime_type: &str) -> Result<()> { + fn print_thunderbolt( + &self, + _device_id: &str, + _document: &[u8], + _mime_type: &str, + ) -> Result<()> { Err(PresswerkError::PlatformUnavailable) } } @@ -1151,7 +1155,12 @@ impl NativeUsbDrivePrint for AndroidBridge { Err(PresswerkError::PlatformUnavailable) } - fn copy_to_usb_drive(&self, _drive_id: &str, _document: &[u8], _filename: &str) -> Result { + fn copy_to_usb_drive( + &self, + _drive_id: &str, + _document: &[u8], + _filename: &str, + ) -> Result { Err(PresswerkError::PlatformUnavailable) } } diff --git a/crates/presswerk-bridge/src/ios/mod.rs b/crates/presswerk-bridge/src/ios/mod.rs index 77b2612..f726f50 100644 --- a/crates/presswerk-bridge/src/ios/mod.rs +++ b/crates/presswerk-bridge/src/ios/mod.rs @@ -879,7 +879,6 @@ impl NativeShare for IosBridge { // Tests // --------------------------------------------------------------------------- - // --------------------------------------------------------------------------- // Stub implementations for connection types not yet wired to iOS APIs // --------------------------------------------------------------------------- @@ -949,7 +948,12 @@ impl NativeThunderboltPrint for IosBridge { Err(PresswerkError::PlatformUnavailable) } - fn print_thunderbolt(&self, _device_id: &str, _document: &[u8], _mime_type: &str) -> Result<()> { + fn print_thunderbolt( + &self, + _device_id: &str, + _document: &[u8], + _mime_type: &str, + ) -> Result<()> { Err(PresswerkError::PlatformUnavailable) } } @@ -1005,7 +1009,12 @@ impl NativeUsbDrivePrint for IosBridge { Err(PresswerkError::PlatformUnavailable) } - fn copy_to_usb_drive(&self, _drive_id: &str, _document: &[u8], _filename: &str) -> Result { + fn copy_to_usb_drive( + &self, + _drive_id: &str, + _document: &[u8], + _filename: &str, + ) -> Result { Err(PresswerkError::PlatformUnavailable) } } diff --git a/crates/presswerk-bridge/src/lib.rs b/crates/presswerk-bridge/src/lib.rs index a5025d1..696bbda 100644 --- a/crates/presswerk-bridge/src/lib.rs +++ b/crates/presswerk-bridge/src/lib.rs @@ -21,7 +21,7 @@ pub mod android; pub mod stub; /// Retrieves the singleton bridge implementation for the target operating system. -/// +/// /// RETURNS: A boxed trait object (`dyn PlatformBridge`) that abstracts away /// the underlying native SDK details. pub fn platform_bridge() -> Box { diff --git a/crates/presswerk-core/benches/core_bench.rs b/crates/presswerk-core/benches/core_bench.rs index b6a9fd6..7e332df 100644 --- a/crates/presswerk-core/benches/core_bench.rs +++ b/crates/presswerk-core/benches/core_bench.rs @@ -4,10 +4,8 @@ // Benchmarks for presswerk-core critical operations. // Run with: cargo bench -p presswerk-core -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use presswerk_core::{ - AppConfig, DocumentType, PrintJob, PrintSettings, JobSource, -}; +use criterion::{Criterion, black_box, criterion_group, criterion_main}; +use presswerk_core::{AppConfig, DocumentType, JobSource, PrintJob, PrintSettings}; fn bench_config_creation(c: &mut Criterion) { c.bench_function("config_default", |b| { @@ -41,8 +39,7 @@ fn bench_config_serialization(c: &mut Criterion) { c.bench_function("config_deserialize_json", |b| { b.iter(|| { - let _config: AppConfig = - serde_json::from_str(&black_box(&json)).expect("deserialize"); + let _config: AppConfig = serde_json::from_str(&black_box(&json)).expect("deserialize"); }) }); } @@ -101,8 +98,7 @@ fn bench_job_serialization(c: &mut Criterion) { c.bench_function("job_deserialize_json", |b| { b.iter(|| { - let _job: PrintJob = - serde_json::from_str(&black_box(&json)).expect("deserialize"); + let _job: PrintJob = serde_json::from_str(&black_box(&json)).expect("deserialize"); }) }); } diff --git a/crates/presswerk-core/src/human_errors.rs b/crates/presswerk-core/src/human_errors.rs index 4e62050..adaf594 100644 --- a/crates/presswerk-core/src/human_errors.rs +++ b/crates/presswerk-core/src/human_errors.rs @@ -208,14 +208,19 @@ fn humanize_ipp_error(detail: &str) -> HumanError { } else if lower.contains("server-error") { HumanError { message: "The printer reported an internal error.".into(), - suggestion: "Try turning the printer off, waiting 10 seconds, and turning it back on.".into(), + suggestion: "Try turning the printer off, waiting 10 seconds, and turning it back on." + .into(), retriable: true, severity: Severity::Transient, } - } else if lower.contains("client-error-not-possible") || lower.contains("client-error-attributes") { + } else if lower.contains("client-error-not-possible") + || lower.contains("client-error-attributes") + { HumanError { message: "The printer can't handle those settings.".into(), - suggestion: "Try changing the print settings (paper size, duplex, colour) and print again.".into(), + suggestion: + "Try changing the print settings (paper size, duplex, colour) and print again." + .into(), retriable: false, severity: Severity::ActionRequired, } @@ -229,7 +234,8 @@ fn humanize_ipp_error(detail: &str) -> HumanError { } else if lower.contains("invalid uri") || lower.contains("invalid url") { HumanError { message: "The printer address doesn't look right.".into(), - suggestion: "Check the printer address and try again. It should look like 192.168.1.100.".into(), + suggestion: + "Check the printer address and try again. It should look like 192.168.1.100.".into(), retriable: false, severity: Severity::ActionRequired, } @@ -240,7 +246,10 @@ fn humanize_ipp_error(detail: &str) -> HumanError { retriable: false, severity: Severity::ActionRequired, } - } else if lower.contains("toner-empty") || lower.contains("ink") || lower.contains("marker-supply") { + } else if lower.contains("toner-empty") + || lower.contains("ink") + || lower.contains("marker-supply") + { HumanError { message: "The printer needs new ink or toner.".into(), suggestion: "You'll need to buy a replacement cartridge. Check your printer's model number and search online for the right one.".into(), @@ -265,7 +274,9 @@ fn humanize_ipp_error(detail: &str) -> HumanError { // Generic IPP error fallback HumanError { message: "The printer had a problem.".into(), - suggestion: format!("Try again. If this keeps happening, try turning the printer off and on again. (Detail: {detail})"), + suggestion: format!( + "Try again. If this keeps happening, try turning the printer off and on again. (Detail: {detail})" + ), retriable: true, severity: Severity::Transient, } diff --git a/crates/presswerk-core/src/types.rs b/crates/presswerk-core/src/types.rs index 9aefc95..0a26ae9 100644 --- a/crates/presswerk-core/src/types.rs +++ b/crates/presswerk-core/src/types.rs @@ -368,17 +368,29 @@ mod tests { #[test] fn test_document_type_extensions() { assert_eq!(DocumentType::from_extension("pdf"), Some(DocumentType::Pdf)); - assert_eq!(DocumentType::from_extension("jpg"), Some(DocumentType::Jpeg)); - assert_eq!(DocumentType::from_extension("jpeg"), Some(DocumentType::Jpeg)); + assert_eq!( + DocumentType::from_extension("jpg"), + Some(DocumentType::Jpeg) + ); + assert_eq!( + DocumentType::from_extension("jpeg"), + Some(DocumentType::Jpeg) + ); assert_eq!(DocumentType::from_extension("png"), Some(DocumentType::Png)); - assert_eq!(DocumentType::from_extension("txt"), Some(DocumentType::PlainText)); + assert_eq!( + DocumentType::from_extension("txt"), + Some(DocumentType::PlainText) + ); assert_eq!(DocumentType::from_extension("unknown"), None); } #[test] fn test_document_type_case_insensitive() { assert_eq!(DocumentType::from_extension("PDF"), Some(DocumentType::Pdf)); - assert_eq!(DocumentType::from_extension("JPG"), Some(DocumentType::Jpeg)); + assert_eq!( + DocumentType::from_extension("JPG"), + Some(DocumentType::Jpeg) + ); assert_eq!(DocumentType::from_extension("Pdf"), Some(DocumentType::Pdf)); } @@ -410,8 +422,14 @@ mod tests { #[test] fn test_duplex_mode_keywords() { assert_eq!(DuplexMode::Simplex.ipp_sides_keyword(), "one-sided"); - assert_eq!(DuplexMode::LongEdge.ipp_sides_keyword(), "two-sided-long-edge"); - assert_eq!(DuplexMode::ShortEdge.ipp_sides_keyword(), "two-sided-short-edge"); + assert_eq!( + DuplexMode::LongEdge.ipp_sides_keyword(), + "two-sided-long-edge" + ); + assert_eq!( + DuplexMode::ShortEdge.ipp_sides_keyword(), + "two-sided-short-edge" + ); } #[test] diff --git a/crates/presswerk-core/tests/aspect_test.rs b/crates/presswerk-core/tests/aspect_test.rs index b263b30..1dfa89b 100644 --- a/crates/presswerk-core/tests/aspect_test.rs +++ b/crates/presswerk-core/tests/aspect_test.rs @@ -5,7 +5,7 @@ // Tests security properties, invariants, and edge cases. use presswerk_core::{ - AppConfig, DocumentType, PrintJob, PrintSettings, JobSource, PageRange, PaperSize, + AppConfig, DocumentType, JobSource, PageRange, PaperSize, PrintJob, PrintSettings, }; // ============================================================================ @@ -34,8 +34,11 @@ fn aspect_html_injection_in_document_name() { for name in malicious_names { // These should be detected as suspicious by the application assert!( - name.contains('<') || name.contains('>') || name.contains('\'') - || name.contains('/') || name.contains('.'), + name.contains('<') + || name.contains('>') + || name.contains('\'') + || name.contains('/') + || name.contains('.'), "Potential injection in: {}", name ); @@ -55,7 +58,11 @@ fn aspect_path_traversal_prevention() { // Check for path traversal indicators let has_traversal = path.contains("../") || path.contains("..\\") || path.starts_with('/'); if should_be_suspicious { - assert!(has_traversal, "Path traversal pattern should be detected in: {}", path); + assert!( + has_traversal, + "Path traversal pattern should be detected in: {}", + path + ); } } } @@ -90,7 +97,10 @@ fn aspect_page_count_validation() { "hash".to_string(), ); - job.settings.page_range = Some(PageRange { start: 100, end: 50 }); + job.settings.page_range = Some(PageRange { + start: 100, + end: 50, + }); // Invalid range: start > end should be detected if let Some(range) = &job.settings.page_range { @@ -113,7 +123,11 @@ fn aspect_invalid_dpi_detection() { const MAX_DPI: u32 = 1200; for dpi in valid_dpis { - assert!(dpi >= MIN_DPI && dpi <= MAX_DPI, "DPI {} should be valid", dpi); + assert!( + dpi >= MIN_DPI && dpi <= MAX_DPI, + "DPI {} should be valid", + dpi + ); } for dpi in invalid_dpis { @@ -177,7 +191,10 @@ fn aspect_error_message_sanitization() { // Check for common sensitive patterns let contains_password = msg.contains("password"); let contains_ip = msg.contains("192.168"); - assert!(contains_password || contains_ip, "Error message should contain sensitive data (for testing)"); + assert!( + contains_password || contains_ip, + "Error message should contain sensitive data (for testing)" + ); // In production, these should be redacted } } @@ -246,7 +263,10 @@ fn aspect_max_copies_limit() { // Beyond practical limits would need manual override let excessive_copies = 10_000u32; - assert!(excessive_copies > MAX_COPIES, "Excessive copies should be detectable"); + assert!( + excessive_copies > MAX_COPIES, + "Excessive copies should be detectable" + ); } #[test] diff --git a/crates/presswerk-core/tests/e2e_test.rs b/crates/presswerk-core/tests/e2e_test.rs index 4475767..63a5891 100644 --- a/crates/presswerk-core/tests/e2e_test.rs +++ b/crates/presswerk-core/tests/e2e_test.rs @@ -5,8 +5,8 @@ // Tests the complete workflows: configuration, job creation, serialization. use presswerk_core::{ - AppConfig, DocumentType, DuplexMode, Orientation, PaperSize, PageRange, - PrintJob, PrintSettings, JobSource, JobStatus, ErrorClass, + AppConfig, DocumentType, DuplexMode, ErrorClass, JobSource, JobStatus, Orientation, PageRange, + PaperSize, PrintJob, PrintSettings, }; #[test] @@ -164,8 +164,14 @@ fn e2e_paper_size_ipp_mapping() { #[test] fn e2e_duplex_mode_ipp_mapping() { assert_eq!(DuplexMode::Simplex.ipp_sides_keyword(), "one-sided"); - assert_eq!(DuplexMode::LongEdge.ipp_sides_keyword(), "two-sided-long-edge"); - assert_eq!(DuplexMode::ShortEdge.ipp_sides_keyword(), "two-sided-short-edge"); + assert_eq!( + DuplexMode::LongEdge.ipp_sides_keyword(), + "two-sided-long-edge" + ); + assert_eq!( + DuplexMode::ShortEdge.ipp_sides_keyword(), + "two-sided-short-edge" + ); } #[test] diff --git a/crates/presswerk-core/tests/property_test.rs b/crates/presswerk-core/tests/property_test.rs index 15a11fa..dd8859e 100644 --- a/crates/presswerk-core/tests/property_test.rs +++ b/crates/presswerk-core/tests/property_test.rs @@ -4,7 +4,9 @@ // Property-based tests for presswerk-core using proptest. // These tests verify invariants that must hold across all valid inputs. -use presswerk_core::{AppConfig, DuplexMode, DocumentType, JobSource, Orientation, PaperSize, PrintJob, PrintSettings}; +use presswerk_core::{ + AppConfig, DocumentType, DuplexMode, JobSource, Orientation, PaperSize, PrintJob, PrintSettings, +}; use proptest::prelude::*; // ============================================================================ diff --git a/crates/presswerk-document/src/convert.rs b/crates/presswerk-document/src/convert.rs index d2d65dd..4e36ef1 100644 --- a/crates/presswerk-document/src/convert.rs +++ b/crates/presswerk-document/src/convert.rs @@ -91,14 +91,10 @@ fn conversion_chain(source: DocumentType) -> Vec { DocumentType::Pcl, DocumentType::PwgRaster, ], - DocumentType::PlainText => vec![ - DocumentType::Pdf, - DocumentType::PostScript, - ], - DocumentType::Jpeg | DocumentType::Png | DocumentType::Tiff => vec![ - DocumentType::Pdf, - DocumentType::PwgRaster, - ], + DocumentType::PlainText => vec![DocumentType::Pdf, DocumentType::PostScript], + DocumentType::Jpeg | DocumentType::Png | DocumentType::Tiff => { + vec![DocumentType::Pdf, DocumentType::PwgRaster] + } _ => vec![DocumentType::Pdf], } } @@ -109,11 +105,7 @@ fn conversion_chain(source: DocumentType) -> Vec { /// - PDF → PostScript: Ghostscript bindings or pure-Rust PS generator /// - PDF → Raster: pdf-render or similar crate /// - Text → PDF: Already handled by PdfWriter::create_from_text -fn convert( - document_bytes: &[u8], - from: DocumentType, - to: DocumentType, -) -> Result> { +fn convert(document_bytes: &[u8], from: DocumentType, to: DocumentType) -> Result> { match (from, to) { // Text → PDF: use PdfWriter (DocumentType::PlainText, DocumentType::Pdf) => { @@ -164,10 +156,7 @@ fn convert( } /// Rasterise a document to PNG as the ultimate fallback. -fn rasterise_to_png( - document_bytes: &[u8], - source: DocumentType, -) -> Result> { +fn rasterise_to_png(document_bytes: &[u8], source: DocumentType) -> Result> { match source { // Images: just convert to PNG DocumentType::Jpeg | DocumentType::Tiff => { @@ -202,7 +191,8 @@ mod tests { supported.insert("application/pdf".into()); let Ok((result, doc_type)) = - DocumentConverter::auto_convert(bytes, DocumentType::Pdf, &supported) else { + DocumentConverter::auto_convert(bytes, DocumentType::Pdf, &supported) + else { panic!("auto_convert failed for native format"); }; assert_eq!(result, bytes); @@ -215,7 +205,8 @@ mod tests { let supported = HashSet::new(); let Ok((result, doc_type)) = - DocumentConverter::auto_convert(bytes, DocumentType::Pdf, &supported) else { + DocumentConverter::auto_convert(bytes, DocumentType::Pdf, &supported) + else { panic!("auto_convert failed for empty supported"); }; assert_eq!(result, bytes); diff --git a/crates/presswerk-print/src/capabilities.rs b/crates/presswerk-print/src/capabilities.rs index ef72e7d..7c3442b 100644 --- a/crates/presswerk-print/src/capabilities.rs +++ b/crates/presswerk-print/src/capabilities.rs @@ -34,8 +34,7 @@ impl PrinterCapabilities { pub fn from_attributes(attrs: &PrinterAttributes) -> Self { let media_supported = parse_set(attrs.get("media-supported")); let sides_supported = parse_set(attrs.get("sides-supported")); - let document_formats_supported = - parse_set(attrs.get("document-format-supported")); + let document_formats_supported = parse_set(attrs.get("document-format-supported")); // Default to true (assume colour) when attribute is absent — same // "unknown = assume yes" pattern used for media and sides. @@ -209,19 +208,13 @@ pub fn auto_correct_settings( } /// Validate settings and return the result without correcting. -pub fn validate_settings( - settings: &PrintSettings, - caps: &PrinterCapabilities, -) -> ValidationResult { +pub fn validate_settings(settings: &PrintSettings, caps: &PrinterCapabilities) -> ValidationResult { let (_, result) = auto_correct_settings(settings, caps); result } /// Try to find the closest standard paper size from the supported set. -fn find_closest_media( - requested: &PaperSize, - supported: &HashSet, -) -> Option { +fn find_closest_media(requested: &PaperSize, supported: &HashSet) -> Option { let (req_w, req_h) = requested.dimensions_mm(); let req_area = req_w * req_h; diff --git a/crates/presswerk-print/src/diagnostics.rs b/crates/presswerk-print/src/diagnostics.rs index caa9a96..c7c5078 100644 --- a/crates/presswerk-print/src/diagnostics.rs +++ b/crates/presswerk-print/src/diagnostics.rs @@ -208,7 +208,10 @@ fn check_network() -> StepResult { name: "Network Check".into(), passed: false, detail: "No network connection found.".into(), - fix: Some("Connect to your home Wi-Fi network. Go to Settings \u{2192} Wi-Fi on your phone.".into()), + fix: Some( + "Connect to your home Wi-Fi network. Go to Settings \u{2192} Wi-Fi on your phone." + .into(), + ), escalation: None, } } @@ -313,10 +316,7 @@ async fn check_ipp_support(uri: &str) -> StepResult { } } -async fn check_printer_ready( - uri: &str, - report: &mut DiagnosticReport, -) -> StepResult { +async fn check_printer_ready(uri: &str, report: &mut DiagnosticReport) -> StepResult { let client = match crate::ipp_client::IppClient::new(uri) { Ok(c) => c, Err(_) => { @@ -404,10 +404,7 @@ async fn check_printer_ready( } /// Interpret printer-state-reasons into human messages. -fn interpret_stop_reasons( - name: &str, - reasons: &[String], -) -> (String, String, Option) { +fn interpret_stop_reasons(name: &str, reasons: &[String]) -> (String, String, Option) { for reason in reasons { let lower = reason.to_ascii_lowercase(); if lower.contains("media-empty") || lower.contains("paper") && lower.contains("empty") { @@ -417,7 +414,8 @@ fn interpret_stop_reasons( None, ); } - if lower.contains("toner-empty") || lower.contains("marker-supply") || lower.contains("ink") { + if lower.contains("toner-empty") || lower.contains("marker-supply") || lower.contains("ink") + { return ( format!("{name} needs new ink or toner."), "You'll need to buy a replacement cartridge. Check the printer model number and search online.".into(), diff --git a/crates/presswerk-print/src/health.rs b/crates/presswerk-print/src/health.rs index 082d7da..9d9d629 100644 --- a/crates/presswerk-print/src/health.rs +++ b/crates/presswerk-print/src/health.rs @@ -78,10 +78,7 @@ impl HealthTracker { /// Returns `true` if the circuit is closed or half-open (probe allowed). /// Returns `false` if the circuit is open (cooldown still active). pub fn allow_request(&mut self, printer_uri: &str) -> bool { - let health = self - .printers - .entry(printer_uri.to_string()) - .or_default(); + let health = self.printers.entry(printer_uri.to_string()).or_default(); match health.state { CircuitState::Closed => true, @@ -120,10 +117,7 @@ impl HealthTracker { /// Record a successful operation for this printer. pub fn record_success(&mut self, printer_uri: &str) { - let health = self - .printers - .entry(printer_uri.to_string()) - .or_default(); + let health = self.printers.entry(printer_uri.to_string()).or_default(); if health.state != CircuitState::Closed { info!( @@ -142,10 +136,7 @@ impl HealthTracker { /// Record a failed operation for this printer. pub fn record_failure(&mut self, printer_uri: &str, error: &str) { - let health = self - .printers - .entry(printer_uri.to_string()) - .or_default(); + let health = self.printers.entry(printer_uri.to_string()).or_default(); health.consecutive_failures += 1; health.last_error = Some(error.to_string()); @@ -193,9 +184,7 @@ impl HealthTracker { remaining.as_secs() )) } - CircuitState::HalfOpen => { - Some("Checking if the printer has recovered...".into()) - } + CircuitState::HalfOpen => Some("Checking if the printer has recovered...".into()), } } } diff --git a/crates/presswerk-print/src/ipp_client.rs b/crates/presswerk-print/src/ipp_client.rs index e5bc0fc..4ce6e6d 100644 --- a/crates/presswerk-print/src/ipp_client.rs +++ b/crates/presswerk-print/src/ipp_client.rs @@ -148,7 +148,12 @@ impl IppClient { builder = builder.attribute(IppAttribute::new( "print-color-mode", IppValue::Keyword( - if settings.color { "color" } else { "monochrome" }.into(), + if settings.color { + "color" + } else { + "monochrome" + } + .into(), ), )); @@ -217,10 +222,7 @@ impl IppClient { ) .await .map_err(|_| { - PresswerkError::IppRequest(format!( - "Get-Jobs timed out after {}s", - QUERY_TIMEOUT_SECS - )) + PresswerkError::IppRequest(format!("Get-Jobs timed out after {}s", QUERY_TIMEOUT_SECS)) })? .map_err(|e| PresswerkError::IppRequest(format!("Get-Jobs: {e}")))?; diff --git a/crates/presswerk-print/src/ipp_server.rs b/crates/presswerk-print/src/ipp_server.rs index 28fe1ca..42271e9 100644 --- a/crates/presswerk-print/src/ipp_server.rs +++ b/crates/presswerk-print/src/ipp_server.rs @@ -1599,7 +1599,13 @@ mod tests { 99 ); // Last byte is end-of-attributes - assert_eq!(bytes.last().copied().unwrap_or_else(|| panic!("empty bytes")), TAG_END_OF_ATTRIBUTES); + assert_eq!( + bytes + .last() + .copied() + .unwrap_or_else(|| panic!("empty bytes")), + TAG_END_OF_ATTRIBUTES + ); } #[test] @@ -1673,7 +1679,10 @@ mod tests { "; let result = parse_http_envelope(http); assert!(result.is_some()); - let Some(req) = result else { panic!("parse_http_envelope failed") }; let req = req; + let Some(req) = result else { + panic!("parse_http_envelope failed") + }; + let req = req; assert_eq!(req.content_length, Some(42)); assert!(req.body_offset > 0); assert_eq!(&http[req.body_offset..], b""); @@ -1775,7 +1784,9 @@ mod tests { let state = make_shared_state(); let data = build_test_ipp_request(OP_GET_PRINTER_ATTRIBUTES, 50, &[], &[]); let req = parse_ipp_request(&data).expect("parse_ipp_request failed"); - let peer: SocketAddr = "127.0.0.1:12345".parse().expect("hardcoded address is invalid"); + let peer: SocketAddr = "127.0.0.1:12345" + .parse() + .expect("hardcoded address is invalid"); let response = dispatch_operation(&req, peer, &state); let parsed = parse_ipp_request(&response).expect("parse_ipp_request failed"); @@ -1804,7 +1815,9 @@ mod tests { let state = make_shared_state(); let data = build_test_ipp_request(OP_VALIDATE_JOB, 12, &[], &[]); let req = parse_ipp_request(&data).expect("parse_ipp_request failed"); - let peer: SocketAddr = "127.0.0.1:12345".parse().expect("hardcoded address is invalid"); + let peer: SocketAddr = "127.0.0.1:12345" + .parse() + .expect("hardcoded address is invalid"); let response = dispatch_operation(&req, peer, &state); let parsed = parse_ipp_request(&response).expect("parse_ipp_request failed"); @@ -1823,7 +1836,9 @@ mod tests { ]; let data = build_test_ipp_request(OP_PRINT_JOB, 20, &attrs, doc); let req = parse_ipp_request(&data).expect("parse_ipp_request failed"); - let peer: SocketAddr = "192.168.1.50:54321".parse().expect("hardcoded address is invalid"); + let peer: SocketAddr = "192.168.1.50:54321" + .parse() + .expect("hardcoded address is invalid"); let response = dispatch_operation(&req, peer, &state); let parsed = parse_ipp_request(&response).expect("parse_ipp_request failed"); @@ -1857,16 +1872,21 @@ mod tests { let doc = b"some data"; let data = build_test_ipp_request(OP_PRINT_JOB, 30, &[], doc); let req = parse_ipp_request(&data).expect("parse_ipp_request failed"); - let peer: SocketAddr = "127.0.0.1:12345".parse().expect("hardcoded address is invalid"); + let peer: SocketAddr = "127.0.0.1:12345" + .parse() + .expect("hardcoded address is invalid"); let response = dispatch_operation(&req, peer, &state); let parsed = parse_ipp_request(&response).expect("parse_ipp_request failed"); let Some(job_group) = parsed .attribute_groups .iter() - .find(|g| g.delimiter == TAG_JOB_ATTRIBUTES) else { + .find(|g| g.delimiter == TAG_JOB_ATTRIBUTES) + else { panic!("job attributes group missing from response"); }; - let ipp_job_id = job_group.get_integer("job-id").expect("job-id attribute missing"); + let ipp_job_id = job_group + .get_integer("job-id") + .expect("job-id attribute missing"); // Now cancel it. let job_id_bytes = ipp_job_id.to_be_bytes(); @@ -1894,7 +1914,9 @@ mod tests { let attrs = vec![(VALUE_TAG_INTEGER, "job-id", &job_id_bytes[..])]; let data = build_test_ipp_request(OP_CANCEL_JOB, 40, &attrs, &[]); let req = parse_ipp_request(&data).expect("parse_ipp_request failed"); - let peer: SocketAddr = "127.0.0.1:12345".parse().expect("hardcoded address is invalid"); + let peer: SocketAddr = "127.0.0.1:12345" + .parse() + .expect("hardcoded address is invalid"); let response = dispatch_operation(&req, peer, &state); let parsed = parse_ipp_request(&response).expect("parse_ipp_request failed"); @@ -1907,7 +1929,9 @@ mod tests { let state = make_shared_state(); let data = build_test_ipp_request(OP_GET_JOBS, 60, &[], &[]); let req = parse_ipp_request(&data).expect("parse_ipp_request failed"); - let peer: SocketAddr = "127.0.0.1:12345".parse().expect("hardcoded address is invalid"); + let peer: SocketAddr = "127.0.0.1:12345" + .parse() + .expect("hardcoded address is invalid"); let response = dispatch_operation(&req, peer, &state); let parsed = parse_ipp_request(&response).expect("parse_ipp_request failed"); @@ -1920,7 +1944,9 @@ mod tests { #[test] fn dispatch_get_jobs_after_print() { let state = make_shared_state(); - let peer: SocketAddr = "127.0.0.1:12345".parse().expect("hardcoded address is invalid"); + let peer: SocketAddr = "127.0.0.1:12345" + .parse() + .expect("hardcoded address is invalid"); // Submit two jobs. for i in 0..2 { @@ -1953,7 +1979,9 @@ mod tests { // Use a non-existent operation ID. let data = build_test_ipp_request(0x00FF, 70, &[], &[]); let req = parse_ipp_request(&data).expect("parse_ipp_request failed"); - let peer: SocketAddr = "127.0.0.1:12345".parse().expect("hardcoded address is invalid"); + let peer: SocketAddr = "127.0.0.1:12345" + .parse() + .expect("hardcoded address is invalid"); let response = dispatch_operation(&req, peer, &state); let parsed = parse_ipp_request(&response).expect("parse_ipp_request failed"); @@ -2024,7 +2052,9 @@ mod tests { ]; let data = build_test_ipp_request(OP_PRINT_JOB, 200, &attrs, doc); let req = parse_ipp_request(&data).expect("parse_ipp_request failed"); - let peer: SocketAddr = "10.0.0.1:9999".parse().expect("hardcoded address is invalid"); + let peer: SocketAddr = "10.0.0.1:9999" + .parse() + .expect("hardcoded address is invalid"); let response = dispatch_operation(&req, peer, &state); let parsed = parse_ipp_request(&response).expect("parse_ipp_request failed"); @@ -2052,7 +2082,9 @@ mod tests { let state = make_shared_state_with_dir(tmp.path()); let doc = b"identical content for dedup test"; - let peer: SocketAddr = "10.0.0.1:9999".parse().expect("hardcoded address is invalid"); + let peer: SocketAddr = "10.0.0.1:9999" + .parse() + .expect("hardcoded address is invalid"); // Submit the same document data twice (different job names). for i in 0..2u32 { @@ -2088,7 +2120,9 @@ mod tests { // Submit a job with empty document data. let data = build_test_ipp_request(OP_PRINT_JOB, 400, &[], &[]); let req = parse_ipp_request(&data).expect("parse_ipp_request failed"); - let peer: SocketAddr = "10.0.0.1:9999".parse().expect("hardcoded address is invalid"); + let peer: SocketAddr = "10.0.0.1:9999" + .parse() + .expect("hardcoded address is invalid"); let response = dispatch_operation(&req, peer, &state); let parsed = parse_ipp_request(&response).expect("parse_ipp_request failed"); @@ -2123,7 +2157,8 @@ mod tests { let documents_dir = tmp.path().join("documents"); std::fs::create_dir_all(&documents_dir).expect("filesystem operation failed"); let content = b"test document bytes"; - std::fs::write(documents_dir.join("deadbeef.dat"), content).expect("filesystem operation failed"); + std::fs::write(documents_dir.join("deadbeef.dat"), content) + .expect("filesystem operation failed"); let retrieved = server .retrieve_document("deadbeef") @@ -2153,7 +2188,9 @@ mod tests { let attrs = vec![(VALUE_TAG_NAME, "job-name", b"Roundtrip Test" as &[u8])]; let data = build_test_ipp_request(OP_PRINT_JOB, 500, &attrs, doc); let req = parse_ipp_request(&data).expect("parse_ipp_request failed"); - let peer: SocketAddr = "10.0.0.1:9999".parse().expect("hardcoded address is invalid"); + let peer: SocketAddr = "10.0.0.1:9999" + .parse() + .expect("hardcoded address is invalid"); let response = dispatch_operation(&req, peer, &state); let parsed = parse_ipp_request(&response).expect("parse_ipp_request failed"); diff --git a/crates/presswerk-print/src/lpr_client.rs b/crates/presswerk-print/src/lpr_client.rs index 25fb192..6d2c894 100644 --- a/crates/presswerk-print/src/lpr_client.rs +++ b/crates/presswerk-print/src/lpr_client.rs @@ -27,12 +27,7 @@ const LPR_TIMEOUT_SECS: u64 = 60; /// 1. Send "receive job" command (0x02) /// 2. Send control file with job metadata /// 3. Send data file with document bytes -pub async fn send_lpr( - ip: &str, - port: u16, - document_bytes: &[u8], - job_name: &str, -) -> Result<()> { +pub async fn send_lpr(ip: &str, port: u16, document_bytes: &[u8], job_name: &str) -> Result<()> { let addr = format!("{}:{}", ip, port); info!(addr = %addr, job = job_name, "connecting via LPR"); @@ -73,13 +68,10 @@ pub async fn send_lpr( // Send control file let job_num = 1; // simplified — a real client would track this let hostname = "presswerk"; - let control_file = format!("H{hostname}\nP{hostname}\nJ{job_name}\nldfA{job_num:03}{hostname}\nUdfA{job_num:03}{hostname}\nN{job_name}\n"); - let cf_header = format!( - "\x02{} cfA{:03}{}\n", - control_file.len(), - job_num, - hostname + let control_file = format!( + "H{hostname}\nP{hostname}\nJ{job_name}\nldfA{job_num:03}{hostname}\nUdfA{job_num:03}{hostname}\nN{job_name}\n" ); + let cf_header = format!("\x02{} cfA{:03}{}\n", control_file.len(), job_num, hostname); stream .write_all(cf_header.as_bytes()) diff --git a/crates/presswerk-print/src/protocol.rs b/crates/presswerk-print/src/protocol.rs index da337fe..60beea4 100644 --- a/crates/presswerk-print/src/protocol.rs +++ b/crates/presswerk-print/src/protocol.rs @@ -73,10 +73,7 @@ pub struct ProbeResult { /// /// Returns the results for ALL protocols (not just the first success). /// This is used by the diagnostics engine to give a complete picture. -pub async fn probe_all_protocols( - ip: &str, - base_port: u16, -) -> Vec { +pub async fn probe_all_protocols(ip: &str, base_port: u16) -> Vec { let mut results = Vec::new(); for protocol in PrintProtocol::chain() { @@ -97,10 +94,7 @@ pub async fn probe_all_protocols( /// Tries each protocol in security order and returns the first that works. /// Transparent to the user — they just see "Trying the best way to talk to /// your printer..." -pub async fn find_best_protocol( - ip: &str, - base_port: u16, -) -> Option { +pub async fn find_best_protocol(ip: &str, base_port: u16) -> Option { for protocol in PrintProtocol::chain() { let port = if base_port != 631 { base_port @@ -111,9 +105,7 @@ pub async fn find_best_protocol( if result.success { info!( protocol = protocol.display_name(), - ip, - port, - "found working protocol" + ip, port, "found working protocol" ); return Some(*protocol); } @@ -158,9 +150,7 @@ pub async fn send_via_protocol( PrintProtocol::Lpr => { crate::lpr_client::send_lpr(ip, port, &document_bytes, job_name).await } - PrintProtocol::RawTcp => { - crate::raw_client::send_raw(ip, port, &document_bytes).await - } + PrintProtocol::RawTcp => crate::raw_client::send_raw(ip, port, &document_bytes).await, } } @@ -187,8 +177,7 @@ async fn probe_protocol(ip: &str, port: u16, protocol: PrintProtocol) -> ProbeRe } async fn probe_ipp(uri: &str) -> std::result::Result<(), String> { - let client = - crate::ipp_client::IppClient::new(uri).map_err(|e| e.to_string())?; + let client = crate::ipp_client::IppClient::new(uri).map_err(|e| e.to_string())?; client .get_printer_attributes() .await @@ -198,7 +187,9 @@ async fn probe_ipp(uri: &str) -> std::result::Result<(), String> { async fn probe_tcp(ip: &str, port: u16) -> std::result::Result<(), String> { let addr = format!("{}:{}", ip, port); - let addr: std::net::SocketAddr = addr.parse().map_err(|e: std::net::AddrParseError| e.to_string())?; + let addr: std::net::SocketAddr = addr + .parse() + .map_err(|e: std::net::AddrParseError| e.to_string())?; tokio::net::TcpStream::connect(addr) .await .map_err(|e| e.to_string())?; diff --git a/crates/presswerk-print/src/queue.rs b/crates/presswerk-print/src/queue.rs index a4cc78a..2737283 100644 --- a/crates/presswerk-print/src/queue.rs +++ b/crates/presswerk-print/src/queue.rs @@ -13,7 +13,9 @@ use rusqlite::{Connection, params}; use tracing::{debug, info, instrument}; use presswerk_core::error::{PresswerkError, Result}; -use presswerk_core::types::{DocumentType, ErrorClass, JobId, JobSource, JobStatus, PrintJob, PrintSettings}; +use presswerk_core::types::{ + DocumentType, ErrorClass, JobId, JobSource, JobStatus, PrintJob, PrintSettings, +}; /// SQLite schema for the jobs table. const CREATE_TABLE_SQL: &str = r#" @@ -354,8 +356,7 @@ fn row_to_print_job(row: &rusqlite::Row<'_>) -> rusqlite::Result { let error_class: Option = error_class_json.and_then(|s| serde_json::from_str(&s).ok()); - let error_history: Vec = - serde_json::from_str(&error_history_json).unwrap_or_default(); + let error_history: Vec = serde_json::from_str(&error_history_json).unwrap_or_default(); Ok(PrintJob { id: JobId(uuid), diff --git a/crates/presswerk-print/src/raw_client.rs b/crates/presswerk-print/src/raw_client.rs index 252fb0f..cecb68d 100644 --- a/crates/presswerk-print/src/raw_client.rs +++ b/crates/presswerk-print/src/raw_client.rs @@ -29,11 +29,7 @@ const RAW_TIMEOUT_SECS: u64 = 60; /// transmission, no job tracking. /// /// Returns the number of bytes sent (for progress tracking/resumption). -pub async fn send_raw( - ip: &str, - port: u16, - document_bytes: &[u8], -) -> Result<()> { +pub async fn send_raw(ip: &str, port: u16, document_bytes: &[u8]) -> Result<()> { send_raw_with_offset(ip, port, document_bytes, 0).await } @@ -71,15 +67,9 @@ pub async fn send_raw_with_offset( let mut sent = offset; for chunk in remaining.chunks(chunk_size) { - stream - .write_all(chunk) - .await - .map_err(|e| { - PresswerkError::IppRequest(format!( - "Raw TCP send failed at byte {}: {}", - sent, e - )) - })?; + stream.write_all(chunk).await.map_err(|e| { + PresswerkError::IppRequest(format!("Raw TCP send failed at byte {}: {}", sent, e)) + })?; sent += chunk.len(); debug!(sent, total = document_bytes.len(), "raw TCP progress"); } diff --git a/crates/presswerk-print/src/resilience.rs b/crates/presswerk-print/src/resilience.rs index 0eb5817..9542a89 100644 --- a/crates/presswerk-print/src/resilience.rs +++ b/crates/presswerk-print/src/resilience.rs @@ -106,10 +106,7 @@ impl NetworkResilience { /// Number of jobs in the buffer. pub fn buffered_count(&self) -> usize { - self.buffer - .lock() - .map(|b| b.len()) - .unwrap_or(0) + self.buffer.lock().map(|b| b.len()).unwrap_or(0) } /// Take all buffered jobs for delivery (empties the buffer). diff --git a/crates/presswerk-print/src/retry.rs b/crates/presswerk-print/src/retry.rs index 55e0e39..d4d2d60 100644 --- a/crates/presswerk-print/src/retry.rs +++ b/crates/presswerk-print/src/retry.rs @@ -122,11 +122,7 @@ fn classify_ipp_detail(detail: &str) -> ErrorClass { } /// Decide whether to retry based on the error class and attempt count. -pub fn should_retry( - err: &PresswerkError, - attempt: u32, - config: &RetryConfig, -) -> RetryDecision { +pub fn should_retry(err: &PresswerkError, attempt: u32, config: &RetryConfig) -> RetryDecision { let class = classify_error(err); match class { @@ -195,8 +191,7 @@ mod tests { #[test] fn bad_format_is_permanent() { - let err = - PresswerkError::IppRequest("client-error-document-format-not-supported".into()); + let err = PresswerkError::IppRequest("client-error-document-format-not-supported".into()); assert_eq!(classify_error(&err), ErrorClass::Permanent); } @@ -207,8 +202,14 @@ mod tests { ..Default::default() }; let err = PresswerkError::IppRequest("connection refused".into()); - assert!(matches!(should_retry(&err, 0, &config), RetryDecision::RetryAfter(_))); - assert!(matches!(should_retry(&err, 3, &config), RetryDecision::Exhausted)); + assert!(matches!( + should_retry(&err, 0, &config), + RetryDecision::RetryAfter(_) + )); + assert!(matches!( + should_retry(&err, 3, &config), + RetryDecision::Exhausted + )); } #[test] diff --git a/crates/presswerk-print/src/revival.rs b/crates/presswerk-print/src/revival.rs index edad0e4..3e81162 100644 --- a/crates/presswerk-print/src/revival.rs +++ b/crates/presswerk-print/src/revival.rs @@ -96,9 +96,7 @@ pub async fn purge_stuck_jobs(printer_uri: &str) -> Result<()> { /// Probe a printer's current status. /// /// Returns a tuple of (state, reasons) from Get-Printer-Attributes. -pub async fn probe_status( - printer_uri: &str, -) -> Result<(String, Vec)> { +pub async fn probe_status(printer_uri: &str) -> Result<(String, Vec)> { let client = crate::ipp_client::IppClient::new(printer_uri)?; let attrs = client.get_printer_attributes().await?;