use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginMetadata {
pub name: String,
pub version: String,
pub author: String,
pub description: String,
pub hooks: Vec<PluginHook>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq)]
pub enum PluginHook {
OnIssueCreated,
OnIssueUpdated,
OnIssueDeleted,
OnStatusChanged,
OnSyncPush,
OnSyncPull,
OnMergeRequestCreated,
OnCommand(String),
OnSchedule(String),
OnBulkOperation(BulkOp),
OnExternalSync,
OnWebhookReceived,
OnSprintStart(u32),
OnSprintEnd(u32),
OnDueDateApproaching,
OnDueDatePassed,
OnReportRequested,
OnMetricQuery,
}
impl PartialEq for PluginHook {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(PluginHook::OnCommand(_), PluginHook::OnCommand(_)) => true,
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum BulkOp {
Import,
Export,
Archive,
Delete,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Issue {
pub id: String,
pub title: String,
pub description: String,
pub status: String,
pub tags: Vec<String>,
pub assignee: Option<String>,
pub effort: Option<u8>,
pub blocked: bool,
pub created: String,
pub updated: String,
pub due: Option<String>,
pub metadata: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginContext {
pub repo_path: String,
pub user: Option<String>,
pub env: HashMap<String, String>,
pub config: HashMap<String, serde_json::Value>,
}
pub type PluginResult<T> = Result<T, PluginError>;
#[derive(Debug, thiserror::Error)]
pub enum PluginError {
#[error("Plugin initialization failed: {0}")]
InitError(String),
#[error("Plugin execution failed: {0}")]
ExecutionError(String),
#[error("Invalid plugin configuration: {0}")]
ConfigError(String),
#[error("Hook not supported: {0:?}")]
UnsupportedHook(PluginHook),
#[error("Serialization error: {0}")]
SerializationError(#[from] serde_json::Error),
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("Storage error: {0}")]
StorageError(String),
#[error("Sync error: {0}")]
SyncError(String),
#[error("External API error: {0}")]
ExternalApiError(String),
}
pub trait Plugin {
fn metadata(&self) -> &PluginMetadata;
fn init(&mut self, context: &PluginContext) -> PluginResult<()>;
fn execute_hook(
&mut self,
hook: &PluginHook,
data: &serde_json::Value,
) -> PluginResult<serde_json::Value>;
fn supports_hook(&self, hook: &PluginHook) -> bool {
self.metadata().hooks.contains(hook)
}
fn on_event(&mut self, event: &serde_json::Value) -> PluginResult<Option<serde_json::Value>> {
let _ = event; Ok(None)
}
fn highlight(
&mut self,
request: &crate::render::HighlightRequest,
) -> PluginResult<Option<crate::render::HighlightResponse>> {
let _ = request;
Ok(None)
}
}
pub trait IssuePlugin: Plugin {
fn on_issue_created(&mut self, issue: &Issue) -> PluginResult<()> {
let data = serde_json::to_value(issue)?;
self.execute_hook(&PluginHook::OnIssueCreated, &data)?;
Ok(())
}
fn on_issue_updated(&mut self, issue: &Issue) -> PluginResult<()> {
let data = serde_json::to_value(issue)?;
self.execute_hook(&PluginHook::OnIssueUpdated, &data)?;
Ok(())
}
fn on_issue_deleted(&mut self, issue_id: &str) -> PluginResult<()> {
let data = serde_json::json!({ "id": issue_id });
self.execute_hook(&PluginHook::OnIssueDeleted, &data)?;
Ok(())
}
}
pub trait SyncPlugin: Plugin {
fn on_sync_push(&mut self, issues: &[Issue]) -> PluginResult<()> {
let data = serde_json::to_value(issues)?;
self.execute_hook(&PluginHook::OnSyncPush, &data)?;
Ok(())
}
fn on_sync_pull(&mut self, issues: &[Issue]) -> PluginResult<()> {
let data = serde_json::to_value(issues)?;
self.execute_hook(&PluginHook::OnSyncPull, &data)?;
Ok(())
}
}