use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ResourceType {
Agent,
Snippet,
Command,
#[serde(rename = "mcp-server")]
McpServer,
Script,
Hook,
}
impl ResourceType {
pub const fn all() -> &'static [ResourceType] {
&[
ResourceType::Agent,
ResourceType::Snippet,
ResourceType::Command,
ResourceType::McpServer,
ResourceType::Script,
ResourceType::Hook,
]
}
#[must_use]
pub fn default_directory(&self) -> &str {
match self {
ResourceType::Agent => ".claude/agents",
ResourceType::Snippet => ".claude/ccpm/snippets",
ResourceType::Command => ".claude/commands",
ResourceType::McpServer => ".claude/ccpm/mcp-servers",
ResourceType::Script => ".claude/ccpm/scripts",
ResourceType::Hook => ".claude/ccpm/hooks",
}
}
}
impl std::fmt::Display for ResourceType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ResourceType::Agent => write!(f, "agent"),
ResourceType::Snippet => write!(f, "snippet"),
ResourceType::Command => write!(f, "command"),
ResourceType::McpServer => write!(f, "mcp-server"),
ResourceType::Script => write!(f, "script"),
ResourceType::Hook => write!(f, "hook"),
}
}
}
impl std::str::FromStr for ResourceType {
type Err = crate::core::CcpmError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"agent" => Ok(ResourceType::Agent),
"snippet" => Ok(ResourceType::Snippet),
"command" => Ok(ResourceType::Command),
"mcp-server" | "mcpserver" | "mcp" => Ok(ResourceType::McpServer),
"script" => Ok(ResourceType::Script),
"hook" => Ok(ResourceType::Hook),
_ => Err(crate::core::CcpmError::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/ccpm/snippets"
);
assert_eq!(
ResourceType::Command.default_directory(),
".claude/commands"
);
assert_eq!(
ResourceType::McpServer.default_directory(),
".claude/ccpm/mcp-servers"
);
assert_eq!(
ResourceType::Script.default_directory(),
".claude/ccpm/scripts"
);
assert_eq!(ResourceType::Hook.default_directory(), ".claude/ccpm/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);
}
}