Skip to main content

vs_plugin_api/
model.rs

1//! Core plugin manifest and installation data model.
2
3use std::collections::BTreeMap;
4use std::path::{Path, PathBuf};
5
6use serde::{Deserialize, Serialize};
7
8/// Supported plugin backend implementations.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "kebab-case")]
11pub enum PluginBackendKind {
12    /// Lua-compatible plugin backend.
13    Lua,
14    /// Native plugin backend modeled after a WASI component contract.
15    Wasi,
16}
17
18/// Registry metadata for a plugin.
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20pub struct PluginManifest {
21    /// Stable plugin identifier.
22    pub name: String,
23    /// Runtime backend used to execute hooks.
24    pub backend: PluginBackendKind,
25    /// Local source directory of the plugin.
26    pub source: PathBuf,
27    /// Optional human readable description.
28    #[serde(default)]
29    pub description: Option<String>,
30    /// Alternative names accepted by the CLI.
31    #[serde(default)]
32    pub aliases: Vec<String>,
33    /// Plugin runtime version.
34    #[serde(default)]
35    pub version: Option<String>,
36    /// Plugin homepage or repository.
37    #[serde(default)]
38    pub homepage: Option<String>,
39    /// Plugin update URL.
40    #[serde(default)]
41    pub update_url: Option<String>,
42    /// Plugin manifest URL.
43    #[serde(default)]
44    pub manifest_url: Option<String>,
45    /// Minimum runtime version required by the plugin.
46    #[serde(default)]
47    pub min_runtime_version: Option<String>,
48    /// Additional plugin notes.
49    #[serde(default)]
50    pub notes: Vec<String>,
51    /// Legacy file names handled by the plugin.
52    #[serde(default)]
53    pub legacy_filenames: Vec<String>,
54}
55
56/// A version published by a plugin.
57#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
58pub struct AvailableVersion {
59    /// Version string as understood by the plugin.
60    pub version: String,
61    /// Optional human-readable note.
62    #[serde(default)]
63    pub note: Option<String>,
64    /// Additional packages associated with the version.
65    #[serde(default)]
66    pub additions: Vec<AvailableAddition>,
67}
68
69/// An additional package listed next to an available version.
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
71pub struct AvailableAddition {
72    /// Package name.
73    pub name: String,
74    /// Package version.
75    pub version: String,
76    /// Optional note.
77    #[serde(default)]
78    pub note: Option<String>,
79}
80
81/// An environment key emitted by a plugin.
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
83pub struct EnvKey {
84    /// Environment variable name.
85    pub key: String,
86    /// Environment variable value.
87    pub value: String,
88}
89
90/// A checksum used to verify a downloaded artifact.
91#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
92pub struct Checksum {
93    /// Checksum algorithm.
94    pub algorithm: String,
95    /// Checksum value.
96    pub value: String,
97}
98
99/// An artifact source referenced by an install plan.
100#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
101#[serde(tag = "kind", rename_all = "kebab-case")]
102pub enum InstallSource {
103    /// A local directory that should be copied recursively.
104    Directory { path: PathBuf },
105    /// A local file that may be moved or extracted.
106    File { path: PathBuf },
107    /// A remote URL that should be downloaded.
108    Url {
109        url: String,
110        #[serde(default)]
111        headers: BTreeMap<String, String>,
112    },
113}
114
115/// An artifact returned by a plugin install hook.
116#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
117pub struct InstallArtifact {
118    /// Artifact name.
119    pub name: String,
120    /// Artifact version.
121    pub version: String,
122    /// Artifact source.
123    pub source: InstallSource,
124    /// Optional note.
125    #[serde(default)]
126    pub note: Option<String>,
127    /// Optional checksum.
128    #[serde(default)]
129    pub checksum: Option<Checksum>,
130}
131
132/// Installation plan returned by a plugin.
133#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
134pub struct InstallPlan {
135    /// Plugin name used for diagnostics.
136    pub plugin: String,
137    /// Version that will be installed.
138    pub version: String,
139    /// Primary runtime artifact.
140    pub main: InstallArtifact,
141    /// Additional artifacts.
142    #[serde(default)]
143    pub additions: Vec<InstallArtifact>,
144    /// Legacy file names understood by the plugin.
145    #[serde(default)]
146    pub legacy_filenames: Vec<String>,
147}
148
149/// An installed artifact on disk.
150#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
151pub struct InstalledArtifact {
152    /// Artifact name.
153    pub name: String,
154    /// Artifact version.
155    pub version: String,
156    /// Installed path.
157    pub path: PathBuf,
158    /// Optional note.
159    #[serde(default)]
160    pub note: Option<String>,
161}
162
163/// Fully installed runtime layout for a plugin version.
164#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
165pub struct InstalledRuntime {
166    /// Plugin identifier.
167    pub plugin: String,
168    /// Installed version.
169    pub version: String,
170    /// Version root directory.
171    pub root_dir: PathBuf,
172    /// Main installed artifact.
173    pub main: InstalledArtifact,
174    /// Additional installed artifacts.
175    #[serde(default)]
176    pub additions: Vec<InstalledArtifact>,
177}
178
179impl InstalledRuntime {
180    /// Returns the main runtime path.
181    pub fn main_path(&self) -> &Path {
182        &self.main.path
183    }
184
185    /// Returns a copy of this runtime with all paths relocated under
186    /// `new_root` instead of the original `root_dir`.  This is used to
187    /// make env-keys point at scope-specific symlinks rather than the raw
188    /// cache directory.
189    pub fn relocate(&self, new_root: &Path) -> Self {
190        let relocate_path = |p: &Path| -> PathBuf {
191            p.strip_prefix(&self.root_dir)
192                .map(|rel| new_root.join(rel))
193                .unwrap_or_else(|_| p.to_path_buf())
194        };
195        Self {
196            plugin: self.plugin.clone(),
197            version: self.version.clone(),
198            root_dir: new_root.to_path_buf(),
199            main: InstalledArtifact {
200                path: relocate_path(&self.main.path),
201                ..self.main.clone()
202            },
203            additions: self
204                .additions
205                .iter()
206                .map(|a| InstalledArtifact {
207                    path: relocate_path(&a.path),
208                    ..a.clone()
209                })
210                .collect(),
211        }
212    }
213}