1use crate::{SwiftField, ValidationError, ValidationResult};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
125pub struct Field77T {
126 pub envelope_type: String,
128
129 pub envelope_format: String,
131
132 pub envelope_identifier: String,
134}
135
136impl SwiftField for Field77T {
137 fn parse(value: &str) -> Result<Self, crate::ParseError> {
138 let content = if let Some(stripped) = value.strip_prefix(":77T:") {
139 stripped
140 } else if let Some(stripped) = value.strip_prefix("77T:") {
141 stripped
142 } else {
143 value
144 };
145
146 if content.is_empty() {
147 return Err(crate::ParseError::InvalidFieldFormat {
148 field_tag: "77T".to_string(),
149 message: "Field content cannot be empty".to_string(),
150 });
151 }
152
153 let content = content.trim();
154
155 if content.len() < 4 {
157 return Err(crate::ParseError::InvalidFieldFormat {
158 field_tag: "77T".to_string(),
159 message: "Content too short (minimum format: XY/id)".to_string(),
160 });
161 }
162
163 let envelope_type = content.chars().nth(0).unwrap().to_string();
164 let envelope_format = content.chars().nth(1).unwrap().to_string();
165
166 if content.chars().nth(2) != Some('/') {
167 return Err(crate::ParseError::InvalidFieldFormat {
168 field_tag: "77T".to_string(),
169 message: "Missing separator '/' after envelope codes".to_string(),
170 });
171 }
172
173 let envelope_identifier = content[3..].to_string();
174
175 Self::new(envelope_type, envelope_format, envelope_identifier)
176 }
177
178 fn to_swift_string(&self) -> String {
179 format!(
180 ":77T:{}{}/{}",
181 self.envelope_type, self.envelope_format, self.envelope_identifier
182 )
183 }
184
185 fn validate(&self) -> ValidationResult {
186 let mut errors = Vec::new();
187
188 if self.envelope_type.len() != 1 {
190 errors.push(ValidationError::LengthValidation {
191 field_tag: "77T".to_string(),
192 expected: "1 character".to_string(),
193 actual: self.envelope_type.len(),
194 });
195 } else if !self.envelope_type.chars().all(|c| c.is_ascii_alphabetic()) {
196 errors.push(ValidationError::FormatValidation {
197 field_tag: "77T".to_string(),
198 message: "Envelope type must be alphabetic".to_string(),
199 });
200 }
201
202 if self.envelope_format.len() != 1 {
204 errors.push(ValidationError::LengthValidation {
205 field_tag: "77T".to_string(),
206 expected: "1 character".to_string(),
207 actual: self.envelope_format.len(),
208 });
209 } else if !self
210 .envelope_format
211 .chars()
212 .all(|c| c.is_ascii_alphabetic())
213 {
214 errors.push(ValidationError::FormatValidation {
215 field_tag: "77T".to_string(),
216 message: "Envelope format must be alphabetic".to_string(),
217 });
218 }
219
220 if self.envelope_identifier.is_empty() {
222 errors.push(ValidationError::ValueValidation {
223 field_tag: "77T".to_string(),
224 message: "Envelope identifier cannot be empty".to_string(),
225 });
226 }
227
228 if self.envelope_identifier.len() > 35 {
229 errors.push(ValidationError::LengthValidation {
230 field_tag: "77T".to_string(),
231 expected: "max 35 characters".to_string(),
232 actual: self.envelope_identifier.len(),
233 });
234 }
235
236 if !self
238 .envelope_identifier
239 .chars()
240 .all(|c| c.is_ascii() && !c.is_control())
241 {
242 errors.push(ValidationError::FormatValidation {
243 field_tag: "77T".to_string(),
244 message: "Envelope identifier contains invalid characters".to_string(),
245 });
246 }
247
248 ValidationResult {
249 is_valid: errors.is_empty(),
250 errors,
251 warnings: Vec::new(),
252 }
253 }
254
255 fn format_spec() -> &'static str {
256 "1!a1!a/35x"
257 }
258}
259
260impl Field77T {
261 pub fn new(
282 envelope_type: impl Into<String>,
283 envelope_format: impl Into<String>,
284 envelope_identifier: impl Into<String>,
285 ) -> crate::Result<Self> {
286 let envelope_type = envelope_type.into().trim().to_uppercase();
287 let envelope_format = envelope_format.into().trim().to_uppercase();
288 let envelope_identifier = envelope_identifier.into().trim().to_string();
289
290 if envelope_type.len() != 1 {
292 return Err(crate::ParseError::InvalidFieldFormat {
293 field_tag: "77T".to_string(),
294 message: "Envelope type must be exactly 1 character".to_string(),
295 });
296 }
297
298 if !envelope_type.chars().all(|c| c.is_ascii_alphabetic()) {
299 return Err(crate::ParseError::InvalidFieldFormat {
300 field_tag: "77T".to_string(),
301 message: "Envelope type must be alphabetic".to_string(),
302 });
303 }
304
305 if envelope_format.len() != 1 {
307 return Err(crate::ParseError::InvalidFieldFormat {
308 field_tag: "77T".to_string(),
309 message: "Envelope format must be exactly 1 character".to_string(),
310 });
311 }
312
313 if !envelope_format.chars().all(|c| c.is_ascii_alphabetic()) {
314 return Err(crate::ParseError::InvalidFieldFormat {
315 field_tag: "77T".to_string(),
316 message: "Envelope format must be alphabetic".to_string(),
317 });
318 }
319
320 if envelope_identifier.is_empty() {
322 return Err(crate::ParseError::InvalidFieldFormat {
323 field_tag: "77T".to_string(),
324 message: "Envelope identifier cannot be empty".to_string(),
325 });
326 }
327
328 if envelope_identifier.len() > 35 {
329 return Err(crate::ParseError::InvalidFieldFormat {
330 field_tag: "77T".to_string(),
331 message: "Envelope identifier cannot exceed 35 characters".to_string(),
332 });
333 }
334
335 if !envelope_identifier
336 .chars()
337 .all(|c| c.is_ascii() && !c.is_control())
338 {
339 return Err(crate::ParseError::InvalidFieldFormat {
340 field_tag: "77T".to_string(),
341 message: "Envelope identifier contains invalid characters".to_string(),
342 });
343 }
344
345 Ok(Field77T {
346 envelope_type,
347 envelope_format,
348 envelope_identifier,
349 })
350 }
351
352 pub fn envelope_type(&self) -> &str {
354 &self.envelope_type
355 }
356
357 pub fn envelope_format(&self) -> &str {
359 &self.envelope_format
360 }
361
362 pub fn envelope_identifier(&self) -> &str {
364 &self.envelope_identifier
365 }
366
367 pub fn is_remittance_envelope(&self) -> bool {
369 self.envelope_type == "R"
370 }
371
372 pub fn is_supplementary_envelope(&self) -> bool {
374 self.envelope_type == "S"
375 }
376
377 pub fn is_trade_envelope(&self) -> bool {
379 self.envelope_type == "T"
380 }
381
382 pub fn is_detailed_format(&self) -> bool {
384 self.envelope_format == "D"
385 }
386
387 pub fn is_summary_format(&self) -> bool {
389 self.envelope_format == "S"
390 }
391
392 pub fn is_custom_format(&self) -> bool {
394 self.envelope_format == "C"
395 }
396
397 pub fn description(&self) -> String {
399 let type_desc = match self.envelope_type.as_str() {
400 "R" => "Remittance Information",
401 "S" => "Supplementary Information",
402 "T" => "Trade Information",
403 _ => "Unknown Type",
404 };
405
406 let format_desc = match self.envelope_format.as_str() {
407 "D" => "Detailed Format",
408 "S" => "Summary Format",
409 "C" => "Custom Format",
410 _ => "Unknown Format",
411 };
412
413 format!(
414 "{} - {} ({})",
415 type_desc, format_desc, self.envelope_identifier
416 )
417 }
418
419 pub fn is_required_for_remit(&self) -> bool {
421 true }
423
424 pub fn is_allowed_in_core_stp(&self) -> bool {
426 false }
428}
429
430impl std::fmt::Display for Field77T {
431 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
432 write!(
433 f,
434 "{}{}/{}",
435 self.envelope_type, self.envelope_format, self.envelope_identifier
436 )
437 }
438}
439
440#[cfg(test)]
441mod tests {
442 use super::*;
443
444 #[test]
445 fn test_field77t_creation() {
446 let field = Field77T::new("R", "D", "REMITTANCE-2024-001234567890").unwrap();
447 assert_eq!(field.envelope_type(), "R");
448 assert_eq!(field.envelope_format(), "D");
449 assert_eq!(field.envelope_identifier(), "REMITTANCE-2024-001234567890");
450 }
451
452 #[test]
453 fn test_field77t_parse() {
454 let field = Field77T::parse("RD/REMITTANCE-2024-001234567890").unwrap();
455 assert_eq!(field.envelope_type(), "R");
456 assert_eq!(field.envelope_format(), "D");
457 assert_eq!(field.envelope_identifier(), "REMITTANCE-2024-001234567890");
458 }
459
460 #[test]
461 fn test_field77t_parse_with_prefix() {
462 let field = Field77T::parse(":77T:SS/SUPP-INFO-2024-03-15-001").unwrap();
463 assert_eq!(field.envelope_type(), "S");
464 assert_eq!(field.envelope_format(), "S");
465 assert_eq!(field.envelope_identifier(), "SUPP-INFO-2024-03-15-001");
466
467 let field = Field77T::parse("77T:TC/TRADE-LC-2024-567890123").unwrap();
468 assert_eq!(field.envelope_type(), "T");
469 assert_eq!(field.envelope_format(), "C");
470 assert_eq!(field.envelope_identifier(), "TRADE-LC-2024-567890123");
471 }
472
473 #[test]
474 fn test_field77t_case_normalization() {
475 let field = Field77T::new("r", "d", "remittance-2024-001").unwrap();
476 assert_eq!(field.envelope_type(), "R");
477 assert_eq!(field.envelope_format(), "D");
478 assert_eq!(field.envelope_identifier(), "remittance-2024-001");
479 }
480
481 #[test]
482 fn test_field77t_invalid_envelope_type() {
483 let result = Field77T::new("", "D", "REMITTANCE-2024-001");
484 assert!(result.is_err());
485
486 let result = Field77T::new("AB", "D", "REMITTANCE-2024-001");
487 assert!(result.is_err());
488
489 let result = Field77T::new("1", "D", "REMITTANCE-2024-001");
490 assert!(result.is_err());
491 }
492
493 #[test]
494 fn test_field77t_invalid_envelope_format() {
495 let result = Field77T::new("R", "", "REMITTANCE-2024-001");
496 assert!(result.is_err());
497
498 let result = Field77T::new("R", "AB", "REMITTANCE-2024-001");
499 assert!(result.is_err());
500
501 let result = Field77T::new("R", "1", "REMITTANCE-2024-001");
502 assert!(result.is_err());
503 }
504
505 #[test]
506 fn test_field77t_invalid_identifier() {
507 let result = Field77T::new("R", "D", "");
508 assert!(result.is_err());
509
510 let result = Field77T::new("R", "D", "a".repeat(36));
511 assert!(result.is_err());
512 }
513
514 #[test]
515 fn test_field77t_invalid_format() {
516 let result = Field77T::parse("RD");
517 assert!(result.is_err());
518
519 let result = Field77T::parse("R/IDENTIFIER");
520 assert!(result.is_err());
521
522 let result = Field77T::parse("RDIDENTIFIER");
523 assert!(result.is_err());
524 }
525
526 #[test]
527 fn test_field77t_to_swift_string() {
528 let field = Field77T::new("R", "D", "REMITTANCE-2024-001234567890").unwrap();
529 assert_eq!(
530 field.to_swift_string(),
531 ":77T:RD/REMITTANCE-2024-001234567890"
532 );
533
534 let field = Field77T::new("S", "S", "SUPP-INFO-2024-03-15-001").unwrap();
535 assert_eq!(field.to_swift_string(), ":77T:SS/SUPP-INFO-2024-03-15-001");
536 }
537
538 #[test]
539 fn test_field77t_validation() {
540 let field = Field77T::new("R", "D", "REMITTANCE-2024-001234567890").unwrap();
541 let result = field.validate();
542 assert!(result.is_valid);
543
544 let invalid_field = Field77T {
545 envelope_type: "".to_string(),
546 envelope_format: "D".to_string(),
547 envelope_identifier: "REMITTANCE-2024-001".to_string(),
548 };
549 let result = invalid_field.validate();
550 assert!(!result.is_valid);
551 }
552
553 #[test]
554 fn test_field77t_type_checks() {
555 let remittance_field = Field77T::new("R", "D", "REMITTANCE-2024-001").unwrap();
556 assert!(remittance_field.is_remittance_envelope());
557 assert!(!remittance_field.is_supplementary_envelope());
558 assert!(!remittance_field.is_trade_envelope());
559
560 let supp_field = Field77T::new("S", "S", "SUPP-INFO-2024-001").unwrap();
561 assert!(!supp_field.is_remittance_envelope());
562 assert!(supp_field.is_supplementary_envelope());
563 assert!(!supp_field.is_trade_envelope());
564
565 let trade_field = Field77T::new("T", "C", "TRADE-LC-2024-001").unwrap();
566 assert!(!trade_field.is_remittance_envelope());
567 assert!(!trade_field.is_supplementary_envelope());
568 assert!(trade_field.is_trade_envelope());
569 }
570
571 #[test]
572 fn test_field77t_format_checks() {
573 let detailed_field = Field77T::new("R", "D", "REMITTANCE-2024-001").unwrap();
574 assert!(detailed_field.is_detailed_format());
575 assert!(!detailed_field.is_summary_format());
576 assert!(!detailed_field.is_custom_format());
577
578 let summary_field = Field77T::new("S", "S", "SUPP-INFO-2024-001").unwrap();
579 assert!(!summary_field.is_detailed_format());
580 assert!(summary_field.is_summary_format());
581 assert!(!summary_field.is_custom_format());
582
583 let custom_field = Field77T::new("T", "C", "TRADE-LC-2024-001").unwrap();
584 assert!(!custom_field.is_detailed_format());
585 assert!(!custom_field.is_summary_format());
586 assert!(custom_field.is_custom_format());
587 }
588
589 #[test]
590 fn test_field77t_compliance_checks() {
591 let field = Field77T::new("R", "D", "REMITTANCE-2024-001").unwrap();
592 assert!(field.is_required_for_remit());
593 assert!(!field.is_allowed_in_core_stp());
594 }
595
596 #[test]
597 fn test_field77t_description() {
598 let field = Field77T::new("R", "D", "REMITTANCE-2024-001234567890").unwrap();
599 let description = field.description();
600 assert!(description.contains("Remittance Information"));
601 assert!(description.contains("Detailed Format"));
602 assert!(description.contains("REMITTANCE-2024-001234567890"));
603 }
604
605 #[test]
606 fn test_field77t_display() {
607 let field = Field77T::new("R", "D", "REMITTANCE-2024-001234567890").unwrap();
608 assert_eq!(format!("{}", field), "RD/REMITTANCE-2024-001234567890");
609
610 let field = Field77T::new("S", "S", "SUPP-INFO-2024-03-15-001").unwrap();
611 assert_eq!(format!("{}", field), "SS/SUPP-INFO-2024-03-15-001");
612 }
613
614 #[test]
615 fn test_field77t_format_spec() {
616 assert_eq!(Field77T::format_spec(), "1!a1!a/35x");
617 }
618
619 #[test]
620 fn test_field77t_real_world_examples() {
621 let remittance = Field77T::new("R", "D", "REMITTANCE-2024-001234567890").unwrap();
623 assert_eq!(
624 remittance.to_swift_string(),
625 ":77T:RD/REMITTANCE-2024-001234567890"
626 );
627 assert!(remittance.is_remittance_envelope());
628 assert!(remittance.is_detailed_format());
629
630 let invoice = Field77T::new("R", "D", "INV-2024-001234-PAYMENT-REF").unwrap();
632 assert_eq!(
633 invoice.to_swift_string(),
634 ":77T:RD/INV-2024-001234-PAYMENT-REF"
635 );
636
637 let trade = Field77T::new("T", "C", "TRADE-LC-2024-567890123").unwrap();
639 assert_eq!(trade.to_swift_string(), ":77T:TC/TRADE-LC-2024-567890123");
640 assert!(trade.is_trade_envelope());
641 assert!(trade.is_custom_format());
642
643 let supp = Field77T::new("S", "S", "SUPP-INFO-2024-03-15-001").unwrap();
645 assert_eq!(supp.to_swift_string(), ":77T:SS/SUPP-INFO-2024-03-15-001");
646 assert!(supp.is_supplementary_envelope());
647 assert!(supp.is_summary_format());
648 }
649}