agentzero_plugins/
package_ref.rs1use thiserror::Error;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub struct PluginPackageRef {
5 pub name: String,
6 pub version: String,
7}
8
9#[derive(Debug, Error, PartialEq, Eq)]
10pub enum PluginRefError {
11 #[error("plugin reference must be formatted as <name>@<version>")]
12 InvalidFormat,
13 #[error("plugin name contains invalid characters: {0}")]
14 InvalidName(String),
15 #[error("plugin version cannot be empty")]
16 EmptyVersion,
17}
18
19pub fn parse_plugin_package_ref(input: &str) -> Result<PluginPackageRef, PluginRefError> {
20 let (name, version) = input.split_once('@').ok_or(PluginRefError::InvalidFormat)?;
21
22 if name.is_empty()
23 || !name
24 .chars()
25 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-' || c == '_')
26 {
27 return Err(PluginRefError::InvalidName(name.to_string()));
28 }
29
30 if version.trim().is_empty() {
31 return Err(PluginRefError::EmptyVersion);
32 }
33
34 Ok(PluginPackageRef {
35 name: name.to_string(),
36 version: version.to_string(),
37 })
38}
39
40#[cfg(test)]
41mod tests {
42 use super::*;
43
44 #[test]
45 fn parse_plugin_package_ref_accepts_valid_ref() {
46 let parsed = parse_plugin_package_ref("my_plugin@1.2.3").expect("valid ref");
47 assert_eq!(parsed.name, "my_plugin");
48 assert_eq!(parsed.version, "1.2.3");
49 }
50
51 #[test]
52 fn parse_plugin_package_ref_rejects_missing_separator() {
53 let err = parse_plugin_package_ref("my_plugin").expect_err("missing @ should fail");
54 assert_eq!(err, PluginRefError::InvalidFormat);
55 }
56}