1use thiserror::Error;
4
5pub type ValidationResult<T> = Result<T, ValidationError>;
6
7#[derive(Error, Debug, Clone)]
8pub enum ValidationError {
9 #[error("Unknown option '{option}' for command '{command}'")]
10 UnknownOption {
11 command: String,
12 option: String,
13 suggestions: Vec<String>,
14 },
15
16 #[error("Invalid value '{value}' for option '{option}': {reason}")]
17 InvalidValue {
18 option: String,
19 value: String,
20 reason: String,
21 },
22
23 #[error("Option '{option}' conflicts with '{conflicts_with}'")]
24 OptionConflict {
25 option: String,
26 conflicts_with: String,
27 },
28
29 #[error("Option '{option}' requires '{required}' to be specified")]
30 MissingRequirement { option: String, required: String },
31
32 #[error("Option '{option}' is deprecated: {message}")]
33 DeprecatedOption { option: String, message: String },
34
35 #[error("Option '{option}' expects a value")]
36 MissingValue { option: String },
37
38 #[error("Option '{option}' does not accept a value")]
39 UnexpectedValue { option: String, value: String },
40
41 #[error("Provider error: {message}")]
42 ProviderError { message: String },
43}
44
45impl ValidationError {
46 pub fn unknown_option(
48 command: impl Into<String>,
49 option: impl Into<String>,
50 suggestions: Vec<String>,
51 ) -> Self {
52 Self::UnknownOption {
53 command: command.into(),
54 option: option.into(),
55 suggestions,
56 }
57 }
58
59 pub fn invalid_value(
61 option: impl Into<String>,
62 value: impl Into<String>,
63 reason: impl Into<String>,
64 ) -> Self {
65 Self::InvalidValue {
66 option: option.into(),
67 value: value.into(),
68 reason: reason.into(),
69 }
70 }
71
72 pub fn conflict(option: impl Into<String>, conflicts_with: impl Into<String>) -> Self {
74 Self::OptionConflict {
75 option: option.into(),
76 conflicts_with: conflicts_with.into(),
77 }
78 }
79
80 pub fn missing_requirement(option: impl Into<String>, required: impl Into<String>) -> Self {
82 Self::MissingRequirement {
83 option: option.into(),
84 required: required.into(),
85 }
86 }
87
88 pub fn deprecated(option: impl Into<String>, message: impl Into<String>) -> Self {
90 Self::DeprecatedOption {
91 option: option.into(),
92 message: message.into(),
93 }
94 }
95
96 pub fn format_with_suggestions(&self) -> String {
98 match self {
99 Self::UnknownOption {
100 command,
101 option,
102 suggestions,
103 } => {
104 let mut msg = format!("Unknown option '{option}' for command '{command}'");
105 if !suggestions.is_empty() {
106 msg.push_str(&format!("\nDid you mean: {}", suggestions.join(", ")));
107 }
108 msg
109 }
110 _ => self.to_string(),
111 }
112 }
113}