1use crate::error::{ISO8583Error, Result};
10use std::fmt;
11
12#[allow(missing_docs)]
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub struct MessageType {
16 pub version: u8,
17 pub class: MessageClass,
18 pub function: MessageFunction,
19 pub origin: MessageOrigin,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub enum MessageClass {
25 Reserved = 0,
27 Authorization = 1,
29 Financial = 2,
31 FileActions = 3,
33 Reversal = 4,
35 Reconciliation = 5,
37 Administrative = 6,
39 FeeCollection = 7,
41 NetworkManagement = 8,
43 ReservedISO = 9,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49pub enum MessageFunction {
50 Request = 0,
52 Response = 1,
54 Advice = 2,
56 AdviceResponse = 3,
58 Notification = 4,
60 NotificationAck = 5,
62 Instruction = 6,
64 InstructionAck = 7,
66 Reserved8 = 8,
68 Reserved9 = 9,
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
74pub enum MessageOrigin {
75 Acquirer = 0,
77 AcquirerRepeat = 1,
79 Issuer = 2,
81 IssuerRepeat = 3,
83 Other = 4,
85 OtherRepeat = 5,
87 Reserved6 = 6,
89 Reserved7 = 7,
91 Reserved8 = 8,
93 Reserved9 = 9,
95}
96
97impl MessageType {
98 pub const AUTHORIZATION_REQUEST: Self = Self {
102 version: 0,
103 class: MessageClass::Authorization,
104 function: MessageFunction::Request,
105 origin: MessageOrigin::Acquirer,
106 };
107
108 pub const AUTHORIZATION_RESPONSE: Self = Self {
110 version: 0,
111 class: MessageClass::Authorization,
112 function: MessageFunction::Response,
113 origin: MessageOrigin::Acquirer,
114 };
115
116 pub const AUTHORIZATION_ADVICE: Self = Self {
118 version: 0,
119 class: MessageClass::Authorization,
120 function: MessageFunction::Advice,
121 origin: MessageOrigin::Acquirer,
122 };
123
124 pub const AUTHORIZATION_ADVICE_RESPONSE: Self = Self {
126 version: 0,
127 class: MessageClass::Authorization,
128 function: MessageFunction::AdviceResponse,
129 origin: MessageOrigin::Acquirer,
130 };
131
132 pub const FINANCIAL_REQUEST: Self = Self {
134 version: 0,
135 class: MessageClass::Financial,
136 function: MessageFunction::Request,
137 origin: MessageOrigin::Acquirer,
138 };
139
140 pub const FINANCIAL_RESPONSE: Self = Self {
142 version: 0,
143 class: MessageClass::Financial,
144 function: MessageFunction::Response,
145 origin: MessageOrigin::Acquirer,
146 };
147
148 pub const FINANCIAL_ADVICE: Self = Self {
150 version: 0,
151 class: MessageClass::Financial,
152 function: MessageFunction::Advice,
153 origin: MessageOrigin::Acquirer,
154 };
155
156 pub const FINANCIAL_ADVICE_RESPONSE: Self = Self {
158 version: 0,
159 class: MessageClass::Financial,
160 function: MessageFunction::AdviceResponse,
161 origin: MessageOrigin::Acquirer,
162 };
163
164 pub const REVERSAL_REQUEST: Self = Self {
166 version: 0,
167 class: MessageClass::Reversal,
168 function: MessageFunction::Request,
169 origin: MessageOrigin::Acquirer,
170 };
171
172 pub const REVERSAL_RESPONSE: Self = Self {
174 version: 0,
175 class: MessageClass::Reversal,
176 function: MessageFunction::Response,
177 origin: MessageOrigin::Acquirer,
178 };
179
180 pub const REVERSAL_ADVICE: Self = Self {
182 version: 0,
183 class: MessageClass::Reversal,
184 function: MessageFunction::Advice,
185 origin: MessageOrigin::Acquirer,
186 };
187
188 pub const REVERSAL_ADVICE_RESPONSE: Self = Self {
190 version: 0,
191 class: MessageClass::Reversal,
192 function: MessageFunction::AdviceResponse,
193 origin: MessageOrigin::Acquirer,
194 };
195
196 pub const NETWORK_MANAGEMENT_REQUEST: Self = Self {
198 version: 0,
199 class: MessageClass::NetworkManagement,
200 function: MessageFunction::Request,
201 origin: MessageOrigin::Acquirer,
202 };
203
204 pub const NETWORK_MANAGEMENT_RESPONSE: Self = Self {
206 version: 0,
207 class: MessageClass::NetworkManagement,
208 function: MessageFunction::Response,
209 origin: MessageOrigin::Acquirer,
210 };
211
212 pub const NETWORK_MANAGEMENT_ADVICE: Self = Self {
214 version: 0,
215 class: MessageClass::NetworkManagement,
216 function: MessageFunction::Advice,
217 origin: MessageOrigin::Acquirer,
218 };
219
220 pub fn new(
222 version: u8,
223 class: MessageClass,
224 function: MessageFunction,
225 origin: MessageOrigin,
226 ) -> Self {
227 Self {
228 version,
229 class,
230 function,
231 origin,
232 }
233 }
234
235 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
237 if bytes.len() < 4 {
238 return Err(ISO8583Error::InvalidMTI(format!(
239 "MTI must be at least 4 bytes, got {}",
240 bytes.len()
241 )));
242 }
243
244 let s = std::str::from_utf8(&bytes[..4])
245 .map_err(|e| ISO8583Error::InvalidMTI(format!("Invalid UTF-8: {}", e)))?;
246
247 s.parse()
248 }
249
250 pub fn to_bytes(&self) -> Vec<u8> {
252 self.to_string().into_bytes()
253 }
254
255 pub fn is_request(&self) -> bool {
257 matches!(self.function, MessageFunction::Request)
258 }
259
260 pub fn is_response(&self) -> bool {
262 matches!(self.function, MessageFunction::Response)
263 }
264
265 pub fn is_advice(&self) -> bool {
267 matches!(
268 self.function,
269 MessageFunction::Advice | MessageFunction::AdviceResponse
270 )
271 }
272
273 pub fn to_response(&self) -> Result<Self> {
275 if !self.is_request() {
276 return Err(ISO8583Error::InvalidMTI(
277 "Can only convert request to response".to_string(),
278 ));
279 }
280
281 Ok(Self {
282 version: self.version,
283 class: self.class,
284 function: MessageFunction::Response,
285 origin: self.origin,
286 })
287 }
288}
289
290impl std::str::FromStr for MessageType {
291 type Err = ISO8583Error;
292
293 fn from_str(s: &str) -> Result<Self> {
294 if s.len() != 4 {
295 return Err(ISO8583Error::InvalidMTI(format!(
296 "MTI must be 4 digits, got {}",
297 s.len()
298 )));
299 }
300
301 let digits: Vec<u8> = s
302 .chars()
303 .map(|c| {
304 c.to_digit(10)
305 .map(|d| d as u8)
306 .ok_or_else(|| ISO8583Error::InvalidMTI(format!("Invalid digit: {}", c)))
307 })
308 .collect::<Result<Vec<_>>>()?;
309
310 Ok(Self {
311 version: digits[0],
312 class: MessageClass::from_digit(digits[1])?,
313 function: MessageFunction::from_digit(digits[2])?,
314 origin: MessageOrigin::from_digit(digits[3])?,
315 })
316 }
317}
318
319impl MessageClass {
320 fn from_digit(digit: u8) -> Result<Self> {
321 match digit {
322 0 => Ok(Self::Reserved),
323 1 => Ok(Self::Authorization),
324 2 => Ok(Self::Financial),
325 3 => Ok(Self::FileActions),
326 4 => Ok(Self::Reversal),
327 5 => Ok(Self::Reconciliation),
328 6 => Ok(Self::Administrative),
329 7 => Ok(Self::FeeCollection),
330 8 => Ok(Self::NetworkManagement),
331 9 => Ok(Self::ReservedISO),
332 _ => Err(ISO8583Error::InvalidMessageClass(format!(
333 "Invalid message class digit: {}",
334 digit
335 ))),
336 }
337 }
338
339 fn to_digit(self) -> u8 {
340 self as u8
341 }
342}
343
344impl MessageFunction {
345 fn from_digit(digit: u8) -> Result<Self> {
346 match digit {
347 0 => Ok(Self::Request),
348 1 => Ok(Self::Response),
349 2 => Ok(Self::Advice),
350 3 => Ok(Self::AdviceResponse),
351 4 => Ok(Self::Notification),
352 5 => Ok(Self::NotificationAck),
353 6 => Ok(Self::Instruction),
354 7 => Ok(Self::InstructionAck),
355 8 => Ok(Self::Reserved8),
356 9 => Ok(Self::Reserved9),
357 _ => Err(ISO8583Error::InvalidMessageFunction(format!(
358 "Invalid message function digit: {}",
359 digit
360 ))),
361 }
362 }
363
364 fn to_digit(self) -> u8 {
365 self as u8
366 }
367}
368
369impl MessageOrigin {
370 fn from_digit(digit: u8) -> Result<Self> {
371 match digit {
372 0 => Ok(Self::Acquirer),
373 1 => Ok(Self::AcquirerRepeat),
374 2 => Ok(Self::Issuer),
375 3 => Ok(Self::IssuerRepeat),
376 4 => Ok(Self::Other),
377 5 => Ok(Self::OtherRepeat),
378 6 => Ok(Self::Reserved6),
379 7 => Ok(Self::Reserved7),
380 8 => Ok(Self::Reserved8),
381 9 => Ok(Self::Reserved9),
382 _ => Err(ISO8583Error::InvalidMessageOrigin(format!(
383 "Invalid message origin digit: {}",
384 digit
385 ))),
386 }
387 }
388
389 fn to_digit(self) -> u8 {
390 self as u8
391 }
392}
393
394impl fmt::Display for MessageType {
395 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
396 write!(
397 f,
398 "{}{}{}{}",
399 self.version,
400 self.class.to_digit(),
401 self.function.to_digit(),
402 self.origin.to_digit()
403 )
404 }
405}
406
407#[cfg(test)]
408mod tests {
409 use super::*;
410
411 #[test]
412 fn test_mti_parsing() {
413 let mti: MessageType = "0100".parse().unwrap();
414 assert_eq!(mti.version, 0);
415 assert_eq!(mti.class, MessageClass::Authorization);
416 assert_eq!(mti.function, MessageFunction::Request);
417 assert_eq!(mti.origin, MessageOrigin::Acquirer);
418 assert_eq!(mti.to_string(), "0100");
419 }
420
421 #[test]
422 fn test_mti_constants() {
423 assert_eq!(MessageType::AUTHORIZATION_REQUEST.to_string(), "0100");
424 assert_eq!(MessageType::AUTHORIZATION_RESPONSE.to_string(), "0110");
425 assert_eq!(MessageType::FINANCIAL_REQUEST.to_string(), "0200");
426 assert_eq!(MessageType::FINANCIAL_RESPONSE.to_string(), "0210");
427 assert_eq!(MessageType::REVERSAL_REQUEST.to_string(), "0400");
428 assert_eq!(MessageType::NETWORK_MANAGEMENT_REQUEST.to_string(), "0800");
429 }
430
431 #[test]
432 fn test_mti_predicates() {
433 let request = MessageType::AUTHORIZATION_REQUEST;
434 assert!(request.is_request());
435 assert!(!request.is_response());
436 assert!(!request.is_advice());
437
438 let response = MessageType::AUTHORIZATION_RESPONSE;
439 assert!(!response.is_request());
440 assert!(response.is_response());
441 assert!(!response.is_advice());
442
443 let advice = MessageType::AUTHORIZATION_ADVICE;
444 assert!(!advice.is_request());
445 assert!(!advice.is_response());
446 assert!(advice.is_advice());
447 }
448
449 #[test]
450 fn test_to_response() {
451 let request = MessageType::AUTHORIZATION_REQUEST;
452 let response = request.to_response().unwrap();
453 assert_eq!(response, MessageType::AUTHORIZATION_RESPONSE);
454
455 let err = response.to_response();
457 assert!(err.is_err());
458 }
459
460 #[test]
461 fn test_invalid_mti() {
462 assert!("123".parse::<MessageType>().is_err()); assert!("12345".parse::<MessageType>().is_err()); assert!("abcd".parse::<MessageType>().is_err()); }
466}