jwaliszko / ExpressiveAnnotations

Annotation-based conditional validation library.
MIT License
351 stars 123 forks source link

Ability to compile all usages of annotations at once #40

Closed pawelpabich closed 9 years ago

pawelpabich commented 9 years ago

Hi,

At the moment we can discover only at runtime when If an annotation is not correct. Is there a way to let's say grab a list of types with annotations and compile them to check whether they are correct or not? Having this available would allow us to create a code convention type of unit test which would shorten the feedback cycle.

jwaliszko commented 9 years ago

Hi,

Such a possibility is available (it is partially used by validators). You can try it by writing following extension:

public static class Helper
{
    public static IEnumerable<ExpressiveAttribute> CompileExpressiveAttributes(this Assembly assembly)
    {
        return assembly.GetTypes().SelectMany(t => t.CompileExpressiveAttributes());
    }

    public static IEnumerable<ExpressiveAttribute> CompileExpressiveAttributes(this Type type)
    {
        var properties = type.GetProperties()
            .Where(p => Attribute.IsDefined(p, typeof (ExpressiveAttribute)));

        var attributes = new List<ExpressiveAttribute>();
        foreach (var prop in properties)
        {
            var attribs = prop.GetCustomAttributes<ExpressiveAttribute>().ToList();
            attribs.ForEach(x => x.Compile(prop.DeclaringType));
            attributes.AddRange(attribs);
        }
        return attributes;
    }
}

with the succeeding usage manner:

// compile all expressions for specified model:
var compiled = typeof (SomeModel).CompileExpressiveAttributes().ToList()); // ToList() executes iteration

// ... or for current assembly (this is probably what you'd like to have):
compiled = Assembly.GetExecutingAssembly().CompileExpressiveAttributes().ToList();

// ... or for all assemblies within current domain:
compiled = AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.CompileExpressiveAttributes()).ToList();

Notice that such compiled lambdas will be cached inside attributes instances stored in compiled list. That means that subsequent compilation requests:

compiled.ForEach(x => x.Compile(typeof (SomeModel));
...

do nothing (due to optimization purposes), unless invoked with enabled recompilation switch:

compiled.ForEach(x => x.Compile(typeof (SomeModel), force: true); 

Obviously this is recompilation, so you still can get runtime errors like in any other statically-typed language, e.g.:

var parser = new Parser();
parser.AddFunction<object, bool>("CastToBool", obj => (bool) obj);

parser.Parse<object>("CastToBool(null)"); // compilation succeeds
parser.Parse<object>("CastToBool(null)").Invoke(null); // invocation fails (type casting error)
pawelpabich commented 9 years ago

Awesome. Thanks a lot!