forge/lib/services/src/actor.rs
2025-11-26 18:33:09 -06:00

134 lines
5.2 KiB
Rust

//! Actor service layer providing business logic for actor management operations.
//!
//! Implements the service layer of the actor management system, handling business logic,
//! validation, and orchestration.
//!
//! For full documentation, architecture, and examples, see the [crate README](../README.md).
use forge_models::Actor;
use forge_repositories::ActorRepository;
/// Service layer implementation for actor business logic and operations.
///
/// Orchestrates actor management operations, handling business logic, validation,
/// and data transformation. See [crate README](../README.md) for details.
///
/// # Thread Safety
/// Thread-safe when used with a thread-safe repository.
pub struct ActorService<R: ActorRepository> {
/// The repository instance used for all data persistence operations.
///
/// This repository handles the actual storage and retrieval of actor data,
/// abstracting away the specific database implementation details.
repository: R,
}
impl<R: ActorRepository> ActorService<R> {
/// Creates a new actor service with the provided repository.
///
/// The repository must be initialized and ready for use.
pub fn new(repository: R) -> Self {
Self { repository }
}
/// Creates a new actor with the provided ID and JSON data.
///
/// Handles validation, duplicate checking, and persistence.
/// See [crate README](../README.md) for JSON format and business rules.
pub fn create_actor(&self, actor_id: String, json_data: String) -> Result<Actor, String> {
// Create base actor with the provided UID
let new_actor = Actor::new(actor_id.clone()).map_err(|e| e.to_string())?;
// Check if actor already exists to prevent duplicates
if self.repository.exists(&actor_id)? {
return Err(format!("Actor with UID '{}' already exists", actor_id));
}
// Parse and validate JSON input
let json_obj: serde_json::Value =
serde_json::from_str(&json_data).map_err(|e| format!("Invalid Actor JSON: {}", e))?;
// Ensure JSON is an object (not array, string, etc.)
if let serde_json::Value::Object(mut map) = json_obj {
// Remove UID field to prevent conflicts (UID is immutable)
map.remove("uid");
let update_data = serde_json::Value::Object(map);
// Apply JSON data to the base actor
let mut final_actor = new_actor;
final_actor.update_from_json(update_data)?;
// Store the actor in the repository
self.repository.create(&final_actor)?;
Ok(final_actor)
} else {
Err("JSON data must be an object".to_string())
}
}
/// Retrieves an actor by their unique identifier with automatic fallback creation.
///
/// Implements a "get-or-create" pattern: if the actor doesn't exist, a new one
/// with default values is returned (but not persisted).
pub fn get_actor(&self, actor_id: String) -> Result<Actor, String> {
// Attempt to retrieve actor from repository
match self.repository.get_by_id(&actor_id)? {
// Actor found - return it
Some(actor) => Ok(actor),
// Actor not found - create fallback actor with default values
None => Actor::new(actor_id).map_err(|e| e.to_string()),
}
}
/// Updates an existing actor with new data from JSON.
///
/// Handles partial updates, validation, and persistence.
/// See [crate README](../README.md) for JSON format and concurrency details.
pub fn update_actor(&self, actor_id: String, json_update: String) -> Result<Actor, String> {
// Retrieve existing actor from repository
let mut actor = match self.repository.get_by_id(&actor_id)? {
Some(actor) => actor,
None => return Err(format!("Actor with UID '{}' not found", actor_id)),
};
// Parse and validate JSON update data
let update_data: serde_json::Value =
serde_json::from_str(&json_update).map_err(|e| format!("Invalid JSON: {}", e))?;
// Ensure update data is a JSON object
if !update_data.is_object() {
return Err("Update data must be a JSON object".to_string());
}
// Apply updates to the actor (this validates the changes)
actor.update_from_json(update_data)?;
// Persist the updated actor to repository
self.repository.update(&actor)?;
Ok(actor)
}
/// Permanently deletes an actor from the system.
///
/// Irreversible operation. Delegates to repository.
pub fn delete_actor(&self, actor_id: String) -> Result<(), String> {
// Delegate deletion to repository layer
// Future enhancements could add business logic here:
// - Authorization checks
// - Audit logging
// - Cascade deletion
// - Soft deletion
self.repository.delete(&actor_id)
}
/// Checks if an actor exists in the system.
///
/// Lightweight check without data retrieval.
pub fn actor_exists(&self, actor_id: String) -> Result<bool, String> {
// Delegate existence check to repository layer
// This is a lightweight operation that doesn't retrieve data
self.repository.exists(&actor_id)
}
}