#[derive(Debug, thiserror::Error)]
pub enum ValidationError {
#[error("Invalid configuration: {0}")]
InvalidConfig(String),
#[error("Missing required field: {0}")]
MissingField(String),
#[error("Invalid value for {field}: {message}")]
InvalidValue {
field: String,
message: String,
},
#[error("Incompatible configuration: {0}")]
IncompatibleConfig(String),
}
pub struct ConfigValidator {
errors: Vec<ValidationError>,
warnings: Vec<String>,
}
impl ConfigValidator {
pub fn new() -> Self {
Self {
errors: Vec::new(),
warnings: Vec::new(),
}
}
pub fn require_field(&mut self, field_name: &str, value: Option<&str>) {
if value.is_none() || value == Some("") {
self.errors
.push(ValidationError::MissingField(field_name.to_string()));
}
}
pub fn add_error(&mut self, error: ValidationError) {
self.errors.push(error);
}
pub fn add_warning(&mut self, message: String) {
self.warnings.push(message);
}
pub fn is_valid(&self) -> bool {
self.errors.is_empty()
}
pub fn errors(&self) -> &[ValidationError] {
&self.errors
}
pub fn warnings(&self) -> &[String] {
&self.warnings
}
pub fn validate(self) -> Result<Vec<String>, Vec<ValidationError>> {
if self.errors.is_empty() {
Ok(self.warnings)
} else {
Err(self.errors)
}
}
}
impl Default for ConfigValidator {
fn default() -> Self {
Self::new()
}
}
pub fn validate_worker_config(
concurrency: Option<usize>,
prefetch_count: Option<usize>,
) -> Result<Vec<String>, Vec<ValidationError>> {
let mut validator = ConfigValidator::new();
if let Some(c) = concurrency {
if c == 0 {
validator.add_error(ValidationError::InvalidValue {
field: "concurrency".to_string(),
message: "must be greater than 0".to_string(),
});
}
if c > 1000 {
validator.add_warning(format!(
"High concurrency value ({}). Consider if this is intentional.",
c
));
}
}
if let Some(p) = prefetch_count {
if p == 0 {
validator.add_error(ValidationError::InvalidValue {
field: "prefetch_count".to_string(),
message: "must be greater than 0".to_string(),
});
}
if p > 1000 {
validator.add_warning(format!(
"High prefetch_count value ({}). This may consume significant memory.",
p
));
}
}
validator.validate()
}
pub fn validate_broker_url(url: &str) -> Result<String, ValidationError> {
if url.is_empty() {
return Err(ValidationError::InvalidValue {
field: "broker_url".to_string(),
message: "cannot be empty".to_string(),
});
}
if !url.contains("://") {
return Err(ValidationError::InvalidValue {
field: "broker_url".to_string(),
message: "invalid URL format (missing scheme)".to_string(),
});
}
let scheme = url.split("://").next().unwrap_or("");
match scheme {
"redis" | "rediss" | "postgres" | "postgresql" | "mysql" | "amqp" | "amqps" | "sqs" => {
Ok(format!("Valid {} URL", scheme))
}
_ => Err(ValidationError::InvalidValue {
field: "broker_url".to_string(),
message: format!("unsupported scheme: {}", scheme),
}),
}
}
pub fn check_feature_compatibility(features: &[&str]) -> Result<Vec<String>, ValidationError> {
let mut warnings = Vec::new();
let broker_features: Vec<_> = features
.iter()
.filter(|f| ["redis", "postgres", "mysql", "amqp", "sqs"].contains(f))
.collect();
if broker_features.len() > 1 {
warnings.push(format!(
"Multiple broker features enabled: {:?}. Ensure you're using the correct broker.",
broker_features
));
}
let backend_features: Vec<_> = features
.iter()
.filter(|f| ["backend-redis", "backend-db", "backend-rpc"].contains(f))
.collect();
if backend_features.len() > 1 {
warnings.push(format!(
"Multiple backend features enabled: {:?}. Ensure you're using the correct backend.",
backend_features
));
}
Ok(warnings)
}
pub fn feature_compatibility_matrix() -> String {
r#"
╔══════════════════════════════════════════════════════════════════════════════╗
║ CeleRS Feature Compatibility Matrix ║
╚══════════════════════════════════════════════════════════════════════════════╝
BROKER FEATURES (Choose ONE):
✓ redis - Redis broker (recommended for most use cases)
✓ postgres - PostgreSQL broker (good for existing PostgreSQL infrastructure)
✓ mysql - MySQL broker (good for existing MySQL infrastructure)
✓ amqp - RabbitMQ/AMQP broker (enterprise messaging)
✓ sqs - AWS SQS broker (cloud-native, serverless)
BACKEND FEATURES (Choose ONE):
✓ backend-redis - Redis result backend (recommended with redis broker)
✓ backend-db - PostgreSQL/MySQL backend (use with postgres/mysql broker)
✓ backend-rpc - gRPC result backend (distributed systems)
SERIALIZATION FEATURES (Can combine):
✓ json - JSON serialization (default, always available)
✓ msgpack - MessagePack serialization (compact binary format)
OBSERVABILITY FEATURES (Can combine):
✓ metrics - Prometheus metrics
✓ tracing - OpenTelemetry distributed tracing
OTHER FEATURES (Can combine):
✓ beat - Periodic task scheduler
✓ dev-utils - Development and testing utilities
RECOMMENDED COMBINATIONS:
1. Simple Setup:
features = ["redis", "backend-redis", "json"]
2. Production Ready:
features = ["redis", "backend-redis", "json", "metrics", "tracing"]
3. PostgreSQL Stack:
features = ["postgres", "backend-db", "json", "metrics"]
4. AWS Cloud:
features = ["sqs", "backend-rpc", "json", "msgpack", "metrics"]
5. Full Featured:
features = ["full"] # Enables all features
NOTES:
- Multiple brokers can be compiled but only one should be used at runtime
- Multiple backends can be compiled but only one should be used at runtime
- json + msgpack enables both serialization formats
- metrics + tracing provides comprehensive observability
"#
.to_string()
}