spin_doctor/
wasm.rs

1/// Diagnose missing Wasm sources.
2pub mod missing;
3
4use std::path::{Path, PathBuf};
5
6use anyhow::Result;
7use async_trait::async_trait;
8use spin_common::paths::parent_dir;
9use spin_manifest::schema::v2;
10
11use crate::{Diagnosis, Diagnostic, PatientApp};
12
13/// PatientWasm represents a Wasm source to be checked for problems.
14#[derive(Debug)]
15pub struct PatientWasm {
16    app_dir: PathBuf,
17    component_id: String,
18    component: v2::Component,
19}
20
21#[allow(missing_docs)] // WIP
22impl PatientWasm {
23    fn new(app_dir: impl AsRef<Path>, component_id: String, component: v2::Component) -> Self {
24        Self {
25            app_dir: app_dir.as_ref().to_owned(),
26            component_id,
27            component,
28        }
29    }
30
31    pub fn component_id(&self) -> &str {
32        &self.component_id
33    }
34
35    pub fn source_path(&self) -> Option<&Path> {
36        match &self.component.source {
37            v2::ComponentSource::Local(path) => Some(Path::new(path)),
38            _ => None,
39        }
40    }
41
42    pub fn abs_source_path(&self) -> Option<PathBuf> {
43        match &self.component.source {
44            v2::ComponentSource::Local(path) => {
45                // TODO: We probably need a doctor check to see if the path can be expanded!
46                // For now, fall back to the literal path.
47                let can_path = Path::new(path)
48                    .canonicalize()
49                    .unwrap_or(self.app_dir.join(path));
50                Some(can_path)
51            }
52            _ => None,
53        }
54    }
55
56    pub fn has_build(&self) -> bool {
57        self.component.build.is_some()
58    }
59}
60
61/// WasmDiagnostic helps implement [`Diagnostic`] for Wasm source problems.
62#[async_trait]
63pub trait WasmDiagnostic {
64    /// A [`Diagnosis`] representing the problem(s) this can detect.
65    type Diagnosis: Diagnosis;
66
67    /// Check the given [`PatientWasm`], returning any problem(s) found.
68    async fn diagnose_wasm(
69        &self,
70        app: &PatientApp,
71        wasm: PatientWasm,
72    ) -> Result<Vec<Self::Diagnosis>>;
73}
74
75#[async_trait]
76impl<T: WasmDiagnostic + Send + Sync> Diagnostic for T {
77    type Diagnosis = <Self as WasmDiagnostic>::Diagnosis;
78
79    async fn diagnose(&self, patient: &PatientApp) -> Result<Vec<Self::Diagnosis>> {
80        let manifest_str = patient.manifest_doc.to_string();
81        let manifest = spin_manifest::manifest_from_str(&manifest_str)?;
82        let app_dir = parent_dir(&patient.manifest_path)?;
83        let mut diagnoses = vec![];
84        for (component_id, component) in manifest.components {
85            let wasm = PatientWasm::new(&app_dir, component_id.to_string(), component);
86            diagnoses.extend(self.diagnose_wasm(patient, wasm).await?);
87        }
88        Ok(diagnoses)
89    }
90}