Skip to content

Commit

Permalink
Added support for property validation in-addition to the prop's type …
Browse files Browse the repository at this point in the history
…validation.
  • Loading branch information
adrianaisemberg committed Oct 24, 2011
1 parent 816affd commit 4845864
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 18 deletions.
74 changes: 62 additions & 12 deletions CLAP/TypeValidator.cs
@@ -1,4 +1,5 @@
using System.Linq;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using CLAP.Validation;

Expand All @@ -8,27 +9,26 @@ internal static class TypeValidator
{
public static void Validate(object obj)
{
if (obj == null ||
obj is string)
if (Ignore(obj))
{
return;
}

var type = obj.GetType();
Debug.Assert(obj != null);

if (type.IsArray ||
type.IsEnum ||
type.Assembly.GlobalAssemblyCache)
{
return;
}
var type = obj.GetType();

// type's validators:
// first use all the validators defined over this type
//
var validators = type.
GetAttributes<ValidateAttribute>().
Cast<IValidation>().
Select(a => a.GetValidator());

var propsAndValues = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).
var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

var propsAndValues = properties.
Select(p => new ValueInfo(p.Name, p.PropertyType, p.GetValue(obj, null))).
ToArray();

Expand All @@ -37,12 +37,62 @@ public static void Validate(object obj)
validator.Validate(propsAndValues);
}

// recursion
// property validators:
// validate each property value, in case property is a custom class
//
foreach (var property in properties)
{
var value = property.GetValue(obj, null);

if (Ignore(value))
{
continue;
}

var propertyValidators = property.
GetAttributes<ValidateAttribute>().
Cast<IValidation>().
Select(a => a.GetValidator());

foreach (var propertyValidator in propertyValidators)
{
var propertyPropsAndValues = value.GetType().
GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).
Select(p => new ValueInfo(p.Name, p.PropertyType, p.GetValue(value, null))).
ToArray();

propertyValidator.Validate(propertyPropsAndValues);
}
}

// recursion:
// validate all values, in case their type has additional validation
//
foreach (var value in propsAndValues.Select(p => p.Value))
{
Validate(value);
}
}

private static bool Ignore(object obj)
{
if (obj == null ||
obj is string)
{
return true;
}

var type = obj.GetType();

if (type.IsArray ||
type.IsEnum ||
type.Assembly.GlobalAssemblyCache)
{
return true;
}


return false;
}
}
}
12 changes: 7 additions & 5 deletions CLAP/Utils.cs
Expand Up @@ -36,6 +36,13 @@ public static string FormatWith(this string format, params object[] args)
return atts;
}

public static IEnumerable<T> GetAttributes<T>(this PropertyInfo property) where T : Attribute
{
var atts = Attribute.GetCustomAttributes(property, typeof(T)).Cast<T>();

return atts;
}

public static IEnumerable<T> GetInterfaceAttributes<T>(this MethodInfo method)
{
return method.GetCustomAttributes(true).
Expand All @@ -55,11 +62,6 @@ public static IEnumerable<T> GetInterfaceAttributes<T>(this MethodInfo method)
return Attribute.IsDefined(method, typeof(T));
}

public static bool HasAttribute<T>(this Type type) where T : Attribute
{
return Attribute.IsDefined(type, typeof(T));
}

public static bool HasAttribute<T>(this ParameterInfo parameter) where T : Attribute
{
return Attribute.IsDefined(parameter, typeof(T));
Expand Down
2 changes: 1 addition & 1 deletion CLAP/Validation/ValidateAttribute.cs
Expand Up @@ -10,7 +10,7 @@ namespace CLAP.Validation
/// For full documentation, see MSDN:
/// http://msdn.microsoft.com/en-us/library/system.data.datacolumn.expression.aspx
/// </remarks>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = true)]
public sealed class ValidateAttribute : Attribute, IValidation
{
#region Properties
Expand Down
14 changes: 14 additions & 0 deletions Tests/Samples.cs
Expand Up @@ -770,6 +770,11 @@ public void Val(MyValidatedType t)
public void Complex(MyComplexType t)
{
}

[Verb]
public void Props(MyComplexType_2 t)
{
}
}

public enum Case
Expand Down Expand Up @@ -799,4 +804,13 @@ public class MyComplexType
public string Name { get; set; }
public MyValidatedType Validated { get; set; }
}

public class MyComplexType_2
{
public int Number { get; set; }
public string Name { get; set; }

[Validate("Number < 50 AND Name IN ('foo','bar')")]
public MyValidatedType Validated { get; set; }
}
}
25 changes: 25 additions & 0 deletions Tests/Tests.cs
Expand Up @@ -1796,5 +1796,30 @@ public void ComplexGraphType_WithTypeValidation_Pass()
"-t:{Number: 40, Name: 'bar', Validated: { Number: 100, Name: 'barfoo' }}",
}, s);
}

[Test]
[ExpectedException(typeof(ValidationException))]
public void ComplexGraphType_WithPropertyValidation_Fail()
{
var s = new Sample_42();

Parser.Run(new[]
{
"props",
"-t:{Number: 40, Name: 'foobar', Validated: { Number: 100, Name: 'blah' }}",
}, s);
}

[Test]
public void ComplexGraphType_WithPropertyValidation_Pass()
{
var s = new Sample_42();

Parser.Run(new[]
{
"props",
"-t:{Number: 40000, Name: 'something', Validated: { Number: 40, Name: 'foo' }}",
}, s);
}
}
}

0 comments on commit 4845864

Please sign in to comment.