Skip to content

Commit 85f5c98

Browse files
committed
Resolve #32 - Add mechanism to get validation failure messages
1 parent 1fa1db8 commit 85f5c98

14 files changed

Lines changed: 804 additions & 0 deletions

CSF.Validation.Tests/CSF.Validation.Tests.csproj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@
6868
<Folder Include="Manifest\" />
6969
<Folder Include="Integration\" />
7070
<Folder Include="Rules\" />
71+
<Folder Include="Resources\" />
72+
<Folder Include="Messages\" />
7173
</ItemGroup>
7274
<ItemGroup>
7375
<Compile Include="StockRules\NotNullRuleTests.cs" />
@@ -96,9 +98,16 @@
9698
<Compile Include="StockRules\IsDefinedEnumMemberValueRuleTests.cs" />
9799
<Compile Include="StockRules\NullableIsDefinedEnumMemberValueRuleTests.cs" />
98100
<Compile Include="StockRules\RegexMatchValueRuleTests.cs" />
101+
<Compile Include="Resources\FailureMessageTemplates.cs" />
102+
<Compile Include="Messages\MessageProviderIntegrationTests.cs" />
99103
</ItemGroup>
100104
<ItemGroup>
101105
<None Include="packages.config" />
102106
<None Include="App.config" />
103107
</ItemGroup>
108+
<ItemGroup>
109+
<EmbeddedResource Include="Resources\FailureMessageTemplates.resx">
110+
<LastGenOutput>FailureMessages.Designer.cs</LastGenOutput>
111+
</EmbeddedResource>
112+
</ItemGroup>
104113
</Project>
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
//
2+
// MessageProviderIntegrationTests.cs
3+
//
4+
// Author:
5+
// Craig Fowler <craig@csf-dev.com>
6+
//
7+
// Copyright (c) 2017 Craig Fowler
8+
//
9+
// Permission is hereby granted, free of charge, to any person obtaining a copy
10+
// of this software and associated documentation files (the "Software"), to deal
11+
// in the Software without restriction, including without limitation the rights
12+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
// copies of the Software, and to permit persons to whom the Software is
14+
// furnished to do so, subject to the following conditions:
15+
//
16+
// The above copyright notice and this permission notice shall be included in
17+
// all copies or substantial portions of the Software.
18+
//
19+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
// THE SOFTWARE.
26+
using System;
27+
using System.Collections.Generic;
28+
using CSF.Reflection;
29+
using CSF.Validation.Manifest;
30+
using CSF.Validation.Messages;
31+
using CSF.Validation.StockRules;
32+
using CSF.Validation.Tests.Resources;
33+
using CSF.Validation.ValidationRuns;
34+
using Moq;
35+
using NUnit.Framework;
36+
37+
namespace CSF.Validation.Tests.Messages
38+
{
39+
[TestFixture]
40+
public class MessageProviderIntegrationTests
41+
{
42+
[Test]
43+
public void GetFailureMessage_returns_expected_formatted_message_with_constant_replacements()
44+
{
45+
// Arrange
46+
var sut = GetSut();
47+
var failure = GetFailures()[0];
48+
49+
// Act
50+
var result = sut.GetFailureMessage(failure);
51+
52+
// Assert
53+
Assert.AreEqual("Number must be between 5 and 20.", result);
54+
}
55+
56+
[Test]
57+
public void GetFailureMessage_returns_expected_formatted_message_with_dynamic_replacement()
58+
{
59+
// Arrange
60+
var sut = GetSut();
61+
var failure = GetFailures()[1];
62+
63+
// Act
64+
var result = sut.GetFailureMessage(failure);
65+
66+
// Assert
67+
Assert.AreEqual("The year 2001 is too early.", result);
68+
}
69+
70+
[Test]
71+
public void GetFailureMessage_returns_expected_formatted_message_with_no_formatter()
72+
{
73+
// Arrange
74+
var sut = GetSut();
75+
var failure = GetFailures()[2];
76+
77+
// Act
78+
var result = sut.GetFailureMessage(failure);
79+
80+
// Assert
81+
Assert.AreEqual("Value must not be null.", result);
82+
}
83+
84+
IMessageProvider GetSut()
85+
{
86+
var templateProvider = new FailureMessageTemplates();
87+
var provider = new ByIdentityFormatterProvider();
88+
89+
object identity;
90+
PlaceholderMessageFormatter<StubValidatedObject> formatter;
91+
92+
identity = new DefaultManifestIdentity(typeof(StubValidatedObject),
93+
typeof(NumericRangeValueRule),
94+
validatedMember: Reflect.Member<StubValidatedObject>(x => x.IntegerProperty));
95+
formatter = new PlaceholderMessageFormatter<StubValidatedObject>();
96+
formatter.AddPlaceholder("{min}", 5);
97+
formatter.AddPlaceholder("{max}", 20);
98+
provider.RegisteredFormatters.Add(identity, formatter);
99+
100+
identity = new DefaultManifestIdentity(typeof(StubValidatedObject),
101+
typeof(DateTimeRangeValueRule),
102+
validatedMember: Reflect.Member<StubValidatedObject>(x => x.DateTimeProperty));
103+
formatter = new PlaceholderMessageFormatter<StubValidatedObject>();
104+
formatter.AddPlaceholder("{actual}", x => x.DateTimeProperty.Year);
105+
provider.RegisteredFormatters.Add(identity, formatter);
106+
107+
return new MessageProvider(templateProvider, provider);
108+
}
109+
110+
IRunnableRuleResult[] GetFailures()
111+
{
112+
var identityOne = new DefaultManifestIdentity(typeof(StubValidatedObject),
113+
typeof(NumericRangeValueRule),
114+
validatedMember: Reflect.Member<StubValidatedObject>(x => x.IntegerProperty));
115+
var identityTwo = new DefaultManifestIdentity(typeof(StubValidatedObject),
116+
typeof(DateTimeRangeValueRule),
117+
validatedMember: Reflect.Member<StubValidatedObject>(x => x.DateTimeProperty));
118+
var identityThree = new DefaultManifestIdentity(typeof(StubValidatedObject),
119+
typeof(NotNullValueRule),
120+
validatedMember: Reflect.Member<StubValidatedObject>(x => x.StringProperty));
121+
122+
var validated = new StubValidatedObject { DateTimeProperty = new DateTime(2001, 1, 1) };
123+
124+
return new [] {
125+
Mock.Of<IRunnableRuleResult>(x => x.ManifestIdentity == identityOne && x.Validated == validated),
126+
Mock.Of<IRunnableRuleResult>(x => x.ManifestIdentity == identityTwo && x.Validated == validated),
127+
Mock.Of<IRunnableRuleResult>(x => x.ManifestIdentity == identityThree && x.Validated == validated),
128+
};
129+
}
130+
}
131+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//
2+
// FailureMessageTemplates.cs
3+
//
4+
// Author:
5+
// Craig Fowler <craig@csf-dev.com>
6+
//
7+
// Copyright (c) 2017 Craig Fowler
8+
//
9+
// Permission is hereby granted, free of charge, to any person obtaining a copy
10+
// of this software and associated documentation files (the "Software"), to deal
11+
// in the Software without restriction, including without limitation the rights
12+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
// copies of the Software, and to permit persons to whom the Software is
14+
// furnished to do so, subject to the following conditions:
15+
//
16+
// The above copyright notice and this permission notice shall be included in
17+
// all copies or substantial portions of the Software.
18+
//
19+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
// THE SOFTWARE.
26+
using System;
27+
using System.Reflection;
28+
using System.Resources;
29+
using CSF.Validation.Messages;
30+
31+
namespace CSF.Validation.Tests.Resources
32+
{
33+
public class FailureMessageTemplates : ResourceFileTemplateProvider {}
34+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<root>
3+
<resheader name="resmimetype">
4+
<value>text/microsoft-resx</value>
5+
</resheader>
6+
<resheader name="version">
7+
<value>2.0</value>
8+
</resheader>
9+
<resheader name="reader">
10+
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
11+
</resheader>
12+
<resheader name="writer">
13+
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
14+
</resheader>
15+
<data name="StubValidatedObject.IntegerProperty.NumericRangeValueRule" xml:space="preserve">
16+
<value>Number must be between {min} and {max}.</value>
17+
</data>
18+
<data name="StubValidatedObject.DateTimeProperty.DateTimeRangeValueRule" xml:space="preserve">
19+
<value>The year {actual} is too early.</value>
20+
</data>
21+
<data name="StubValidatedObject.StringProperty.NotNullValueRule" xml:space="preserve">
22+
<value>Value must not be null.</value>
23+
</data>
24+
</root>

CSF.Validation/CSF.Validation.csproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,15 @@
111111
<Compile Include="StockRules\IsDefinedEnumMemberValueRule.cs" />
112112
<Compile Include="StockRules\NullableIsDefinedEnumMemberValueRule.cs" />
113113
<Compile Include="StockRules\RegexMatchValueRule.cs" />
114+
<Compile Include="Messages\IMessageProvider.cs" />
115+
<Compile Include="Messages\IMessageFormatter.cs" />
116+
<Compile Include="Messages\NoOpFailureMessageFormatter.cs" />
117+
<Compile Include="Messages\PlaceholderMessageFormatter.cs" />
118+
<Compile Include="Messages\IFormatterProvider.cs" />
119+
<Compile Include="Messages\ByIdentityFormatterProvider.cs" />
120+
<Compile Include="Messages\ITemplateProvider.cs" />
121+
<Compile Include="Messages\ResourceFileTemplateProvider.cs" />
122+
<Compile Include="Messages\MessageProvider.cs" />
114123
</ItemGroup>
115124
<ItemGroup>
116125
<Folder Include="Rules\" />
@@ -120,6 +129,7 @@
120129
<Folder Include="Manifest\" />
121130
<Folder Include="Options\" />
122131
<Folder Include="Manifest\Fluent\" />
132+
<Folder Include="Messages\" />
123133
</ItemGroup>
124134
<ItemGroup>
125135
<None Include="packages.config" />
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
//
2+
// RegisteredFormatterProvider.cs
3+
//
4+
// Author:
5+
// Craig Fowler <craig@csf-dev.com>
6+
//
7+
// Copyright (c) 2017 Craig Fowler
8+
//
9+
// Permission is hereby granted, free of charge, to any person obtaining a copy
10+
// of this software and associated documentation files (the "Software"), to deal
11+
// in the Software without restriction, including without limitation the rights
12+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
// copies of the Software, and to permit persons to whom the Software is
14+
// furnished to do so, subject to the following conditions:
15+
//
16+
// The above copyright notice and this permission notice shall be included in
17+
// all copies or substantial portions of the Software.
18+
//
19+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
// THE SOFTWARE.
26+
using System;
27+
using System.Collections.Generic;
28+
using CSF.Validation.ValidationRuns;
29+
30+
namespace CSF.Validation.Messages
31+
{
32+
/// <summary>
33+
/// Service which selects and returns an <see cref="IMessageFormatter"/> based on the manifest identity
34+
/// of the <see cref="IRunnableRuleResult"/>.
35+
/// </summary>
36+
public class ByIdentityFormatterProvider : IFormatterProvider
37+
{
38+
static readonly IMessageFormatter defaultFormatter;
39+
readonly IDictionary<object,IMessageFormatter> registeredFormatters;
40+
41+
/// <summary>
42+
/// Gets a collection of the registered formatters.
43+
/// </summary>
44+
/// <value>The registered formatters.</value>
45+
public virtual IDictionary<object,IMessageFormatter> RegisteredFormatters => registeredFormatters;
46+
47+
/// <summary>
48+
/// Gets the message formatter.
49+
/// </summary>
50+
/// <returns>The formatter.</returns>
51+
/// <param name="result">Result.</param>
52+
public IMessageFormatter GetFormatter(IRunnableRuleResult result)
53+
{
54+
if(result == null)
55+
throw new ArgumentNullException(nameof(result));
56+
57+
return GetRegisteredFormatter(result.ManifestIdentity)?? GetDefaultFormatter();
58+
}
59+
60+
/// <summary>
61+
/// Gets a formatter which has been registered with the current instance.
62+
/// </summary>
63+
/// <returns>The formatter.</returns>
64+
/// <param name="identity">The identity of the validation rule.</param>
65+
protected virtual IMessageFormatter GetRegisteredFormatter(object identity)
66+
{
67+
if(!RegisteredFormatters.ContainsKey(identity))
68+
{
69+
return null;
70+
}
71+
72+
return RegisteredFormatters[identity];
73+
}
74+
75+
/// <summary>
76+
/// Gets a fallback/default formatter.
77+
/// </summary>
78+
/// <returns>The default formatter.</returns>
79+
protected virtual IMessageFormatter GetDefaultFormatter()
80+
{
81+
return defaultFormatter;
82+
}
83+
84+
/// <summary>
85+
/// Initializes a new instance of the <see cref="T:CSF.Validation.Messages.ByIdentityFormatterProvider"/> class.
86+
/// </summary>
87+
public ByIdentityFormatterProvider()
88+
{
89+
registeredFormatters = new Dictionary<object,IMessageFormatter>();
90+
}
91+
92+
/// <summary>
93+
/// Initializes the <see cref="T:CSF.Validation.Messages.ByIdentityFormatterProvider"/> class.
94+
/// </summary>
95+
static ByIdentityFormatterProvider()
96+
{
97+
defaultFormatter = new NoOpFailureMessageFormatter();
98+
}
99+
}
100+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//
2+
// IFormatterChooser.cs
3+
//
4+
// Author:
5+
// Craig Fowler <craig@csf-dev.com>
6+
//
7+
// Copyright (c) 2017 Craig Fowler
8+
//
9+
// Permission is hereby granted, free of charge, to any person obtaining a copy
10+
// of this software and associated documentation files (the "Software"), to deal
11+
// in the Software without restriction, including without limitation the rights
12+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
// copies of the Software, and to permit persons to whom the Software is
14+
// furnished to do so, subject to the following conditions:
15+
//
16+
// The above copyright notice and this permission notice shall be included in
17+
// all copies or substantial portions of the Software.
18+
//
19+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
// THE SOFTWARE.
26+
using System;
27+
using CSF.Validation.ValidationRuns;
28+
29+
namespace CSF.Validation.Messages
30+
{
31+
/// <summary>
32+
/// Service which selects and returns an appropriate <see cref="IMessageFormatter"/> to format the
33+
/// failure message associated with a given <see cref="IRunnableRuleResult"/>.
34+
/// </summary>
35+
public interface IFormatterProvider
36+
{
37+
/// <summary>
38+
/// Gets the message formatter.
39+
/// </summary>
40+
/// <returns>The formatter.</returns>
41+
/// <param name="result">Result.</param>
42+
IMessageFormatter GetFormatter(IRunnableRuleResult result);
43+
}
44+
}

0 commit comments

Comments
 (0)