Tutti i playbookNative Utility

Aggiungere un job handler

Eseguire un lavoro lungo del prodotto come job in un crate dedicato, con checkpoint per pausa e annullamento, eventi di avanzamento, codici di errore stabili e test tramite l'app-api pubblica.

Quando usarlo

Il prodotto svolge un lavoro che può durare secondi o ore: leggere un albero di cartelle, importare file, comunicare con un dispositivo. I passi usano come esempio un job acme.import in un crate crates/acme-import; examples/rescuewerk mostra lo stesso schema con la scansione di una cartella.

Regole da rispettare

  • Il codice di prodotto vive in crate di prodotto che dipendono da app-api; i crate del template (crates/app-*) non dipendono mai da un prodotto.
  • Un comando non aspetta mai un job. start_job restituisce subito l'id e la UI segue gli eventi; wait_for_job serve solo alla CLI e ai test.
  • L'handler chiama ctx.checkpoint()? almeno una volta per unità di lavoro: pausa e annullamento hanno effetto solo lì.
  • Errori e avvisi sono codici RW_* stabili, tradotti nelle tre lingue. Non mettere mai frasi o percorsi dell'utente in un codice.
  • Un percorso scelto nella UI passa da validatePath prima di diventare un parametro del job.

Passi

  1. Crea crates/acme-import/ con un Cargo.toml come examples/rescuewerk/crates/rescuewerk-scan/Cargo.toml (campi del workspace, app-api e serde_json, app-config per i test). Il workspace include già crates/*. Tieni i crate di prodotto sotto crates/: il test del catalogo in packages/i18n/src/catalog.test.ts controlla i codici RW_* trovati in crates/ e apps/, non in examples/.
  2. Implementa JobHandler come in rescuewerk-scan/src/lib.rs e docs/job-engine.md: leggi e controlla ctx.params(), fallendo con JobError::failed("RW_IMPORT_...") per un input non valido; chiama ctx.checkpoint()? nel ciclo; segnala ctx.progress(done, total) (None finché il totale non è noto, il valore finale alla fine); usa ctx.warn per gli elementi che si possono saltare. Esporta il tipo come costante, per esempio pub const IMPORT_JOB_KIND: &str = "acme.import";.
  3. Testa il job in crates/acme-import/tests/ tramite AppApi, come fa rescuewerk-scan/tests/scan.rs: costruisci l'API con in_memory() e .job_handler(IMPORT_JOB_KIND, Arc::new(ImportJob)), avvia il job, attendi con wait_for_job e verifica lo stato finale, done/total e l'error_code per un input non valido. Usa file in fixtures/, mai percorsi della macchina dello sviluppatore.
  4. Registra l'handler nella shell: aggiungi acme-import = { path = "crates/acme-import" } in [workspace.dependencies] di Cargo.toml e acme-import.workspace = true in apps/desktop/src-tauri/Cargo.toml, poi chiama .job_handler(...) sul builder in apps/desktop/src-tauri/src/main.rs. Fai lo stesso in apps/cli se la CLI deve eseguirlo, e fai commit del Cargo.lock aggiornato (la CI esegue cargo fetch --locked). Non serve un nuovo comando Tauri: start_job, pause_job, resume_job e cancel_job esistono già.
  5. Nella UI costruisci la richiesta in un service della feature, come sampleJobRequest in apps/desktop/src/features/jobs/services/job-requests.ts: valida e limita i parametri e risolvi prima i percorsi con validatePath. Avvialo con startJob e seguilo tramite le query dei job, che gli eventi già aggiornano.
  6. Insegna il nuovo tipo a MockNativeClient in packages/native-client/src/mock.ts (rifiuta i tipi sconosciuti con RW_JOB_KIND_UNKNOWN), così pnpm dev e i test dei componenti funzionano senza Rust.
  7. Aggiungi ogni nuovo codice RW_* in packages/i18n/locales/*/errors.json in de-DE, en e it-IT, e i testi della UI in common.json.

Completato quando

  • i comandi di verifica superano;
  • il job si può mettere in pausa, riprendere e annullare mentre gira, e arriva a COMPLETED o FAILED con un codice tradotto;
  • il test di integrazione passa da AppApi senza rete né percorsi della macchina;
  • ogni nuovo codice esiste nelle tre lingue.