Skip to content

Commit db70522

Browse files
committed
Add SerializationTest
1 parent f28662a commit db70522

6 files changed

Lines changed: 152 additions & 132 deletions

File tree

src/plugins/NodaMoneyTest/CurrencyCode.cs

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
using NodaMoney;
22
using System.Collections.Frozen;
3-
using System.Diagnostics;
4-
using System.Reflection;
53

64
namespace NodaMoneyTest;
75

@@ -11,41 +9,30 @@ public static class CurrencyCode
119
#region Constants & Statics
1210

1311
// "¥"
14-
public static readonly string CNY = "CNY";
12+
public const string CNY = "CNY";
1513

1614
// "€"
17-
public static readonly string EUR = "EUR";
15+
public const string EUR = "EUR";
1816

1917
// "HK$"
20-
public static readonly string HKD = "HKD";
18+
public const string HKD = "HKD";
2119

2220
// "¥"
23-
public static readonly string JPY = "JPY";
21+
public const string JPY = "JPY";
2422

2523
// "MOP$"
26-
public static readonly string MOP = "MOP";
24+
public const string MOP = "MOP";
2725

2826
// "NT$"
29-
public static readonly string TWD = "TWD";
27+
public const string TWD = "TWD";
3028

3129
// "US$"
32-
public static readonly string USD = "USD";
30+
public const string USD = "USD";
3331

34-
static CurrencyCode()
35-
{
36-
var props = typeof(CurrencyCode).GetFields(BindingFlags.Static | BindingFlags.DeclaredOnly);
37-
38-
var list = new List<Currency>();
39-
foreach (var field in props)
40-
{
41-
var value = field.GetValue(null) as string;
42-
Debug.Assert(value is not null, $"{nameof(value)} is null.");
43-
44-
list.Add(Currency.FromCode(value));
45-
}
46-
47-
var dic = list.ToFrozenDictionary(static o => o.Code, o => o);
48-
}
32+
public static readonly FrozenDictionary<string, Currency> SupportedCurrency = typeof(CurrencyCode).GetFields()
33+
.Where(o => o.IsLiteral)
34+
.Select(o => Currency.FromCode((string)o.GetValue(null)!))
35+
.ToFrozenDictionary(static o => o.Code, o => o);
4936

5037
#endregion
5138

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using NodaMoney;
2+
3+
namespace NodaMoneyTest;
4+
5+
public class FastMoneyDto
6+
{
7+
8+
#region Properties
9+
10+
public decimal Amount { get; init; }
11+
12+
public string Currency { get; init; } = CurrencyCode.CNY;
13+
14+
#endregion
15+
16+
}
17+
18+
public static class FastMoneyExtensions
19+
{
20+
21+
#region Constants & Statics
22+
23+
public static FastMoneyDto ToDto(this FastMoney fastMoney)
24+
{
25+
return new FastMoneyDto { Amount = fastMoney.Amount, Currency = fastMoney.Currency.Code };
26+
}
27+
28+
#endregion
29+
30+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using NodaMoney;
2+
using System.ComponentModel.DataAnnotations;
3+
using System.Diagnostics.CodeAnalysis;
4+
using System.Globalization;
5+
6+
namespace NodaMoneyTest;
7+
8+
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
9+
public sealed class FastMoneyRangeAttribute : ValidationAttribute
10+
{
11+
[SuppressMessage("Design", "CA1019:Define accessors for attribute arguments", Justification = "<Pending>")]
12+
public FastMoneyRangeAttribute(string minimum, string maximum)
13+
{
14+
Minimum = decimal.Parse(minimum, CultureInfo.InvariantCulture);
15+
Maximum = decimal.Parse(maximum, CultureInfo.InvariantCulture);
16+
}
17+
18+
#region Properties
19+
20+
/// <summary>
21+
/// Gets the maximum allowed field value.
22+
/// </summary>
23+
public decimal Maximum { get; }
24+
25+
/// <summary>
26+
/// Specifies whether validation should fail for values that are equal to System.ComponentModel.DataAnnotations.RangeAttribute.Maximum.
27+
/// </summary>
28+
public bool MaximumIsExclusive { get; set; }
29+
30+
/// <summary>
31+
/// Gets the minimum allowed field value.
32+
/// </summary>
33+
public decimal Minimum { get; }
34+
35+
/// <summary>
36+
/// Specifies whether validation should fail for values that are equal to System.ComponentModel.DataAnnotations.RangeAttribute.Minimum.
37+
/// </summary>
38+
public bool MinimumIsExclusive { get; set; }
39+
40+
#endregion
41+
42+
#region Methods
43+
44+
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
45+
{
46+
ArgumentOutOfRangeException.ThrowIfLessThan(Minimum, FastMoney.MinValue.Amount);
47+
ArgumentOutOfRangeException.ThrowIfGreaterThan(Maximum, FastMoney.MaxValue.Amount);
48+
49+
if (Minimum.Scale > 4)
50+
{
51+
throw new ArgumentException("Minimum scale cannot be greater than 4.");
52+
}
53+
if (Maximum.Scale > 4)
54+
{
55+
throw new ArgumentException("Maximum scale cannot be greater than 4.");
56+
}
57+
58+
if (value is FastMoneyDto dto)
59+
{
60+
if (dto.Amount < Minimum || (MinimumIsExclusive && dto.Amount == Minimum))
61+
{
62+
return new ValidationResult(
63+
$"The field {validationContext.MemberName}.{nameof(FastMoneyDto.Amount)} must be between {Minimum} and {Maximum}.",
64+
[nameof(FastMoneyDto.Amount)]);
65+
}
66+
if (dto.Amount > Maximum || (MaximumIsExclusive && dto.Amount == Maximum))
67+
{
68+
return new ValidationResult(
69+
$"The field {validationContext.MemberName}.{nameof(FastMoneyDto.Amount)} must be between {Minimum} and {Maximum}.",
70+
[nameof(FastMoneyDto.Amount)]);
71+
}
72+
73+
if (!CurrencyCode.SupportedCurrency.ContainsKey(dto.Currency))
74+
{
75+
return new ValidationResult("Invalid currency code.", [nameof(Currency)]);
76+
}
77+
}
78+
79+
return ValidationResult.Success;
80+
}
81+
82+
#endregion
83+
84+
}

src/plugins/NodaMoneyTest/MoneyTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public static void DefaultCurrency_Test()
3434

3535
public static void FastMoney_Test()
3636
{
37-
var eur = new FastMoney(10.1264m, CurrencyCode.EUR);
37+
var eur = new FastMoney(10.12645m, CurrencyCode.EUR);
3838
var fee = new FastMoney(0.1000m, CurrencyCode.EUR);
3939
var total = eur + fee;
4040

src/plugins/NodaMoneyTest/Program.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ private static void Main()
1212
//MoneyTest.Parse_Test();
1313
//MoneyTest.FastMoney_Test();
1414

15-
SerializationTest.Serialize_Test();
15+
//SerializationTest.Serialize_Test();
16+
SerializationTest.Output_Serialization_Test();
17+
SerializationTest.Input_Validation_ModelState_Test();
1618
}
1719

1820
#endregion
Lines changed: 23 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
using NodaMoney;
22
using System.ComponentModel.DataAnnotations;
3-
using System.Diagnostics.CodeAnalysis;
4-
using System.Globalization;
53
using System.Text.Json;
64

75
namespace NodaMoneyTest;
@@ -11,11 +9,29 @@ public static class SerializationTest
119

1210
#region Constants & Statics
1311

14-
public static void Input_Validation_Test()
12+
public static void Input_Validation_ModelState_Test()
1513
{
16-
var input = new MyInput("Jane Doe", 25, new FastMoneyDto { Amount = 49.95m, });
17-
var json = JsonSerializer.Serialize(input);
18-
Console.WriteLine(json);
14+
var input = new MyInput("Jane Doe", 25, new FastMoneyDto { Amount = 101m, });
15+
var context = new ValidationContext(input);
16+
var results = new List<ValidationResult>();
17+
var isValid = Validator.TryValidateObject(input, context, results, true);
18+
19+
Console.WriteLine($"MyInput Over Max IsValid: {isValid}");
20+
foreach (var result in results)
21+
{
22+
Console.WriteLine($"Error: {result.ErrorMessage}");
23+
}
24+
25+
var input2 = new MyInput("Jane Doe", 25, new FastMoneyDto { Amount = 1m, Currency = "abc" });
26+
context = new ValidationContext(input2);
27+
results = [];
28+
isValid = Validator.TryValidateObject(input2, context, results, true);
29+
30+
Console.WriteLine($"MyInput Currency IsValid: {isValid}");
31+
foreach (var result in results)
32+
{
33+
Console.WriteLine($"Error: {result.ErrorMessage}");
34+
}
1935
}
2036

2137
public static void Output_Serialization_Test()
@@ -43,105 +59,6 @@ public static void Serialize_Test()
4359

4460
public record MyDto(string Name, int Age, FastMoney Price);
4561

46-
public record MyInput(string Name, int Age, [AmountValidation("1.00000", "100")] FastMoneyDto Price);
62+
public record MyInput(string Name, int Age, [property: FastMoneyRange("1.0000", "100")] FastMoneyDto Price);
4763

4864
public record MyOutput(string Name, int Age, FastMoneyDto Price);
49-
50-
public static class FastMoneyExtensions
51-
{
52-
53-
#region Constants & Statics
54-
55-
public static FastMoneyDto ToDto(this FastMoney fastMoney)
56-
{
57-
return new FastMoneyDto { Amount = fastMoney.Amount, Currency = fastMoney.Currency.Code };
58-
}
59-
60-
#endregion
61-
62-
}
63-
64-
public class FastMoneyDto : IValidatableObject
65-
{
66-
67-
#region Properties
68-
69-
public decimal Amount { get; init; }
70-
71-
public string Currency { get; init; } = CurrencyCode.CNY;
72-
73-
#endregion
74-
75-
#region IValidatableObject implementations
76-
77-
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
78-
{
79-
var results = new List<ValidationResult>();
80-
81-
//validate AmountValidationAttribute
82-
var context = new ValidationContext(this);
83-
_ = Validator.TryValidateProperty(Amount, context, results);
84-
85-
try
86-
{
87-
_ = CurrencyInfo.FromCode(Currency);
88-
}
89-
catch
90-
{
91-
results.Add(new ValidationResult("Invalid currency code.", [nameof(Currency)]));
92-
}
93-
94-
return results;
95-
}
96-
97-
#endregion
98-
99-
}
100-
101-
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
102-
public sealed class AmountValidationAttribute : ValidationAttribute
103-
{
104-
[SuppressMessage("Design", "CA1019:Define accessors for attribute arguments", Justification = "<Pending>")]
105-
public AmountValidationAttribute(string minimum, string maximum)
106-
{
107-
Minimum = decimal.Parse(minimum, CultureInfo.InvariantCulture);
108-
Maximum = decimal.Parse(maximum, CultureInfo.InvariantCulture);
109-
110-
ArgumentOutOfRangeException.ThrowIfLessThan(Minimum, FastMoney.MinValue.Amount);
111-
ArgumentOutOfRangeException.ThrowIfGreaterThan(Maximum, FastMoney.MaxValue.Amount);
112-
113-
if (Minimum.Scale > 4)
114-
{
115-
throw new ArgumentException("Scale cannot be greater than 4.", nameof(minimum));
116-
}
117-
if (Maximum.Scale > 4)
118-
{
119-
throw new ArgumentException("Scale cannot be greater than 4.", nameof(maximum));
120-
}
121-
}
122-
123-
#region Properties
124-
125-
/// <summary>
126-
/// Gets the maximum allowed field value.
127-
/// </summary>
128-
public decimal Maximum { get; }
129-
130-
/// <summary>
131-
/// Specifies whether validation should fail for values that are equal to System.ComponentModel.DataAnnotations.RangeAttribute.Maximum.
132-
/// </summary>
133-
public bool MaximumIsExclusive { get; set; }
134-
135-
/// <summary>
136-
/// Gets the minimum allowed field value.
137-
/// </summary>
138-
public decimal Minimum { get; }
139-
140-
/// <summary>
141-
/// Specifies whether validation should fail for values that are equal to System.ComponentModel.DataAnnotations.RangeAttribute.Minimum.
142-
/// </summary>
143-
public bool MinimumIsExclusive { get; set; }
144-
145-
#endregion
146-
147-
}

0 commit comments

Comments
 (0)