diff --git a/src/SharedKernel/Helpers/PhoneUtil.cs b/src/SharedKernel/Helpers/PhoneUtil.cs new file mode 100644 index 0000000..4961809 --- /dev/null +++ b/src/SharedKernel/Helpers/PhoneUtil.cs @@ -0,0 +1,115 @@ +using System.Text; + +namespace SharedKernel.Helpers; + +public static class PhoneUtil +{ + public static bool TryFormatArmenianMsisdn(string? input, out string? formatted) + { + formatted = null; + if (string.IsNullOrWhiteSpace(input)) + { + return false; + } + + var s = input.Trim(); + + // Special-case exact "(374)" prefix + if (s.StartsWith("(374)", StringComparison.Ordinal)) + { + var rest = DigitsOnly(s.AsSpan(5)); + if (rest.Length == 8) + { + formatted = "+374" + rest; + return true; + } + + formatted = input; + return false; + } + + // General tolerant parse: allow spaces, dashes, dots, parentheses; '+' only at start + var sb = new StringBuilder(s.Length); + for (var i = 0; i < s.Length; i++) + { + var c = s[i]; + if (c is >= '0' and <= '9') + { + sb.Append(c); + continue; + } + + if (i == 0 && c == '+') + { + continue; + } + + if (c == ' ' || c == '-' || c == '.' || c == '(' || c == ')' || char.IsWhiteSpace(c)) + { + continue; + } + + formatted = input; + return false; + } + + var digits = sb.ToString(); + var span = digits.AsSpan(); + ReadOnlySpan last8; + + var plusAtStart = s[0] == '+'; + if (plusAtStart) + { + if (span.Length == 11 && span.StartsWith("374".AsSpan())) + { + last8 = span[3..]; + } + else + { + formatted = input; + return false; + } + } + else + { + switch (span.Length) + { + case 11 when span.StartsWith("374".AsSpan()): + last8 = span[3..]; + break; + case 9 when span[0] == '0': + last8 = span[1..]; + break; + case 8: + last8 = span; + break; + default: + formatted = input; + return false; + } + } + + if (last8.Length != 8) + { + formatted = input; + return false; + } + + formatted = $"+374{last8.ToString()}"; + return true; + } + + private static string DigitsOnly(ReadOnlySpan span) + { + var sb = new StringBuilder(span.Length); + foreach (var c in span) + { + if (c is >= '0' and <= '9') + { + sb.Append(c); + } + } + + return sb.ToString(); + } +} \ No newline at end of file diff --git a/src/SharedKernel/SharedKernel.csproj b/src/SharedKernel/SharedKernel.csproj index 2ca0104..41f0bca 100644 --- a/src/SharedKernel/SharedKernel.csproj +++ b/src/SharedKernel/SharedKernel.csproj @@ -8,13 +8,13 @@ Readme.md Pandatech MIT - 1.6.4 + 1.6.5 Pandatech.SharedKernel Pandatech Shared Kernel Library Pandatech, shared kernel, library, OpenAPI, Swagger, utilities, scalar Pandatech.SharedKernel provides centralized configurations, utilities, and extensions for ASP.NET Core projects. For more information refere to readme.md document. https://github.com/PandaTechAM/be-lib-sharedkernel - Nuget updates + Phone util added diff --git a/test/SharedKernel.Tests/PhoneUtilTests.cs b/test/SharedKernel.Tests/PhoneUtilTests.cs new file mode 100644 index 0000000..0faca2f --- /dev/null +++ b/test/SharedKernel.Tests/PhoneUtilTests.cs @@ -0,0 +1,61 @@ +using SharedKernel.Helpers; + +namespace SharedKernel.Tests; + +public class PhoneUtilTests +{ + [Theory] + [InlineData("+37493910593", "+37493910593")] + [InlineData("37493910593", "+37493910593")] + [InlineData("093910593", "+37493910593")] + [InlineData("93910593", "+37493910593")] + [InlineData("(374)93910593", "+37493910593")] + // tolerant separators + [InlineData("+374 93 910 593", "+37493910593")] + [InlineData("374-93-910-593", "+37493910593")] + [InlineData("(374) 93 910 593", "+37493910593")] + public void Formats_Armenian_Numbers_When_Valid(string input, string expected) + { + var ok = PhoneUtil.TryFormatArmenianMsisdn(input, out var formatted); + Assert.True(ok); + Assert.Equal(expected, formatted); + } + + [Theory] + [InlineData("+12025550199")] // foreign + [InlineData("441234567890")] // foreign + [InlineData("+++37493910593")] // invalid + [InlineData("37493A10593")] // invalid char + [InlineData("(374)1234567")] // only 7 digits after prefix + [InlineData("123456789")] // 9 digits but not starting with 0 + public void Returns_Original_When_Not_Armenian(string input) + { + var ok = PhoneUtil.TryFormatArmenianMsisdn(input, out var formatted); + Assert.False(ok); + Assert.Equal(input, formatted); + } + + [Fact] + public void Empty_String_Returns_False_And_Null() + { + var ok = PhoneUtil.TryFormatArmenianMsisdn("", out var formatted); + Assert.False(ok); + Assert.Null(formatted); + } + + [Fact] + public void Whitespace_String_Returns_False_And_Null() + { + var ok = PhoneUtil.TryFormatArmenianMsisdn(" ", out var formatted); + Assert.False(ok); + Assert.Null(formatted); + } + + [Fact] + public void Null_Returns_False_And_Null() + { + var ok = PhoneUtil.TryFormatArmenianMsisdn(null, out var formatted); + Assert.False(ok); + Assert.Null(formatted); + } +} \ No newline at end of file