fix44_forge_helpers/
errors.rs

1//! Error types for FIX protocol parsing and validation.
2//!
3//! This module provides comprehensive error handling for FIX protocol data parsing,
4//! including missing required fields and invalid value errors.
5
6/// Strict parse error type for generated read() APIs.
7///
8/// This error type is designed for high-performance parsing scenarios where
9/// detailed error information is needed for debugging and validation.
10#[derive(Debug)]
11#[allow(dead_code)]
12pub enum ReadError {
13    /// Aggregated (bitmask) missing required members: fields (kind=0), components (kind=1), groups (kind=2).
14    ///
15    /// The meta slice layout is: (name, tag_or_count_tag, kind)
16    MissingRequiredFields {
17        /// Bitmask indicating which required fields are missing
18        missing_mask: u64,
19        /// Metadata about the fields, components, and groups
20        meta: &'static [(&'static str, u16, u8)],
21    },
22    /// Invalid value encountered during parsing
23    InvalidValue {
24        /// Name of the field that had an invalid value
25        name: &'static str,
26        /// FIX tag number
27        tag: u16,
28        /// Description of what went wrong
29        msg: &'static str,
30    },
31}
32
33impl core::fmt::Display for ReadError {
34    fn fmt(
35        &self,
36        f: &mut core::fmt::Formatter<'_>,
37    ) -> core::fmt::Result {
38        match self {
39            ReadError::MissingRequiredFields { missing_mask, meta } => {
40                if *missing_mask == 0 {
41                    return write!(
42                        f,
43                        "No required members are missing"
44                    );
45                }
46                write!(
47                    f,
48                    "Missing required members (mask=0x{missing_mask:0X}): "
49                )?;
50                let mut first = true;
51                for (i, (name, tag, kind)) in meta.iter().enumerate() {
52                    if (missing_mask >> i) & 1 == 1 {
53                        if !first {
54                            write!(f, ", ")?;
55                        }
56                        first = false;
57                        let kind_str = match kind {
58                            0 => "field",
59                            1 => "component",
60                            _ => "group",
61                        };
62                        if *kind == 2 {
63                            // group: tag is the count tag
64                            write!(
65                                f,
66                                "{kind_str} {name}(countTag={tag})"
67                            )?;
68                        } else {
69                            write!(
70                                f,
71                                "{kind_str} {name}(tag={tag})"
72                            )?;
73                        }
74                    }
75                }
76                Ok(())
77            }
78            ReadError::InvalidValue { name, tag, msg } => {
79                write!(
80                    f,
81                    "Invalid value for {name} (tag={tag}): {msg}"
82                )
83            }
84        }
85    }
86}
87
88impl std::error::Error for ReadError {}
89
90impl ReadError {
91    /// Returns the list of names of missing required members when this is
92    /// `ReadError::MissingRequiredFields`.
93    ///
94    /// Returns `Some(Vec::new())` if the variant is present but no bits are
95    /// actually missing (mask == 0), and `None` for other variants.
96    #[allow(dead_code)]
97    pub fn missing_member_names(&self) -> Option<Vec<&'static str>> {
98        match self {
99            ReadError::MissingRequiredFields { missing_mask, meta } => {
100                let mut v = Vec::new();
101                if *missing_mask != 0 {
102                    for (i, (name, _tag, _kind)) in meta.iter().enumerate() {
103                        if (missing_mask >> i) & 1 == 1 {
104                            v.push(*name);
105                        }
106                    }
107                }
108                Some(v)
109            }
110            _ => None,
111        }
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn test_read_error_display_no_missing_fields() {
121        let error = ReadError::MissingRequiredFields {
122            missing_mask: 0,
123            meta: &[("TestField", 1, 0)],
124        };
125        assert_eq!(
126            error.to_string(),
127            "No required members are missing"
128        );
129    }
130
131    #[test]
132    fn test_read_error_display_missing_fields() {
133        let meta = &[("Field1", 1, 0), ("Component1", 2, 1), ("Group1", 3, 2)];
134        let error = ReadError::MissingRequiredFields {
135            missing_mask: 0b101, // First and third items missing
136            meta,
137        };
138        let error_str = error.to_string();
139        assert!(error_str.contains("Missing required members"));
140        assert!(error_str.contains("field Field1(tag=1)"));
141        assert!(error_str.contains("group Group1(countTag=3)"));
142    }
143
144    #[test]
145    fn test_read_error_display_invalid_value() {
146        let error = ReadError::InvalidValue {
147            name: "TestField",
148            tag: 42,
149            msg: "Expected numeric value",
150        };
151        assert_eq!(
152            error.to_string(),
153            "Invalid value for TestField (tag=42): Expected numeric value"
154        );
155    }
156
157    #[test]
158    fn test_missing_member_names() {
159        let meta = &[("Field1", 1, 0), ("Field2", 2, 0), ("Field3", 3, 0)];
160
161        // Test with missing fields
162        let error = ReadError::MissingRequiredFields {
163            missing_mask: 0b101, // First and third fields missing
164            meta,
165        };
166        let names = error
167            .missing_member_names()
168            .unwrap();
169        assert_eq!(
170            names,
171            vec!["Field1", "Field3"]
172        );
173
174        // Test with no missing fields
175        let error = ReadError::MissingRequiredFields {
176            missing_mask: 0,
177            meta,
178        };
179        let names = error
180            .missing_member_names()
181            .unwrap();
182        assert!(names.is_empty());
183
184        // Test with invalid value error
185        let error = ReadError::InvalidValue {
186            name: "TestField",
187            tag: 42,
188            msg: "Test message",
189        };
190        assert!(error
191            .missing_member_names()
192            .is_none());
193    }
194}