1use std::collections::{HashMap, HashSet};
46
47use crate::errors::{ParseError, ParserConfig, Result, SwiftValidationError};
48use crate::headers::{ApplicationHeader, BasicHeader, Trailer, UserHeader};
49use crate::messages::{
50 MT101, MT103, MT104, MT107, MT110, MT111, MT112, MT192, MT196, MT199, MT200, MT202, MT204,
51 MT205, MT210, MT292, MT296, MT299, MT900, MT910, MT920, MT935, MT940, MT941, MT942, MT950,
52};
53use crate::swift_error_codes::t_series;
54use crate::{ParsedSwiftMessage, SwiftMessage, SwiftMessageBody};
55
56type FieldParseResult = Result<HashMap<String, Vec<(String, usize)>>>;
61
62#[derive(Debug, Clone)]
64pub struct ParsingContext {
65 pub current_field: Option<String>,
67 pub current_component: Option<String>,
69 pub message_type: String,
71 pub original_message: String,
73}
74
75impl ParsingContext {
76 pub fn new(message_type: String, original_message: String) -> Self {
78 Self {
79 current_field: None,
80 current_component: None,
81 message_type,
82 original_message,
83 }
84 }
85
86 pub fn with_field(&self, field: String) -> Self {
88 let mut ctx = self.clone();
89 ctx.current_field = Some(field);
90 ctx.current_component = None;
91 ctx
92 }
93
94 pub fn with_component(&self, component: String) -> Self {
96 let mut ctx = self.clone();
97 ctx.current_component = Some(component);
98 ctx
99 }
100}
101
102#[derive(Debug, Clone)]
131pub struct FieldConsumptionTracker {
132 consumed_indices: HashMap<String, HashSet<usize>>,
134}
135
136impl Default for FieldConsumptionTracker {
137 fn default() -> Self {
138 Self::new()
139 }
140}
141
142impl FieldConsumptionTracker {
143 pub fn new() -> Self {
145 Self {
146 consumed_indices: HashMap::new(),
147 }
148 }
149
150 pub fn mark_consumed(&mut self, tag: &str, index: usize) {
152 use std::collections::hash_map::Entry;
154 match self.consumed_indices.entry(tag.to_string()) {
155 Entry::Occupied(mut e) => {
156 e.get_mut().insert(index);
157 }
158 Entry::Vacant(e) => {
159 let mut set = HashSet::new();
160 set.insert(index);
161 e.insert(set);
162 }
163 }
164 }
165
166 pub fn get_next_available<'a>(
168 &self,
169 tag: &str,
170 values: &'a [(String, usize)],
171 ) -> Option<(&'a str, usize)> {
172 let consumed_set = self.consumed_indices.get(tag);
173
174 values
176 .iter()
177 .find(|(_, pos)| consumed_set.is_none_or(|set| !set.contains(pos)))
178 .map(|(value, pos)| (value.as_str(), *pos))
179 }
180}
181
182pub fn find_field_with_variant_sequential_constrained(
201 fields: &HashMap<String, Vec<(String, usize)>>,
202 base_tag: &str,
203 tracker: &mut FieldConsumptionTracker,
204 valid_variants: Option<&[&str]>,
205) -> Option<(String, Option<String>, usize)> {
206 if let Some(values) = fields.get(base_tag)
208 && let Some((value, pos)) = tracker.get_next_available(base_tag, values)
209 {
210 tracker.mark_consumed(base_tag, pos);
211 return Some((value.to_string(), None, pos));
212 }
213
214 let mut variant_candidates: Vec<(&String, &Vec<(String, usize)>)> = fields
217 .iter()
218 .filter(|(tag, _)| {
219 tag.starts_with(base_tag)
220 && tag.len() == base_tag.len() + 1
221 && tag
222 .chars()
223 .last()
224 .is_some_and(|c| c.is_ascii_alphabetic() && c.is_ascii_uppercase())
225 })
226 .collect();
227
228 variant_candidates.sort_by_key(|(tag, values)| {
230 values
231 .iter()
232 .filter(|(_, pos)| {
233 tracker
234 .consumed_indices
235 .get(*tag)
236 .is_none_or(|set| !set.contains(pos))
237 })
238 .map(|(_, pos)| *pos)
239 .min()
240 .unwrap_or(usize::MAX)
241 });
242
243 for (tag, values) in variant_candidates {
244 let variant_char = tag.chars().last().unwrap();
245 let variant_str = variant_char.to_string();
246
247 if let Some(valid) = valid_variants
249 && !valid.contains(&variant_str.as_str())
250 {
251 continue; }
253
254 if let Some((value, pos)) = tracker.get_next_available(tag, values) {
255 tracker.mark_consumed(tag, pos);
256 return Some((value.to_string(), Some(variant_str), pos));
257 }
258 }
259
260 None
261}
262
263pub struct SwiftParser {
289 config: ParserConfig,
290}
291
292impl Default for SwiftParser {
293 fn default() -> Self {
294 Self::new()
295 }
296}
297
298impl SwiftParser {
299 pub fn new() -> Self {
301 Self {
302 config: ParserConfig::default(),
303 }
304 }
305
306 pub fn with_config(config: ParserConfig) -> Self {
308 Self { config }
309 }
310
311 pub fn parse_with_errors<T: SwiftMessageBody>(
313 &self,
314 raw_message: &str,
315 ) -> Result<crate::errors::ParseResult<SwiftMessage<T>>> {
316 let block1 = Self::extract_block(raw_message, 1)?;
317 let block2 = Self::extract_block(raw_message, 2)?;
318 let block3 = Self::extract_block(raw_message, 3)?;
319 let block4 = Self::extract_block(raw_message, 4)?;
320 let block5 = Self::extract_block(raw_message, 5)?;
321
322 let basic_header = BasicHeader::parse(&block1.unwrap_or_default())?;
324 let application_header = ApplicationHeader::parse(&block2.unwrap_or_default())?;
325 let user_header = block3.map(|b| UserHeader::parse(&b)).transpose()?;
326 let trailer = block5.map(|b| Trailer::parse(&b)).transpose()?;
327
328 let message_type = application_header.message_type.clone();
330
331 if message_type != T::message_type() {
333 return Err(ParseError::SwiftValidation(Box::new(
334 SwiftValidationError::format_error(
335 t_series::T03,
336 "MESSAGE_TYPE",
337 &message_type,
338 T::message_type(),
339 &format!(
340 "Message type mismatch: expected {}, got {}",
341 T::message_type(),
342 message_type
343 ),
344 ),
345 )));
346 }
347
348 let field_map_with_positions = Self::parse_block4_fields(&block4.unwrap_or_default())?;
350
351 let parse_result = T::from_fields_with_config(field_map_with_positions, &self.config)?;
353
354 match parse_result {
355 crate::errors::ParseResult::Success(fields) => {
356 Ok(crate::errors::ParseResult::Success(SwiftMessage {
357 basic_header,
358 application_header,
359 user_header,
360 trailer,
361 message_type,
362 fields,
363 }))
364 }
365 crate::errors::ParseResult::PartialSuccess(fields, errors) => {
366 Ok(crate::errors::ParseResult::PartialSuccess(
367 SwiftMessage {
368 basic_header,
369 application_header,
370 user_header,
371 trailer,
372 message_type,
373 fields,
374 },
375 errors,
376 ))
377 }
378 crate::errors::ParseResult::Failure(errors) => {
379 Ok(crate::errors::ParseResult::Failure(errors))
380 }
381 }
382 }
383 pub fn parse<T: SwiftMessageBody>(raw_message: &str) -> Result<SwiftMessage<T>> {
385 Self::new().parse_message(raw_message)
386 }
387
388 pub fn parse_message<T: SwiftMessageBody>(&self, raw_message: &str) -> Result<SwiftMessage<T>> {
390 let block1 = Self::extract_block(raw_message, 1)?;
391 let block2 = Self::extract_block(raw_message, 2)?;
392 let block3 = Self::extract_block(raw_message, 3)?;
393 let block4 = Self::extract_block(raw_message, 4)?;
394 let block5 = Self::extract_block(raw_message, 5)?;
395
396 let basic_header = BasicHeader::parse(&block1.unwrap_or_default())?;
398 let application_header = ApplicationHeader::parse(&block2.unwrap_or_default())?;
399 let user_header = block3.map(|b| UserHeader::parse(&b)).transpose()?;
400 let trailer = block5.map(|b| Trailer::parse(&b)).transpose()?;
401
402 let message_type = application_header.message_type.clone();
404
405 if message_type != T::message_type() {
407 return Err(ParseError::SwiftValidation(Box::new(
408 SwiftValidationError::format_error(
409 t_series::T03,
410 "MESSAGE_TYPE",
411 &message_type,
412 T::message_type(),
413 &format!(
414 "Message type mismatch: expected {}, got {}",
415 T::message_type(),
416 message_type
417 ),
418 ),
419 )));
420 }
421
422 let field_map_with_positions = Self::parse_block4_fields(&block4.unwrap_or_default())?;
424
425 let parse_result = T::from_fields_with_config(field_map_with_positions, &self.config)?;
427
428 match parse_result {
429 crate::errors::ParseResult::Success(fields) => Ok(SwiftMessage {
430 basic_header,
431 application_header,
432 user_header,
433 trailer,
434 message_type,
435 fields,
436 }),
437 crate::errors::ParseResult::PartialSuccess(fields, errors) => {
438 eprintln!("Warning: Parsed with {} non-critical errors", errors.len());
441 for error in &errors {
442 eprintln!(" - {error}");
443 }
444 Ok(SwiftMessage {
445 basic_header,
446 application_header,
447 user_header,
448 trailer,
449 message_type,
450 fields,
451 })
452 }
453 crate::errors::ParseResult::Failure(errors) => {
454 Err(ParseError::MultipleErrors(errors))
456 }
457 }
458 }
459
460 pub fn parse_auto(raw_message: &str) -> Result<ParsedSwiftMessage> {
462 Self::new().parse_message_auto(raw_message)
463 }
464
465 pub fn parse_message_auto(&self, raw_message: &str) -> Result<ParsedSwiftMessage> {
467 let block2 = Self::extract_block(raw_message, 2)?;
469
470 let application_header = ApplicationHeader::parse(&block2.unwrap_or_default())?;
472 let message_type = &application_header.message_type;
473
474 match message_type.as_str() {
476 "101" => {
477 let parsed = self.parse_message::<MT101>(raw_message)?;
478 Ok(ParsedSwiftMessage::MT101(Box::new(parsed)))
479 }
480 "103" => {
481 let parsed = self.parse_message::<MT103>(raw_message)?;
482 Ok(ParsedSwiftMessage::MT103(Box::new(parsed)))
483 }
484 "104" => {
485 let parsed = self.parse_message::<MT104>(raw_message)?;
486 Ok(ParsedSwiftMessage::MT104(Box::new(parsed)))
487 }
488 "107" => {
489 let parsed = self.parse_message::<MT107>(raw_message)?;
490 Ok(ParsedSwiftMessage::MT107(Box::new(parsed)))
491 }
492 "110" => {
493 let parsed = self.parse_message::<MT110>(raw_message)?;
494 Ok(ParsedSwiftMessage::MT110(Box::new(parsed)))
495 }
496 "111" => {
497 let parsed = self.parse_message::<MT111>(raw_message)?;
498 Ok(ParsedSwiftMessage::MT111(Box::new(parsed)))
499 }
500 "112" => {
501 let parsed = self.parse_message::<MT112>(raw_message)?;
502 Ok(ParsedSwiftMessage::MT112(Box::new(parsed)))
503 }
504 "200" => {
505 let parsed = self.parse_message::<MT200>(raw_message)?;
506 Ok(ParsedSwiftMessage::MT200(Box::new(parsed)))
507 }
508 "202" => {
509 let parsed = self.parse_message::<MT202>(raw_message)?;
510 Ok(ParsedSwiftMessage::MT202(Box::new(parsed)))
511 }
512 "204" => {
513 let parsed = self.parse_message::<MT204>(raw_message)?;
514 Ok(ParsedSwiftMessage::MT204(Box::new(parsed)))
515 }
516 "205" => {
517 let parsed = self.parse_message::<MT205>(raw_message)?;
518 Ok(ParsedSwiftMessage::MT205(Box::new(parsed)))
519 }
520 "210" => {
521 let parsed = self.parse_message::<MT210>(raw_message)?;
522 Ok(ParsedSwiftMessage::MT210(Box::new(parsed)))
523 }
524 "900" => {
525 let parsed = self.parse_message::<MT900>(raw_message)?;
526 Ok(ParsedSwiftMessage::MT900(Box::new(parsed)))
527 }
528 "910" => {
529 let parsed = self.parse_message::<MT910>(raw_message)?;
530 Ok(ParsedSwiftMessage::MT910(Box::new(parsed)))
531 }
532 "920" => {
533 let parsed = self.parse_message::<MT920>(raw_message)?;
534 Ok(ParsedSwiftMessage::MT920(Box::new(parsed)))
535 }
536 "935" => {
537 let parsed = self.parse_message::<MT935>(raw_message)?;
538 Ok(ParsedSwiftMessage::MT935(Box::new(parsed)))
539 }
540 "940" => {
541 let parsed = self.parse_message::<MT940>(raw_message)?;
542 Ok(ParsedSwiftMessage::MT940(Box::new(parsed)))
543 }
544 "941" => {
545 let parsed = self.parse_message::<MT941>(raw_message)?;
546 Ok(ParsedSwiftMessage::MT941(Box::new(parsed)))
547 }
548 "942" => {
549 let parsed = self.parse_message::<MT942>(raw_message)?;
550 Ok(ParsedSwiftMessage::MT942(Box::new(parsed)))
551 }
552 "950" => {
553 let parsed = self.parse_message::<MT950>(raw_message)?;
554 Ok(ParsedSwiftMessage::MT950(Box::new(parsed)))
555 }
556 "192" => {
557 let parsed = self.parse_message::<MT192>(raw_message)?;
558 Ok(ParsedSwiftMessage::MT192(Box::new(parsed)))
559 }
560 "196" => {
561 let parsed = self.parse_message::<MT196>(raw_message)?;
562 Ok(ParsedSwiftMessage::MT196(Box::new(parsed)))
563 }
564 "292" => {
565 let parsed = self.parse_message::<MT292>(raw_message)?;
566 Ok(ParsedSwiftMessage::MT292(Box::new(parsed)))
567 }
568 "296" => {
569 let parsed = self.parse_message::<MT296>(raw_message)?;
570 Ok(ParsedSwiftMessage::MT296(Box::new(parsed)))
571 }
572 "199" => {
573 let parsed = self.parse_message::<MT199>(raw_message)?;
574 Ok(ParsedSwiftMessage::MT199(Box::new(parsed)))
575 }
576 "299" => {
577 let parsed = self.parse_message::<MT299>(raw_message)?;
578 Ok(ParsedSwiftMessage::MT299(Box::new(parsed)))
579 }
580 _ => Err(ParseError::UnsupportedMessageType {
581 message_type: message_type.clone(),
582 }),
583 }
584 }
585
586 pub fn extract_block(raw_message: &str, block_index: u8) -> Result<Option<String>> {
588 if !(1..=5).contains(&block_index) {
590 return Err(ParseError::SwiftValidation(Box::new(
591 crate::errors::SwiftValidationError::format_error(
592 crate::swift_error_codes::t_series::T01,
593 "BLOCK_INDEX",
594 &block_index.to_string(),
595 "1-5",
596 &format!("Invalid block index: {block_index}"),
597 ),
598 )));
599 }
600
601 let block_marker = format!("{{{block_index}:");
602
603 if let Some(start) = raw_message.find(&block_marker) {
604 let content_start = start + block_marker.len();
605
606 match block_index {
607 1 | 2 => {
608 if let Some(end) = raw_message[start..].find('}') {
610 let end = start + end;
611 Ok(Some(raw_message[content_start..end].to_string()))
612 } else {
613 Ok(None)
614 }
615 }
616 3 | 5 => {
617 if let Some(end) = Self::find_matching_brace(&raw_message[start..]) {
619 let end = start + end;
620 Ok(Some(raw_message[content_start..end].to_string()))
621 } else {
622 Ok(None)
623 }
624 }
625 4 => {
626 if let Some(end) = raw_message[start..].find("-}") {
628 let end = start + end;
629 Ok(Some(raw_message[content_start..end].to_string()))
630 } else {
631 Ok(None)
632 }
633 }
634 _ => Err(ParseError::SwiftValidation(Box::new(
635 crate::errors::SwiftValidationError::format_error(
636 crate::swift_error_codes::t_series::T02,
637 "BLOCK",
638 &block_index.to_string(),
639 "1-5",
640 &format!("Invalid block index: {block_index}"),
641 ),
642 ))),
643 }
644 } else {
645 Ok(None)
646 }
647 }
648
649 fn parse_block4_fields(block4: &str) -> FieldParseResult {
651 crate::parser::parse_block4_fields(block4)
653 }
654
655 fn find_matching_brace(text: &str) -> Option<usize> {
658 let mut chars = text.char_indices();
659
660 let mut brace_count = if let Some((_, '{')) = chars.next() {
662 1
663 } else {
664 return None;
665 };
666
667 for (i, ch) in chars {
668 match ch {
669 '{' => brace_count += 1,
670 '}' => {
671 brace_count -= 1;
672 if brace_count == 0 {
673 return Some(i);
674 }
675 }
676 _ => {}
677 }
678 }
679
680 None
681 }
682}
683
684pub fn parse_swift_message_from_string(value: &str) -> Result<HashMap<String, Vec<String>>> {
687 let mut field_map = HashMap::new();
694
695 for line in value.lines() {
697 if line.trim().is_empty() {
698 continue;
699 }
700
701 if let Some(colon_pos) = line.find(':')
703 && let Some(second_colon) = line[colon_pos + 1..].find(':')
704 {
705 let second_colon_pos = colon_pos + 1 + second_colon;
706 let field_tag = line[colon_pos + 1..second_colon_pos].to_string();
707 let _field_value = line[second_colon_pos + 1..].to_string();
708
709 field_map
710 .entry(field_tag)
711 .or_insert_with(Vec::new)
712 .push(format!(":{}", &line[colon_pos + 1..]));
713 }
714 }
715
716 Ok(field_map)
717}
718
719pub fn parse_sequences<T>(
725 fields: &HashMap<String, Vec<(String, usize)>>,
726 tracker: &mut FieldConsumptionTracker,
727) -> Result<Vec<T>>
728where
729 T: crate::SwiftMessageBody,
730{
731 let message_type = std::any::type_name::<T>();
733
734 if message_type.contains("MT104Transaction") {
735 use crate::parser::sequence_parser::{get_sequence_config, split_into_sequences};
737
738 let config = get_sequence_config("MT104");
739 let parsed_sequences = split_into_sequences(fields, &config)?;
740
741 return parse_sequence_b_items::<T>(&parsed_sequences.sequence_b, tracker);
743 }
744
745 if message_type.contains("MT204Transaction") {
746 let field_20_count = fields.get("20").map(|v| v.len()).unwrap_or(0);
751 if field_20_count <= 1 {
752 return Ok(Vec::new()); }
754
755 let num_transactions = field_20_count - 1; let mut transactions = Vec::new();
759
760 for i in 0..num_transactions {
761 let mut tx_fields = HashMap::new();
762
763 if let Some(field_20_values) = fields.get("20")
765 && i + 1 < field_20_values.len()
766 {
767 tx_fields.insert("20".to_string(), vec![field_20_values[i + 1].clone()]);
768 }
769
770 if let Some(field_21_values) = fields.get("21")
772 && i < field_21_values.len()
773 {
774 tx_fields.insert("21".to_string(), vec![field_21_values[i].clone()]);
775 }
776
777 if let Some(field_32b_values) = fields.get("32B")
779 && i < field_32b_values.len()
780 {
781 tx_fields.insert("32B".to_string(), vec![field_32b_values[i].clone()]);
782 }
783
784 for variant in ["53", "53A", "53B", "53D"] {
786 if let Some(field_53_values) = fields.get(variant)
787 && i < field_53_values.len()
788 {
789 tx_fields.insert(variant.to_string(), vec![field_53_values[i].clone()]);
790 break; }
792 }
793
794 if let Some(field_72_values) = fields.get("72") {
796 if i + 2 < field_72_values.len() {
799 tx_fields.insert("72".to_string(), vec![field_72_values[i + 2].clone()]);
800 }
801 }
802
803 if let Ok(transaction) = T::from_fields(tx_fields) {
805 transactions.push(transaction);
806 }
807 }
808
809 return Ok(transactions);
810 }
811
812 let mut all_fields: Vec<(String, String, usize)> = Vec::new();
814 for (tag, values) in fields {
815 for (value, pos) in values {
816 if tracker
818 .consumed_indices
819 .get(tag)
820 .is_none_or(|set| !set.contains(pos))
821 {
822 all_fields.push((tag.clone(), value.clone(), *pos));
823 }
824 }
825 }
826 all_fields.sort_by_key(|(_, _, pos)| *pos);
827
828 let (primary_marker, secondary_marker) = if message_type.contains("MT920Sequence") {
830 ("12", None)
831 } else if message_type.contains("MT935RateChange") {
832 ("23", Some("25"))
833 } else if message_type.contains("MT940StatementLine")
834 || message_type.contains("MT942StatementLine")
835 {
836 ("61", None)
837 } else {
838 ("21", None)
839 };
840
841 let mut sequences = Vec::new();
842 let mut current_sequence_fields: HashMap<String, Vec<(String, usize)>> = HashMap::new();
843 let mut in_sequence = false;
844
845 for (tag, value, pos) in all_fields {
846 let is_sequence_start = (tag == primary_marker
848 || secondary_marker.is_some_and(|m| tag == m))
849 && !tag.ends_with("R")
850 && !tag.ends_with("F")
851 && !tag.ends_with("C")
852 && !tag.ends_with("D");
853
854 if is_sequence_start {
855 if in_sequence && !current_sequence_fields.is_empty() {
857 if let Ok(sequence_item) = T::from_fields(current_sequence_fields.clone()) {
858 sequences.push(sequence_item);
859 }
860 current_sequence_fields.clear();
861 }
862 in_sequence = true;
863 }
864
865 if in_sequence {
867 current_sequence_fields
868 .entry(tag.clone())
869 .or_default()
870 .push((value, pos));
871
872 tracker.mark_consumed(&tag, pos);
874 }
875 }
876
877 if in_sequence && !current_sequence_fields.is_empty() {
879 match T::from_fields(current_sequence_fields) {
880 Ok(sequence_item) => {
881 sequences.push(sequence_item);
882 }
883 Err(_e) => {
884 #[cfg(debug_assertions)]
885 eprintln!("DEBUG: Failed to parse final sequence item: {_e:?}");
886 }
887 }
888 }
889
890 Ok(sequences)
891}
892
893fn parse_sequence_b_items<T>(
895 fields: &HashMap<String, Vec<(String, usize)>>,
896 tracker: &mut FieldConsumptionTracker,
897) -> Result<Vec<T>>
898where
899 T: crate::SwiftMessageBody,
900{
901 let mut sequences = Vec::new();
902
903 let mut all_fields: Vec<(String, String, usize)> = Vec::new();
905 for (tag, values) in fields {
906 for (value, pos) in values {
907 all_fields.push((tag.clone(), value.clone(), *pos));
908 }
909 }
910 all_fields.sort_by_key(|(_, _, pos)| *pos);
911
912 let message_type = std::any::type_name::<T>();
914 let sequence_start_tag = if message_type.contains("MT204Transaction") {
915 "20" } else {
917 "21" };
919 let mut current_sequence_fields: HashMap<String, Vec<(String, usize)>> = HashMap::new();
920 let mut in_sequence = false;
921
922 for (tag, value, pos) in all_fields {
923 if tag == sequence_start_tag
925 && !tag.ends_with("R")
926 && !tag.ends_with("F")
927 && !tag.ends_with("C")
928 && !tag.ends_with("D")
929 {
930 if in_sequence && !current_sequence_fields.is_empty() {
932 if let Ok(sequence_item) = T::from_fields(current_sequence_fields.clone()) {
933 sequences.push(sequence_item);
934 }
935 current_sequence_fields.clear();
936 }
937 in_sequence = true;
938 }
939
940 if in_sequence {
942 current_sequence_fields
943 .entry(tag.clone())
944 .or_default()
945 .push((value, pos));
946
947 tracker.mark_consumed(&tag, pos);
949 }
950 }
951
952 if in_sequence && !current_sequence_fields.is_empty() {
954 match T::from_fields(current_sequence_fields) {
955 Ok(sequence_item) => {
956 sequences.push(sequence_item);
957 }
958 Err(_e) => {
959 #[cfg(debug_assertions)]
960 eprintln!("DEBUG: Failed to parse final sequence item: {_e:?}");
961 }
962 }
963 }
964
965 Ok(sequences)
966}
967
968pub fn serialize_swift_message_to_string(fields: &HashMap<String, Vec<String>>) -> String {
971 let mut result = String::new();
976
977 for field_values in fields.values() {
979 for field_value in field_values {
980 result.push_str(field_value);
982 result.push('\n');
983 }
984 }
985
986 if result.ends_with('\n') {
988 result.pop();
989 }
990
991 result
992}