use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct FactionId(String);
impl FactionId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for FactionId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for FactionId {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for FactionId {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Faction {
pub id: FactionId,
pub name: String,
#[serde(default)]
pub metadata: serde_json::Value,
}
impl Faction {
pub fn new(id: impl Into<String>, name: impl Into<String>) -> Self {
Self {
id: FactionId::new(id),
name: name.into(),
metadata: serde_json::Value::Null,
}
}
pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
self.metadata = metadata;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct OperationId(String);
impl OperationId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for OperationId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for OperationId {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for OperationId {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum OperationStatus {
Pending,
InProgress,
Completed,
Failed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Operation {
pub id: OperationId,
pub faction_id: FactionId,
pub name: String,
pub status: OperationStatus,
#[serde(default)]
pub metadata: serde_json::Value,
}
impl Operation {
pub fn new(id: impl Into<String>, faction_id: FactionId, name: impl Into<String>) -> Self {
Self {
id: OperationId::new(id),
faction_id,
name: name.into(),
status: OperationStatus::Pending,
metadata: serde_json::Value::Null,
}
}
pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
self.metadata = metadata;
self
}
pub fn with_status(mut self, status: OperationStatus) -> Self {
self.status = status;
self
}
pub fn is_pending(&self) -> bool {
matches!(self.status, OperationStatus::Pending)
}
pub fn is_in_progress(&self) -> bool {
matches!(self.status, OperationStatus::InProgress)
}
pub fn is_completed(&self) -> bool {
matches!(self.status, OperationStatus::Completed)
}
pub fn is_failed(&self) -> bool {
matches!(self.status, OperationStatus::Failed)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Outcome {
pub operation_id: OperationId,
pub success: bool,
#[serde(default)]
pub metrics: HashMap<String, f32>,
#[serde(default)]
pub metadata: serde_json::Value,
}
impl Outcome {
pub fn new(operation_id: impl Into<String>, success: bool) -> Self {
Self {
operation_id: OperationId::new(operation_id),
success,
metrics: HashMap::new(),
metadata: serde_json::Value::Null,
}
}
pub fn with_metric(mut self, key: impl Into<String>, value: f32) -> Self {
self.metrics.insert(key.into(), value);
self
}
pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
self.metadata = metadata;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FactionError {
FactionNotFound,
OperationNotFound,
InvalidStatusTransition,
OperationAlreadyExists,
}
impl fmt::Display for FactionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FactionError::FactionNotFound => write!(f, "Faction not found"),
FactionError::OperationNotFound => write!(f, "Operation not found"),
FactionError::InvalidStatusTransition => {
write!(f, "Invalid operation status transition")
}
FactionError::OperationAlreadyExists => write!(f, "Operation already exists"),
}
}
}
impl std::error::Error for FactionError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_faction_id() {
let id = FactionId::new("crimson-syndicate");
assert_eq!(id.as_str(), "crimson-syndicate");
assert_eq!(id.to_string(), "crimson-syndicate");
}
#[test]
fn test_faction_creation() {
let faction = Faction::new("crimson", "Crimson Syndicate");
assert_eq!(faction.id.as_str(), "crimson");
assert_eq!(faction.name, "Crimson Syndicate");
assert!(faction.metadata.is_null());
}
#[test]
fn test_faction_with_metadata() {
let faction = Faction::new("crimson", "Crimson Syndicate")
.with_metadata(serde_json::json!({ "reputation": 75 }));
assert_eq!(faction.metadata["reputation"], 75);
}
#[test]
fn test_operation_id() {
let id = OperationId::new("op-001");
assert_eq!(id.as_str(), "op-001");
assert_eq!(id.to_string(), "op-001");
}
#[test]
fn test_operation_creation() {
let op = Operation::new("op-001", FactionId::new("crimson"), "Capture Nova Harbor");
assert_eq!(op.id.as_str(), "op-001");
assert_eq!(op.faction_id.as_str(), "crimson");
assert_eq!(op.name, "Capture Nova Harbor");
assert!(op.is_pending());
assert!(!op.is_completed());
}
#[test]
fn test_operation_status_checks() {
let op = Operation::new("op-001", FactionId::new("crimson"), "Test")
.with_status(OperationStatus::InProgress);
assert!(op.is_in_progress());
assert!(!op.is_pending());
assert!(!op.is_completed());
assert!(!op.is_failed());
}
#[test]
fn test_outcome_creation() {
let outcome = Outcome::new("op-001", true)
.with_metric("casualties", 12.0)
.with_metric("control_gained", 0.15);
assert!(outcome.success);
assert_eq!(outcome.metrics.get("casualties"), Some(&12.0));
assert_eq!(outcome.metrics.get("control_gained"), Some(&0.15));
}
#[test]
fn test_faction_error_display() {
assert_eq!(
FactionError::FactionNotFound.to_string(),
"Faction not found"
);
assert_eq!(
FactionError::OperationNotFound.to_string(),
"Operation not found"
);
}
}