Skip to content

Commit 2e5de52

Browse files
committed
Refactor error handling
1 parent c1c73ac commit 2e5de52

File tree

1 file changed

+66
-112
lines changed

1 file changed

+66
-112
lines changed

src/lib.rs

Lines changed: 66 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -113,68 +113,42 @@ pub enum Argument<'a> {
113113
Stdio,
114114
}
115115

116-
impl<'a> Argument<'a> {
117-
/// Converts this argument into a [`ParsingError::UnexpectedArg`] error.
116+
impl Argument<'_> {
117+
/// Converts this argument into a [`ParsingError::Unexpected`] error.
118118
///
119119
/// This is a convenience method for creating contextual error messages when an argument
120120
/// is encountered but not expected by the application. The resulting error message
121121
/// includes appropriate formatting based on the argument type.
122122
///
123-
/// # Parameters
124-
///
125-
/// * `value` - Optional value associated with the argument (primarily used with options)
126-
///
127123
/// # Examples
128124
///
129125
/// ```rust
130126
/// use sap::Argument;
131127
///
132-
/// // Long option with value
128+
/// // Long option
133129
/// let arg = Argument::Long("unknown");
134-
/// let error = arg.into_error(Some("value"));
135-
/// assert_eq!(error.to_string(), "unexpected argument: --unknown=value");
130+
/// let error = arg.unexpected();
131+
/// assert_eq!(error.to_string(), "unexpected argument: --unknown");
136132
///
137-
/// // Short option without value
133+
/// // Short option
138134
/// let arg = Argument::Short('x');
139-
/// let error = arg.into_error(None::<&str>);
135+
/// let error = arg.unexpected();
140136
/// assert_eq!(error.to_string(), "unexpected argument: -x");
141137
///
138+
/// // Positional value
139+
/// let arg = Argument::Value("file".into());
140+
/// let error = arg.unexpected();
141+
/// assert_eq!(error.to_string(), "unexpected argument: file");
142+
///
142143
/// // Stdio argument
143144
/// let arg = Argument::Stdio;
144-
/// let error = arg.into_error(None::<&str>);
145+
/// let error = arg.unexpected();
145146
/// assert_eq!(error.to_string(), "unexpected argument: -");
146147
/// ```
147-
pub fn into_error<A>(self, value: A) -> ParsingError
148-
where
149-
A: Into<Option<&'a str>>,
150-
{
151-
use Argument::{Long, Short, Stdio, Value};
152-
153-
match self {
154-
Long(arg) => ParsingError::UnexpectedArg {
155-
offender: arg.to_string(),
156-
value: value.into().map(String::from),
157-
format: "=",
158-
prefix: "--",
159-
},
160-
Short(arg) => ParsingError::UnexpectedArg {
161-
offender: arg.to_string(),
162-
value: value.into().map(String::from),
163-
format: " ",
164-
prefix: "-",
165-
},
166-
Value(arg) => ParsingError::UnexpectedArg {
167-
offender: arg.to_string(),
168-
value: None,
169-
format: "",
170-
prefix: "",
171-
},
172-
Stdio => ParsingError::UnexpectedArg {
173-
offender: "-".to_string(),
174-
value: None,
175-
format: "",
176-
prefix: "",
177-
},
148+
#[must_use]
149+
pub fn unexpected(&self) -> ParsingError {
150+
ParsingError::Unexpected {
151+
argument: self.to_string(),
178152
}
179153
}
180154
}
@@ -380,9 +354,8 @@ where
380354
if char == '=' {
381355
self.state = State::Poisoned;
382356

383-
return Err(ParsingError::InvalidOption {
357+
return Err(ParsingError::InvalidSyntax {
384358
reason: "Short options do not support values",
385-
offender: None,
386359
});
387360
}
388361

@@ -623,8 +596,14 @@ where
623596
///
624597
/// All parsing operations return a `Result` with this error type. Each variant
625598
/// provides specific context about what went wrong during parsing.
626-
#[derive(Debug)]
599+
#[derive(Debug, Clone, PartialEq, Eq)]
627600
pub enum ParsingError {
601+
/// The argument iterator was empty (contained no program name).
602+
///
603+
/// This should not occur during normal program execution but may happen
604+
/// when creating parsers from empty custom iterators.
605+
Empty,
606+
628607
/// Invalid option syntax or format was encountered.
629608
///
630609
/// This typically occurs when:
@@ -634,11 +613,7 @@ pub enum ParsingError {
634613
/// # Fields
635614
///
636615
/// * `reason` - Human-readable description of what was invalid
637-
/// * `offender` - The specific argument that caused the error (if available)
638-
InvalidOption {
639-
reason: &'static str,
640-
offender: Option<String>,
641-
},
616+
InvalidSyntax { reason: &'static str },
642617

643618
/// An option value was not consumed after being parsed.
644619
///
@@ -651,63 +626,33 @@ pub enum ParsingError {
651626
/// * `value` - The unconsumed value that was attached to the option
652627
UnconsumedValue { value: String },
653628

654-
/// The argument iterator was empty (contained no program name).
655-
///
656-
/// This should not occur during normal program execution but may happen
657-
/// when creating parsers from empty custom iterators.
658-
Empty,
659-
660629
/// An unexpected or unrecognized argument was encountered.
661630
///
662-
/// This error is typically created by calling [`Argument::into_error`] when
631+
/// This error is typically created by calling [`Argument::unexpected`] when
663632
/// the application encounters an argument it doesn't know how to handle.
664633
///
665634
/// # Fields
666635
///
667-
/// * `offender` - The argument name that was unexpected
668-
/// * `value` - Associated value (if any)
669-
/// * `format` - How the value is formatted in error messages (e.g., "=" or " ")
670-
/// * `prefix` - The argument prefix (e.g., "--" for long options, "-" for short)
671-
UnexpectedArg {
672-
offender: String,
673-
value: Option<String>,
674-
format: &'static str,
675-
prefix: &'static str,
676-
},
636+
/// * `argument` - The formatted argument string (e.g., "--unknown", "-x value", "file.txt")
637+
///
638+
/// # Examples
639+
///
640+
/// ```rust
641+
/// use sap::{Argument, ParsingError};
642+
///
643+
/// let error = Argument::Long("unknown").unexpected();
644+
/// assert_eq!(error.to_string(), "unexpected argument: --unknown");
645+
/// ```
646+
Unexpected { argument: String },
677647
}
678648

679649
impl Display for ParsingError {
680650
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
681651
match self {
682-
Self::InvalidOption { reason, offender } => {
683-
write!(f, "reason: {reason}")?;
684-
if let Some(sentence) = offender {
685-
write!(f, " at: {sentence}")?;
686-
}
687-
688-
Ok(())
689-
}
690-
691-
Self::UnconsumedValue { value } => {
692-
write!(f, "leftover value: {value}",)
693-
}
694-
695-
Self::UnexpectedArg {
696-
offender,
697-
value,
698-
format,
699-
prefix,
700-
} => match value {
701-
Some(val) => {
702-
write!(f, "unexpected argument: {prefix}{offender}{format}{val}")
703-
}
704-
705-
None => {
706-
write!(f, "unexpected argument: {prefix}{offender}")
707-
}
708-
},
709-
710-
Self::Empty => write!(f, "env variables were empty"),
652+
Self::Empty => write!(f, "argument list is empty"),
653+
Self::InvalidSyntax { reason } => write!(f, "invalid syntax: {reason}"),
654+
Self::UnconsumedValue { value } => write!(f, "unconsumed value: {value}"),
655+
Self::Unexpected { argument } => write!(f, "unexpected argument: {argument}"),
711656
}
712657
}
713658
}
@@ -950,30 +895,39 @@ mod tests {
950895
}
951896

952897
#[test]
953-
fn argument_into_error() {
954-
assert_eq!(
955-
Long("test").into_error("value").to_string(),
956-
"unexpected argument: --test=value"
957-
);
898+
fn argument_unexpected() {
899+
use crate::ParsingError;
900+
901+
// Test unexpected() without values
958902
assert_eq!(
959-
Long("test").into_error(None::<&str>).to_string(),
960-
"unexpected argument: --test"
903+
Long("test").unexpected(),
904+
ParsingError::Unexpected {
905+
argument: "--test".to_string()
906+
}
961907
);
962908
assert_eq!(
963-
Short('x').into_error("val").to_string(),
964-
"unexpected argument: -x val"
909+
Short('x').unexpected(),
910+
ParsingError::Unexpected {
911+
argument: "-x".to_string()
912+
}
965913
);
966914
assert_eq!(
967-
Short('x').into_error(None::<&str>).to_string(),
968-
"unexpected argument: -x"
915+
Value("file".into()).unexpected(),
916+
ParsingError::Unexpected {
917+
argument: "file".to_string()
918+
}
969919
);
970920
assert_eq!(
971-
Value("file".into()).into_error(None::<&str>).to_string(),
972-
"unexpected argument: file"
921+
Stdio.unexpected(),
922+
ParsingError::Unexpected {
923+
argument: "-".to_string()
924+
}
973925
);
926+
927+
// Verify Display implementation still works correctly
974928
assert_eq!(
975-
Stdio.into_error(None::<&str>).to_string(),
976-
"unexpected argument: -"
929+
Long("test").unexpected().to_string(),
930+
"unexpected argument: --test"
977931
);
978932
}
979933

0 commit comments

Comments
 (0)