6
6
using CommandDotNet . Execution ;
7
7
using CommandDotNet . Extensions ;
8
8
9
- namespace CommandDotNet . DataAnnotations
9
+ namespace CommandDotNet . DataAnnotations ;
10
+
11
+ public static class DataAnnotationsMiddleware
10
12
{
11
- public static class DataAnnotationsMiddleware
13
+ public static AppRunner UseDataAnnotationValidations ( this AppRunner appRunner , bool showHelpOnError = false ,
14
+ Resources ? resourcesOverride = null )
12
15
{
13
- public static AppRunner UseDataAnnotationValidations ( this AppRunner appRunner , bool showHelpOnError = false ,
14
- Resources ? resourcesOverride = null )
16
+ return appRunner . Configure ( c =>
15
17
{
16
- return appRunner . Configure ( c =>
18
+ var localizationAppSettings = appRunner . AppSettings . Localization ;
19
+ if ( resourcesOverride != null )
17
20
{
18
- var localizationAppSettings = appRunner . AppSettings . Localization ;
19
- if ( resourcesOverride != null )
20
- {
21
- Resources . A = resourcesOverride ;
22
- }
23
- else if ( localizationAppSettings . Localize != null )
24
- {
25
- Resources . A = new ResourcesProxy (
26
- localizationAppSettings . Localize ,
27
- localizationAppSettings . UseMemberNamesAsKeys ) ;
28
- }
29
- c . UseMiddleware ( DataAnnotationsValidation , MiddlewareSteps . DataAnnotations ) ;
30
- c . Services . Add ( new Config ( showHelpOnError ) ) ;
31
- } ) ;
32
- }
33
-
34
- private class Config
35
- {
36
- public bool ShowHelpOnError { get ; }
37
-
38
- public Config ( bool showHelpOnError )
21
+ Resources . A = resourcesOverride ;
22
+ }
23
+ else if ( localizationAppSettings . Localize != null )
39
24
{
40
- ShowHelpOnError = showHelpOnError ;
25
+ Resources . A = new ResourcesProxy (
26
+ localizationAppSettings . Localize ,
27
+ localizationAppSettings . UseMemberNamesAsKeys ) ;
41
28
}
42
- }
29
+ c . UseMiddleware ( DataAnnotationsValidation , MiddlewareSteps . DataAnnotations ) ;
30
+ c . Services . Add ( new Config ( showHelpOnError ) ) ;
31
+ } ) ;
32
+ }
43
33
44
- private static Task < int > DataAnnotationsValidation ( CommandContext ctx , ExecutionDelegate next )
34
+ private class Config
35
+ {
36
+ public bool ShowHelpOnError { get ; }
37
+
38
+ public Config ( bool showHelpOnError )
45
39
{
46
- var errors = ctx . InvocationPipeline . All
47
- . SelectMany ( ValidateStep )
48
- . ToList ( ) ;
40
+ ShowHelpOnError = showHelpOnError ;
41
+ }
42
+ }
49
43
50
- if ( errors . Any ( ) )
51
- {
52
- var console = ctx . Console ;
53
- errors . ForEach ( error =>
54
- {
55
- console . Error . WriteLine ( error ) ;
56
- } ) ;
44
+ private static Task < int > DataAnnotationsValidation ( CommandContext ctx , ExecutionDelegate next )
45
+ {
46
+ var errors = ctx . InvocationPipeline . All
47
+ . SelectMany ( ValidateStep )
48
+ . ToList ( ) ;
57
49
58
- ctx . ShowHelpOnExit = ctx . AppConfig . Services . GetOrThrow < Config > ( ) . ShowHelpOnError ;
50
+ if ( errors . Any ( ) )
51
+ {
52
+ var console = ctx . Console ;
53
+ errors . ForEach ( error =>
54
+ {
55
+ console . Error . WriteLine ( error ) ;
56
+ } ) ;
59
57
60
- if ( ctx . ShowHelpOnExit )
61
- {
62
- console . Error . WriteLine ( ) ;
63
- }
58
+ ctx . ShowHelpOnExit = ctx . AppConfig . Services . GetOrThrow < Config > ( ) . ShowHelpOnError ;
64
59
65
- return ExitCodes . ValidationErrorAsync ;
60
+ if ( ctx . ShowHelpOnExit )
61
+ {
62
+ console . Error . WriteLine ( ) ;
66
63
}
67
-
68
- return next ( ctx ) ;
64
+
65
+ return ExitCodes . ValidationErrorAsync ;
69
66
}
67
+
68
+ return next ( ctx ) ;
69
+ }
70
70
71
- private static IEnumerable < string > ValidateStep ( InvocationStep step )
72
- {
73
- var phrasesToReplace = Resources . A
74
- . Error_DataAnnotation_phrases_to_replace_with_argument_name ( )
75
- . Split ( '|' ) ;
71
+ private static IEnumerable < string > ValidateStep ( InvocationStep step )
72
+ {
73
+ var phrasesToReplace = Resources . A
74
+ . Error_DataAnnotation_phrases_to_replace_with_argument_name ( )
75
+ . Split ( '|' ) ;
76
76
77
- var propertyArgumentErrors = step . Invocation . Arguments
78
- . Select ( argument => GetParameterArgumentErrors ( argument , phrasesToReplace ) ) ;
77
+ var propertyArgumentErrors = step . Invocation . Arguments
78
+ . Select ( argument => GetParameterArgumentErrors ( argument , phrasesToReplace ) ) ;
79
79
80
- var argumentModelErrors = step . Invocation . FlattenedArgumentModels
81
- . Select ( m => GetArgumentModelErrors ( m , step . Invocation . Arguments , phrasesToReplace ) ) ;
80
+ var argumentModelErrors = step . Invocation . FlattenedArgumentModels
81
+ . Select ( m => GetArgumentModelErrors ( m , step . Invocation . Arguments , phrasesToReplace ) ) ;
82
82
83
- return propertyArgumentErrors
84
- . Concat ( argumentModelErrors )
85
- . SelectMany ( e => e ) ;
86
- }
83
+ return propertyArgumentErrors
84
+ . Concat ( argumentModelErrors )
85
+ . SelectMany ( e => e ) ;
86
+ }
87
87
88
- private static IEnumerable < string > GetArgumentModelErrors ( IArgumentModel model ,
89
- IReadOnlyCollection < IArgument > arguments , string [ ] phrasesToReplace )
88
+ private static IEnumerable < string > GetArgumentModelErrors ( IArgumentModel model ,
89
+ IReadOnlyCollection < IArgument > arguments , string [ ] phrasesToReplace )
90
+ {
91
+ var validationContext = new ValidationContext ( model ) ;
92
+ var results = new List < ValidationResult > ( ) ;
93
+ if ( Validator . TryValidateObject ( model , validationContext , results , validateAllProperties : true ) )
90
94
{
91
- var validationContext = new ValidationContext ( model ) ;
92
- var results = new List < ValidationResult > ( ) ;
93
- if ( Validator . TryValidateObject ( model , validationContext , results , validateAllProperties : true ) )
94
- {
95
- return Enumerable . Empty < string > ( ) ;
96
- }
95
+ return [ ] ;
96
+ }
97
97
98
- IArgument ? GetArgument ( string propertyName )
98
+ return results
99
+ . Select ( SanitizedErrorMessage )
100
+ . Where ( m => m != null ) ! ;
101
+
102
+ IArgument ? GetArgument ( string propertyName )
103
+ {
104
+ return arguments . FirstOrDefault ( a =>
99
105
{
100
- return arguments . FirstOrDefault ( a =>
101
- {
102
- var info = a . Services . GetOrDefault < PropertyInfo > ( ) ;
103
- return info != null
104
- && ( info . DeclaringType ? . IsInstanceOfType ( model ) ?? false )
105
- && propertyName . Equals ( info . Name ) ;
106
- } ) ;
107
- }
106
+ var info = a . Services . GetOrDefault < PropertyInfo > ( ) ;
107
+ return info != null
108
+ && ( info . DeclaringType ? . IsInstanceOfType ( model ) ?? false )
109
+ && propertyName . Equals ( info . Name ) ;
110
+ } ) ;
111
+ }
108
112
109
- string ? SanitizedErrorMessage ( ValidationResult validationResult )
113
+ string ? SanitizedErrorMessage ( ValidationResult validationResult )
114
+ {
115
+ var errorMessage = validationResult . ErrorMessage ;
116
+ if ( errorMessage is not null )
110
117
{
111
- var errorMessage = validationResult . ErrorMessage ;
112
- if ( errorMessage is not null )
118
+ foreach ( var memberName in validationResult . MemberNames )
113
119
{
114
- foreach ( var memberName in validationResult . MemberNames )
120
+ var argument = GetArgument ( memberName ) ;
121
+ if ( argument is not null )
115
122
{
116
- var argument = GetArgument ( memberName ) ;
117
- if ( argument is { } )
118
- {
119
- errorMessage = errorMessage . RemoveFieldTerminology ( memberName , argument , phrasesToReplace ) ;
120
- }
123
+ errorMessage = errorMessage . RemoveFieldTerminology ( memberName , argument , phrasesToReplace ) ;
121
124
}
122
125
}
123
- return errorMessage ;
124
126
}
125
-
126
- return results
127
- . Select ( SanitizedErrorMessage )
128
- . Where ( m => m != null ) ! ;
127
+ return errorMessage ;
129
128
}
129
+ }
130
130
131
- private static IEnumerable < string > GetParameterArgumentErrors ( IArgument argument ,
132
- string [ ] phrasesToReplace )
131
+ private static IEnumerable < string > GetParameterArgumentErrors ( IArgument argument ,
132
+ string [ ] phrasesToReplace )
133
+ {
134
+ var parameterInfo = argument . Services . GetOrDefault < ParameterInfo > ( ) ;
135
+ if ( parameterInfo is null )
133
136
{
134
- var parameterInfo = argument . Services . GetOrDefault < ParameterInfo > ( ) ;
135
- if ( parameterInfo is null )
136
- {
137
- return Enumerable . Empty < string > ( ) ;
138
- }
137
+ return [ ] ;
138
+ }
139
139
140
- var validationAttributes = argument . CustomAttributes
141
- . GetCustomAttributes ( true )
142
- . OfType < ValidationAttribute > ( )
143
- . OrderBy ( a => a is RequiredAttribute ? 0 : 1 )
144
- . ToList ( ) ;
140
+ var validationAttributes = argument . CustomAttributes
141
+ . GetCustomAttributes ( true )
142
+ . OfType < ValidationAttribute > ( )
143
+ . OrderBy ( a => a is RequiredAttribute ? 0 : 1 )
144
+ . ToList ( ) ;
145
145
146
- List < string > ? errors = null ;
146
+ List < string > ? errors = null ;
147
147
148
- foreach ( var validationAttribute in validationAttributes )
148
+ foreach ( var validationAttribute in validationAttributes )
149
+ {
150
+ if ( ! validationAttribute . IsValid ( argument . Value ) )
149
151
{
150
- if ( ! validationAttribute . IsValid ( argument . Value ) )
152
+ // the user expects the name to map to an argument, not a field.
153
+ // update the terminology. This is naive and will need to change
154
+ // when we handle localization
155
+ var message = validationAttribute
156
+ . FormatErrorMessage ( argument . Name )
157
+ . RemoveFieldTerminology ( parameterInfo . Name , argument , phrasesToReplace ) ;
158
+ ( errors ??= [ ] ) . Add ( message ) ;
159
+
160
+ if ( validationAttribute is RequiredAttribute )
151
161
{
152
- // the user expects the name to map to an argument, not a field.
153
- // update the terminology. This is naive and will need to change
154
- // when we handle localization
155
- var message = validationAttribute
156
- . FormatErrorMessage ( argument . Name )
157
- . RemoveFieldTerminology ( parameterInfo . Name , argument , phrasesToReplace ) ;
158
- ( errors ??= new List < string > ( ) ) . Add ( message ) ;
159
-
160
- if ( validationAttribute is RequiredAttribute )
161
- {
162
- // If the value is not provided and it is required, no other validation needs to be performed.
163
- // this is why the RequiredAttribute is first.
164
- break ;
165
- }
162
+ // If the value is not provided and it is required, no other validation needs to be performed.
163
+ // this is why the RequiredAttribute is first.
164
+ break ;
166
165
}
167
166
}
168
-
169
- return errors ?? Enumerable . Empty < string > ( ) ;
170
167
}
168
+
169
+ return errors ?? Enumerable . Empty < string > ( ) ;
170
+ }
171
171
172
- /// <summary>the user expects the name to map to an argument, not a field.</summary>
173
- /// <remarks>
174
- /// This is naive and will need to change when we handle localization
175
- /// </remarks>
176
- private static string RemoveFieldTerminology ( this string error , string ? memberName , IArgument argument , IEnumerable < string > phrasesToReplace )
172
+ /// <summary>the user expects the name to map to an argument, not a field.</summary>
173
+ /// <remarks>
174
+ /// This is naive and will need to change when we handle localization
175
+ /// </remarks>
176
+ private static string RemoveFieldTerminology ( this string error , string ? memberName , IArgument argument , IEnumerable < string > phrasesToReplace )
177
+ {
178
+ memberName = argument . GetCustomAttribute < DisplayAttribute > ( ) ? . Name ?? memberName ;
179
+ if ( memberName is null )
177
180
{
178
- memberName = argument . GetCustomAttribute < DisplayAttribute > ( ) ? . Name ?? memberName ;
179
- if ( memberName is null )
180
- {
181
- return error ;
182
- }
183
-
184
- var argName = $ "'{ argument . Name } '";
185
- foreach ( var phrase in phrasesToReplace . Select ( p => string . Format ( p , memberName ) ) )
186
- {
187
- error = error . Replace ( phrase , argName ) ;
188
- }
189
181
return error ;
190
182
}
183
+
184
+ var argName = $ "'{ argument . Name } '";
185
+ foreach ( var phrase in phrasesToReplace . Select ( p => string . Format ( p , memberName ) ) )
186
+ {
187
+ error = error . Replace ( phrase , argName ) ;
188
+ }
189
+ return error ;
191
190
}
192
191
}
0 commit comments