Files
iban-ex/docs/international_wide_ibans/README.md
2025-11-29 21:20:32 -05:00

12 KiB

IBAN Registry - Single Source of Truth

This directory contains the official SWIFT IBAN Registry data and tools to parse it into test fixtures for the IbanEx library.

🎯 Purpose

This is the single source of truth for IBAN validation rules, formats, and test data. All test cases should be derived from this official registry to ensure accuracy and compliance with international standards.

📁 Files

Source Data

  • iban-registry-100.txt - Official SWIFT IBAN Registry (Release 100) in TXT format

Processing Scripts

  • get_iban_registry.py - Original script to fetch from SWIFT website (requires network)
  • parse_local_registry.py - Recommended parser for local iban-registry-100.txt file

Generated Fixtures

  • iban_registry_full.json (88 KB) - Complete registry with all fields
  • iban_test_fixtures.json (81 KB) - Simplified fixtures for testing

🚀 Quick Start

Generate Test Fixtures

cd docs/international_wide_ibans
python3 parse_local_registry.py

Output:

✓ Parsed 89 records
✓ Processed 105 country codes
✓ Generated fixtures for 105 countries
✓ SEPA countries: 53
✓ Saved: iban_registry_full.json
✓ Saved: iban_test_fixtures.json

Use in Elixir Tests

# In your test setup
defmodule IbanEx.RegistryFixtures do
  @fixtures_path "docs/international_wide_ibans/iban_test_fixtures.json"
  
  @external_resource @fixtures_path
  @fixtures @fixtures_path
            |> File.read!()
            |> Jason.decode!()
  
  def all_valid_ibans do
    @fixtures["valid_ibans"]
    |> Enum.map(fn {_code, data} -> data["electronic"] end)
  end
  
  def valid_iban(country_code) do
    @fixtures["valid_ibans"][country_code]["electronic"]
  end
  
  def country_spec(country_code) do
    @fixtures["country_specs"][country_code]
  end
  
  def sepa_countries do
    @fixtures["country_specs"]
    |> Enum.filter(fn {_code, spec} -> spec["sepa"] end)
    |> Enum.map(fn {code, _spec} -> code end)
  end
end

📊 Registry Statistics

Coverage

  • Total Countries/Territories: 105
  • SEPA Countries: 53
  • Non-SEPA Countries: 52

IBAN Length Distribution

Length Count Example Countries
15 1 Norway (NO) - Shortest
16 1 Belgium (BE)
18 8 Denmark (DK), Finland (FI), Greenland (GL), Faroe Islands (FO)
19 2 Mongolia (MN), Slovakia (SK)
20 8 Austria (AT), Estonia (EE), Kosovo (XK)
21 4 Switzerland (CH), Croatia (HR), Latvia (LV), Lithuania (LT)
22 13 Germany (DE), Bulgaria (BG), Georgia (GE), Bahrain (BH)
23 7 UAE (AE), Israel (IL), Iraq (IQ), Iceland (IS), Qatar (QA), El Salvador (SV)
24 11 Andorra (AD), Czech Republic (CZ), Spain (ES), Poland (PL), Romania (RO), San Marino (SM)
25 3 Libya (LY), Portugal (PT), Serbia (RS)
26 2 Italy (IT), Yemen (YE)
27 20 France (FR), Greece (GR), Burundi (BI), many territories
28 12 Albania (AL), Azerbaijan (AZ), Cyprus (CY), Dominican Republic (DO), Nicaragua (NI)
29 5 Brazil (BR), Egypt (EG), Pakistan (PK), Qatar (QA)
30 4 Jordan (JO), Kuwait (KW), Mauritius (MU)
31 2 Malta (MT), Sweden (SE)
32 1 Saint Lucia (LC)
33 1 Russia (RU) - Longest

Special Characteristics

Shortest IBAN:

  • NO (Norway) - 15 characters
  • Example: NO9386011117947

Longest IBAN:

  • RU (Russian Federation) - 33 characters
  • Example: RU0304452522540817810538091310419

SEPA Countries Include Territories:

  • FR (France): GF, GP, MQ, YT, RE, PM, BL, MF
  • GB (United Kingdom): IM, JE, GG
  • FI (Finland): AX (Åland Islands)
  • PT (Portugal): Azores, Madeira
  • ES (Spain): AX (listed separately)

📋 Data Structure

Valid IBANs (iban_test_fixtures.json)

{
  "valid_ibans": {
    "DE": {
      "electronic": "DE89370400440532013000",
      "print": "DE89 3704 0044 0532 0130 00",
      "country_name": "Germany"
    }
  },
  "country_specs": {
    "DE": {
      "country_name": "Germany",
      "iban_length": 22,
      "bban_length": 18,
      "iban_spec": "DE2!n8!n10!n",
      "bban_spec": "8!n10!n",
      "sepa": true,
      "positions": {
        "bank_code": {
          "start": 0,
          "end": 8,
          "pattern": "8!n",
          "example": "37040044"
        },
        "branch_code": {
          "start": 8,
          "end": 8,
          "pattern": "",
          "example": ""
        },
        "account_code": {
          "start": 8,
          "end": 18,
          "example": "0532013000"
        }
      },
      "effective_date": "Jul-07"
    }
  },
  "metadata": {
    "total_countries": 105,
    "sepa_countries": 53,
    "source": "SWIFT IBAN Registry",
    "format_version": "TXT Release 100"
  }
}

Full Registry (iban_registry_full.json)

Contains additional fields:

  • other_territories: List of territory codes covered
  • parent_country: For territories, reference to parent country
  • Complete BBAN structure specifications
  • All position information with patterns and examples

🔍 Pattern Specifications

IBAN and BBAN structures use these format codes:

  • n - Numeric digits (0-9)
  • a - Uppercase alphabetic letters (A-Z)
  • c - Alphanumeric characters (A-Z, 0-9)
  • ! - Fixed length indicator
  • Number - Length of the field

Examples:

  • DE2!n8!n10!n = DE + 2 check digits + 8 numeric (bank) + 10 numeric (account)
  • FR2!n5!n5!n11!c2!n = FR + 2 check digits + 5n (bank) + 5n (branch) + 11c (account) + 2n (check)

Test Coverage Validation

Using Registry for Comprehensive Tests

defmodule IbanEx.RegistryValidationTest do
  use ExUnit.Case, async: true
  
  @fixtures "docs/international_wide_ibans/iban_test_fixtures.json"
            |> File.read!()
            |> Jason.decode!()
  
  describe "validate against official SWIFT registry" do
    test "all registry IBANs parse successfully" do
      for {code, data} <- @fixtures["valid_ibans"] do
        iban = data["electronic"]
        
        assert {:ok, parsed} = IbanEx.Parser.parse(iban),
               "Failed to parse official IBAN for #{code}: #{iban}"
        
        assert parsed.country_code == code
      end
    end
    
    test "all registry IBANs pass validation" do
      for {_code, data} <- @fixtures["valid_ibans"] do
        iban = data["electronic"]
        
        assert {:ok, _} = IbanEx.Validator.validate(iban),
               "Validation failed for official IBAN: #{iban}"
      end
    end
    
    test "all registry IBANs have correct length" do
      for {code, data} <- @fixtures["valid_ibans"] do
        iban = data["electronic"]
        spec = @fixtures["country_specs"][code]
        
        assert String.length(iban) == spec["iban_length"],
               "Length mismatch for #{code}: expected #{spec["iban_length"]}, got #{String.length(iban)}"
      end
    end
    
    test "SEPA countries match registry" do
      sepa_countries = @fixtures["country_specs"]
                      |> Enum.filter(fn {_code, spec} -> spec["sepa"] end)
                      |> Enum.map(fn {code, _} -> code end)
                      |> MapSet.new()
      
      # Compare with your implementation
      our_sepa = IbanEx.Country.sepa_countries() |> MapSet.new()
      
      assert MapSet.equal?(sepa_countries, our_sepa),
             "SEPA country mismatch. Missing: #{inspect(MapSet.difference(sepa_countries, our_sepa))}, Extra: #{inspect(MapSet.difference(our_sepa, sepa_countries))}"
    end
  end
end

Regression Test Generation

defmodule IbanEx.RegistryRegressionTest do
  use ExUnit.Case, async: true
  
  @fixtures "docs/international_wide_ibans/iban_test_fixtures.json"
            |> File.read!()
            |> Jason.decode!()
  
  for {code, data} <- @fixtures["valid_ibans"] do
    @tag :registry
    test "#{code} - #{data["country_name"]}: parses and validates" do
      iban = unquote(data["electronic"])
      
      # Parse
      assert {:ok, parsed} = IbanEx.Parser.parse(iban)
      assert parsed.country_code == unquote(code)
      
      # Validate
      assert {:ok, _} = IbanEx.Validator.validate(iban)
      
      # Round-trip
      formatted = IbanEx.Formatter.compact(parsed)
      assert formatted == iban
    end
  end
end

🔄 Updating the Registry

When to Update

  • SWIFT releases new IBAN Registry version
  • New countries added to IBAN system
  • Existing country specifications change
  • SEPA membership changes

Update Process

  1. Download latest registry:

  2. Update references:

    mv iban-registry-XXX.txt iban-registry-100.txt  # Update to new version
    
  3. Regenerate fixtures:

    python3 parse_local_registry.py
    
  4. Run regression tests:

    mix test --only registry
    
  5. Review changes:

    git diff iban_test_fixtures.json
    
  6. Update IbanEx country modules if needed:

    • Compare new specs with existing lib/iban_ex/country/*.ex files
    • Add new countries as needed
    • Update changed specifications

📖 References

Official Sources

Additional Resources

🧪 Test Data Best Practices

DO

  • Use registry data for valid IBAN examples
  • Generate invalid IBANs by mutating valid ones from registry
  • Test all countries from the registry
  • Verify IBAN length against registry specifications
  • Check SEPA status from registry metadata
  • Use official examples for documentation

DON'T

  • Hard-code IBAN examples without verifying against registry
  • Assume IBAN length is constant across countries
  • Skip testing edge cases (shortest: NO, longest: RU)
  • Ignore territory codes (they share parent country rules)
  • Test with outdated IBAN formats

🔧 Troubleshooting

Parser Issues

Problem: ValueError: max() arg is an empty sequence

  • Cause: File encoding mismatch or wrong line endings
  • Solution: Ensure file is Latin-1 encoded with CRLF endings

Problem: Missing country codes

  • Cause: Incorrect tab parsing
  • Solution: Verify \t separator and handle empty cells

Fixture Generation

Problem: Some countries missing in output

  • Cause: Empty country code or IBAN example
  • Solution: Check source file for completeness

Problem: Position ranges incorrect

  • Cause: Off-by-one error in range parsing
  • Solution: Verify 1-indexed to 0-indexed conversion

📝 License & Attribution

  • Source Data: © SWIFT - Society for Worldwide Interbank Financial Telecommunication
  • Usage: For validation and testing purposes in accordance with SWIFT standards
  • Parser Script: Open source, provided as-is

🤝 Contributing

When adding tests based on this registry:

  1. Reference the specific registry version used (e.g., "Release 100")
  2. Include the generation date in test documentation
  3. Link test cases to registry entries by country code
  4. Document any discrepancies between registry and implementation

Last Updated: 2025-01-29
Registry Version: Release 100
Total Countries: 105
Parser Version: 1.0.0