1use crate::types::{Config, Field, OrderType, Side, SubID};
2use chrono::Utc;
3use std::collections::HashMap;
4
5#[derive(Debug, Clone)]
7pub struct ResponseMessage {
8 message: String,
9 field_idx: HashMap<u32, usize>,
10 fields: Vec<(u32, String)>,
11}
12
13impl ResponseMessage {
14 pub fn new(message: &str, delimiter: &str) -> Self {
15 let message = message.replace(delimiter, "|");
16 let mut idx_map = HashMap::new();
17 let fields = message
18 .split("|")
19 .filter(|field| !field.is_empty() && field.contains("="))
20 .enumerate()
21 .map(|(idx, field)| {
22 let parts = field.split("=").collect::<Vec<_>>();
23 let value = &parts[1..].join("=");
24
25 let field = parts[0].parse::<u32>().unwrap();
26 if !idx_map.contains_key(&field) {
27 idx_map.insert(parts[0].parse::<u32>().unwrap(), idx);
28 }
29 (field, value.to_string())
30 })
31 .collect::<Vec<_>>();
32 Self {
33 message,
34 field_idx: idx_map,
35 fields,
36 }
37 }
38
39 pub fn matching_field_value(&self, msg_type: &str, field: Field, value: &str) -> bool {
40 (self.get_message_type() == msg_type)
41 && (self
42 .get_field_value(field)
43 .map(|v| v == value)
44 .unwrap_or(false))
45 }
46
47 pub fn get_field_value(&self, field: Field) -> Option<String> {
48 self.field_idx
49 .get(&(field as u32))
50 .map(|idx| self.fields[*idx].1.clone())
51 }
53
54 pub fn get_message_type(&self) -> &str {
55 self.field_idx
56 .get(&(Field::MsgType as u32))
57 .map(|idx| &self.fields[*idx].1)
58 .unwrap()
59 }
60
61 pub fn get_message(&self) -> &str {
62 &self.message
63 }
64
65 pub fn get_repeating_groups(
66 &self,
67 count_key: Field,
68 start_field: Field,
69 end_field: Option<Field>,
70 ) -> Vec<HashMap<Field, String>> {
71 let count_key = count_key as u32;
73
74 let mut count = None;
75 let mut result = vec![];
76 let mut item = HashMap::new();
77 if let Some(start_idx) = self.field_idx.get(&(count_key as u32)) {
79 for (k, v) in &self.fields[*start_idx..] {
80 match count {
81 Some(0) => {
82 return result;
83 }
84 None => {
85 if *k == count_key as u32 {
86 count = Some(v.parse::<usize>().unwrap());
87 }
88 continue;
89 }
90 _ => {
91 let key = Field::try_from(*k)
92 .expect("Failed to parse u32 to Field in get_repeating_groups");
93
94 match end_field {
95 Some(end_key) => {
96 if key != Field::CheckSum {
98 item.insert(key, v.clone());
99 }
100
101 if key == end_key {
102 result.push(item.clone());
103 item.clear();
104 count = count.map(|c| c - 1);
105 }
106 }
107 None => {
108 if key == start_field && !item.is_empty() {
109 result.push(item.clone());
110 item.clear();
111 count = count.map(|c| c - 1);
112 }
113
114 if key != Field::CheckSum {
115 item.insert(key, v.clone());
116 }
117 }
118 }
119 }
125 }
126 }
127 result.push(item.clone());
128 }
129 result
130 }
131}
132
133fn format_field<T: std::fmt::Display>(field: Field, value: T) -> String {
134 format!("{}={}", field as u32, value)
135}
136pub trait RequestMessage: Send {
140 fn build(
141 &self,
142 sub_id: SubID,
143 sequence_number: u32,
144 delimiter: &str,
145 config: &Config,
146 ) -> String {
147 let body = self.get_body(delimiter, config);
148 let header = self.get_header(
149 sub_id,
150 body.as_ref().map(|s| s.len()).unwrap_or(0),
151 sequence_number,
152 delimiter,
153 config,
154 );
155 let header_and_body = match body {
156 Some(body) => format!("{}{}{}{}", header, delimiter, body, delimiter),
157 None => format!("{}{}", header, delimiter),
158 };
159 let trailer = self.get_trailer(&header_and_body);
160 format!("{}{}{}", header_and_body, trailer, delimiter)
161 }
162
163 fn get_header(
164 &self,
165 sub_id: SubID,
166 len_body: usize,
167 sequence_number: u32,
168 delimiter: &str,
169 config: &Config,
170 ) -> String {
171 let fields = vec![
172 format_field(Field::MsgType, self.get_message_type()),
173 format_field(Field::SenderCompID, &config.sender_comp_id),
174 format_field(Field::TargetCompID, "CSERVER"),
175 format_field(Field::TargetSubID, sub_id.to_string()),
176 format_field(Field::SenderSubID, sub_id.to_string()),
177 format_field(Field::MsgSeqNum, sequence_number),
178 format_field(Field::SendingTime, Utc::now().format("%Y%m%d-%H:%M:%S")),
179 ];
180 let fields_joined = fields.join(delimiter);
181 format!(
182 "8=FIX.4.4{}9={}{}{}",
183 delimiter,
184 len_body + fields_joined.len() + 2,
185 delimiter,
186 fields_joined
187 )
188 }
189
190 fn get_trailer(&self, header_and_body: &str) -> String {
191 let message_bytes = header_and_body.as_bytes();
192 let checksum = message_bytes.iter().map(|byte| *byte as u32).sum::<u32>() % 256;
193 format!("10={:03}", checksum)
194 }
195
196 fn get_body(&self, delimiter: &str, config: &Config) -> Option<String>;
197
198 fn get_message_type(&self) -> &str;
199}
200
201#[derive(Debug, Clone, Default)]
202pub struct LogonReq {
203 pub encryption_scheme: i32,
204 pub reset_seq_num: Option<bool>,
205}
206
207impl LogonReq {
208 pub fn new(reset_seq_num: Option<bool>) -> Self {
209 Self {
210 encryption_scheme: 0,
211 reset_seq_num,
212 }
213 }
214}
215
216impl RequestMessage for LogonReq {
217 fn get_body(&self, delimiter: &str, config: &Config) -> Option<String> {
218 let mut fields = vec![
219 format_field(Field::EncryptMethod, self.encryption_scheme),
220 format_field(Field::HeartBtInt, config.heart_beat),
221 format_field(Field::Username, &config.username),
222 format_field(Field::Password, &config.password),
223 ];
224
225 match self.reset_seq_num {
226 Some(true) => {
227 fields.push("141=Y".to_string());
228 } _ => {}
230 }
231
232 Some(fields.join(delimiter))
233 }
234
235 fn get_message_type(&self) -> &str {
236 "A"
237 }
238}
239
240#[derive(Debug, Clone, Default)]
241pub struct LogoutReq;
242
243impl RequestMessage for LogoutReq {
244 fn get_body(&self, _delimiter: &str, _config: &Config) -> Option<String> {
245 None
246 }
247
248 fn get_message_type(&self) -> &str {
249 "5"
250 }
251}
252
253#[derive(Debug, Clone, Default)]
254pub struct HeartbeatReq {
255 test_req_id: Option<String>,
256}
257
258impl HeartbeatReq {
259 pub fn new(test_req_id: Option<String>) -> Self {
260 Self { test_req_id }
261 }
262}
263
264impl RequestMessage for HeartbeatReq {
265 fn get_body(&self, _delimiter: &str, _config: &Config) -> Option<String> {
266 self.test_req_id
267 .as_ref()
268 .map(|test_req_id| format_field(Field::TestReqID, test_req_id))
269 }
270
271 fn get_message_type(&self) -> &str {
272 "0"
273 }
274}
275
276#[derive(Debug, Clone, Default)]
277pub struct TestReq {
278 test_req_id: String,
279}
280
281impl TestReq {
282 pub fn new(test_req_id: String) -> Self {
283 Self { test_req_id }
284 }
285}
286
287impl RequestMessage for TestReq {
288 fn get_body(&self, _delimiter: &str, _config: &Config) -> Option<String> {
289 Some(format_field(Field::TestReqID, &self.test_req_id))
290 }
291
292 fn get_message_type(&self) -> &str {
293 "1"
294 }
295}
296
297#[derive(Debug, Clone, Default)]
298pub struct ResendReq {
299 begin_seq_no: u32,
300 end_seq_no: u32,
301}
302
303impl ResendReq {
304 pub fn new(begin_seq_no: u32, end_seq_no: u32) -> Self {
305 Self {
306 begin_seq_no,
307 end_seq_no,
308 }
309 }
310}
311
312impl RequestMessage for ResendReq {
313 fn get_body(&self, delimiter: &str, _config: &Config) -> Option<String> {
314 let fields = vec![
315 format_field(Field::BeginSeqNo, self.begin_seq_no),
316 format_field(Field::EndSeqNo, self.end_seq_no),
317 ];
318 Some(fields.join(delimiter))
319 }
320
321 fn get_message_type(&self) -> &str {
322 "2"
323 }
324}
325
326#[derive(Debug, Clone, Default)]
327pub struct SequenceReset {
328 gap_fill_flag: Option<bool>,
329 new_seq_no: u32,
330}
331impl SequenceReset {
332 pub fn new(gap_fill_flag: Option<bool>, new_seq_no: u32) -> Self {
333 Self {
334 gap_fill_flag,
335 new_seq_no,
336 }
337 }
338}
339
340impl RequestMessage for SequenceReset {
341 fn get_body(&self, delimiter: &str, _config: &Config) -> Option<String> {
342 let mut fields = vec![format_field(Field::NewSeqNo, self.new_seq_no)];
343
344 if let Some(gap_fill_flag) = self.gap_fill_flag {
345 fields.push(format_field(
346 Field::GapFillFlag,
347 if gap_fill_flag { "Y" } else { "N" },
348 ));
349 }
350
351 Some(fields.join(delimiter))
352 }
353
354 fn get_message_type(&self) -> &str {
355 "4"
356 }
357}
358
359#[derive(Debug, Clone, Default)]
360pub struct MarketDataReq {
361 md_req_id: String,
362 subscription_req_type: char,
363 market_depth: u32,
364 md_update_type: Option<u32>,
365 no_md_entry_types: u32,
366 md_entry_type: Vec<char>,
367 no_related_sym: u32,
368 symbol: u32,
369}
370
371impl MarketDataReq {
372 pub fn new(
373 md_req_id: String,
374 subscription_req_type: char,
375 market_depth: u32,
376 md_update_type: Option<u32>,
377 md_entry_type: &[char],
378 no_related_sym: u32,
379 symbol: u32,
380 ) -> Self {
381 Self {
382 md_req_id,
383 subscription_req_type,
384 market_depth,
385 md_update_type,
386 no_md_entry_types: md_entry_type.len() as u32,
387 md_entry_type: md_entry_type.into(),
388 no_related_sym,
389 symbol,
390 }
391 }
392}
393
394impl RequestMessage for MarketDataReq {
395 fn get_body(&self, delimiter: &str, _config: &Config) -> Option<String> {
396 let mut fields = vec![
397 format_field(Field::MDReqID, &self.md_req_id),
398 format_field(Field::SubscriptionRequestType, self.subscription_req_type),
399 format_field(Field::MarketDepth, self.market_depth),
400 format_field(Field::NoMDEntryTypes, self.no_md_entry_types),
401 ];
402 self.md_entry_type
404 .iter()
405 .for_each(|c| fields.push(format_field(Field::MDEntryType, c)));
406
407 fields.extend([
408 format_field(Field::NoRelatedSym, self.no_related_sym),
409 format_field(Field::Symbol, &self.symbol),
410 ]);
411
412 if let Some(md_update_type) = self.md_update_type {
413 fields.push(format_field(Field::MDUpdateType, md_update_type));
414 }
415
416 Some(fields.join(delimiter))
417 }
418
419 fn get_message_type(&self) -> &str {
420 "V"
421 }
422}
423
424#[derive(Debug, Clone, Default)]
425pub struct NewOrderSingleReq {
426 pub cl_ord_id: String,
427 pub symbol: u32,
428 pub side: Side,
429 pub transact_time: Option<chrono::NaiveDateTime>,
430 pub order_qty: f64,
431 pub ord_type: OrderType,
432 pub price: Option<f64>,
433 pub stop_px: Option<f64>,
434 pub expire_time: Option<chrono::NaiveDateTime>,
435 pub pos_maint_rpt_id: Option<String>,
436 pub designation: Option<String>,
437}
438
439impl NewOrderSingleReq {
440 pub fn new(
441 cl_ord_id: String,
442 symbol: u32,
443 side: Side,
444 transact_time: Option<chrono::NaiveDateTime>,
445 order_qty: f64,
446 ord_type: OrderType,
447 price: Option<f64>,
448 stop_px: Option<f64>,
449 expire_time: Option<chrono::NaiveDateTime>,
450 pos_maint_rpt_id: Option<String>,
451 designation: Option<String>,
452 ) -> Self {
453 Self {
454 cl_ord_id,
455 symbol,
456 side,
457 transact_time,
458 order_qty,
459 ord_type,
460 price,
461 stop_px,
462 expire_time,
463 pos_maint_rpt_id,
464 designation,
465 }
466 }
467}
468
469impl RequestMessage for NewOrderSingleReq {
470 fn get_body(&self, delimiter: &str, _config: &Config) -> Option<String> {
471 let mut fields = vec![
472 format_field(Field::ClOrdId, &self.cl_ord_id),
473 format_field(Field::Symbol, &self.symbol),
474 format_field(Field::Side, self.side as u32),
475 format_field(
476 Field::TransactTime,
477 self.transact_time.map_or_else(
478 || Utc::now().format("%Y%m%d-%H:%M:%S").to_string(),
479 |d| d.format("%Y%m%d-%H:%M:%S").to_string(),
480 ),
481 ),
482 format_field(Field::OrderQty, self.order_qty),
483 format_field(Field::OrdType, self.ord_type as u32),
484 ];
485
486 if let Some(price) = self.price {
487 fields.push(format_field(Field::Price, price));
488 }
489 if let Some(stop_px) = self.stop_px {
490 fields.push(format_field(Field::StopPx, stop_px));
491 }
492 if let Some(expire_time) = self.expire_time {
493 fields.push(format_field(
494 Field::ExpireTime,
495 expire_time.format("%Y%m%d-%H:%M:%S"),
496 ));
497 }
498 if let Some(pos_maint_rpt_id) = &self.pos_maint_rpt_id {
499 fields.push(format_field(Field::PosMaintRptID, pos_maint_rpt_id));
500 }
501 if let Some(designation) = &self.designation {
502 fields.push(format_field(Field::Designation, designation));
503 }
504
505 Some(fields.join(delimiter))
506 }
507
508 fn get_message_type(&self) -> &str {
509 "D"
510 }
511}
512
513#[derive(Debug, Clone, Default)]
514pub struct OrderStatusReq {
515 pub cl_ord_id: String,
516 pub side: Option<Side>,
517}
518
519impl RequestMessage for OrderStatusReq {
520 fn get_body(&self, delimiter: &str, _config: &Config) -> Option<String> {
521 let mut fields = vec![format_field(Field::ClOrdId, &self.cl_ord_id)];
522
523 if let Some(side) = self.side {
524 fields.push(format_field(Field::Side, side as u32));
525 }
526
527 Some(fields.join(delimiter))
528 }
529
530 fn get_message_type(&self) -> &str {
531 "H"
532 }
533}
534
535impl OrderStatusReq {
536 pub fn new(cl_ord_id: String, side: Option<Side>) -> Self {
537 Self { cl_ord_id, side }
538 }
539}
540
541#[derive(Debug, Clone, Default)]
542pub struct OrderMassStatusReq {
543 pub mass_status_req_id: String,
544 pub mass_status_req_type: u32,
545 pub issue_date: Option<chrono::NaiveDateTime>,
546}
547
548impl OrderMassStatusReq {
549 pub fn new(
550 mass_status_req_id: String,
551 mass_status_req_type: u32,
552 issue_date: Option<chrono::NaiveDateTime>,
553 ) -> Self {
554 Self {
555 mass_status_req_id,
556 mass_status_req_type,
557 issue_date,
558 }
559 }
560}
561
562impl RequestMessage for OrderMassStatusReq {
563 fn get_body(&self, delimiter: &str, _config: &Config) -> Option<String> {
564 let mut fields = vec![
565 format_field(Field::MassStatusReqID, &self.mass_status_req_id),
566 format_field(Field::MassStatusReqType, self.mass_status_req_type),
567 ];
568
569 if let Some(issue_date) = self.issue_date {
570 fields.push(format_field(
571 Field::IssueDate,
572 issue_date.format("%Y%m%d-%H:%M:%S"),
573 ));
574 }
575
576 Some(fields.join(delimiter))
577 }
578
579 fn get_message_type(&self) -> &str {
580 "AF"
581 }
582}
583
584#[derive(Debug, Clone, Default)]
585pub struct PositionsReq {
586 pub pos_req_id: String,
587 pub pos_maint_rpt_id: Option<String>,
588}
589
590impl PositionsReq {
591 pub fn new(pos_req_id: String, pos_maint_rpt_id: Option<String>) -> Self {
592 Self {
593 pos_req_id,
594 pos_maint_rpt_id,
595 }
596 }
597}
598
599impl RequestMessage for PositionsReq {
600 fn get_body(&self, delimiter: &str, _config: &Config) -> Option<String> {
601 let mut fields = vec![format_field(Field::PosReqID, &self.pos_req_id)];
602
603 if let Some(pos_maint_rpt_id) = &self.pos_maint_rpt_id {
604 fields.push(format_field(Field::PosMaintRptID, pos_maint_rpt_id));
605 }
606
607 Some(fields.join(delimiter))
608 }
609
610 fn get_message_type(&self) -> &str {
611 "AN"
612 }
613}
614
615#[derive(Debug, Clone, Default)]
616pub struct OrderCancelReq {
617 pub orig_cl_ord_id: String,
618 pub order_id: Option<String>,
619 pub cl_ord_id: String,
620}
621impl OrderCancelReq {
622 pub fn new(orig_cl_ord_id: String, order_id: Option<String>, cl_ord_id: String) -> Self {
623 Self {
624 orig_cl_ord_id,
625 order_id,
626 cl_ord_id,
627 }
628 }
629}
630impl RequestMessage for OrderCancelReq {
631 fn get_body(&self, delimiter: &str, _config: &Config) -> Option<String> {
632 let mut fields = vec![
633 format_field(Field::OrigClOrdID, &self.orig_cl_ord_id),
634 format_field(Field::ClOrdId, &self.cl_ord_id),
635 ];
636
637 if let Some(order_id) = &self.order_id {
638 fields.push(format_field(Field::OrderID, order_id));
639 }
640
641 Some(fields.join(delimiter))
642 }
643
644 fn get_message_type(&self) -> &str {
645 "F"
646 }
647}
648
649#[derive(Debug, Clone, Default)]
650pub struct OrderCancelReplaceReq {
651 pub orig_cl_ord_id: String,
652 pub order_id: Option<String>,
653 pub cl_ord_id: String,
654 pub order_qty: f64,
655 pub price: Option<f64>,
656 pub stop_px: Option<f64>,
657 pub expire_time: Option<chrono::NaiveDateTime>,
658}
659
660impl OrderCancelReplaceReq {
661 pub fn new(
662 orig_cl_ord_id: String,
663 order_id: Option<String>,
664 cl_ord_id: String,
665 order_qty: f64,
666 price: Option<f64>,
667 stop_px: Option<f64>,
668 expire_time: Option<chrono::NaiveDateTime>,
669 ) -> Self {
670 Self {
671 orig_cl_ord_id,
672 order_id,
673 cl_ord_id,
674 order_qty,
675 price,
676 stop_px,
677 expire_time,
678 }
679 }
680}
681
682impl RequestMessage for OrderCancelReplaceReq {
683 fn get_body(&self, delimiter: &str, _config: &Config) -> Option<String> {
684 let mut fields = vec![
685 format_field(Field::OrigClOrdID, &self.orig_cl_ord_id),
686 format_field(Field::ClOrdId, &self.cl_ord_id),
687 format_field(Field::OrderQty, self.order_qty),
688 ];
689
690 if let Some(order_id) = &self.order_id {
691 fields.push(format_field(Field::OrderID, order_id));
692 }
693 if let Some(price) = self.price {
694 fields.push(format_field(Field::Price, price));
695 }
696 if let Some(stop_px) = self.stop_px {
697 fields.push(format_field(Field::StopPx, stop_px));
698 }
699 if let Some(expire_time) = self.expire_time {
700 fields.push(format_field(
701 Field::ExpireTime,
702 expire_time.format("%Y%m%d-%H:%M:%S"),
703 ));
704 }
705
706 Some(fields.join(delimiter))
707 }
708
709 fn get_message_type(&self) -> &str {
710 "G"
711 }
712}
713
714#[derive(Debug, Clone, Default)]
715pub struct SecurityListReq {
716 pub security_req_id: String,
717 pub security_list_req_type: u32,
718 pub symbol: Option<String>,
719}
720
721impl SecurityListReq {
722 pub fn new(
723 security_req_id: String,
724 security_list_req_type: u32,
725 symbol: Option<String>,
726 ) -> Self {
727 Self {
728 security_req_id,
729 security_list_req_type,
730 symbol,
731 }
732 }
733}
734
735impl RequestMessage for SecurityListReq {
736 fn get_body(&self, delimiter: &str, _config: &Config) -> Option<String> {
737 let mut fields = vec![
738 format_field(Field::SecurityReqID, &self.security_req_id),
739 format_field(Field::SecurityListRequestType, self.security_list_req_type),
740 ];
741
742 if let Some(symbol) = &self.symbol {
743 fields.push(format_field(Field::Symbol, symbol));
744 }
745
746 Some(fields.join(delimiter))
747 }
748
749 fn get_message_type(&self) -> &str {
750 "x"
751 }
752}
753
754#[cfg(test)]
755mod tests {
756 use super::ResponseMessage;
757 use crate::types::{Field, DELIMITER};
758 #[test]
759 fn test_parse_repeating_group_spot_market() {
760 let res = "8=FIX.4.4|9=134|35=W|34=2|49=CSERVER|50=QUOTE|52=20170117-10:26:54.630|56=live.theBroker.12345|57=any_string|55=1|268=2|269=0|270=1.06625|269=1|270=1.0663|10=118|".to_string().replace("|", DELIMITER);
761 let msg = ResponseMessage::new(&res, DELIMITER);
762 let result = msg.get_repeating_groups(
763 Field::NoMDEntries,
764 Field::MDEntryType,
765 Some(Field::MDEntryPx),
766 );
767 assert_eq!(result.len(), 2);
768 assert_eq!(result[0].len(), 2);
769 assert_eq!(result[1].len(), 2);
770
771 let result = msg.get_repeating_groups(Field::NoMDEntries, Field::MDEntryType, None);
772
773 assert_eq!(result.len(), 2);
774 assert_eq!(result[0].len(), 2);
775 assert_eq!(result[1].len(), 2);
776 }
777
778 #[test]
779 fn test_parse_repeating_group_depth_market() {
780 let res = "8=FIX.4.4|9=310|35=W|34=2|49=CSERVER|50=QUOTE|52=20180925-12:05:28.284|56=live.theBroker.12345|57=Quote|55=1|268=6|269=1|270=1.11132|271=3000000|278=16|269=1|270=1.11134|271=5000000|278=17|269=1|270=1.11133|271=3000000|278=15|269=0|270=1.1112|271=2000000|278=12|269=0|270=1.11121|271=1000000|278=13|269=0|270=1.11122|271=3000000|278=14|10=247|".to_string().replace("|", DELIMITER);
781 let msg = ResponseMessage::new(&res, DELIMITER);
782 let result = msg.get_repeating_groups(Field::NoMDEntries, Field::MDEntryType, None);
783
784 assert_eq!(6, result.len());
785 for group in result.into_iter() {
786 assert_eq!(4, group.len());
787 assert!(group.contains_key(&Field::MDEntryID));
788 assert!(group.contains_key(&Field::MDEntrySize));
789 assert!(group.contains_key(&Field::MDEntryPx));
790 assert!(group.contains_key(&Field::MDEntryType));
791 }
792 }
793 #[test]
794 fn test_parse_repeating_group_market_incre() {
795 let res = "8=FIX.4.4|9=376|35=X|34=3|49=CSERVER|50=QUOTE|52=20170117-11:13:44.555|56=live.theBroker.12345|57=any_string|268=8|279=0|269=0|278=7491|55=1|270=1.06897|271=1000000|279=0|269=0|278=7490|55=1|270=1.06898|271=1000000|279=0|269=0|278=7489|55=1|270=1.06874|271=32373000|279=0|269=1|278=7496|55=1|270=1.06931|271=34580000|279=2|278=7477|55=1|279=2|278=7468|55=1|279=2|278=7467|55=1|279=2|278=7484|55=1|10=192|
796".to_string().replace("|", DELIMITER);
797 let msg = ResponseMessage::new(&res, DELIMITER);
798 let result = msg.get_repeating_groups(Field::NoMDEntries, Field::MDUpdateAction, None);
799
800 assert_eq!(8, result.len());
801 for group in result.into_iter() {
802 match group.get(&Field::MDUpdateAction).unwrap().as_str() {
803 "0" => {
804 assert_eq!(6, group.len());
805 assert!(group.contains_key(&Field::Symbol));
806 assert!(group.contains_key(&Field::MDEntryID));
807 assert!(group.contains_key(&Field::MDEntryPx));
808 assert!(group.contains_key(&Field::MDEntryType));
809 assert!(group.contains_key(&Field::MDEntrySize));
810 }
811 "2" => {
812 assert_eq!(3, group.len());
813 assert!(group.contains_key(&Field::Symbol));
814 assert!(group.contains_key(&Field::MDEntryID));
815 }
816 _ => {
817 assert!(false);
818 }
819 }
820 }
821 }
822}