Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions Semantics.Strings.Identifiers/Iban.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace ktsu.Semantics.Strings.Identifiers;

using System.Linq;
using System.Text;

using ktsu.Semantics.Strings;

/// <summary>
Expand All @@ -12,12 +15,20 @@ namespace ktsu.Semantics.Strings.Identifiers;
[IsIban]
public sealed record Iban : SemanticString<Iban>
{
/// <summary>Normalizes the input by stripping spaces and uppercasing.</summary>
/// <summary>
/// Normalizes the input by stripping all whitespace (spaces, tabs, line breaks) and uppercasing.
/// </summary>
/// <param name="input">The raw input string.</param>
/// <returns>The canonical IBAN string.</returns>
protected override string MakeCanonical(string input)
{
Ensure.NotNull(input);
return input.Replace(" ", string.Empty).ToUpperInvariant();
StringBuilder canonical = new(input.Length);
foreach (char c in input.Where(static ch => !char.IsWhiteSpace(ch)))
{
canonical.Append(char.ToUpperInvariant(c));
}

return canonical.ToString();
}
}
2 changes: 1 addition & 1 deletion Semantics.Strings.Identifiers/IsIbanAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public sealed class IsIbanAttribute : NativeSemanticStringValidationAttribute

private sealed class IbanValidator : ValidationAdapter
{
private const string Pattern = "^[A-Z]{2}[0-9]{2}[A-Z0-9]+$";
private const string Pattern = "^[A-Z]{2}[0-9]{2}[A-Z0-9]+\\z";

protected override ValidationResult ValidateValue(string value)
{
Expand Down
2 changes: 1 addition & 1 deletion Semantics.Strings.Identifiers/IsUlidAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public sealed class IsUlidAttribute : NativeSemanticStringValidationAttribute

private sealed class UlidValidator : ValidationAdapter
{
private const string Pattern = "^[0-7][0-9A-HJKMNP-TV-Z]{25}$";
private const string Pattern = "^[0-7][0-9A-HJKMNP-TV-Z]{25}\\z";

protected override ValidationResult ValidateValue(string value) =>
Regex.IsMatch(value, Pattern, RegexOptions.None, TimeSpan.FromSeconds(1))
Expand Down
2 changes: 1 addition & 1 deletion Semantics.Strings.Identifiers/IsUuidAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public sealed class IsUuidAttribute : NativeSemanticStringValidationAttribute

private sealed class UuidValidator : ValidationAdapter
{
private const string Pattern = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$";
private const string Pattern = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\z";

protected override ValidationResult ValidateValue(string value) =>
Regex.IsMatch(value, Pattern, RegexOptions.None, TimeSpan.FromSeconds(1))
Expand Down
20 changes: 20 additions & 0 deletions Semantics.Test/Identifiers/IbanTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,24 @@ public void Create_Empty_Throws()
{
Assert.ThrowsExactly<ArgumentException>(() => Iban.Create(string.Empty));
}

[TestMethod]
[DataRow("GB67WEST12345698765432\n")]
[DataRow("GB67WEST12345698765432\r\n")]
public void Create_InvalidChecksumWithTrailingNewline_Throws(string input)
{
Assert.ThrowsExactly<ArgumentException>(() => Iban.Create(input));
}

[TestMethod]
[DataRow("GB82WEST12345698765432\n")]
[DataRow("GB82WEST12345698765432\t")]
[DataRow("\tGB82WEST12345698765432\r\n")]
[DataRow("GB82 WEST 1234 5698 7654 32\n")]
[DataRow("GB82\u00A0WEST\u00A012345698765432")]
public void Create_ValidIbanWithWhitespace_StripsItAndValidates(string input)
{
Iban iban = Iban.Create(input);
Assert.AreEqual("GB82WEST12345698765432", iban.WeakString);
}
}
16 changes: 16 additions & 0 deletions Semantics.Test/Identifiers/UlidTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace ktsu.Semantics.Test.Identifiers;

using ktsu.Semantics.Strings;
using ktsu.Semantics.Strings.Identifiers;

[TestClass]
Expand Down Expand Up @@ -44,4 +45,19 @@ public void Create_Empty_Throws()
{
Assert.ThrowsExactly<ArgumentException>(() => Ulid.Create(string.Empty));
}

// Ulid trims its input, which hides the attribute's own anchoring; a type that applies [IsUlid]
// without canonicalizing sees the raw value.
[IsUlid]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1812", Justification = "Used via generic type references")]
private sealed partial record UntrimmedUlid : SemanticString<UntrimmedUlid> { }

[TestMethod]
[DataRow("01ARZ3NDEKTSV4RRFFQ69G5FAV\n")]
[DataRow("01ARZ3NDEKTSV4RRFFQ69G5FAV\r\n")]
public void IsUlid_OnUntrimmedType_RejectsTrailingNewline(string input)
{
Assert.ThrowsExactly<ArgumentException>(() => SemanticString<UntrimmedUlid>.Create<UntrimmedUlid>(input));
Assert.AreEqual("01ARZ3NDEKTSV4RRFFQ69G5FAV", SemanticString<UntrimmedUlid>.Create<UntrimmedUlid>("01ARZ3NDEKTSV4RRFFQ69G5FAV").WeakString);
}
}
16 changes: 16 additions & 0 deletions Semantics.Test/Identifiers/UuidTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace ktsu.Semantics.Test.Identifiers;

using ktsu.Semantics.Strings;
using ktsu.Semantics.Strings.Identifiers;

[TestClass]
Expand Down Expand Up @@ -62,4 +63,19 @@ public void As_RoundTrip_PreservesValue()
Assert.AreEqual(uuid, roundTripped);
Assert.AreEqual(uuid.WeakString, roundTripped.WeakString);
}

// Uuid trims its input, which hides the attribute's own anchoring; a type that applies [IsUuid]
// without canonicalizing sees the raw value.
[IsUuid]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1812", Justification = "Used via generic type references")]
private sealed partial record UntrimmedUuid : SemanticString<UntrimmedUuid> { }

[TestMethod]
[DataRow("123e4567-e89b-12d3-a456-426614174000\n")]
[DataRow("123e4567-e89b-12d3-a456-426614174000\r\n")]
public void IsUuid_OnUntrimmedType_RejectsTrailingNewline(string input)
{
Assert.ThrowsExactly<ArgumentException>(() => SemanticString<UntrimmedUuid>.Create<UntrimmedUuid>(input));
Assert.AreEqual("123e4567-e89b-12d3-a456-426614174000", SemanticString<UntrimmedUuid>.Create<UntrimmedUuid>("123e4567-e89b-12d3-a456-426614174000").WeakString);
}
}
Loading