use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Action {
pub kind: ActionKind,
pub target: Option<String>,
pub args: HashMap<String, String>,
pub reason: Option<String>,
}
impl Action {
pub fn new(kind: ActionKind) -> Self {
Self {
kind,
target: None,
args: HashMap::new(),
reason: None,
}
}
pub fn with_target(mut self, target: impl Into<String>) -> Self {
self.target = Some(target.into());
self
}
pub fn with_arg(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.args.insert(key.into(), value.into());
self
}
pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
self.reason = Some(reason.into());
self
}
pub fn read(path: impl Into<String>) -> Self {
Self::new(ActionKind::Read).with_target(path)
}
pub fn grep(pattern: impl Into<String>) -> Self {
Self::new(ActionKind::Grep).with_arg("pattern", pattern)
}
pub fn glob(pattern: impl Into<String>) -> Self {
Self::new(ActionKind::Glob).with_arg("pattern", pattern)
}
pub fn mutate(mutation_type: impl Into<String>, target: impl Into<String>) -> Self {
Self::new(ActionKind::Mutate)
.with_arg("mutation_type", mutation_type)
.with_target(target)
}
pub fn rest(reason: impl Into<String>) -> Self {
Self::new(ActionKind::Rest).with_reason(reason)
}
pub fn done() -> Self {
Self::new(ActionKind::Done)
}
pub fn is_terminal(&self) -> bool {
matches!(self.kind, ActionKind::Done | ActionKind::Rest)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ActionKind {
Read,
Grep,
Glob,
List,
Mutate,
MutateBatch,
Rest,
Done,
Escalate,
}
impl ActionKind {
pub fn as_str(&self) -> &'static str {
match self {
Self::Read => "read",
Self::Grep => "grep",
Self::Glob => "glob",
Self::List => "list",
Self::Mutate => "mutate",
Self::MutateBatch => "mutate_batch",
Self::Rest => "rest",
Self::Done => "done",
Self::Escalate => "escalate",
}
}
pub fn is_investigation(&self) -> bool {
matches!(self, Self::Read | Self::Grep | Self::Glob | Self::List)
}
pub fn is_mutation(&self) -> bool {
matches!(self, Self::Mutate | Self::MutateBatch)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionResult {
pub action: Action,
pub success: bool,
pub output: Option<String>,
pub error: Option<String>,
pub duration_us: u64,
pub affected_files: Vec<PathBuf>,
pub changes: usize,
}
impl ActionResult {
pub fn success(action: Action) -> Self {
Self {
action,
success: true,
output: None,
error: None,
duration_us: 0,
affected_files: Vec::new(),
changes: 0,
}
}
pub fn failure(action: Action, error: impl Into<String>) -> Self {
Self {
action,
success: false,
output: None,
error: Some(error.into()),
duration_us: 0,
affected_files: Vec::new(),
changes: 0,
}
}
pub fn with_output(mut self, output: impl Into<String>) -> Self {
self.output = Some(output.into());
self
}
pub fn with_duration(mut self, duration_us: u64) -> Self {
self.duration_us = duration_us;
self
}
pub fn with_affected_files(mut self, files: Vec<PathBuf>) -> Self {
self.affected_files = files;
self
}
pub fn with_changes(mut self, changes: usize) -> Self {
self.changes = changes;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_action_creation() {
let action = Action::read("src/lib.rs");
assert_eq!(action.kind, ActionKind::Read);
assert_eq!(action.target, Some("src/lib.rs".to_string()));
let action = Action::grep("TODO").with_target("src/");
assert_eq!(action.kind, ActionKind::Grep);
assert_eq!(action.args.get("pattern"), Some(&"TODO".to_string()));
let action = Action::mutate("Rename", "old_fn");
assert_eq!(action.kind, ActionKind::Mutate);
assert_eq!(action.target, Some("old_fn".to_string()));
}
#[test]
fn test_action_result() {
let action = Action::read("test.rs");
let result = ActionResult::success(action.clone())
.with_output("file contents")
.with_duration(100);
assert!(result.success);
assert_eq!(result.output, Some("file contents".to_string()));
assert_eq!(result.duration_us, 100);
let result = ActionResult::failure(action, "File not found");
assert!(!result.success);
assert_eq!(result.error, Some("File not found".to_string()));
}
}