use std::fmt::Debug;
use std::path::Path;
use std::sync::Arc;
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use crate::core::error::Result;
pub trait Processor: Send + Sync + Debug {
type Input;
type Output;
type Context;
fn process(
&self,
input: Self::Input,
context: Option<&Self::Context>,
) -> Result<Self::Output>;
}
pub trait Transform: Send + Sync + Debug {
type Input;
type Output;
fn transform(&self, input: Self::Input) -> Result<Self::Output>;
}
pub trait Generator: Send + Sync + Debug {
fn generate(
&self,
content: &str,
path: &Path,
options: Option<&JsonValue>,
) -> Result<()>;
fn validate(
&self,
path: &Path,
options: Option<&JsonValue>,
) -> Result<()>;
}
pub trait Validator: Send + Sync + Debug {
type Input;
fn validate(&self, input: &Self::Input) -> Result<()>;
}
pub trait ProcessingContext {
fn into_context(self) -> JsonValue;
}
pub trait Shareable: Sized + Send + Sync + Debug {
fn into_shared(self) -> Arc<RwLock<Self>>;
}
impl<T: Send + Sync + Debug + 'static> Shareable for T {
fn into_shared(self) -> Arc<RwLock<Self>> {
Arc::new(RwLock::new(self))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessingOptions {
#[serde(default)]
pub strict_mode: bool,
#[serde(default = "default_true")]
pub validate: bool,
#[serde(default = "default_true")]
pub cache_enabled: bool,
#[serde(default)]
pub custom: JsonValue,
}
impl Default for ProcessingOptions {
fn default() -> Self {
Self {
strict_mode: false,
validate: true,
cache_enabled: true,
custom: JsonValue::Null,
}
}
}
fn default_true() -> bool {
true
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_processor() {
#[derive(Debug)]
struct TestProcessor;
impl Processor for TestProcessor {
type Input = String;
type Output = String;
type Context = ProcessingOptions;
fn process(
&self,
input: Self::Input,
context: Option<&Self::Context>,
) -> Result<Self::Output> {
if let Some(ctx) = context {
if ctx.strict_mode && input.is_empty() {
return Err(crate::core::error::ProcessingError::Validation {
details: "Input cannot be empty in strict mode".to_string(),
context: None,
});
}
}
Ok(input.to_uppercase())
}
}
let processor = TestProcessor;
let options = ProcessingOptions {
strict_mode: true,
..Default::default()
};
let result =
processor.process("test".to_string(), Some(&options));
assert_eq!(result.unwrap(), "TEST");
let result = processor.process("".to_string(), Some(&options));
assert!(result.is_err());
}
#[test]
fn test_transform() {
#[derive(Debug)]
struct TestTransform;
impl Transform for TestTransform {
type Input = String;
type Output = String;
fn transform(
&self,
input: Self::Input,
) -> Result<Self::Output> {
Ok(input.chars().rev().collect())
}
}
let transform = TestTransform;
let result = transform.transform("hello".to_string()).unwrap();
assert_eq!(result, "olleh");
}
#[test]
fn test_validator() {
#[derive(Debug)]
struct TestValidator;
impl Validator for TestValidator {
type Input = String;
fn validate(&self, input: &Self::Input) -> Result<()> {
if input.is_empty() {
return Err(crate::core::error::ProcessingError::Validation {
details: "Input cannot be empty".to_string(),
context: None,
});
}
Ok(())
}
}
let validator = TestValidator;
assert!(validator.validate(&"test".to_string()).is_ok());
assert!(validator.validate(&"".to_string()).is_err());
}
#[test]
fn test_processing_context() {
#[derive(Debug, Clone)]
struct TestContext {
settings: HashMap<String, String>,
}
impl ProcessingContext for TestContext {
fn into_context(self) -> JsonValue {
serde_json::to_value(self.settings)
.unwrap_or(JsonValue::Null)
}
}
let mut settings = HashMap::new();
_ = settings.insert("key".to_string(), "value".to_string());
let context = TestContext { settings };
let json_context = context.into_context();
assert_eq!(json_context["key"], "value");
}
#[test]
fn test_generator() {
#[derive(Debug)]
struct TestGenerator;
impl Generator for TestGenerator {
fn generate(
&self,
content: &str,
path: &Path,
_options: Option<&JsonValue>,
) -> Result<()> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(path, content)?;
Ok(())
}
fn validate(
&self,
path: &Path,
_options: Option<&JsonValue>,
) -> Result<()> {
if let Some(parent) = path.parent() {
if !parent.exists() {
fs::create_dir_all(parent)?;
}
}
Ok(())
}
}
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.txt");
let generator = TestGenerator;
generator
.generate("test content", &test_file, None)
.unwrap();
assert!(test_file.exists());
assert_eq!(
fs::read_to_string(test_file).unwrap(),
"test content"
);
}
#[test]
fn test_shareable() {
#[derive(Debug)]
struct TestState {
counter: usize,
}
let state = TestState { counter: 0 };
let shared = state.into_shared();
{
let mut write_guard = shared.write();
write_guard.counter += 1;
}
let read_guard = shared.read();
assert_eq!(read_guard.counter, 1);
}
#[test]
fn test_processing_options() {
let default_options = ProcessingOptions::default();
assert!(!default_options.strict_mode);
assert!(default_options.validate);
assert!(default_options.cache_enabled);
let custom_options = ProcessingOptions {
strict_mode: true,
validate: false,
cache_enabled: false,
custom: serde_json::json!({"key": "value"}),
};
assert!(custom_options.strict_mode);
assert!(!custom_options.validate);
assert!(!custom_options.cache_enabled);
assert_eq!(custom_options.custom["key"], "value");
}
}