Skip to main content

agentzero_plugins/
package_ref.rs

1use 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}