Skip to content

Instantly share code, notes, and snippets.

@andrewabest
Last active January 30, 2020 02:53
Show Gist options
  • Save andrewabest/1df91aef9116488480ad8df4d90e8fa0 to your computer and use it in GitHub Desktop.
Save andrewabest/1df91aef9116488480ad8df4d90e8fa0 to your computer and use it in GitHub Desktop.
MustConformToOneOf - Strongly Typed Conventions spike
void Main()
{
typeof(NonConforming).MustConformToOneOf(new PropertyConventionSpecification[] {
new PropertiesMustHavePrivateSettersConventionSpecification(),
new PropertiesMustHavePublicSettersConventionSpecification()
});
}
public class NonConforming
{
public string Name {get;set;}
public string PrivateName {get;private set;}
public string ProtectedName {get;protected set;}
}
public static class PropertyConventionConformist
{
public static PropertyConventionResult[] MustConformToOneOf(this Type type, PropertyConventionSpecification[] conventionSpecifications)
{
var results = conventionSpecifications.Select(x => x.IsSatisfiedBy(type)).ToArray();
if (results.All(r => r.IsSatisfied))
{
return results;
}
var groupedFailures = results.SelectMany(r => r.Failures).GroupBy(k => k.Subject);
if (groupedFailures.Any(g => g.Count() == conventionSpecifications.Count()))
{
"Fail Whale".Dump();
}
else
{
"Success".Dump();
}
return results;
}
}
public class PropertiesMustHavePrivateSettersConventionSpecification : PropertyConventionSpecification
{
protected override string FailureMessage => "All properties must have private setters";
protected override PropertyInfo[] GetNonConformingProperties(Type type)
{
return type.GetDeclaredProperties()
.Where(subject => subject.CanWrite == false || subject.GetSetMethod(true).IsPrivate == false)
.ToArray();
}
}
public class PropertiesMustHavePublicSettersConventionSpecification : PropertyConventionSpecification
{
protected override string FailureMessage => "All properties must have public setters";
protected override PropertyInfo[] GetNonConformingProperties(Type type)
{
return type.GetDeclaredProperties()
.Where(subject => subject.CanWrite == false || subject.GetSetMethod(true).IsPublic == false)
.ToArray();
}
}
public abstract class PropertyConventionSpecification : ConventionSpecification<Type, PropertyConventionResult>
{
protected abstract PropertyInfo[] GetNonConformingProperties(Type type);
public override PropertyConventionResult IsSatisfiedBy(Type type)
{
var failures = GetNonConformingProperties(type);
if (failures.Any())
{
var failureMessage =
BuildFailureMessage(failures.Aggregate(string.Empty,
(s, info) => s + "\t- " + info.Name + Environment.NewLine));
return PropertyConventionResult.NotSatisfied(type, failures.Select(f => new Failure<PropertyInfo>(f, "failed")).ToArray());
}
return PropertyConventionResult.Satisfied(type);
}
}
public abstract class ConventionSpecification<TSubject, TResult> : IConventionSpecification<TSubject, TResult> where TResult : IHaveSubject<TSubject>
{
protected abstract string FailureMessage { get; }
protected string BuildFailureMessage(string details)
{
return FailureMessage +
Environment.NewLine +
details;
}
public abstract TResult IsSatisfiedBy(TSubject type);
}
public interface IConventionSpecification<TSubject, TResult> where TResult : IHaveSubject<TSubject>
{
TResult IsSatisfiedBy(TSubject type);
}
public class PropertyConventionResult : ConventionResult<Type, PropertyInfo>
{
public PropertyConventionResult(Type subject) : base(subject)
{
}
public static PropertyConventionResult Satisfied(Type subject)
{
return new PropertyConventionResult(subject) { IsSatisfied = true };
}
public static PropertyConventionResult NotSatisfied(Type subject, Failure<PropertyInfo>[] failures)
{
return new PropertyConventionResult(subject) { Failures = failures };
}
}
public class GenericConventionResult : ConventionResult<string, string>
{
public GenericConventionResult(string subject) : base(subject)
{
}
}
// Define other methods and classes here
public class ConventionResult<TSubject, TFailure> : IHaveSubject<TSubject>
{
public ConventionResult(TSubject subject)
{
Subject = subject;
Failures = new Failure<TFailure>[0];
}
public TSubject Subject { get; }
public bool IsSatisfied { get; protected set; }
public IReadOnlyCollection<Failure<TFailure>> Failures { get; protected set; }
}
public interface IHaveSubject<T>
{
T Subject { get; }
}
public class Failure<T>
{
public Failure(T subject, string message)
{
Subject = subject;
Message = message;
}
public T Subject {get;}
public string Message {get;}
}
public static class TypeExtensions
{
internal static PropertyInfo[] GetDeclaredProperties(this Type type)
{
return type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static |
BindingFlags.Public);
}
internal static bool IsGenericImplementation(this Type type)
{
return (type.BaseType != null && type.BaseType.IsGenericType) ||
type.GetInterfaces().Any(i => i.IsGenericType);
}
public static IEnumerable<Type> WhereTypes(this IEnumerable<Assembly> assemblies, Func<Type, bool> predicate)
{
var types = assemblies.SelectMany(x => x.GetExportedTypes()).Where(predicate).ToArray();
return types;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment