Skip to content

moov-io/wire20022

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

wire20022

License Go Report Card Go Reference

A comprehensive Go library for reading, writing, and validating Fedwire ISO 20022 messages. The primary purpose is to read and write Fedwire XML files, with idiomatic Go patterns and robust error handling.

Overview

wire20022 provides a complete wrapper around ISO 20022 message processing for Fedwire payments, built on top of generated structs from XSD schemas. The library is designed XML-first to simplify reading and writing Fedwire XML files through idiomatic Go interfaces, comprehensive validation, and detailed error reporting.

✨ Key Features

  • XML-First Design: Primary API uses ReadXML(), WriteXML(), and ParseXML() methods
  • Complete Message Support: Handles all major Fedwire ISO 20022 message types
  • Idiomatic Go Interfaces: Uses io.Reader/io.Writer for flexible XML processing
  • Modern Architecture: Generic processors and embedded structs for zero code duplication
  • Type-Safe Processing: Compile-time safety with proper error handling patterns
  • Comprehensive Validation: Field-level validation with detailed error reporting
  • Version Management: Support for multiple message versions with sensible defaults
  • Developer-Friendly: Clean APIs following Go conventions

πŸ—οΈ Architecture Highlights

  • Base Abstractions: Common functionality shared across all message types
  • Type-Safe Generics: Compile-time safety with no runtime overhead
  • Embedded Structs: Zero-cost composition for field patterns
  • Factory Patterns: Clean version management and extensibility

πŸ“‹ Supported Message Types

Message Type ISO Code Versions Description
CustomerCreditTransfer pacs.008 .001.02 - .001.12 Customer credit transfer initiation
PaymentReturn pacs.004 .001.02 - .001.12 Payment return
PaymentStatusRequest pacs.028 .001.01 - .001.05 Payment status request
FedwireFundsPaymentStatus pacs.002 .001.03 - .001.14 Payment status report
FedwireFundsSystemResponse admi.010 .001.01 System event acknowledgment
DrawdownRequest pain.013 .001.01 - .001.10 Creditor payment activation request
DrawdownResponse pain.014 .001.01 - .001.10 Creditor payment activation request status report
AccountReportingRequest camt.060 .001.01 - .001.06 Account reporting request
ActivityReport camt.086 .001.01 - .001.02 Intraday transaction query
EndpointDetailsReport camt.090 .001.01 - .001.02 Service availability acknowledgment
EndpointGapReport camt.087 .001.01 - .001.02 Request to modify payment
EndpointTotalsReport camt.089 .001.01 - .001.02 Payment status report
ReturnRequestResponse camt.029 .001.01 - .001.12 Resolution of investigation

πŸš€ Quick Start

Installation

go get github.com/wadearnold/wire20022

XML-First API Usage

package main

import (
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/moov-io/wire20022/pkg/models/CustomerCreditTransfer"
)

func main() {
	// Reading XML from file
	file, err := os.Open("payment.xml")
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	var message CustomerCreditTransfer.MessageModel
	if err := message.ReadXML(file); err != nil {
		log.Fatal("Failed to read XML:", err)
	}

	fmt.Printf("Message ID: %s\n", message.MessageId)
	fmt.Printf("Created: %s\n", message.CreatedDateTime.Format("2006-01-02 15:04:05"))

	// Writing XML to file with specific version
	outFile, err := os.Create("output.xml")
	if err != nil {
		log.Fatal(err)
	}
	defer outFile.Close()

	if err := message.WriteXML(outFile, CustomerCreditTransfer.PACS_008_001_10); err != nil {
		log.Fatal("Failed to write XML:", err)
	}

	// Parsing XML from bytes
	xmlData := []byte(`<?xml version="1.0"?>
	<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.008.001.08">
		<FIToFICstmrCdtTrf>
			<GrpHdr>
				<MsgId>MSG001</MsgId>
				<CreDtTm>2025-01-15T10:00:00</CreDtTm>
				<NbOfTxs>1</NbOfTxs>
			</GrpHdr>
		</FIToFICstmrCdtTrf>
	</Document>`)

	msg, err := CustomerCreditTransfer.ParseXML(xmlData)
	if err != nil {
		log.Fatal("Failed to parse XML:", err)
	}

	fmt.Printf("Parsed Message ID: %s\n", msg.MessageId)

	// Writing to any io.Writer (e.g., strings.Builder)
	var buf strings.Builder
	if err := msg.WriteXML(&buf); err != nil {
		log.Fatal("Failed to write XML:", err)
	}

	fmt.Printf("Generated XML (%d bytes)\n", len(buf.String()))
}

Core XML API Methods

Every message type provides these idiomatic Go methods:

// ReadXML reads XML data from any io.Reader into the MessageModel
func (m *MessageModel) ReadXML(r io.Reader) error

// WriteXML writes the MessageModel as XML to any io.Writer
// If no version is specified, uses the latest version
func (m *MessageModel) WriteXML(w io.Writer, version ...VERSION) error

// ParseXML reads XML data directly from bytes
func ParseXML(data []byte) (*MessageModel, error)

// DocumentWith creates a versioned ISO 20022 document
func DocumentWith(model MessageModel, version VERSION) (models.ISODocument, error)

// NewMessageForVersion creates a MessageModel with version-specific fields initialized
func NewMessageForVersion(version VERSION) MessageModel

// ValidateForVersion performs type-safe validation for a specific version
func (m MessageModel) ValidateForVersion(version VERSION) error

// GetVersionCapabilities returns which version-specific features are available
func (m MessageModel) GetVersionCapabilities() map[string]bool

Available Message Types

// Import message types for XML processing
import (
	"github.com/moov-io/wire20022/pkg/models/CustomerCreditTransfer"
	"github.com/moov-io/wire20022/pkg/models/PaymentReturn"
	"github.com/moov-io/wire20022/pkg/models/AccountReportingRequest"
	"github.com/moov-io/wire20022/pkg/models/DrawdownRequest"
	// ... other message types
)

// All message types support the same XML API:
var msg CustomerCreditTransfer.MessageModel
var payment PaymentReturn.MessageModel
var request AccountReportingRequest.MessageModel

// ReadXML, WriteXML, ParseXML available on all types
msg.ReadXML(reader)
payment.WriteXML(writer)
parsed, err := DrawdownRequest.ParseXML(xmlData)

🎯 Key Benefits

Type Safety

  • Compile-time validation: Wrong model/version combinations are caught at compile time
  • Enhanced error messages: All errors include message type context for better debugging
  • Zero runtime type assertions: Generics eliminate the need for type casting

Developer Experience

  • Consistent API: All message types share the same interface
  • Comprehensive documentation: Built-in help system with field descriptions
  • Clear error messages: Detailed validation feedback with field paths

Performance & Maintainability

  • 68% code reduction: Generic architecture eliminates duplication
  • Minimal overhead: ~3% performance cost for significant safety gains
  • Single point of maintenance: Centralized logic for all message types

πŸ”§ Advanced Usage

Working with Different Versions

processor := messages.NewCustomerCreditTransfer()

// Use different message versions
versions := []CustomerCreditTransfer.PACS_008_001_VERSION{
	CustomerCreditTransfer.PACS_008_001_08,
	CustomerCreditTransfer.PACS_008_001_09,
	CustomerCreditTransfer.PACS_008_001_10,
}

for _, version := range versions {
	xml, err := processor.CreateDocument(jsonData, version)
	// Handle each version...
}

Error Handling

processor := messages.NewActivityReport()

err := processor.ValidateDocument(invalidJSON, version)
if err != nil {
	// Enhanced error messages include message type context
	fmt.Printf("ActivityReport validation failed: %v\n", err)
	
	// Errors support Go 1.13+ unwrapping
	var validationErr *errors.ValidationError
	if errors.As(err, &validationErr) {
		fmt.Printf("Field: %s, Issue: %s\n", validationErr.Field, validationErr.Message)
	}
}

Field Documentation

processor := messages.NewAccountReportingRequest()

// Get comprehensive field documentation
helpJSON, err := processor.GetHelp()
if err != nil {
	log.Fatal(err)
}

// Parse the help JSON to display field information
var help map[string]interface{}
json.Unmarshal([]byte(helpJSON), &help)
fmt.Printf("Available fields: %+v\n", help)

Version Management and Advanced Usage

import "github.com/moov-io/wire20022/pkg/models/CustomerCreditTransfer"

func advancedExample() {
    // Use specific versions when writing XML
    var message CustomerCreditTransfer.MessageModel
    
    // Write with different versions
    versions := []CustomerCreditTransfer.PACS_008_001_VERSION{
        CustomerCreditTransfer.PACS_008_001_08,
        CustomerCreditTransfer.PACS_008_001_10,
        CustomerCreditTransfer.PACS_008_001_12, // Latest
    }
    
    for _, version := range versions {
        var buf strings.Builder
        if err := message.WriteXML(&buf, version); err != nil {
            log.Printf("Failed to write version %s: %v", version, err)
            continue
        }
        fmt.Printf("Generated XML for version %s (%d bytes)\n", version, len(buf.String()))
    }
    
    // Create message with version-specific fields
    message := CustomerCreditTransfer.NewMessageForVersion(CustomerCreditTransfer.PACS_008_001_12)
    
    // Validate for specific version
    if err := message.ValidateForVersion(CustomerCreditTransfer.PACS_008_001_12); err != nil {
        log.Printf("Validation failed: %v", err)
    }
    
    // Check version capabilities
    capabilities := message.GetVersionCapabilities()
    fmt.Printf("Version capabilities: %+v\n", capabilities)
    
    // Create ISO document for further processing
    doc, err := CustomerCreditTransfer.DocumentWith(message, CustomerCreditTransfer.PACS_008_001_12)
    if err != nil {
        log.Fatal("Failed to create document:", err)
    }
    
    // Document can be marshaled to XML using standard library
    xmlBytes, err := xml.MarshalIndent(doc, "", "  ")
    if err != nil {
        log.Fatal("Failed to marshal document:", err)
    }
    
    fmt.Printf("Full ISO 20022 document: %s\n", string(xmlBytes))
}

πŸ—οΈ Architecture

Package Structure

wire20022/
β”œβ”€β”€ pkg/
β”‚   β”œβ”€β”€ base/             # Core abstractions for XML-first processing
β”‚   β”‚   β”œβ”€β”€ message_header.go    # Common message structures  
β”‚   β”‚   β”œβ”€β”€ processor.go         # Generic XML message processor
β”‚   β”‚   β”œβ”€β”€ factory.go           # Versioned document factory
β”‚   β”‚   └── helpers.go           # Shared ElementHelper definitions
β”‚   β”œβ”€β”€ models/           # XML-first message type implementations
β”‚   β”‚   β”œβ”€β”€ CustomerCreditTransfer/
β”‚   β”‚   β”‚   β”œβ”€β”€ Message.go       # MessageModel with ReadXML/WriteXML
β”‚   β”‚   β”‚   β”œβ”€β”€ map.go           # XML field mappings
β”‚   β”‚   β”‚   └── swiftSample/     # Authoritative XML samples
β”‚   β”‚   β”œβ”€β”€ PaymentReturn/
β”‚   β”‚   β”œβ”€β”€ DrawdownRequest/
β”‚   β”‚   └── ...                  # All 16 supported message types
β”‚   β”œβ”€β”€ messages/         # Type-safe message processors (v1.0 API)
β”‚   β”œβ”€β”€ errors/           # Domain-specific error types
β”‚   └── fedwire/          # Common types and utilities
β”œβ”€β”€ cmd/wire20022/        # Command-line tools
└── internal/server/      # HTTP server implementation

XML-First Message Type Architecture

All message types follow a consistent XML-first architecture:

File Structure:

  • Message.go - MessageModel with ReadXML(), WriteXML(), ParseXML() methods
  • MessageHelper.go - Helper functions for message creation and validation
  • Message_test.go - Comprehensive test suite using sample XML files
  • map.go - XML to Go struct field mapping configuration
  • swiftSample/ - Authoritative XML sample files for validation and testing
  • version.go - Version constants and namespace definitions

Core Features:

  • XML-first API - Primary methods use io.Reader/io.Writer interfaces
  • Base abstractions - Common functionality (MessageHeader, PaymentCore, AgentPair)
  • Type-safe generics - Compile-time safety for XML processing
  • Version management - Support for multiple message versions with defaults
  • Factory patterns - Clean document creation and namespace handling
  • Embedded structs - Zero-cost composition eliminating code duplication

πŸ” Advanced Usage

WriteXML vs DocumentWith

The library provides two methods for XML generation, each serving different use cases:

WriteXML (Recommended for Most Use Cases)

// WriteXML is the primary method for XML serialization
// Use this for standard XML output to files, network connections, or buffers

file, _ := os.Create("payment.xml")
defer file.Close()
err := model.WriteXML(file, CustomerCreditTransfer.PACS_008_001_10)

// Features:
// - Writes complete XML with declaration
// - Handles formatting and indentation
// - Validates before writing
// - Direct output to any io.Writer

DocumentWith (Advanced Use Cases)

// DocumentWith creates a document structure for inspection/modification
// Use this when you need programmatic access to the document before serialization

doc, _ := CustomerCreditTransfer.DocumentWith(model, CustomerCreditTransfer.PACS_008_001_10)

// Use cases:
// - Inspect document structure before serialization
// - Integrate with other XML libraries
// - Custom validation at document level
// - Modify document before final output

// You can then marshal it yourself:
xmlBytes, _ := xml.MarshalIndent(doc, "", "  ")

When to use which:

  • WriteXML: 95% of use cases - direct XML file/stream generation
  • DocumentWith: Advanced scenarios requiring document manipulation

Error Handling

wire20022 implements idiomatic Go error handling with detailed error types:

// Reading XML with error handling
var message CustomerCreditTransfer.MessageModel
if err := message.ReadXML(reader); err != nil {
    // Handle specific error types
    var parseErr *errors.ParseError
    var validationErr *errors.ValidationError
    
    if errors.As(err, &parseErr) {
        fmt.Printf("Parse error in %s: %v\n", parseErr.Field, parseErr.Err)
    } else if errors.As(err, &validationErr) {
        fmt.Printf("Validation failed for %s: %s\n", validationErr.Field, validationErr.Reason)
    }
}

// Parsing XML with error handling
message, err := CustomerCreditTransfer.ParseXML(invalidXML)
if err != nil {
    fmt.Printf("Failed to parse XML: %v\n", err)
    return
}

// Writing XML with error handling
var buf strings.Builder
if err := message.WriteXML(&buf); err != nil {
    fmt.Printf("Failed to write XML: %v\n", err)
    return
}

Version-Specific Processing

// Handle different message versions when writing XML
var message CustomerCreditTransfer.MessageModel

versions := []CustomerCreditTransfer.PACS_008_001_VERSION{
    CustomerCreditTransfer.PACS_008_001_08,
    CustomerCreditTransfer.PACS_008_001_09,
    CustomerCreditTransfer.PACS_008_001_10,
    CustomerCreditTransfer.PACS_008_001_12, // Latest
}

for _, version := range versions {
    var buf strings.Builder
    if err := message.WriteXML(&buf, version); err != nil {
        fmt.Printf("Failed to write version %s: %v\n", version, err)
        continue // Try next version
    }
    
    fmt.Printf("Successfully created XML with version %s (%d bytes)\n", version, len(buf.String()))
    
    // Create ISO document for advanced processing
    document, err := CustomerCreditTransfer.DocumentWith(message, version)
    if err != nil {
        fmt.Printf("Failed to create document: %v\n", err)
        continue
    }
    
    fmt.Printf("Document created successfully for version %s\n", version)
    break
}

Field Mapping and Debugging

For debugging XML field mapping issues, consult XML_TO_GO_MAPPING.md:

// Understanding error paths
// XML: <CdtrPmtActvtnReq><GrpHdr><MsgId>
// Go:  CdtrPmtActvtnReq.GrpHdr.MsgId
// Error: "field copy CdtrPmtActvtnReq.GrpHdr.MsgId failed: ..."

πŸ§ͺ Development & Testing

Running Tests

# Run all tests with coverage
make check

# Run tests for specific message type
go test ./pkg/models/CustomerCreditTransfer

# Run with verbose output
go test -v ./pkg/models/CustomerCreditTransfer

# Generate coverage report
make cover-test
make cover-web

Building

# Build the library
make dist

# Build Docker image
make docker

# Clean build artifacts
make clean

Development Setup

# Start development environment
make setup

# Stop development environment
make teardown

πŸ“š Documentation

🀝 Contributing

We welcome contributions! This project uses modern Go practices and follows strict quality standards.

Current Needs

  • Test Coverage Expansion - Help us reach >90% coverage across all message types
  • New Message Types - Add support for additional ISO 20022 message types following our Implementation Guide
  • Performance Optimization - Optimize XML parsing and validation performance
  • Documentation - Improve examples and usage documentation
  • Real-World Testing - Test with actual Fedwire message samples
  • Error Testing - Expand test coverage for error handling scenarios and edge cases

Development Guidelines

  1. Always run make check before committing - This catches issues early
  2. Validate XML mappings - Use swiftSample/ directories as source of truth
  3. Follow idiomatic Go - Type safety, proper error handling, clear interfaces
  4. Add comprehensive tests - Cover both success and failure scenarios
  5. Update documentation - Keep README and mapping guides current

Getting Started

# Fork and clone the repository
git clone https://github.com/your-username/wire20022.git
cd wire20022

# Install dependencies
go mod download

# Run tests to ensure everything works
make check

# Make your changes and test
# ... your development work ...

# Verify before submitting
make check

Pull Request Process

  1. Create a feature branch from master
  2. Make your changes with tests
  3. Ensure make check passes
  4. Submit a pull request with clear description
  5. Respond to review feedback

πŸ“Š Performance

Architecture Benefits

The base abstractions architecture provides significant performance advantages:

  • Zero-cost abstractions: Embedded structs have no runtime overhead
  • Type safety: Generic processors eliminate interface{} boxing
  • Compile-time optimization: Dead code elimination for unused versions
  • Efficient field access: Direct struct access via embedding

Benchmarks

Current performance characteristics (run go test -bench=.):

  • XML Parsing: ~1000 messages/second for typical CustomerCreditTransfer
  • Validation: ~5000 validations/second
  • Memory Usage: ~500KB per message processing
  • Concurrent Processing: Thread-safe for read operations

Memory Management

The library is designed for efficient memory usage:

  • Minimal allocations during parsing
  • Reusable validation contexts
  • No reflection in hot paths (except validation)
  • Optional object pooling for high-throughput scenarios

πŸ“‹ Current Status

  • βœ… Architecture: All message types use consistent base abstractions
  • βœ… Core Functionality: Complete XML parsing and generation
  • βœ… Error Handling: Idiomatic Go error patterns with detailed context
  • βœ… Message Types: 16 message types supported with multiple versions
  • βœ… Validation: Comprehensive field validation with XML path reporting
  • βœ… Testing: Growing test suite with comprehensive coverage
  • πŸ”„ Performance: Optimized with zero-cost abstractions
  • πŸ“ Documentation: Complete implementation guides and architecture docs

πŸ› Issues & Support

πŸ“„ License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

πŸ™ Acknowledgments


πŸ“’ Ready to contribute? Start by reading our Implementation Guide to understand the architecture, then check out our good first issues or join the discussion in GitHub Discussions!

About

A go reader & writer for supporting fedwire iso20022

Resources

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 5