use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, Default,
)]
#[serde(rename_all = "lowercase")]
pub enum ResourceType {
Agent,
#[default]
Snippet,
Command,
#[serde(rename = "mcp-server")]
McpServer,
Script,
Hook,
}
impl ResourceType {
pub const fn all() -> &'static [Self] {
&[Self::Agent, Self::Snippet, Self::Command, Self::McpServer, Self::Script, Self::Hook]
}
pub const fn to_plural(&self) -> &'static str {
match self {
Self::Agent => "agents",
Self::Snippet => "snippets",
Self::Command => "commands",
Self::Script => "scripts",
Self::Hook => "hooks",
Self::McpServer => "mcp-servers",
}
}
#[must_use]
pub const fn default_directory(&self) -> &str {
match self {
Self::Agent => ".claude/agents",
Self::Snippet => ".claude/agpm/snippets",
Self::Command => ".claude/commands",
Self::McpServer => ".claude/agpm/mcp-servers",
Self::Script => ".claude/agpm/scripts",
Self::Hook => ".claude/agpm/hooks",
}
}
}
impl std::fmt::Display for ResourceType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Agent => write!(f, "agent"),
Self::Snippet => write!(f, "snippet"),
Self::Command => write!(f, "command"),
Self::McpServer => write!(f, "mcp-server"),
Self::Script => write!(f, "script"),
Self::Hook => write!(f, "hook"),
}
}
}
impl std::str::FromStr for ResourceType {
type Err = crate::core::AgpmError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"agent" | "agents" => Ok(Self::Agent),
"snippet" | "snippets" => Ok(Self::Snippet),
"command" | "commands" => Ok(Self::Command),
"mcp-server" | "mcp-servers" | "mcpserver" | "mcp" => Ok(Self::McpServer),
"script" | "scripts" => Ok(Self::Script),
"hook" | "hooks" => Ok(Self::Hook),
_ => Err(crate::core::AgpmError::InvalidResourceType {
resource_type: s.to_string(),
}),
}
}
}
pub trait Resource {
fn name(&self) -> &str;
fn resource_type(&self) -> ResourceType;
fn description(&self) -> Option<&str>;
fn dependencies(&self) -> Option<&[crate::config::Dependency]>;
fn validate(&self) -> Result<()>;
fn install(&self, target: &Path, profile: Option<&str>) -> Result<()>;
fn install_path(&self, base: &Path) -> std::path::PathBuf;
fn metadata(&self) -> Result<serde_json::Value>;
fn as_any(&self) -> &dyn std::any::Any;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resource_type_default_directory() {
assert_eq!(ResourceType::Agent.default_directory(), ".claude/agents");
assert_eq!(ResourceType::Snippet.default_directory(), ".claude/agpm/snippets");
assert_eq!(ResourceType::Command.default_directory(), ".claude/commands");
assert_eq!(ResourceType::McpServer.default_directory(), ".claude/agpm/mcp-servers");
assert_eq!(ResourceType::Script.default_directory(), ".claude/agpm/scripts");
assert_eq!(ResourceType::Hook.default_directory(), ".claude/agpm/hooks");
}
#[test]
fn test_resource_type_display() {
assert_eq!(ResourceType::Agent.to_string(), "agent");
assert_eq!(ResourceType::Snippet.to_string(), "snippet");
assert_eq!(ResourceType::Command.to_string(), "command");
assert_eq!(ResourceType::McpServer.to_string(), "mcp-server");
assert_eq!(ResourceType::Script.to_string(), "script");
assert_eq!(ResourceType::Hook.to_string(), "hook");
}
#[test]
fn test_resource_type_from_str() {
use std::str::FromStr;
assert_eq!(ResourceType::from_str("agent").unwrap(), ResourceType::Agent);
assert_eq!(ResourceType::from_str("snippet").unwrap(), ResourceType::Snippet);
assert_eq!(ResourceType::from_str("AGENT").unwrap(), ResourceType::Agent);
assert_eq!(ResourceType::from_str("Snippet").unwrap(), ResourceType::Snippet);
assert_eq!(ResourceType::from_str("command").unwrap(), ResourceType::Command);
assert_eq!(ResourceType::from_str("COMMAND").unwrap(), ResourceType::Command);
assert_eq!(ResourceType::from_str("mcp-server").unwrap(), ResourceType::McpServer);
assert_eq!(ResourceType::from_str("MCP").unwrap(), ResourceType::McpServer);
assert_eq!(ResourceType::from_str("script").unwrap(), ResourceType::Script);
assert_eq!(ResourceType::from_str("SCRIPT").unwrap(), ResourceType::Script);
assert_eq!(ResourceType::from_str("hook").unwrap(), ResourceType::Hook);
assert_eq!(ResourceType::from_str("HOOK").unwrap(), ResourceType::Hook);
assert!(ResourceType::from_str("invalid").is_err());
}
#[test]
fn test_resource_type_serialization() {
let agent = ResourceType::Agent;
let json = serde_json::to_string(&agent).unwrap();
assert_eq!(json, "\"agent\"");
let deserialized: ResourceType = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, ResourceType::Agent);
let command = ResourceType::Command;
let json = serde_json::to_string(&command).unwrap();
assert_eq!(json, "\"command\"");
let deserialized: ResourceType = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, ResourceType::Command);
let mcp_server = ResourceType::McpServer;
let json = serde_json::to_string(&mcp_server).unwrap();
assert_eq!(json, "\"mcp-server\"");
let deserialized: ResourceType = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, ResourceType::McpServer);
let script = ResourceType::Script;
let json = serde_json::to_string(&script).unwrap();
assert_eq!(json, "\"script\"");
let deserialized: ResourceType = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, ResourceType::Script);
let hook = ResourceType::Hook;
let json = serde_json::to_string(&hook).unwrap();
assert_eq!(json, "\"hook\"");
let deserialized: ResourceType = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, ResourceType::Hook);
}
#[test]
fn test_resource_type_equality() {
assert_eq!(ResourceType::Command, ResourceType::Command);
assert_ne!(ResourceType::Command, ResourceType::Agent);
assert_ne!(ResourceType::Command, ResourceType::Snippet);
assert_eq!(ResourceType::McpServer, ResourceType::McpServer);
assert_ne!(ResourceType::McpServer, ResourceType::Agent);
assert_eq!(ResourceType::Script, ResourceType::Script);
assert_ne!(ResourceType::Script, ResourceType::Hook);
assert_eq!(ResourceType::Hook, ResourceType::Hook);
assert_ne!(ResourceType::Hook, ResourceType::Agent);
}
#[test]
fn test_resource_type_copy() {
let command = ResourceType::Command;
let copied = command; assert_eq!(command, copied);
}
#[test]
fn test_resource_type_all() {
let all_types = ResourceType::all();
assert_eq!(all_types.len(), 6);
assert_eq!(all_types[0], ResourceType::Agent);
assert_eq!(all_types[1], ResourceType::Snippet);
assert_eq!(all_types[2], ResourceType::Command);
assert_eq!(all_types[3], ResourceType::McpServer);
assert_eq!(all_types[4], ResourceType::Script);
assert_eq!(all_types[5], ResourceType::Hook);
let mut count = 0;
for _ in ResourceType::all() {
count += 1;
}
assert_eq!(count, 6);
}
}