11//! Spin doctor: check and automatically fix problems with Spin apps.
22#![ deny( missing_docs) ]
33
4- use std:: { fmt:: Debug , fs, future :: Future , path:: PathBuf , pin :: Pin , sync :: Arc } ;
4+ use std:: { collections :: VecDeque , fmt:: Debug , fs, path:: PathBuf } ;
55
66use anyhow:: { ensure, Context , Result } ;
77use async_trait:: async_trait;
8- use tokio:: sync:: Mutex ;
98use toml_edit:: Document ;
109
1110/// Diagnoses for app manifest format problems.
@@ -19,87 +18,98 @@ pub mod wasm;
1918
2019/// Configuration for an app to be checked for problems.
2120pub struct Checkup {
22- manifest_path : PathBuf ,
23- diagnostics : Vec < Box < dyn BoxingDiagnostic > > ,
21+ patient : PatientApp ,
22+ diagnostics : VecDeque < Box < dyn BoxingDiagnostic > > ,
23+ unprocessed_diagnoses : VecDeque < Box < dyn Diagnosis > > ,
2424}
2525
2626impl Checkup {
2727 /// Return a new checkup for the app manifest at the given path.
28- pub fn new ( manifest_path : impl Into < PathBuf > ) -> Self {
28+ pub fn new ( manifest_path : impl Into < PathBuf > ) -> Result < Self > {
29+ let patient = PatientApp :: new ( manifest_path) ?;
2930 let mut checkup = Self {
30- manifest_path : manifest_path. into ( ) ,
31- diagnostics : vec ! [ ] ,
31+ patient,
32+ diagnostics : Default :: default ( ) ,
33+ unprocessed_diagnoses : Default :: default ( ) ,
3234 } ;
33- checkup. add_diagnostic :: < manifest:: version:: VersionDiagnostic > ( ) ;
34- checkup. add_diagnostic :: < manifest:: trigger:: TriggerDiagnostic > ( ) ;
35- checkup. add_diagnostic :: < rustlang:: target:: TargetDiagnostic > ( ) ; // Do toolchain checks _before_ build checks
36- checkup. add_diagnostic :: < wasm:: missing:: WasmMissingDiagnostic > ( ) ;
3735 checkup
36+ . add_diagnostic :: < manifest:: version:: VersionDiagnostic > ( )
37+ . add_diagnostic :: < manifest:: trigger:: TriggerDiagnostic > ( )
38+ . add_diagnostic :: < rustlang:: target:: TargetDiagnostic > ( ) // Do toolchain checks _before_ build check
39+ . add_diagnostic :: < wasm:: missing:: WasmMissingDiagnostic > ( ) ;
40+ Ok ( checkup)
41+ }
42+
43+ /// Returns the [`PatientApp`] being checked.
44+ pub fn patient ( & self ) -> & PatientApp {
45+ & self . patient
3846 }
3947
4048 /// Add a detectable problem to this checkup.
4149 pub fn add_diagnostic < D : Diagnostic + Default + ' static > ( & mut self ) -> & mut Self {
42- self . diagnostics . push ( Box :: < D > :: default ( ) ) ;
50+ self . diagnostics . push_back ( Box :: < D > :: default ( ) ) ;
4351 self
4452 }
4553
46- fn patient ( & self ) -> Result < PatientApp > {
47- let path = & self . manifest_path ;
54+ /// Returns the next detected problem.
55+ pub async fn next_diagnosis ( & mut self ) -> Result < Option < PatientDiagnosis > > {
56+ while self . unprocessed_diagnoses . is_empty ( ) {
57+ let Some ( diagnostic) = self . diagnostics . pop_front ( ) else {
58+ return Ok ( None ) ;
59+ } ;
60+ self . unprocessed_diagnoses = diagnostic
61+ . diagnose_boxed ( & self . patient )
62+ . await
63+ . unwrap_or_else ( |err| {
64+ tracing:: debug!( "Diagnose failed: {err:?}" ) ;
65+ vec ! [ ]
66+ } )
67+ . into ( )
68+ }
69+ Ok ( Some ( PatientDiagnosis {
70+ patient : & mut self . patient ,
71+ diagnosis : self . unprocessed_diagnoses . pop_front ( ) . unwrap ( ) ,
72+ } ) )
73+ }
74+ }
75+
76+ /// An app "patient" to be checked for problems.
77+ #[ derive( Clone ) ]
78+ pub struct PatientApp {
79+ /// Path to an app manifest file.
80+ pub manifest_path : PathBuf ,
81+ /// Parsed app manifest TOML document.
82+ pub manifest_doc : Document ,
83+ }
84+
85+ impl PatientApp {
86+ fn new ( manifest_path : impl Into < PathBuf > ) -> Result < Self > {
87+ let path = manifest_path. into ( ) ;
4888 ensure ! (
4989 path. is_file( ) ,
5090 "No Spin app manifest file found at {path:?}"
5191 ) ;
5292
53- let contents = fs:: read_to_string ( path)
93+ let contents = fs:: read_to_string ( & path)
5494 . with_context ( || format ! ( "Couldn't read Spin app manifest file at {path:?}" ) ) ?;
5595
5696 let manifest_doc: Document = contents
5797 . parse ( )
5898 . with_context ( || format ! ( "Couldn't parse manifest file at {path:?} as valid TOML" ) ) ?;
5999
60- Ok ( PatientApp {
61- manifest_path : path. into ( ) ,
100+ Ok ( Self {
101+ manifest_path : path,
62102 manifest_doc,
63103 } )
64104 }
65-
66- /// Find problems with the configured app, calling the given closure with
67- /// each problem found.
68- pub async fn for_each_diagnosis < F > ( & self , mut f : F ) -> Result < usize >
69- where
70- F : for < ' a > FnMut (
71- Box < dyn Diagnosis + ' static > ,
72- & ' a mut PatientApp ,
73- ) -> Pin < Box < dyn Future < Output = Result < ( ) > > + ' a > > ,
74- {
75- let patient = Arc :: new ( Mutex :: new ( self . patient ( ) ?) ) ;
76- let mut count = 0 ;
77- for diagnostic in & self . diagnostics {
78- let patient = patient. clone ( ) ;
79- let diags = diagnostic
80- . diagnose_boxed ( & * patient. lock ( ) . await )
81- . await
82- . unwrap_or_else ( |err| {
83- tracing:: debug!( "Diagnose failed: {err:?}" ) ;
84- vec ! [ ]
85- } ) ;
86- count += diags. len ( ) ;
87- for diag in diags {
88- let mut patient = patient. lock ( ) . await ;
89- f ( diag, & mut patient) . await ?;
90- }
91- }
92- Ok ( count)
93- }
94105}
95106
96- /// An app "patient" to be checked for problems.
97- #[ derive( Clone ) ]
98- pub struct PatientApp {
99- /// Path to an app manifest file.
100- pub manifest_path : PathBuf ,
101- /// Parsed app manifest TOML document.
102- pub manifest_doc : Document ,
107+ /// A PatientDiagnosis bundles a [`Diagnosis`] with its (borrowed) [`PatientApp`].
108+ pub struct PatientDiagnosis < ' a > {
109+ /// The diagnosis
110+ pub diagnosis : Box < dyn Diagnosis > ,
111+ /// A reference to the patient this diagnosis applies to
112+ pub patient : & ' a mut PatientApp ,
103113}
104114
105115/// The Diagnose trait implements the detection of a particular Spin app problem.
0 commit comments