1use schemars::JsonSchema;
2use semver::Version;
3use serde::{Deserialize, Serialize};
4
5use super::command::Namespace;
6
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
9pub struct CompatibilityRange {
10 pub min_inclusive: String,
12 pub max_exclusive: Option<String>,
14}
15
16impl CompatibilityRange {
17 pub fn new(min_inclusive: &str, max_exclusive: Option<&str>) -> Result<Self, String> {
19 let _ = Version::parse(min_inclusive)
20 .map_err(|error| format!("invalid min_inclusive semver: {error}"))?;
21 if let Some(max) = max_exclusive {
22 let _ = Version::parse(max)
23 .map_err(|error| format!("invalid max_exclusive semver: {error}"))?;
24 }
25 Ok(Self {
26 min_inclusive: min_inclusive.to_string(),
27 max_exclusive: max_exclusive.map(ToString::to_string),
28 })
29 }
30
31 pub fn supports_host(&self, host_version: &str) -> Result<bool, String> {
33 let host = Version::parse(host_version)
34 .map_err(|error| format!("invalid host semver: {error}"))?;
35 let min = Version::parse(&self.min_inclusive)
36 .map_err(|error| format!("invalid min_inclusive semver: {error}"))?;
37 if host < min {
38 return Ok(false);
39 }
40 if let Some(max) = &self.max_exclusive {
41 let max = Version::parse(max)
42 .map_err(|error| format!("invalid max_exclusive semver: {error}"))?;
43 return Ok(host < max);
44 }
45 Ok(true)
46 }
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
51pub struct PluginCapability {
52 pub name: String,
54 pub version: Option<String>,
56}
57
58impl PluginCapability {
59 pub fn new(name: &str, version: Option<&str>) -> Result<Self, String> {
61 if name.trim().is_empty() {
62 return Err("capability name cannot be empty".to_string());
63 }
64 Ok(Self { name: name.to_string(), version: version.map(ToString::to_string) })
65 }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
70#[serde(rename_all = "kebab-case")]
71#[non_exhaustive]
72pub enum PluginKind {
73 Native,
75 #[default]
77 Delegated,
78 Python,
80 ExternalExec,
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
86#[serde(rename_all = "lowercase")]
87#[non_exhaustive]
88pub enum PluginLifecycleState {
89 Discovered,
91 Validated,
93 Installed,
95 Enabled,
97 Disabled,
99 Broken,
101 Incompatible,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
107pub struct PluginManifestV2 {
108 pub name: String,
110 pub version: String,
112 pub schema_version: String,
114 pub manifest_version: String,
116 pub compatibility: CompatibilityRange,
118 pub namespace: Namespace,
120 #[serde(default)]
122 pub kind: PluginKind,
123 #[serde(default)]
125 pub aliases: Vec<String>,
126 pub entrypoint: String,
128 pub capabilities: Vec<PluginCapability>,
130}
131
132impl PluginManifestV2 {
133 #[allow(clippy::too_many_arguments)]
135 pub fn new(
136 name: &str,
137 version: &str,
138 schema_version: &str,
139 manifest_version: &str,
140 compatibility: CompatibilityRange,
141 namespace: Namespace,
142 kind: PluginKind,
143 aliases: Vec<String>,
144 entrypoint: &str,
145 capabilities: Vec<PluginCapability>,
146 ) -> Result<Self, String> {
147 if name.trim().is_empty() {
148 return Err("plugin name cannot be empty".to_string());
149 }
150 if version.trim().is_empty() {
151 return Err("plugin version cannot be empty".to_string());
152 }
153 if schema_version.trim().is_empty() {
154 return Err("plugin schema_version cannot be empty".to_string());
155 }
156 if schema_version != "v2" {
157 return Err("plugin schema_version must be v2".to_string());
158 }
159 if manifest_version.trim().is_empty() {
160 return Err("plugin manifest_version cannot be empty".to_string());
161 }
162 if manifest_version != "v2" {
163 return Err("plugin manifest_version must be v2".to_string());
164 }
165 if entrypoint.trim().is_empty() {
166 return Err("plugin entrypoint cannot be empty".to_string());
167 }
168 Ok(Self {
169 name: name.to_string(),
170 version: version.to_string(),
171 schema_version: schema_version.to_string(),
172 manifest_version: manifest_version.to_string(),
173 compatibility,
174 namespace,
175 kind,
176 aliases,
177 entrypoint: entrypoint.to_string(),
178 capabilities,
179 })
180 }
181}