use crate::error::{ValidationError, ValidationResult};
use serde::{Deserialize, Serialize};
pub trait OptionProvider: Send + Sync {
fn name(&self) -> &str;
fn get_options(&self, command: &str) -> Vec<OptionDef>;
fn validate(&self, command: &str, option: &str, value: Option<&str>) -> ValidationResult<()>;
fn get_commands(&self) -> Vec<String>;
fn supports_command(&self, command: &str) -> bool {
self.get_commands().contains(&command.to_string())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OptionDef {
pub name: String,
pub value_type: OptionValueType,
pub description: String,
pub conflicts_with: Vec<String>,
pub requires: Vec<String>,
pub deprecated: Option<String>,
pub repeatable: bool,
}
impl OptionDef {
pub fn flag(name: impl Into<String>, description: impl Into<String>) -> Self {
Self {
name: name.into(),
value_type: OptionValueType::Flag,
description: description.into(),
conflicts_with: Vec::new(),
requires: Vec::new(),
deprecated: None,
repeatable: false,
}
}
pub fn single(
name: impl Into<String>,
description: impl Into<String>,
validator: ValueValidator,
) -> Self {
Self {
name: name.into(),
value_type: OptionValueType::Single(validator),
description: description.into(),
conflicts_with: Vec::new(),
requires: Vec::new(),
deprecated: None,
repeatable: false,
}
}
pub fn multiple(
name: impl Into<String>,
description: impl Into<String>,
validator: ValueValidator,
) -> Self {
Self {
name: name.into(),
value_type: OptionValueType::Multiple(validator),
description: description.into(),
conflicts_with: Vec::new(),
requires: Vec::new(),
deprecated: None,
repeatable: false,
}
}
pub fn conflicts_with(mut self, options: Vec<String>) -> Self {
self.conflicts_with = options;
self
}
pub fn requires(mut self, options: Vec<String>) -> Self {
self.requires = options;
self
}
pub fn deprecated(mut self, message: impl Into<String>) -> Self {
self.deprecated = Some(message.into());
self
}
pub fn repeatable(mut self) -> Self {
self.repeatable = true;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum OptionValueType {
Flag,
Single(ValueValidator),
Multiple(ValueValidator),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ValueValidator {
Any,
Number,
Enum(Vec<String>),
Regex(String),
FilePath,
DirectoryPath,
Custom(String),
}
impl ValueValidator {
pub fn validate(&self, value: &str) -> ValidationResult<()> {
match self {
Self::Any => Ok(()),
Self::Number => value
.parse::<f64>()
.map(|_| ())
.map_err(|_| ValidationError::invalid_value("", value, "must be a number")),
Self::Enum(valid_values) => {
if valid_values.contains(&value.to_string()) {
Ok(())
} else {
Err(ValidationError::invalid_value(
"",
value,
format!("must be one of: {}", valid_values.join(", ")),
))
}
}
Self::Regex(pattern) => {
let regex = regex::Regex::new(pattern).map_err(|_| {
ValidationError::invalid_value("", value, "invalid regex pattern")
})?;
if regex.is_match(value) {
Ok(())
} else {
Err(ValidationError::invalid_value(
"",
value,
format!("must match pattern: {pattern}"),
))
}
}
Self::FilePath => {
if value.is_empty() {
Err(ValidationError::invalid_value(
"",
value,
"file path cannot be empty",
))
} else {
Ok(())
}
}
Self::DirectoryPath => {
if value.is_empty() {
Err(ValidationError::invalid_value(
"",
value,
"directory path cannot be empty",
))
} else {
Ok(())
}
}
Self::Custom(_name) => {
Ok(())
}
}
}
}
pub mod cargo;
pub mod leptos;