1use thiserror::Error;
7
8#[derive(Error, Debug, Clone, PartialEq, Eq)]
13pub enum LpParseError {
14 #[error("Invalid constraint syntax at position {position}: {context}")]
16 ConstraintSyntax { position: usize, context: String },
17
18 #[error("Invalid objective syntax at position {position}: {context}")]
20 ObjectiveSyntax { position: usize, context: String },
21
22 #[error("Unknown variable type '{var_type}' for variable '{variable}'")]
24 UnknownVariableType { variable: String, var_type: String },
25
26 #[error("Undefined variable '{variable}' referenced in {context}")]
28 UndefinedVariable { variable: String, context: String },
29
30 #[error("Duplicate {component_type} '{name}' defined")]
32 DuplicateDefinition { component_type: String, name: String },
33
34 #[error("Invalid number format '{value}' at position {position}")]
36 InvalidNumber { value: String, position: usize },
37
38 #[error("Missing required section: {section}")]
40 MissingSection { section: String },
41
42 #[error("Invalid bounds for variable '{variable}': {details}")]
44 InvalidBounds { variable: String, details: String },
45
46 #[error("Invalid SOS constraint '{constraint}': {details}")]
48 InvalidSosConstraint { constraint: String, details: String },
49
50 #[error("Validation error: {message}")]
52 ValidationError { message: String },
53
54 #[error("Parse error at position {position}: {message}")]
56 ParseError { position: usize, message: String },
57
58 #[error("File I/O error: {message}")]
60 IoError { message: String },
61
62 #[error("Internal parser error: {message}")]
64 InternalError { message: String },
65}
66
67impl LpParseError {
68 pub fn constraint_syntax(position: usize, context: impl Into<String>) -> Self {
70 Self::ConstraintSyntax { position, context: context.into() }
71 }
72
73 pub fn objective_syntax(position: usize, context: impl Into<String>) -> Self {
75 Self::ObjectiveSyntax { position, context: context.into() }
76 }
77
78 pub fn unknown_variable_type(variable: impl Into<String>, var_type: impl Into<String>) -> Self {
80 Self::UnknownVariableType { variable: variable.into(), var_type: var_type.into() }
81 }
82
83 pub fn undefined_variable(variable: impl Into<String>, context: impl Into<String>) -> Self {
85 Self::UndefinedVariable { variable: variable.into(), context: context.into() }
86 }
87
88 pub fn duplicate_definition(component_type: impl Into<String>, name: impl Into<String>) -> Self {
90 Self::DuplicateDefinition { component_type: component_type.into(), name: name.into() }
91 }
92
93 pub fn invalid_number(value: impl Into<String>, position: usize) -> Self {
95 Self::InvalidNumber { value: value.into(), position }
96 }
97
98 pub fn missing_section(section: impl Into<String>) -> Self {
100 Self::MissingSection { section: section.into() }
101 }
102
103 pub fn invalid_bounds(variable: impl Into<String>, details: impl Into<String>) -> Self {
105 Self::InvalidBounds { variable: variable.into(), details: details.into() }
106 }
107
108 pub fn invalid_sos_constraint(constraint: impl Into<String>, details: impl Into<String>) -> Self {
110 Self::InvalidSosConstraint { constraint: constraint.into(), details: details.into() }
111 }
112
113 pub fn validation_error(message: impl Into<String>) -> Self {
115 Self::ValidationError { message: message.into() }
116 }
117
118 pub fn parse_error(position: usize, message: impl Into<String>) -> Self {
120 Self::ParseError { position, message: message.into() }
121 }
122
123 pub fn io_error(message: impl Into<String>) -> Self {
125 Self::IoError { message: message.into() }
126 }
127
128 pub fn internal_error(message: impl Into<String>) -> Self {
130 Self::InternalError { message: message.into() }
131 }
132}
133
134impl<'a> From<nom::Err<nom::error::Error<&'a str>>> for LpParseError {
136 fn from(err: nom::Err<nom::error::Error<&'a str>>) -> Self {
137 match err {
138 nom::Err::Incomplete(_) => Self::parse_error(0, "Incomplete input"),
139 nom::Err::Error(e) | nom::Err::Failure(e) => Self::parse_error(e.input.as_ptr() as usize, format!("Parse error: {:?}", e.code)),
140 }
141 }
142}
143
144impl From<std::io::Error> for LpParseError {
146 fn from(err: std::io::Error) -> Self {
147 Self::io_error(err.to_string())
148 }
149}
150
151impl From<Box<dyn std::error::Error>> for LpParseError {
153 fn from(err: Box<dyn std::error::Error>) -> Self {
154 Self::io_error(err.to_string())
155 }
156}
157
158pub type LpResult<T> = Result<T, LpParseError>;
160
161pub trait ErrorContext<T> {
163 fn with_position(self, position: usize) -> LpResult<T>;
165
166 fn with_context(self, context: &str) -> LpResult<T>;
168}
169
170impl<T> ErrorContext<T> for Result<T, LpParseError> {
171 fn with_position(self, position: usize) -> Self {
172 self.map_err(|mut err| {
173 if let LpParseError::ParseError { position: ref mut pos, .. } = &mut err {
174 *pos = position;
175 }
176 err
177 })
178 }
179
180 fn with_context(self, context: &str) -> Self {
181 self.map_err(|err| match err {
182 LpParseError::ParseError { position, message } => LpParseError::parse_error(position, format!("{context}: {message}")),
183 other => other,
184 })
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 #[test]
193 fn test_error_creation() {
194 let err = LpParseError::constraint_syntax(42, "missing operator");
195 assert_eq!(err.to_string(), "Invalid constraint syntax at position 42: missing operator");
196 }
197
198 #[test]
199 fn test_error_context() {
200 let result: LpResult<()> = Err(LpParseError::parse_error(10, "test error"));
201 let with_context = result.with_context("parsing constraint");
202
203 assert!(with_context.is_err());
204 assert!(with_context.unwrap_err().to_string().contains("parsing constraint"));
205 }
206
207 #[test]
208 fn test_io_error_conversion() {
209 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
210 let lp_err: LpParseError = io_err.into();
211
212 match lp_err {
213 LpParseError::IoError { message } => assert!(message.contains("file not found")),
214 _ => panic!("Expected IoError"),
215 }
216 }
217}