Skip to content

Commit

Permalink
Dev (#29)
Browse files Browse the repository at this point in the history
* `CurrencyCodes.Currency` is  record now
* `EuVatId.Syntax` is frozen now
* `(I)ValidationInfo.Seen` is a hash set now
* `ObjectValidationEventArgs.Seen` is a hash set now
* `ReflectionHelper` stores frozen sets now
* `ValidatableTypes` stores hash sets now
* By-ref and by-ref-like properties will be skipped
+ Updated references
+ `CurrencyCodes.Currency.Factor/Validate` are virtual now
+ Added `(Item)HostAttribute`
  • Loading branch information
nd1012 authored Aug 25, 2024
1 parent a7ef170 commit a818eb8
Show file tree
Hide file tree
Showing 31 changed files with 313 additions and 172 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ enumerable lengths
- XRechnung routing validation
- European VAT ID validation
- XML validation
- Hostname or IP address validation
- Conditional value requirements
- Enumeration value validation
- Validation references
Expand Down Expand Up @@ -81,6 +82,7 @@ The **ObjectValidation-CountryValidator** extension is licensed using the
| XRechnung routing validation | `XRechnungRouteAttribute` |
| European VAT ID validation | `EuVatIdAttribute` |
| XML validation | `XmlAttribute` |
| Hostname or IP address validation | `HostAttribute` |
| Conditional value requirement | `RequiredIfAttribute` |
| Allowed/denied values | `AllowedValuesAttribute`, `DeniedValuesAttribute` |
| Enumeration value | (none - using the type) |
Expand Down Expand Up @@ -298,6 +300,7 @@ These item validation adapters exist:
| `CompareAttribute` | `ItemCompareAttribute` |
| `CreditCardAttribute` | `ItemCreditCardAttribute` |
| `EmailAddressAttribute` | `ItemEmailAddressAttribute` |
| `HostAttribute` | `ItemHostAttribute` |
| `MaxLengthAttribute` | `ItemMaxLengthAttribute` |
| `MinLengthAttribute` | `ItemMinLengthAttribute` |
| `NoValidationAttribute` | `ItemNoValidationAttribute` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<PackageId>ObjectValidation-CountryValidator</PackageId>
<Title>ObjectValidation-CountryValidator</Title>
<Version>2.3.0</Version>
<Version>2.4.0</Version>
<Authors>nd1012</Authors>
<Company>Andreas Zimmermann, wan24.de</Company>
<Product>ObjectValidation</Product>
Expand All @@ -29,7 +29,7 @@
<ItemGroup>
<PackageReference Include="CountryValidator" Version="1.1.3" />
<PackageReference Include="CountryValidator.DataAnnotations" Version="1.1.3" />
<PackageReference Include="ObjectValidation" Version="2.5.0" />
<PackageReference Include="ObjectValidation" Version="2.6.0" />
</ItemGroup>

<ItemGroup>
Expand Down
9 changes: 9 additions & 0 deletions src/ObjectValidation Tests/A_Initialization.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace ObjectValidation_Tests
{
[TestClass]
public class A_Initialization
{
[AssemblyInitialize]
public static void Init(TestContext tc) => wan24.Tests.TestsInitialization.Init(tc);
}
}
3 changes: 2 additions & 1 deletion src/ObjectValidation Tests/Aba_Tests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using wan24.ObjectValidation;
using wan24.Tests;

namespace ObjectValidation_Tests
{
[TestClass]
public class Aba_Tests
public class Aba_Tests : TestBase
{
[TestMethod]
public void AbaRtn_Tests()
Expand Down
3 changes: 2 additions & 1 deletion src/ObjectValidation Tests/EuVatId_Tests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using wan24.ObjectValidation;
using wan24.Tests;

namespace ObjectValidation_Tests
{
[TestClass]
public class EuVatId_Tests
public class EuVatId_Tests : TestBase
{
[TestMethod]
public void VatId_Tests()
Expand Down
1 change: 1 addition & 0 deletions src/ObjectValidation Tests/InvalidTestObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public InvalidTestObject() : base()
RequiredIfConditional = true;
EnumProperty = TestEnum.Invalid;
Enum2Property = (TestEnum)3;
HostProperty = "test 123";
}
}
}
3 changes: 2 additions & 1 deletion src/ObjectValidation Tests/Luhn_Tests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using wan24.ObjectValidation;
using wan24.Tests;

namespace ObjectValidation_Tests
{
[TestClass]
public class Luhn_Tests
public class Luhn_Tests : TestBase
{
[TestMethod]
public void Luhn_Checksum_Tests()
Expand Down
7 changes: 4 additions & 3 deletions src/ObjectValidation Tests/ObjectValidation Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.4.3" />
<PackageReference Include="MSTest.TestFramework" Version="3.4.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.5.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.5.2" />
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="wan24-Tests" Version="1.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
4 changes: 3 additions & 1 deletion src/ObjectValidation Tests/ObjectValidation_Tests.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using System.ComponentModel.DataAnnotations;
using wan24.ObjectValidation;
using wan24.Tests;

namespace ObjectValidation_Tests
{
[TestClass]
public class ObjectValidation_Tests
public class ObjectValidation_Tests : TestBase
{
public ObjectValidation_Tests()
{
Expand Down Expand Up @@ -84,6 +85,7 @@ from name in res.MemberNames
Assert.IsTrue(failedMembers.Contains(nameof(ValidTestObject.RequiredProperty)));
Assert.IsTrue(failedMembers.Contains(nameof(ValidTestObject.EnumProperty)));
Assert.IsTrue(failedMembers.Contains(nameof(ValidTestObject.Enum2Property)));
Assert.IsTrue(failedMembers.Contains(nameof(ValidTestObject.HostProperty)));
Assert.AreEqual(54, results.Count);

// Validation exception
Expand Down
3 changes: 2 additions & 1 deletion src/ObjectValidation Tests/Swift_Tests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using wan24.ObjectValidation;
using wan24.Tests;

namespace ObjectValidation_Tests
{
[TestClass]
public class Swift_Tests
public class Swift_Tests : TestBase
{
[TestMethod]
public void Bic_Tests()
Expand Down
3 changes: 3 additions & 0 deletions src/ObjectValidation Tests/ValidTestObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ public class ValidTestObject : IValidatableObject

public TestEnum Enum2Property { get; set; } = TestEnum.Valid;

[Host(CheckIfExists = true)]
public string HostProperty { get; set; } = "localhost";

IEnumerable<ValidationResult> IValidatableObject.Validate(ValidationContext validationContext)
{
List<ValidationResult> results = new();
Expand Down
3 changes: 2 additions & 1 deletion src/ObjectValidation Tests/XRechnung_Tests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using wan24.ObjectValidation;
using wan24.Tests;

namespace ObjectValidation_Tests
{
[TestClass]
public class XRechnung_Tests
public class XRechnung_Tests : TestBase
{
[TestMethod]
public void XRechnung_Route_Tests()
Expand Down
10 changes: 4 additions & 6 deletions src/ObjectValidation/CountryAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ namespace wan24.ObjectValidation
/// <summary>
/// Country code validation attribute
/// </summary>
public class CountryAttribute : ValidationAttributeBase
/// <remarks>
/// Constructor
/// </remarks>
public class CountryAttribute() : ValidationAttributeBase()
{
/// <summary>
/// Constructor
/// </summary>
public CountryAttribute() : base() { }

/// <inheritdoc/>
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
Expand Down
4 changes: 3 additions & 1 deletion src/ObjectValidation/CountryCodes.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace wan24.ObjectValidation
using System.Collections.Frozen;

namespace wan24.ObjectValidation
{
/// <summary>
/// Country ISO 3166-1 alpha-2 codes
Expand Down
10 changes: 6 additions & 4 deletions src/ObjectValidation/CurrencyCodes.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace wan24.ObjectValidation
using System.Collections.Frozen;

namespace wan24.ObjectValidation
{
/// <summary>
/// Currency ISO 4217 codes
Expand Down Expand Up @@ -298,7 +300,7 @@ public static class CurrencyCodes
/// <param name="numericCode">Numeric code</param>
/// <param name="name">Name</param>
/// <param name="minorUnit">Minor unit</param>
public class Currency(string code, string numericCode, string name, int minorUnit = 2)
public record class Currency(string code, string numericCode, string name, int minorUnit = 2)
{
/// <summary>
/// Factor
Expand Down Expand Up @@ -328,14 +330,14 @@ public class Currency(string code, string numericCode, string name, int minorUni
/// <summary>
/// Factor
/// </summary>
public int Factor => _Factor ??= (int)Math.Pow(10, MinorUnit);
public virtual int Factor => _Factor ??= (int)Math.Pow(10, MinorUnit);

/// <summary>
/// Validate a value
/// </summary>
/// <param name="value">Value</param>
/// <returns>Valid?</returns>
public bool Validate(decimal value) => Factor < 1 ? Math.Round(value) == value : Math.Round(value * Factor) / Factor == value;
public virtual bool Validate(decimal value) => Factor < 1 ? Math.Round(value) == value : Math.Round(value * Factor) / Factor == value;
}
}
}
7 changes: 4 additions & 3 deletions src/ObjectValidation/EuVatId.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.RegularExpressions;
using System.Collections.Frozen;
using System.Text.RegularExpressions;

namespace wan24.ObjectValidation
{
Expand All @@ -15,7 +16,7 @@ public static partial class EuVatId
/// <summary>
/// VAT ID syntax regular expressions
/// </summary>
private static readonly Dictionary<string, Regex> Syntax = new()
private static readonly FrozenDictionary<string, Regex> Syntax = new Dictionary<string, Regex>()
{
{"AT", AT_Generated()},
{"BE", BE_Generated()},
Expand Down Expand Up @@ -45,7 +46,7 @@ public static partial class EuVatId
{"SI", SI_Generated()},
{"SK", SK_Generated()},
{"XI", XI_Generated()}// North Ireland, since Brexit
};
}.ToFrozenDictionary();

/// <summary>
/// Validate a European VAT ID
Expand Down
86 changes: 86 additions & 0 deletions src/ObjectValidation/HostAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System.ComponentModel.DataAnnotations;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Security.Cryptography;

namespace wan24.ObjectValidation
{
/// <summary>
/// Host name or IP address validation attribute
/// </summary>
/// <remarks>
/// Constructor
/// </remarks>
public class HostAttribute() : ValidationAttributeBase()
{
/// <summary>
/// If IPv4 addresses are allowed
/// </summary>
public bool AllowIPv4 { get; set; } = true;

/// <summary>
/// If IPv6 addresses are allowed
/// </summary>
public bool AllowIPv6 { get; set; } = true;

/// <summary>
/// Check if the hostname (DNS lookup) or IP address (ICMP) exists
/// </summary>
public bool CheckIfExists { get; set; }

/// <summary>
/// Ping timeout in ms
/// </summary>
public int PingTimeout { get; set; } = 300;

/// <inheritdoc/>
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
if (value is null) return null;
if (value is not string str) return this.CreateValidationResult($"Hostname or IP address value as {typeof(string)} expected", validationContext);
UriHostNameType type = Uri.CheckHostName(str);
IPAddress? ip;
switch (type)
{
case UriHostNameType.Dns:
if (!CheckIfExists) return null;
try
{
Dns.GetHostEntry(str);
return null;
}
catch (Exception ex)
{
return this.CreateValidationResult($"Hostname DNS lookup failed: {ex.Message ?? ex.GetType().ToString()}", validationContext);
}
case UriHostNameType.IPv4:
if(!IPAddress.TryParse(str, out ip)) return this.CreateValidationResult($"Host IPv4 address parsing failed", validationContext);
if (ip.AddressFamily != AddressFamily.InterNetwork) return this.CreateValidationResult($"Detected host IPv4 address parsed to IPv6", validationContext);
break;
case UriHostNameType.IPv6:
if (!IPAddress.TryParse(str, out ip)) return this.CreateValidationResult($"Host IPv6 address parsing failed", validationContext);
if (ip.AddressFamily != AddressFamily.InterNetworkV6) return this.CreateValidationResult($"Detected host IPv6 address parsed to IPv4", validationContext);
break;
default:
return this.CreateValidationResult($"Hostname or IP address value invalid ({type})", validationContext);
}
if (!CheckIfExists) return null;
using Ping ping = new();
try
{
PingReply pong = ping.Send(ip, PingTimeout, RandomNumberGenerator.GetBytes(count: 32), new()
{
DontFragment = true
});
return pong.Status == IPStatus.Success
? null
: this.CreateValidationResult($"Ping to {ip} failed: {pong.Status}", validationContext);
}
catch(Exception ex)
{
return this.CreateValidationResult($"Ping to {ip} failed exceptional: {ex.Message ?? ex.GetType().ToString()}", validationContext);
}
}
}
}
2 changes: 1 addition & 1 deletion src/ObjectValidation/IValidationInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public interface IValidationInfo
/// <summary>
/// Seen objects
/// </summary>
List<object> Seen { get; }
HashSet<object> Seen { get; }
/// <summary>
/// Current validation depth
/// </summary>
Expand Down
48 changes: 48 additions & 0 deletions src/ObjectValidation/ItemHostAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
namespace wan24.ObjectValidation
{
/// <summary>
/// Host name or IP address validation attribute
/// </summary>
/// <remarks>
/// Constructor
/// </remarks>
/// <param name="target">Validation target</param>
public class ItemHostAttribute(ItemValidationTargets target = ItemValidationTargets.Item) : ItemValidationAttribute(target, new HostAttribute())
{
/// <summary>
/// If IPv4 addresses are allowed
/// </summary>
public bool AllowIPv4
{
get => ((HostAttribute)ValidationAttribute).AllowIPv4;
set => ((HostAttribute)ValidationAttribute).AllowIPv4 = value;
}

/// <summary>
/// If IPv6 addresses are allowed
/// </summary>
public bool AllowIPv6
{
get => ((HostAttribute)ValidationAttribute).AllowIPv6;
set => ((HostAttribute)ValidationAttribute).AllowIPv6 = value;
}

/// <summary>
/// Check if the hostname (DNS lookup) or IP address (ICMP) exists
/// </summary>
public bool CheckIfExists
{
get => ((HostAttribute)ValidationAttribute).CheckIfExists;
set => ((HostAttribute)ValidationAttribute).CheckIfExists = value;
}

/// <summary>
/// Ping timeout in ms
/// </summary>
public int PingTimeout
{
get => ((HostAttribute)ValidationAttribute).PingTimeout;
set => ((HostAttribute)ValidationAttribute).PingTimeout = value;
}
}
}
Loading

0 comments on commit a818eb8

Please sign in to comment.