Skip to content

Commit

Permalink
Merge pull request #507 from bilal-fazlani/modernize
Browse files Browse the repository at this point in the history
modernize code and fix or ignore all warnings
  • Loading branch information
drewburlingame authored Jan 13, 2025
2 parents 059ad3c + 0a5cb05 commit 0461c6e
Show file tree
Hide file tree
Showing 481 changed files with 25,672 additions and 26,951 deletions.
275 changes: 137 additions & 138 deletions CommandDotNet.DataAnnotations/DataAnnotationsMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,187 +6,186 @@
using CommandDotNet.Execution;
using CommandDotNet.Extensions;

namespace CommandDotNet.DataAnnotations
namespace CommandDotNet.DataAnnotations;

public static class DataAnnotationsMiddleware
{
public static class DataAnnotationsMiddleware
public static AppRunner UseDataAnnotationValidations(this AppRunner appRunner, bool showHelpOnError = false,
Resources? resourcesOverride = null)
{
public static AppRunner UseDataAnnotationValidations(this AppRunner appRunner, bool showHelpOnError = false,
Resources? resourcesOverride = null)
return appRunner.Configure(c =>
{
return appRunner.Configure(c =>
var localizationAppSettings = appRunner.AppSettings.Localization;
if (resourcesOverride != null)
{
var localizationAppSettings = appRunner.AppSettings.Localization;
if (resourcesOverride != null)
{
Resources.A = resourcesOverride;
}
else if (localizationAppSettings.Localize != null)
{
Resources.A = new ResourcesProxy(
localizationAppSettings.Localize,
localizationAppSettings.UseMemberNamesAsKeys);
}
c.UseMiddleware(DataAnnotationsValidation, MiddlewareSteps.DataAnnotations);
c.Services.Add(new Config(showHelpOnError));
});
}

private class Config
{
public bool ShowHelpOnError { get; }

public Config(bool showHelpOnError)
Resources.A = resourcesOverride;
}
else if (localizationAppSettings.Localize != null)
{
ShowHelpOnError = showHelpOnError;
Resources.A = new ResourcesProxy(
localizationAppSettings.Localize,
localizationAppSettings.UseMemberNamesAsKeys);
}
}
c.UseMiddleware(DataAnnotationsValidation, MiddlewareSteps.DataAnnotations);
c.Services.Add(new Config(showHelpOnError));
});
}

private static Task<int> DataAnnotationsValidation(CommandContext ctx, ExecutionDelegate next)
private class Config
{
public bool ShowHelpOnError { get; }

public Config(bool showHelpOnError)
{
var errors = ctx.InvocationPipeline.All
.SelectMany(ValidateStep)
.ToList();
ShowHelpOnError = showHelpOnError;
}
}

if (errors.Any())
{
var console = ctx.Console;
errors.ForEach(error =>
{
console.Error.WriteLine(error);
});
private static Task<int> DataAnnotationsValidation(CommandContext ctx, ExecutionDelegate next)
{
var errors = ctx.InvocationPipeline.All
.SelectMany(ValidateStep)
.ToList();

ctx.ShowHelpOnExit = ctx.AppConfig.Services.GetOrThrow<Config>().ShowHelpOnError;
if (errors.Any())
{
var console = ctx.Console;
errors.ForEach(error =>
{
console.Error.WriteLine(error);
});

if (ctx.ShowHelpOnExit)
{
console.Error.WriteLine();
}
ctx.ShowHelpOnExit = ctx.AppConfig.Services.GetOrThrow<Config>().ShowHelpOnError;

return ExitCodes.ValidationErrorAsync;
if (ctx.ShowHelpOnExit)
{
console.Error.WriteLine();
}
return next(ctx);

return ExitCodes.ValidationErrorAsync;
}

return next(ctx);
}

private static IEnumerable<string> ValidateStep(InvocationStep step)
{
var phrasesToReplace = Resources.A
.Error_DataAnnotation_phrases_to_replace_with_argument_name()
.Split('|');
private static IEnumerable<string> ValidateStep(InvocationStep step)
{
var phrasesToReplace = Resources.A
.Error_DataAnnotation_phrases_to_replace_with_argument_name()
.Split('|');

var propertyArgumentErrors = step.Invocation.Arguments
.Select(argument => GetParameterArgumentErrors(argument, phrasesToReplace));
var propertyArgumentErrors = step.Invocation.Arguments
.Select(argument => GetParameterArgumentErrors(argument, phrasesToReplace));

var argumentModelErrors = step.Invocation.FlattenedArgumentModels
.Select(m => GetArgumentModelErrors(m, step.Invocation.Arguments, phrasesToReplace));
var argumentModelErrors = step.Invocation.FlattenedArgumentModels
.Select(m => GetArgumentModelErrors(m, step.Invocation.Arguments, phrasesToReplace));

return propertyArgumentErrors
.Concat(argumentModelErrors)
.SelectMany(e => e);
}
return propertyArgumentErrors
.Concat(argumentModelErrors)
.SelectMany(e => e);
}

private static IEnumerable<string> GetArgumentModelErrors(IArgumentModel model,
IReadOnlyCollection<IArgument> arguments, string[] phrasesToReplace)
private static IEnumerable<string> GetArgumentModelErrors(IArgumentModel model,
IReadOnlyCollection<IArgument> arguments, string[] phrasesToReplace)
{
var validationContext = new ValidationContext(model);
var results = new List<ValidationResult>();
if (Validator.TryValidateObject(model, validationContext, results, validateAllProperties: true))
{
var validationContext = new ValidationContext(model);
var results = new List<ValidationResult>();
if (Validator.TryValidateObject(model, validationContext, results, validateAllProperties: true))
{
return Enumerable.Empty<string>();
}
return [];
}

IArgument? GetArgument(string propertyName)
return results
.Select(SanitizedErrorMessage)
.Where(m => m != null)!;

IArgument? GetArgument(string propertyName)
{
return arguments.FirstOrDefault(a =>
{
return arguments.FirstOrDefault(a =>
{
var info = a.Services.GetOrDefault<PropertyInfo>();
return info != null
&& (info.DeclaringType?.IsInstanceOfType(model) ?? false)
&& propertyName.Equals(info.Name);
});
}
var info = a.Services.GetOrDefault<PropertyInfo>();
return info != null
&& (info.DeclaringType?.IsInstanceOfType(model) ?? false)
&& propertyName.Equals(info.Name);
});
}

string? SanitizedErrorMessage(ValidationResult validationResult)
string? SanitizedErrorMessage(ValidationResult validationResult)
{
var errorMessage = validationResult.ErrorMessage;
if (errorMessage is not null)
{
var errorMessage = validationResult.ErrorMessage;
if (errorMessage is not null)
foreach (var memberName in validationResult.MemberNames)
{
foreach (var memberName in validationResult.MemberNames)
var argument = GetArgument(memberName);
if (argument is not null)
{
var argument = GetArgument(memberName);
if (argument is { })
{
errorMessage = errorMessage.RemoveFieldTerminology(memberName, argument, phrasesToReplace);
}
errorMessage = errorMessage.RemoveFieldTerminology(memberName, argument, phrasesToReplace);
}
}
return errorMessage;
}

return results
.Select(SanitizedErrorMessage)
.Where(m => m != null)!;
return errorMessage;
}
}

private static IEnumerable<string> GetParameterArgumentErrors(IArgument argument,
string[] phrasesToReplace)
private static IEnumerable<string> GetParameterArgumentErrors(IArgument argument,
string[] phrasesToReplace)
{
var parameterInfo = argument.Services.GetOrDefault<ParameterInfo>();
if (parameterInfo is null)
{
var parameterInfo = argument.Services.GetOrDefault<ParameterInfo>();
if (parameterInfo is null)
{
return Enumerable.Empty<string>();
}
return [];
}

var validationAttributes = argument.CustomAttributes
.GetCustomAttributes(true)
.OfType<ValidationAttribute>()
.OrderBy(a => a is RequiredAttribute ? 0 : 1)
.ToList();
var validationAttributes = argument.CustomAttributes
.GetCustomAttributes(true)
.OfType<ValidationAttribute>()
.OrderBy(a => a is RequiredAttribute ? 0 : 1)
.ToList();

List<string>? errors = null;
List<string>? errors = null;

foreach (var validationAttribute in validationAttributes)
foreach (var validationAttribute in validationAttributes)
{
if (!validationAttribute.IsValid(argument.Value))
{
if (!validationAttribute.IsValid(argument.Value))
// the user expects the name to map to an argument, not a field.
// update the terminology. This is naive and will need to change
// when we handle localization
var message = validationAttribute
.FormatErrorMessage(argument.Name)
.RemoveFieldTerminology(parameterInfo.Name, argument, phrasesToReplace);
(errors ??= []).Add(message);

if (validationAttribute is RequiredAttribute)
{
// the user expects the name to map to an argument, not a field.
// update the terminology. This is naive and will need to change
// when we handle localization
var message = validationAttribute
.FormatErrorMessage(argument.Name)
.RemoveFieldTerminology(parameterInfo.Name, argument, phrasesToReplace);
(errors ??= new List<string>()).Add(message);

if (validationAttribute is RequiredAttribute)
{
// If the value is not provided and it is required, no other validation needs to be performed.
// this is why the RequiredAttribute is first.
break;
}
// If the value is not provided and it is required, no other validation needs to be performed.
// this is why the RequiredAttribute is first.
break;
}
}

return errors ?? Enumerable.Empty<string>();
}

return errors ?? Enumerable.Empty<string>();
}

/// <summary>the user expects the name to map to an argument, not a field.</summary>
/// <remarks>
/// This is naive and will need to change when we handle localization
/// </remarks>
private static string RemoveFieldTerminology(this string error, string? memberName, IArgument argument, IEnumerable<string> phrasesToReplace)
/// <summary>the user expects the name to map to an argument, not a field.</summary>
/// <remarks>
/// This is naive and will need to change when we handle localization
/// </remarks>
private static string RemoveFieldTerminology(this string error, string? memberName, IArgument argument, IEnumerable<string> phrasesToReplace)
{
memberName = argument.GetCustomAttribute<DisplayAttribute>()?.Name ?? memberName;
if (memberName is null)
{
memberName = argument.GetCustomAttribute<DisplayAttribute>()?.Name ?? memberName;
if (memberName is null)
{
return error;
}

var argName = $"'{argument.Name}'";
foreach (var phrase in phrasesToReplace.Select(p => string.Format(p, memberName)))
{
error = error.Replace(phrase, argName);
}
return error;
}

var argName = $"'{argument.Name}'";
foreach (var phrase in phrasesToReplace.Select(p => string.Format(p, memberName)))
{
error = error.Replace(phrase, argName);
}
return error;
}
}
9 changes: 4 additions & 5 deletions CommandDotNet.DataAnnotations/MiddlewareSteps.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
using CommandDotNet.Execution;

namespace CommandDotNet.DataAnnotations
namespace CommandDotNet.DataAnnotations;

public static class MiddlewareSteps
{
public static class MiddlewareSteps
{
public static MiddlewareStep DataAnnotations { get; set; } = Execution.MiddlewareSteps.ValidateArity - 1100;
}
public static MiddlewareStep DataAnnotations { get; set; } = Execution.MiddlewareSteps.ValidateArity - 1100;
}
13 changes: 6 additions & 7 deletions CommandDotNet.DataAnnotations/Resources.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
namespace CommandDotNet.DataAnnotations
namespace CommandDotNet.DataAnnotations;

public class Resources
{
public class Resources
{
public static Resources A = new();
public static Resources A = new();

public virtual string Error_DataAnnotation_phrases_to_replace_with_argument_name() =>
"The {0} field|The field {0}|The {0} property|The property {0}";
}
public virtual string Error_DataAnnotation_phrases_to_replace_with_argument_name() =>
"The {0} field|The field {0}|The {0} property|The property {0}";
}
15 changes: 5 additions & 10 deletions CommandDotNet.DataAnnotations/ResourcesProxy.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
// this file generated by ResourceGenerators.RegenerateProxyClasses

using System;
using JetBrains.Annotations;

namespace CommandDotNet.DataAnnotations
{
// this class generated by ResourcesDef.GenerateProxyClass
public class ResourcesProxy : Resources
[PublicAPI]
public class ResourcesProxy(Func<string, string?> localize, bool memberNameAsKey = false) : Resources
{
private readonly Func<string, string?> _localize;
private readonly bool _memberNameAsKey;

public ResourcesProxy(Func<string, string?> localize, bool memberNameAsKey = false)
{
_localize = localize ?? throw new ArgumentNullException(nameof(localize));
_memberNameAsKey = memberNameAsKey;
}
private readonly Func<string, string?> _localize = localize ?? throw new ArgumentNullException(nameof(localize));

private static string? Format(string? value, params object?[] args) =>
value is null ? null : string.Format(value, args);

public override string Error_DataAnnotation_phrases_to_replace_with_argument_name() =>
_localize(_memberNameAsKey
_localize(memberNameAsKey
? "Error_DataAnnotation_phrases_to_replace_with_argument_name"
: base.Error_DataAnnotation_phrases_to_replace_with_argument_name())
?? base.Error_DataAnnotation_phrases_to_replace_with_argument_name();
Expand Down
Loading

0 comments on commit 0461c6e

Please sign in to comment.