Skip to content

Commit 50c9127

Browse files
committed
revamped how properties are determined to be bindable. Existing tests all passed.
1 parent 983641e commit 50c9127

File tree

10 files changed

+386
-110
lines changed

10 files changed

+386
-110
lines changed

Binder.Tests/MiscTests.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,18 @@ public void StringHandling( CommandLineStyle style, string cmdLine, params strin
1616
var option = Options.Bind<MiscTarget, string?>( x => x.AStringValue, "x" );
1717
option.Should().NotBeNull();
1818

19-
var parser = new Parser( Options, LoggerFactory );
19+
var parser = new Parser( Options );
2020
parser.Parse( cmdLine ).Should().BeTrue();
2121

2222
Options.UnknownKeys.Should().BeEmpty();
2323
Options.UnkeyedValues.Should().BeEmpty();
2424

2525
option!.Values.Count.Should().Be( result.Length );
2626

27-
for( var idx = 0; idx < result.Length; idx++ ) option.Values[ idx ].Should().Be( result[ idx ] );
27+
for( var idx = 0; idx < result.Length; idx++ )
28+
{
29+
option.Values[ idx ].Should().Be( result[ idx ] );
30+
}
2831
}
2932
}
3033
}

Binder.Tests/ParserTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public void Parser( TestConfig config )
3737
{
3838
Initialize( config );
3939

40-
var parser = new Parser( Options, LoggerFactory );
40+
var parser = new Parser( Options );
4141

4242
parser.Options.CreateOptionsFromContextKeys( config.OptionConfigurations );
4343

Binder.Tests/support/BaseTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ protected void Bind<TTarget, TProp>( Expression<Func<TTarget, TProp>> propSelect
5858

5959
protected void ValidateTokenizing()
6060
{
61-
var parser = new Parser( Options, LoggerFactory );
61+
var parser = new Parser( Options );
6262
parser.Parse( TestConfig!.CommandLine ).Should().Be( Options.UnknownKeys.Count == 0 );
6363

6464
Options.UnknownKeys.Count.Should().Be( TestConfig.UnknownKeys );

Binder.Tests/support/CompositionRoot.cs

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,44 @@
88

99
namespace J4JSoftware.Binder.Tests
1010
{
11-
public class CompositionRoot : J4JCompositionRoot
11+
public class CompositionRoot : J4JCompositionRoot<J4JLoggerConfiguration>
1212
{
13-
public static CompositionRoot Default { get; } = new();
13+
static CompositionRoot()
14+
{
15+
Default = new CompositionRoot
16+
{
17+
UseConsoleLifetime = true,
18+
CachedLoggerScope = CachedLoggerScope.SingleInstance,
19+
LoggingSectionKey = string.Empty
20+
};
21+
22+
Default.ChannelInformation
23+
.AddChannel<DebugConfig>("Channels:Debug");
24+
25+
Default.Initialize();
26+
}
27+
28+
public static CompositionRoot Default { get; }
1429

1530
public Func<IJ4JLogger> LoggerFactory => () => Host!.Services.GetRequiredService<IJ4JLogger>();
1631

17-
protected override void ConfigureHostBuilder( IHostBuilder hostBuilder )
32+
protected override void SetupConfigurationEnvironment( IConfigurationBuilder builder )
1833
{
19-
hostBuilder.ConfigureAppConfiguration( ( hc, b ) =>
20-
b.SetBasePath( Environment.CurrentDirectory )
21-
.AddJsonFile( "appConfig.json" )
22-
);
34+
base.SetupConfigurationEnvironment( builder );
2335

24-
hostBuilder.ConfigureContainer<ContainerBuilder>( ConfigureDependencyInjection );
36+
builder.SetBasePath( Environment.CurrentDirectory )
37+
.AddJsonFile( "appConfig.json" );
2538
}
2639

27-
private void ConfigureDependencyInjection( HostBuilderContext context, ContainerBuilder builder )
40+
protected override void SetupDependencyInjection( HostBuilderContext hbc, ContainerBuilder builder )
2841
{
29-
var factory = new ChannelFactory( context.Configuration );
42+
base.SetupDependencyInjection( hbc, builder );
3043

31-
factory.AddChannel<DebugConfig>( "Channels:Debug" );
44+
var channelInfo = new ChannelInformation()
45+
.AddChannel<DebugConfig>( "Channels:Debug" );
3246

47+
var factory = new ChannelFactory( hbc.Configuration, channelInfo );
48+
3349
builder.RegisterJ4JLogging<J4JLoggerConfiguration>( factory );
3450
}
3551
}

Binder/options/IValidationEntry.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System.Collections.Generic;
2+
using System.Collections.ObjectModel;
3+
4+
namespace J4JSoftware.Configuration.CommandLine
5+
{
6+
public interface IValidationEntry
7+
{
8+
string EntryPath { get; }
9+
ReadOnlyCollection<ValidationError> Errors { get; }
10+
bool IsValid { get; }
11+
12+
ValidationEntry<TEntry> CreateChild<TEntry>( TEntry toValidate, string? hint = null );
13+
}
14+
}

Binder/options/OptionCollection.cs

Lines changed: 120 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,12 @@ public IOption Add( string contextPath )
104104
case MemberExpression memExpr:
105105
var propInfo = (PropertyInfo) memExpr.Member;
106106

107-
if( !ValidateProperty( propInfo, out var curStyle ) )
108-
{
109-
_logger?.Error<string>( "Property '{0}' is invalid", propInfo.Name );
107+
// the first PropertyInfo, which is the outermost 'leaf', must
108+
// have a public parameterless constructor and a property setter
109+
if( !ValidatePropertyInfo( propInfo, firstStyle == null ) )
110110
return null;
111-
}
112111

113-
firstStyle ??= curStyle;
112+
firstStyle ??= propInfo.GetOptionStyle();
114113

115114
propElements.Add( propInfo );
116115

@@ -124,13 +123,12 @@ public IOption Add( string contextPath )
124123
{
125124
var propInfo2 = (PropertyInfo) unaryMemExpr.Member;
126125

127-
if( !ValidateProperty( propInfo2, out var curStyle2 ) )
128-
{
129-
_logger?.Error<string>( "Property '{0}' is invalid", propInfo2.Name );
126+
// the first PropertyInfo, which is the outermost 'leaf', must
127+
// have a public parameterless constructor and a property setter
128+
if (!ValidatePropertyInfo(propInfo2, firstStyle == null))
130129
return null;
131-
}
132130

133-
firstStyle ??= curStyle2;
131+
firstStyle ??= propInfo2.GetOptionStyle();
134132

135133
propElements.Add( propInfo2 );
136134
}
@@ -161,6 +159,33 @@ public IOption Add( string contextPath )
161159
return retVal;
162160
}
163161

162+
private bool ValidatePropertyInfo( PropertyInfo propInfo, bool isOuterMostLeaf = false )
163+
{
164+
var piContext = new ValidationContext(propInfo);
165+
166+
var piEntry = ((ValidationEntry<PropertyInfo>)piContext.Current)
167+
.HasSupportedGetter();
168+
169+
var typeEntry = piEntry.CreateChild(piEntry.Value.PropertyType)
170+
.IsSupportedType();
171+
172+
if( isOuterMostLeaf )
173+
{
174+
piEntry.HasSupportedSetter();
175+
typeEntry.HasRequiredConstructor();
176+
}
177+
178+
if( piContext.IsValid )
179+
return true;
180+
181+
foreach (var error in piContext.Errors)
182+
{
183+
_logger?.Error(error.Error);
184+
}
185+
186+
return false;
187+
}
188+
164189
// determines whether or not a key is being used by an existing option, honoring whatever
165190
// case sensitivity is in use
166191
public bool UsesCommandLineKey( string key )
@@ -195,111 +220,111 @@ IEnumerator IEnumerable.GetEnumerator()
195220
return GetEnumerator();
196221
}
197222

198-
private bool ValidateProperty( PropertyInfo propInfo, out OptionStyle? style )
199-
{
200-
style = null;
223+
//private bool ValidateProperty( PropertyInfo propInfo, out OptionStyle? style )
224+
//{
225+
// style = null;
201226

202-
if( !ValidateAccessMethod( propInfo.GetMethod, propInfo.Name, 0 ) )
203-
return false;
227+
// if( !ValidateAccessMethod( propInfo.GetMethod, propInfo.Name, 0 ) )
228+
// return false;
204229

205-
if( !ValidateAccessMethod( propInfo.SetMethod, propInfo.Name, 1 ) )
206-
return false;
230+
// if( !ValidateAccessMethod( propInfo.SetMethod, propInfo.Name, 1 ) )
231+
// return false;
207232

208-
if( propInfo.PropertyType.IsEnum )
209-
{
210-
style = HasAttribute<FlagsAttribute>( propInfo.PropertyType )
211-
? OptionStyle.ConcatenatedSingleValue
212-
: OptionStyle.SingleValued;
233+
// if( propInfo.PropertyType.IsEnum )
234+
// {
235+
// style = HasAttribute<FlagsAttribute>( propInfo.PropertyType )
236+
// ? OptionStyle.ConcatenatedSingleValue
237+
// : OptionStyle.SingleValued;
213238

214-
return true;
215-
}
239+
// return true;
240+
// }
216241

217-
if( propInfo.PropertyType.IsGenericType )
218-
{
219-
if( ValidateGenericType( propInfo.PropertyType, out var innerStyle ) )
220-
style = innerStyle;
242+
// if( propInfo.PropertyType.IsGenericType )
243+
// {
244+
// if( ValidateGenericType( propInfo.PropertyType, out var innerStyle ) )
245+
// style = innerStyle;
221246

222-
return style != null;
223-
}
247+
// return style != null;
248+
// }
224249

225-
if( !ValidateType( propInfo.PropertyType ) )
226-
return false;
250+
// if( !ValidateType( propInfo.PropertyType ) )
251+
// return false;
227252

228-
style = propInfo.PropertyType.IsArray
229-
? OptionStyle.Collection
230-
: typeof(bool).IsAssignableFrom( propInfo.PropertyType )
231-
? OptionStyle.Switch
232-
: OptionStyle.SingleValued;
253+
// style = propInfo.PropertyType.IsArray
254+
// ? OptionStyle.Collection
255+
// : typeof(bool).IsAssignableFrom( propInfo.PropertyType )
256+
// ? OptionStyle.Switch
257+
// : OptionStyle.SingleValued;
233258

234-
return true;
235-
}
259+
// return true;
260+
//}
236261

237-
private bool ValidateGenericType( Type genType, out OptionStyle? style )
238-
{
239-
style = null;
262+
//private bool ValidateGenericType( Type genType, out OptionStyle? style )
263+
//{
264+
// style = null;
240265

241-
if( genType.GenericTypeArguments.Length != 1 )
242-
{
243-
_logger?.Error<string>( "Generic type '{0}' does not have just one generic Type argument",
244-
genType.Name );
266+
// if( genType.GenericTypeArguments.Length != 1 )
267+
// {
268+
// _logger?.Error<string>( "Generic type '{0}' does not have just one generic Type argument",
269+
// genType.Name );
245270

246-
return false;
247-
}
271+
// return false;
272+
// }
248273

249-
if( !ValidateType( genType.GenericTypeArguments[ 0 ] ) )
250-
return false;
274+
// if( !ValidateType( genType.GenericTypeArguments[ 0 ] ) )
275+
// return false;
251276

252-
if( !typeof(List<>).MakeGenericType( genType.GenericTypeArguments[ 0 ] ).IsAssignableFrom( genType ) )
253-
{
254-
_logger?.Error( "Generic type '{0}' is not a List<> type", genType );
255-
return false;
256-
}
277+
// if( !typeof(List<>).MakeGenericType( genType.GenericTypeArguments[ 0 ] ).IsAssignableFrom( genType ) )
278+
// {
279+
// _logger?.Error( "Generic type '{0}' is not a List<> type", genType );
280+
// return false;
281+
// }
257282

258-
style = OptionStyle.Collection;
283+
// style = OptionStyle.Collection;
259284

260-
return true;
261-
}
285+
// return true;
286+
//}
262287

263-
private bool ValidateType( Type toCheck )
264-
{
265-
if( toCheck.IsGenericType )
266-
return false;
288+
//private bool ValidateType( Type toCheck )
289+
//{
290+
// if( toCheck.IsGenericType )
291+
// return false;
267292

268-
if( toCheck.IsArray )
269-
return ValidateType( toCheck.GetElementType()! );
293+
// if( toCheck.IsArray )
294+
// return ValidateType( toCheck.GetElementType()! );
270295

271-
if( toCheck.IsValueType
272-
|| typeof(string).IsAssignableFrom( toCheck )
273-
|| toCheck.GetConstructors().Any( c => c.GetParameters().Length == 0 ) )
274-
return true;
296+
// if( toCheck.IsValueType
297+
// || typeof(string).IsAssignableFrom( toCheck )
298+
// || toCheck.GetConstructors().Any( c => c.GetParameters().Length == 0 ) )
299+
// return true;
275300

276-
_logger?.Error( "Unsupported type '{0}'", toCheck );
301+
// _logger?.Error( "Unsupported type '{0}'", toCheck );
277302

278-
return false;
279-
}
303+
// return false;
304+
//}
280305

281-
private bool ValidateAccessMethod( MethodInfo? methodInfo, string propName, int allowedParams )
282-
{
283-
if( methodInfo == null )
284-
{
285-
_logger?.Error<string>( "Property '{0}' does not have a get or set method", propName );
286-
return false;
287-
}
306+
//private bool ValidateAccessMethod( MethodInfo? methodInfo, string propName, int allowedParams )
307+
//{
308+
// if( methodInfo == null )
309+
// {
310+
// _logger?.Error<string>( "Property '{0}' does not have a get or set method", propName );
311+
// return false;
312+
// }
288313

289-
if( !methodInfo.IsPublic )
290-
{
291-
_logger?.Error<string, string>( "Property '{0}::{1}' is not bindable", propName, methodInfo.Name );
292-
return false;
293-
}
314+
// if( !methodInfo.IsPublic )
315+
// {
316+
// _logger?.Error<string, string>( "Property '{0}::{1}' is not bindable", propName, methodInfo.Name );
317+
// return false;
318+
// }
294319

295-
if( methodInfo.GetParameters().Length > allowedParams )
296-
{
297-
_logger?.Error<string>( "Property '{0}::{1}' is indexed", propName, methodInfo.Name );
298-
return false;
299-
}
320+
// if( methodInfo.GetParameters().Length > allowedParams )
321+
// {
322+
// _logger?.Error<string>( "Property '{0}::{1}' is indexed", propName, methodInfo.Name );
323+
// return false;
324+
// }
300325

301-
return true;
302-
}
326+
// return true;
327+
//}
303328

304329
private IEnumerable<string> ValidateCommandLineKeys( string[] cmdLineKeys )
305330
{
@@ -325,11 +350,11 @@ private static string GetContextPath( List<PropertyInfo> propElements )
325350
);
326351
}
327352

328-
private static bool HasAttribute<TAttr>( Type toCheck )
329-
where TAttr : Attribute
330-
{
331-
return toCheck.GetCustomAttribute<TAttr>() != null;
332-
}
353+
//private static bool HasAttribute<TAttr>( Type toCheck )
354+
// where TAttr : Attribute
355+
//{
356+
// return toCheck.GetCustomAttribute<TAttr>() != null;
357+
//}
333358

334359
private class TypeBoundOptionComparer : IEqualityComparer<ITypeBoundOption>
335360
{

0 commit comments

Comments
 (0)