1use std::collections::{HashMap, HashSet};
5
6use quick_xml::events::Event;
7
8use crate::error::{Error, RejectError, Result, SessionRejectReason};
9use crate::field_map::GroupTemplate;
10use crate::message::{Message, Tag};
11use crate::tags;
12
13pub const USER_DEFINED_TAG_MIN: Tag = 5000;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum FieldType {
18 Int,
19 Length,
20 SeqNum,
21 NumInGroup,
22 DayOfMonth,
23 Float,
24 Qty,
25 Price,
26 PriceOffset,
27 Amt,
28 Percentage,
29 Char,
30 Boolean,
31 String,
32 Data,
33 UtcTimestamp,
34 UtcDateOnly,
35 UtcTimeOnly,
36 LocalMktDate,
37 MonthYear,
38 Other,
39}
40
41impl FieldType {
42 fn from_name(name: &str) -> Self {
43 match name {
44 "INT" => Self::Int,
45 "LENGTH" => Self::Length,
46 "SEQNUM" => Self::SeqNum,
47 "NUMINGROUP" => Self::NumInGroup,
48 "DAYOFMONTH" => Self::DayOfMonth,
49 "FLOAT" => Self::Float,
50 "QTY" | "QUANTITY" => Self::Qty,
51 "PRICE" => Self::Price,
52 "PRICEOFFSET" => Self::PriceOffset,
53 "AMT" => Self::Amt,
54 "PERCENTAGE" => Self::Percentage,
55 "CHAR" => Self::Char,
56 "BOOLEAN" => Self::Boolean,
57 "STRING" | "MULTIPLEVALUESTRING" | "MULTIPLESTRINGVALUE" | "MULTIPLECHARVALUE"
58 | "COUNTRY" | "CURRENCY" | "EXCHANGE" | "LANGUAGE" => Self::String,
59 "DATA" | "XMLDATA" => Self::Data,
60 "UTCTIMESTAMP" | "TIME" => Self::UtcTimestamp,
61 "UTCDATEONLY" | "UTCDATE" | "DATE" => Self::UtcDateOnly,
62 "UTCTIMEONLY" => Self::UtcTimeOnly,
63 "LOCALMKTDATE" => Self::LocalMktDate,
64 "MONTHYEAR" => Self::MonthYear,
65 _ => Self::Other,
66 }
67 }
68}
69
70#[derive(Debug, Clone)]
71pub struct FieldDef {
72 pub tag: Tag,
73 pub name: String,
74 pub field_type: FieldType,
75 pub values: HashSet<String>,
77 pub enum_values: Vec<(String, String)>,
79}
80
81#[derive(Debug, Clone, Default)]
82pub struct GroupDef {
83 pub counter: Tag,
84 pub delimiter: Tag,
85 pub tags: HashSet<Tag>,
87 pub required: Vec<Tag>,
88 pub groups: HashMap<Tag, GroupDef>,
89 pub member_order: Vec<Tag>,
91}
92
93#[derive(Debug, Clone, Default)]
94pub struct MessageDef {
95 pub name: String,
96 pub msg_type: String,
97 pub tags: HashSet<Tag>,
98 pub required: Vec<Tag>,
99 pub groups: HashMap<Tag, GroupDef>,
100 pub field_order: Vec<Tag>,
103}
104
105#[derive(Debug, Clone, Default)]
106pub struct DataDictionary {
107 pub begin_string: String,
108 pub fields_by_tag: HashMap<Tag, FieldDef>,
109 pub tags_by_name: HashMap<String, Tag>,
110 pub header_tags: HashSet<Tag>,
111 pub header_required: Vec<Tag>,
112 pub trailer_tags: HashSet<Tag>,
113 pub trailer_required: Vec<Tag>,
114 pub messages: HashMap<String, MessageDef>,
115}
116
117#[derive(Debug, Clone)]
119pub struct ValidationSettings {
120 pub check_fields_out_of_order: bool,
121 pub check_fields_have_values: bool,
122 pub check_user_defined_fields: bool,
123 pub allow_unknown_message_fields: bool,
124}
125
126impl Default for ValidationSettings {
127 fn default() -> Self {
128 Self {
129 check_fields_out_of_order: true,
130 check_fields_have_values: true,
131 check_user_defined_fields: true,
132 allow_unknown_message_fields: false,
133 }
134 }
135}
136
137struct Node {
141 name: String,
142 attrs: HashMap<String, String>,
143 children: Vec<Node>,
144}
145
146fn parse_xml(text: &str) -> Result<Node> {
147 let mut reader = quick_xml::Reader::from_str(text);
148 reader.config_mut().trim_text(true);
149 let mut stack: Vec<Node> = vec![Node {
150 name: "(root)".into(),
151 attrs: HashMap::new(),
152 children: Vec::new(),
153 }];
154
155 let read_node = |e: &quick_xml::events::BytesStart<'_>| -> Result<Node> {
156 let name = String::from_utf8_lossy(e.name().as_ref()).into_owned();
157 let mut attrs = HashMap::new();
158 for attr in e.attributes() {
159 let attr = attr.map_err(|e| Error::Dictionary(format!("bad attribute: {e}")))?;
160 attrs.insert(
161 String::from_utf8_lossy(attr.key.as_ref()).into_owned(),
162 String::from_utf8_lossy(&attr.value).into_owned(),
163 );
164 }
165 Ok(Node { name, attrs, children: Vec::new() })
166 };
167
168 loop {
169 match reader.read_event() {
170 Ok(Event::Start(e)) => stack.push(read_node(&e)?),
171 Ok(Event::Empty(e)) => {
172 let node = read_node(&e)?;
173 stack.last_mut().unwrap().children.push(node);
174 }
175 Ok(Event::End(_)) => {
176 let node = stack.pop().unwrap();
177 stack
178 .last_mut()
179 .ok_or_else(|| Error::Dictionary("unbalanced XML".into()))?
180 .children
181 .push(node);
182 }
183 Ok(Event::Eof) => break,
184 Ok(_) => {}
185 Err(e) => return Err(Error::Dictionary(format!("XML parse error: {e}"))),
186 }
187 }
188 let mut root = stack.pop().ok_or_else(|| Error::Dictionary("empty document".into()))?;
189 root.children
190 .drain(..)
191 .find(|n| n.name == "fix")
192 .ok_or_else(|| Error::Dictionary("no <fix> root element".into()))
193}
194
195impl DataDictionary {
196 pub fn parse(text: &str) -> Result<Self> {
197 let fix = parse_xml(text)?;
198 let major = fix.attrs.get("major").cloned().unwrap_or_default();
199 let minor = fix.attrs.get("minor").cloned().unwrap_or_default();
200 let fixt = fix.attrs.get("type").map(|t| t == "FIXT").unwrap_or(false);
201 let mut dd = DataDictionary {
202 begin_string: if fixt {
203 format!("FIXT.{major}.{minor}")
204 } else {
205 format!("FIX.{major}.{minor}")
206 },
207 ..Default::default()
208 };
209 let char_is_string = !fixt && dd.begin_string.as_str() < "FIX.4.2";
212
213 let fields = child(&fix, "fields");
215 if let Some(fields) = fields {
216 for f in fields.children.iter().filter(|c| c.name == "field") {
217 let tag: Tag = f
218 .attrs
219 .get("number")
220 .and_then(|n| n.parse().ok())
221 .ok_or_else(|| Error::Dictionary("field without number".into()))?;
222 let name = f.attrs.get("name").cloned().unwrap_or_default();
223 let mut field_type =
224 FieldType::from_name(f.attrs.get("type").map(|s| s.as_str()).unwrap_or(""));
225 if char_is_string && field_type == FieldType::Char {
226 field_type = FieldType::String;
227 }
228 let enum_values: Vec<(String, String)> = f
229 .children
230 .iter()
231 .filter(|c| c.name == "value")
232 .filter_map(|c| {
233 c.attrs.get("enum").map(|v| {
234 (v.clone(), c.attrs.get("description").cloned().unwrap_or_default())
235 })
236 })
237 .collect();
238 let values = enum_values.iter().map(|(v, _)| v.clone()).collect();
239 dd.tags_by_name.insert(name.clone(), tag);
240 dd.fields_by_tag
241 .insert(tag, FieldDef { tag, name, field_type, values, enum_values });
242 }
243 }
244
245 let mut components: HashMap<&str, &Node> = HashMap::new();
247 if let Some(comps) = child(&fix, "components") {
248 for c in comps.children.iter().filter(|c| c.name == "component") {
249 if let Some(name) = c.attrs.get("name") {
250 components.insert(name, c);
251 }
252 }
253 }
254
255 if let Some(header) = child(&fix, "header") {
257 let mut def = MessageDef::default();
258 dd.collect_message(header, &components, &mut def)?;
259 dd.header_tags = def.tags;
260 dd.header_required = def.required;
261 }
262 if let Some(trailer) = child(&fix, "trailer") {
263 let mut def = MessageDef::default();
264 dd.collect_message(trailer, &components, &mut def)?;
265 dd.trailer_tags = def.tags;
266 dd.trailer_required = def.required;
267 }
268
269 if let Some(messages) = child(&fix, "messages") {
271 for m in messages.children.iter().filter(|c| c.name == "message") {
272 let mut def = MessageDef {
273 name: m.attrs.get("name").cloned().unwrap_or_default(),
274 msg_type: m
275 .attrs
276 .get("msgtype")
277 .cloned()
278 .ok_or_else(|| Error::Dictionary("message without msgtype".into()))?,
279 ..Default::default()
280 };
281 dd.collect_message(m, &components, &mut def)?;
282 dd.messages.insert(def.msg_type.clone(), def);
283 }
284 }
285 Ok(dd)
286 }
287
288 pub async fn load(path: impl AsRef<std::path::Path>) -> Result<Self> {
289 let text = tokio::fs::read_to_string(path).await?;
290 Self::parse(&text)
291 }
292
293 fn collect_message(
295 &self,
296 node: &Node,
297 components: &HashMap<&str, &Node>,
298 def: &mut MessageDef,
299 ) -> Result<()> {
300 let MessageDef { tags, required, groups, field_order, .. } = def;
302 self.collect(node, components, tags, required, groups, field_order)
303 }
304
305 fn collect(
306 &self,
307 node: &Node,
308 components: &HashMap<&str, &Node>,
309 tags_out: &mut HashSet<Tag>,
310 required_out: &mut Vec<Tag>,
311 groups_out: &mut HashMap<Tag, GroupDef>,
312 order_out: &mut Vec<Tag>,
313 ) -> Result<()> {
314 for c in &node.children {
315 let required = c.attrs.get("required").map(|r| r == "Y").unwrap_or(false);
316 match c.name.as_str() {
317 "field" => {
318 let name = c.attrs.get("name").cloned().unwrap_or_default();
319 let tag = *self
320 .tags_by_name
321 .get(&name)
322 .ok_or_else(|| Error::Dictionary(format!("unknown field {name}")))?;
323 if tags_out.insert(tag) {
324 order_out.push(tag);
325 }
326 if required {
327 required_out.push(tag);
328 }
329 }
330 "component" => {
331 let name = c.attrs.get("name").cloned().unwrap_or_default();
332 let def = components
333 .get(name.as_str())
334 .ok_or_else(|| Error::Dictionary(format!("unknown component {name}")))?;
335 let mut comp_required = Vec::new();
337 self.collect(def, components, tags_out, &mut comp_required, groups_out, order_out)?;
338 if required {
339 required_out.extend(comp_required);
340 }
341 }
342 "group" => {
343 let name = c.attrs.get("name").cloned().unwrap_or_default();
344 let counter = *self
345 .tags_by_name
346 .get(&name)
347 .ok_or_else(|| Error::Dictionary(format!("unknown group {name}")))?;
348 if tags_out.insert(counter) {
349 order_out.push(counter);
350 }
351 if required {
352 required_out.push(counter);
353 }
354 let mut g = GroupDef { counter, ..Default::default() };
355 let mut member_order: Vec<Tag> = Vec::new();
356 self.collect_group(c, components, &mut g, &mut member_order)?;
357 g.delimiter = *member_order
358 .first()
359 .ok_or_else(|| Error::Dictionary(format!("empty group {name}")))?;
360 g.member_order = member_order;
361 tags_out.extend(g.tags.iter().copied());
364 groups_out.insert(counter, g);
365 }
366 _ => {}
367 }
368 }
369 Ok(())
370 }
371
372 fn collect_group(
373 &self,
374 node: &Node,
375 components: &HashMap<&str, &Node>,
376 group: &mut GroupDef,
377 member_order: &mut Vec<Tag>,
378 ) -> Result<()> {
379 for c in &node.children {
380 let required = c.attrs.get("required").map(|r| r == "Y").unwrap_or(false);
381 match c.name.as_str() {
382 "field" => {
383 let name = c.attrs.get("name").cloned().unwrap_or_default();
384 let tag = *self
385 .tags_by_name
386 .get(&name)
387 .ok_or_else(|| Error::Dictionary(format!("unknown field {name}")))?;
388 group.tags.insert(tag);
389 member_order.push(tag);
390 if required {
391 group.required.push(tag);
392 }
393 }
394 "component" => {
395 let name = c.attrs.get("name").cloned().unwrap_or_default();
396 let def = components
397 .get(name.as_str())
398 .ok_or_else(|| Error::Dictionary(format!("unknown component {name}")))?;
399 self.collect_group(def, components, group, member_order)?;
400 }
401 "group" => {
402 let name = c.attrs.get("name").cloned().unwrap_or_default();
403 let counter = *self
404 .tags_by_name
405 .get(&name)
406 .ok_or_else(|| Error::Dictionary(format!("unknown group {name}")))?;
407 group.tags.insert(counter);
408 member_order.push(counter);
409 if required {
410 group.required.push(counter);
411 }
412 let mut nested = GroupDef { counter, ..Default::default() };
413 let mut nested_order = Vec::new();
414 self.collect_group(c, components, &mut nested, &mut nested_order)?;
415 nested.delimiter = *nested_order
416 .first()
417 .ok_or_else(|| Error::Dictionary(format!("empty group {name}")))?;
418 nested.member_order = nested_order;
419 group.tags.extend(nested.tags.iter().copied());
420 group.groups.insert(counter, nested);
421 }
422 _ => {}
423 }
424 }
425 Ok(())
426 }
427
428 pub fn merged_with_app(mut self, app: &DataDictionary) -> DataDictionary {
432 for (tag, field) in &app.fields_by_tag {
433 self.fields_by_tag.entry(*tag).or_insert_with(|| field.clone());
434 }
435 for (name, tag) in &app.tags_by_name {
436 self.tags_by_name.entry(name.clone()).or_insert(*tag);
437 }
438 for (msg_type, def) in &app.messages {
439 self.messages.entry(msg_type.clone()).or_insert_with(|| def.clone());
440 }
441 self
442 }
443
444 pub fn canonicalize_body(&self, msg: &mut Message) {
448 let Ok(mt) = msg.msg_type() else { return };
449 let Some(def) = self.messages.get(&mt) else { return };
450 let fields = msg.body.take_fields();
451
452 let mut segments: Vec<(Tag, Vec<crate::field_map::TagValue>)> = Vec::new();
453 let mut i = 0;
454 while i < fields.len() {
455 let tag = fields[i].tag;
456 let mut seg = vec![fields[i].clone()];
457 i += 1;
458 if let Some(group) = def.groups.get(&tag) {
459 while i < fields.len() && group.tags.contains(&fields[i].tag) {
460 seg.push(fields[i].clone());
461 i += 1;
462 }
463 }
464 segments.push((tag, seg));
465 }
466 segments.sort_by_key(|s| s.0);
467 msg.body.set_fields(segments.into_iter().flat_map(|(_, seg)| seg).collect());
468 }
469
470 pub fn group_template(&self, msg_type: &str, counter: Tag) -> Option<GroupTemplate> {
473 let g = self.messages.get(msg_type)?.groups.get(&counter)?;
474 let mut members = vec![g.delimiter];
475 members.extend(g.tags.iter().copied().filter(|&t| t != g.delimiter));
476 Some(GroupTemplate::new(counter, members))
477 }
478
479 pub fn validate(
484 &self,
485 msg: &Message,
486 settings: &ValidationSettings,
487 ) -> std::result::Result<(), RejectError> {
488 let msg_type = msg
489 .header
490 .get_string(tags::MSG_TYPE)
491 .map_err(|_| RejectError::with_tag(SessionRejectReason::RequiredTagMissing, tags::MSG_TYPE))?;
492 if msg_type == "n" {
495 return Ok(());
496 }
497 let def = self.messages.get(&msg_type).ok_or_else(|| {
498 if self.begin_string == "FIX.4.2" {
500 RejectError::with_tag(SessionRejectReason::InvalidMsgType, tags::MSG_TYPE)
501 } else {
502 RejectError::new(SessionRejectReason::InvalidMsgType)
503 }
504 })?;
505
506 if settings.check_fields_out_of_order {
507 if let Some(tag) = msg.structure_error() {
508 return Err(RejectError::with_tag(
509 SessionRejectReason::TagSpecifiedOutOfRequiredOrder,
510 tag,
511 ));
512 }
513 }
514
515 let body_fields: Vec<_> = msg.body.iter().collect();
518 for (i, f) in body_fields.iter().enumerate() {
519 let Some(group) = def.groups.get(&f.tag) else { continue };
520 let declared: u64 = msg.body.get_opt(group.counter).ok().flatten().unwrap_or(0);
521 if declared == 0 {
522 continue;
523 }
524 if let Some(first) = body_fields.get(i + 1) {
525 if group.tags.contains(&first.tag) && first.tag != group.delimiter {
526 return Err(RejectError::other(
527 format!(
528 "Group {}'s first entry does not start with delimiter {}",
529 group.counter, group.delimiter
530 ),
531 group.counter,
532 ));
533 }
534 }
535 }
536
537 for &tag in &self.header_required {
539 if !msg.header.contains(tag) {
540 return Err(RejectError::with_tag(SessionRejectReason::RequiredTagMissing, tag));
541 }
542 }
543 for &tag in &self.trailer_required {
544 if !msg.trailer.contains(tag) {
545 return Err(RejectError::with_tag(SessionRejectReason::RequiredTagMissing, tag));
546 }
547 }
548 for &tag in &def.required {
549 if !msg.body.contains(tag) {
550 return Err(RejectError::with_tag(SessionRejectReason::RequiredTagMissing, tag));
551 }
552 }
553
554 let mut group_member_tags: HashSet<Tag> = HashSet::new();
556 for g in def.groups.values() {
557 group_member_tags.extend(g.tags.iter().copied());
558 }
559 let mut seen: HashSet<Tag> = HashSet::new();
560 for f in msg.body.iter() {
561 if !group_member_tags.contains(&f.tag) && !seen.insert(f.tag) {
562 return Err(RejectError::with_tag(
563 SessionRejectReason::TagAppearsMoreThanOnce,
564 f.tag,
565 ));
566 }
567 }
568
569 for group in def.groups.values() {
573 if let Some(raw) = msg.body.get_raw(group.counter) {
574 let declared: usize = std::str::from_utf8(raw)
575 .ok()
576 .and_then(|s| s.parse().ok())
577 .ok_or_else(|| {
578 RejectError::with_tag(
579 SessionRejectReason::IncorrectDataFormatForValue,
580 group.counter,
581 )
582 })?;
583 let actual =
584 msg.body.iter().filter(|f| f.tag == group.delimiter).count();
585 if declared != actual {
586 return Err(RejectError::with_tag(
587 SessionRejectReason::IncorrectNumInGroupCountForRepeatingGroup,
588 group.counter,
589 ));
590 }
591 }
592 }
593
594 for section in [&msg.header, &msg.body, &msg.trailer] {
596 let is_body = std::ptr::eq(section, &msg.body);
597 for f in section.iter() {
598 self.check_field(f.tag, &f.value, is_body, def, settings)?;
599 }
600 }
601 Ok(())
602 }
603
604 fn check_field(
605 &self,
606 tag: Tag,
607 value: &[u8],
608 is_body: bool,
609 def: &MessageDef,
610 settings: &ValidationSettings,
611 ) -> std::result::Result<(), RejectError> {
612 if tag >= USER_DEFINED_TAG_MIN && !settings.check_user_defined_fields {
613 return Ok(());
614 }
615 if settings.check_fields_have_values && value.is_empty() {
616 return Err(RejectError::with_tag(
617 SessionRejectReason::TagSpecifiedWithoutAValue,
618 tag,
619 ));
620 }
621 let Some(field) = self.fields_by_tag.get(&tag) else {
622 if settings.allow_unknown_message_fields {
626 return Ok(());
627 }
628 return Err(RejectError::with_tag(SessionRejectReason::InvalidTagNumber, tag));
629 };
630 if is_body
631 && !settings.allow_unknown_message_fields
632 && !def.tags.contains(&tag)
633 && tag < USER_DEFINED_TAG_MIN
634 {
635 return Err(RejectError::with_tag(
636 SessionRejectReason::TagNotDefinedForThisMessageType,
637 tag,
638 ));
639 }
640 self.check_format(field, value).map_err(|_| {
643 RejectError::with_tag(SessionRejectReason::IncorrectDataFormatForValue, tag)
644 })?;
645 if !field.values.is_empty() {
646 let v = String::from_utf8_lossy(value);
647 let ok = v.split(' ').all(|part| field.values.contains(part));
649 if !ok {
650 return Err(RejectError::with_tag(SessionRejectReason::ValueIsIncorrect, tag));
651 }
652 }
653 Ok(())
654 }
655
656 fn check_format(&self, field: &FieldDef, value: &[u8]) -> std::result::Result<(), ()> {
657 use crate::value::{FixDate, FixDecode, UtcTimestamp};
658 let ok = match field.field_type {
659 FieldType::Int => i64::decode(field.tag, value).is_ok(),
660 FieldType::Length | FieldType::SeqNum | FieldType::NumInGroup => {
661 u64::decode(field.tag, value).is_ok()
662 }
663 FieldType::DayOfMonth => {
664 u64::decode(field.tag, value).map(|d| (1..=31).contains(&d)).unwrap_or(false)
665 }
666 FieldType::Float
667 | FieldType::Qty
668 | FieldType::Price
669 | FieldType::PriceOffset
670 | FieldType::Amt
671 | FieldType::Percentage => f64::decode(field.tag, value).is_ok(),
672 FieldType::Char => value.len() == 1,
673 FieldType::Boolean => matches!(value, b"Y" | b"N"),
674 FieldType::UtcTimestamp => UtcTimestamp::decode(field.tag, value).is_ok(),
675 FieldType::UtcDateOnly | FieldType::LocalMktDate => {
676 FixDate::decode(field.tag, value).is_ok()
677 }
678 FieldType::MonthYear => {
679 value.len() >= 6 && value[..6].iter().all(|b| b.is_ascii_digit())
680 }
681 FieldType::UtcTimeOnly | FieldType::String | FieldType::Data | FieldType::Other => {
682 true
683 }
684 };
685 if ok { Ok(()) } else { Err(()) }
686 }
687}
688
689fn child<'a>(node: &'a Node, name: &str) -> Option<&'a Node> {
690 node.children.iter().find(|c| c.name == name)
691}
692
693#[cfg(test)]
694mod tests {
695 use super::*;
696
697 fn fix44() -> DataDictionary {
698 let text = std::fs::read_to_string(concat!(
699 env!("CARGO_MANIFEST_DIR"),
700 "/spec/FIX44.xml"
701 ))
702 .unwrap();
703 DataDictionary::parse(&text).unwrap()
704 }
705
706 fn valid_order() -> Message {
707 let mut m = Message::with_type("D");
708 m.header.set(tags::BEGIN_STRING, "FIX.4.4");
709 m.header.set(tags::SENDER_COMP_ID, "A");
710 m.header.set(tags::TARGET_COMP_ID, "B");
711 m.header.set(tags::MSG_SEQ_NUM, 2u64);
712 m.stamp_sending_time(crate::value::UtcTimestamp::now());
713 m.set(11, "ORDER-1");
714 m.set(55, "TSLA");
715 m.set(54, '1');
716 m.set(60, crate::value::UtcTimestamp::now());
717 m.set(40, '1');
718 m
719 }
720
721 #[test]
722 fn loads_fix44_spec() {
723 let dd = fix44();
724 assert_eq!(dd.begin_string, "FIX.4.4");
725 assert!(dd.messages.contains_key("D"), "NewOrderSingle should exist");
726 assert!(dd.header_tags.contains(&tags::MSG_SEQ_NUM));
727 assert_eq!(dd.fields_by_tag[&54].field_type, FieldType::Char);
728 assert!(dd.fields_by_tag[&54].values.contains("1"));
729 let d = &dd.messages["D"];
731 assert!(d.groups.contains_key(&453), "NoPartyIDs group expected");
732 assert_eq!(d.groups[&453].delimiter, 448);
733 }
734
735 #[test]
736 fn validates_good_message() {
737 let dd = fix44();
738 let msg =
739 Message::parse(&valid_order().to_bytes(), true).expect("roundtrip");
740 dd.validate(&msg, &ValidationSettings::default()).expect("should validate");
741 }
742
743 #[test]
744 fn missing_required_field_rejected() {
745 let dd = fix44();
746 let mut order = valid_order();
747 order.body.remove(11); let msg = Message::parse(&order.to_bytes(), true).unwrap();
749 let err = dd.validate(&msg, &ValidationSettings::default()).unwrap_err();
750 assert_eq!(err.reason, SessionRejectReason::RequiredTagMissing);
751 assert_eq!(err.ref_tag, Some(11));
752 }
753
754 #[test]
755 fn bad_enum_value_rejected() {
756 let dd = fix44();
757 let mut order = valid_order();
758 order.set(54, 'Z'); let msg = Message::parse(&order.to_bytes(), true).unwrap();
760 let err = dd.validate(&msg, &ValidationSettings::default()).unwrap_err();
761 assert_eq!(err.reason, SessionRejectReason::ValueIsIncorrect);
762 assert_eq!(err.ref_tag, Some(54));
763 }
764
765 #[test]
766 fn undefined_tag_rejected() {
767 let dd = fix44();
768 let mut order = valid_order();
769 order.set(4999, "bogus"); let msg = Message::parse(&order.to_bytes(), true).unwrap();
771 let err = dd.validate(&msg, &ValidationSettings::default()).unwrap_err();
772 assert_eq!(err.reason, SessionRejectReason::InvalidTagNumber);
773 }
774
775 #[test]
776 fn tag_not_defined_for_message_type() {
777 let dd = fix44();
778 let mut order = valid_order();
779 order.set(112, "TR1"); let msg = Message::parse(&order.to_bytes(), true).unwrap();
781 let err = dd.validate(&msg, &ValidationSettings::default()).unwrap_err();
782 assert_eq!(err.reason, SessionRejectReason::TagNotDefinedForThisMessageType);
783 assert_eq!(err.ref_tag, Some(112));
784 }
785
786 #[test]
787 fn wrong_group_count_rejected() {
788 let dd = fix44();
789 let mut order = valid_order();
790 order.set(453, 2u32); order.body.push(448, "PARTY-A"); order.body.push(447, 'D');
793 order.body.push(452, 1u32);
794 let msg = Message::parse(&order.to_bytes(), true).unwrap();
795 let err = dd.validate(&msg, &ValidationSettings::default()).unwrap_err();
796 assert_eq!(
797 err.reason,
798 SessionRejectReason::IncorrectNumInGroupCountForRepeatingGroup
799 );
800 }
801
802 #[test]
803 fn unknown_msg_type_rejected() {
804 let dd = fix44();
805 let mut m = valid_order();
806 m.header.set(tags::MSG_TYPE, "ZZ");
807 let msg = Message::parse(&m.to_bytes(), true).unwrap();
808 let err = dd.validate(&msg, &ValidationSettings::default()).unwrap_err();
809 assert_eq!(err.reason, SessionRejectReason::InvalidMsgType);
810 }
811
812 #[test]
813 fn group_template_reads_parties() {
814 let dd = fix44();
815 let tpl = dd.group_template("D", 453).unwrap();
816 assert_eq!(tpl.num_tag, 453);
817 assert_eq!(tpl.delimiter(), 448);
818
819 let mut order = valid_order();
820 let mut g1 = crate::field_map::FieldMap::new();
821 g1.push(448, "PARTY-A");
822 g1.push(447, 'D');
823 let mut g2 = crate::field_map::FieldMap::new();
824 g2.push(448, "PARTY-B");
825 g2.push(447, 'D');
826 order.body.write_groups(&tpl, &[g1, g2]);
827
828 let msg = Message::parse(&order.to_bytes(), true).unwrap();
829 dd.validate(&msg, &ValidationSettings::default()).expect("groups valid");
830 let groups = msg.body.read_groups(&tpl).unwrap();
831 assert_eq!(groups.len(), 2);
832 assert_eq!(groups[1].get_string(448).unwrap(), "PARTY-B");
833 }
834}