diff --git a/src/IbanNet/Iban.cs b/src/IbanNet/Iban.cs index 710d78e5..f7f465bf 100644 --- a/src/IbanNet/Iban.cs +++ b/src/IbanNet/Iban.cs @@ -18,6 +18,9 @@ namespace IbanNet; #endif public sealed class Iban : IEquatable, +#if NET7_0_OR_GREATER + IParsable, +#endif IFormattable { /// @@ -275,4 +278,52 @@ public override int GetHashCode() : _iban.Substring(structure.Position, structure.Length); #endif } + + /// + /// Converts the specified into an . + /// + /// The IBAN value to parse. + /// An if the is converted successfully. + /// Thrown when the specified is . + /// Thrown when the specified is not a valid IBAN. + public static Iban Parse(string s) => Parse(s, null); + + /// + /// Converts the specified into an . + /// + /// The IBAN value to parse. + /// An object that supplies culture-specific formatting information about . + /// An if the is converted successfully. + /// Thrown when the specified is . + /// Thrown when the specified is not a valid IBAN. +#pragma warning disable IDE0060 + public static Iban Parse(string s, IFormatProvider? provider) +#pragma warning restore IDE0060 + { + var parser = new IbanParser(IbanRegistry.Default); + return parser.Parse(s); + } + + /// + /// Tries to convert the specified into an . + /// + /// The IBAN value to parse. + /// The if the is converted successfully. + /// if the is converted successfully, or otherwise + public static bool TryParse(string? s, [NotNullWhen(true)] out Iban? result) => TryParse(s, null, out result); + + /// + /// Tries to convert the specified into an . + /// + /// The IBAN value to parse. + /// An object that supplies culture-specific formatting information about . + /// The if the is converted successfully. + /// if the is converted successfully, or otherwise +#pragma warning disable IDE0060 + public static bool TryParse(string? s, IFormatProvider? provider, [NotNullWhen(true)] out Iban? result) +#pragma warning restore IDE0060 + { + var parser = new IbanParser(IbanRegistry.Default); + return parser.TryParse(s, out result); + } } diff --git a/test/IbanNet.Tests/IbanTests.cs b/test/IbanNet.Tests/IbanTests.cs index e4975e09..adc32177 100644 --- a/test/IbanNet.Tests/IbanTests.cs +++ b/test/IbanNet.Tests/IbanTests.cs @@ -1,5 +1,6 @@ using IbanNet.Registry; using IbanNet.Registry.Swift; +using IbanNet.Validation.Results; using TestHelpers; namespace IbanNet; @@ -375,4 +376,113 @@ public void Given_that_json_iban_is_invalid_when_deserializing_it_should_throw() } } #endif + + public sealed class When_parsing_iban : IbanTests + { + [Fact] + public void With_null_value_should_throw() + { + string? value = null; + + // Act + Action act = () => Iban.Parse(value!); + + // Assert + act.Should() + .Throw("the provided value was null") + .WithParameterName(nameof(value)); + } + + [Fact] + public void With_invalid_value_should_throw() + { + // Act + Action act = () => Iban.Parse(TestValues.InvalidIban); + + // Assert + IbanFormatException ex = act.Should().Throw("the provided value was invalid").Which; + ex.Result.Should().BeEquivalentTo(new ValidationResult + { + Error = new IllegalCountryCodeCharactersResult(0), + AttemptedValue = TestValues.InvalidIban + }); + ex.InnerException.Should().BeNull(); + } + + [Theory] + [InlineData("NL91 ABNA 0417 1643 00")] + [InlineData("NL91\tABNA\t0417\t1643\t00")] + [InlineData(" NL91 ABNA041 716 4300 ")] + [InlineData("nl91 ABNA041716\t4300")] + public void Given_that_iban_contains_whitespace_or_lowercase_when_parsing_it_should_succeed(string iban) + { + const string expectedNormalizedIban = "NL91ABNA0417164300"; + + // Act + var actual = Iban.Parse(iban); + + // Assert + actual.ToString().Should().Be(expectedNormalizedIban); + } + + [Fact] + public void With_valid_value_should_return_iban() + { + Iban? iban = null; + + // Act + Action act = () => iban = Iban.Parse(TestValues.ValidIban); + + // Assert + act.Should().NotThrow(); + iban.Should() + .NotBeNull("the value should be parsed") + .And.BeOfType() + .Which.ToString() + .Should() + .Be(TestValues.ValidIban, "the returned value should match the provided value"); + } + } + + public sealed class When_trying_to_parse_iban : IbanTests + { + [Fact] + public void With_null_value_should_return_false() + { + // Act + bool actual = Iban.TryParse(null, out Iban? iban); + + // Assert + actual.Should().BeFalse("the provided value was null which is not valid"); + iban.Should().BeNull("parsing did not succeed"); + } + + [Fact] + public void With_invalid_value_should_return_false() + { + // Act + bool actual = Iban.TryParse(TestValues.InvalidIban, out Iban? iban); + + // Assert + actual.Should().BeFalse("the provided value was invalid"); + iban.Should().BeNull("parsing did not succeed"); + } + + [Fact] + public void With_valid_value_should_pass() + { + // Act + bool actual = Iban.TryParse(TestValues.ValidIban, out Iban? iban); + + // Assert + actual.Should().BeTrue("the provided value was valid"); + iban.Should() + .NotBeNull() + .And.BeOfType() + .Which.ToString() + .Should() + .Be(TestValues.ValidIban); + } + } + }