Skip to content

Commit

Permalink
Features should be configurable within other features (#10)
Browse files Browse the repository at this point in the history
* Stuff

* Cleanup And Tests

* Make Extension and Rename TConfig to TOptions
  • Loading branch information
pmdevers committed Mar 23, 2023
1 parent f3d2632 commit a483ffd
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 20 deletions.
13 changes: 3 additions & 10 deletions src/Featurize.AspNetCore/WebApplicationBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@

namespace Microsoft.AspNetCore.Builder;

/// <summary>
/// Extension methods for <see cref="WebApplicationBuilder"/>.
/// </summary>
public static class WebApplicationBuilderExtensions
/// <summary>/// Extension methods for <see cref="WebApplicationBuilder"/>./// </summary>public static class WebApplicationBuilderExtensions
{
/// <summary>Gets the <see cref="IFeatureCollection"/>.</summary>
[Pure]
Expand All @@ -25,15 +22,11 @@ public static IFeatureCollection Features(this WebApplicationBuilder application
return features;
}

/// <summary>
/// Builds the <see cref="WebApplication"/> with the registerd features.
/// </summary>
/// <param name="builder">The <see cref="WebApplicationBuilder"/>.</param>
/// <returns>Instance of <see cref="WebApplication"/>.</returns>
[Pure]
/// <summary> /// Builds the <see cref="WebApplication"/> with the registerd features. /// </summary> /// <param name="builder">The <see cref="WebApplicationBuilder"/>.</param> /// <returns>Instance of <see cref="WebApplication"/>.</returns> [Pure]
public static WebApplication BuildWithFeatures(this WebApplicationBuilder builder)
{
var features = builder.Features();

foreach (var feature in features.GetServiceCollectionFeatures())
{
feature.Configure(builder.Services);
Expand Down
75 changes: 75 additions & 0 deletions src/Featurize/ConfigFeatures/ConfigureFeatureWithOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Featurize.ConfigFeatures;

internal static class ConfigureFeatureWithOptions
{
private static Type[] GetConfigInterfaces(IFeature feature)
{
return feature.GetType().GetInterfaces().Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IConfigureFeature<,>)).ToArray();
}

internal static IFeatureCollection Configure(this IFeatureCollection features)
{
var results = features.Where(x => GetConfigInterfaces(x).Any())
.SelectMany(x => {
var interfaces = GetConfigInterfaces(x);
var list = new List<IFeatureWithOptions>();
foreach (var i in interfaces)
{
var con = Activator.CreateInstance(typeof(FeatureWithOptionsRunner<,>).MakeGenericType(i.GetGenericArguments()), x);
if (con is IFeatureWithOptions r)
list.Add(r);
}
return list.ToArray();
})
.ToArray();

foreach (var item in results)
{
item.ConfigureFeature(features);
}

return features;
}

internal static IFeature[] GetConfigurableFeatures(FeatureCollection features)
{
return features
.Where(x => {
var interfaces = x.GetType().GetInterfaces()
.Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IConfigureFeature<,>));
return interfaces.Any();
}).ToArray();
}
}

internal interface IFeatureWithOptions
{
void ConfigureFeature(IFeatureCollection features);
}

internal class FeatureWithOptionsRunner<TFeature, TOption> : IFeatureWithOptions
where TFeature : IFeatureWithConfigurableOptions<TOption>
where TOption : class
{
private readonly IConfigureFeature<TFeature, TOption> _configFeature;

public FeatureWithOptionsRunner(IConfigureFeature<TFeature, TOption> configFeature)
{
_configFeature = configFeature;
}

public void ConfigureFeature(IFeatureCollection features)
{
var feature = features.OfType<IFeatureWithConfigurableOptions<TOption>>().FirstOrDefault();
if(feature != null)
{
_configFeature.Configure(feature.Options);
}
}
}
18 changes: 15 additions & 3 deletions src/Featurize/FeatureCollection.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections;
using Featurize.ConfigFeatures;
using System.Collections;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
Expand All @@ -11,6 +12,8 @@ namespace Featurize;
[DebuggerDisplay("Count = {Count}")]
public sealed class FeatureCollection : IFeatureCollection
{
private bool _isConfigured = false;

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private static readonly EqualityComparer _comparer = new();

Expand Down Expand Up @@ -59,7 +62,7 @@ public IFeatureCollection Add(IFeature feature)
/// </returns>
[Pure]
public TFeature? Get<TFeature>() where TFeature : IFeature
=> _features.OfType<TFeature>().FirstOrDefault();
=> this.OfType<TFeature>().FirstOrDefault();

/// <summary>
/// Returns an enumerator that iterates through the collection.
Expand All @@ -68,7 +71,16 @@ public IFeatureCollection Add(IFeature feature)
/// An enumerator that can be used to iterate through the collection.
/// </returns>
[Pure]
public IEnumerator<IFeature> GetEnumerator() => _features.GetEnumerator();
public IEnumerator<IFeature> GetEnumerator()
{
if (!_isConfigured)
{
_isConfigured = true;
this.Configure();
}

return _features.GetEnumerator();
}

/// <summary>
/// Returns an enumerator that iterates through a collection.
Expand Down
16 changes: 16 additions & 0 deletions src/Featurize/IConfigureFeature.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Featurize;

/// <summary>
/// Marks a Feature to Configure another feature.
/// </summary>
/// <typeparam name="TFeature"></typeparam>
/// <typeparam name="TOptions"></typeparam>
public interface IConfigureFeature<TFeature, TOptions> : IFeature
where TFeature : IFeatureWithConfigurableOptions<TOptions>
{
/// <summary>
/// Method called to configure Dependent Feature.
/// </summary>
/// <param name="options"></param>
void Configure(TOptions options);
}
22 changes: 17 additions & 5 deletions src/Featurize/IFeatureWithOptions.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
namespace Featurize;

/// <summary>
/// Marks a feature that its options can be configured by other features
/// </summary>
/// <typeparam name="TOptions"></typeparam>
public interface IFeatureWithConfigurableOptions<TOptions>
{
/// <summary>
/// The configurable options of the feature.
/// </summary>
public TOptions Options { get; }
}

/// <summary>
/// Defines a configurable feature.
/// </summary>
/// <typeparam name="TSelf">The type of the feature self.</typeparam>
/// <typeparam name="TConfig">The type of the configuration.</typeparam>
/// <typeparam name="TOptions">The type of the configuration.</typeparam>
/// <seealso cref="Featurize.IFeature" />
public interface IFeatureWithOptions<TSelf, TConfig> : IFeature
public interface IFeatureWithOptions<TSelf, TOptions> : IFeature
where TSelf : IFeature
where TConfig : class
where TOptions : class
{
/// <summary>
/// Creates the specified feature with configuration.
/// </summary>
/// <param name="config">The configuration.</param>
/// <param name="options">The configuration.</param>
/// <returns>Returns a instance of the feature.</returns>
static abstract TSelf Create(TConfig config);
static abstract TSelf Create(TOptions options);
}

/// <summary>
Expand Down
5 changes: 4 additions & 1 deletion test/Featurize.Tests/AddWithOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ public class FeatureWithOptions : IFeatureWithOptions<FeatureWithOptions, Featur
{
public string Name { get; set; }

public FeatureOptions Options { get;}

private FeatureWithOptions(FeatureOptions options)
{
Name= options.Name;
Name = options.Name;
Options = options;
}

public static FeatureWithOptions Create(FeatureOptions config)
Expand Down
88 changes: 88 additions & 0 deletions test/Featurize.Tests/ConfigureOptionsFeatures.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
namespace Featurize.Tests;
public partial class Get
{
[Test]
public void Should_configure_options()
{
var features = new FeatureCollection();

features.AddWithOptions<FeatureWithConfigurableOptions, FeatureOptions>( x=>
{
x.Items.Add("First");
});
features.Add(new ConfigureOptionsFeature());
features.Add(new ConfigureOptionsFeature1());

var feature = features.Get<FeatureWithConfigurableOptions>();

feature.Should().NotBeNull();
feature!.Options.Items.Should().HaveCount(3);

}
}

public partial class GetEnumerator
{

[Test]
public void Should_Configure_Options()
{
var features = new FeatureCollection();
var feature = FeatureWithConfigurableOptions.Create(new FeatureOptions());
var config = new ConfigureOptionsFeature();

features.Add(feature);
features.Add(config);
//features.Add(new ConfigureOptionsFeature1());

_ = features.GetEnumerator();
_ = features.GetEnumerator();

config.IsCalled.Should().BeTrue();
feature.Options.Items.Should().HaveCount(1);
}
}



public class ConfigureOptionsFeature : IConfigureFeature<FeatureWithConfigurableOptions, FeatureOptions>
{
public bool IsCalled = false;
public void Configure(FeatureOptions options)
{
IsCalled= true;
options.Items.Add(GetType().Name);
}
}

public class ConfigureOptionsFeature1 : IConfigureFeature<FeatureWithConfigurableOptions, FeatureOptions>
{
public bool IsCalled = false;
public void Configure(FeatureOptions options)
{
IsCalled = true;
options.Items.Add(GetType().Name);
}
}

public class FeatureOptions
{
public HashSet<string> Items { get; set; } = new();
}

public class FeatureWithConfigurableOptions :
IFeatureWithConfigurableOptions<FeatureOptions>,
IFeatureWithOptions<FeatureWithConfigurableOptions, FeatureOptions>

{
public FeatureOptions Options { get; private set; }

public static FeatureWithConfigurableOptions Create(FeatureOptions config)
{
return new FeatureWithConfigurableOptions() {
Options = config
};
}
}


2 changes: 1 addition & 1 deletion test/Featurize.Tests/FeatureCollection_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Featurize.Tests;

public class Get
public partial class Get
{
[Test]
public void Should_Return_Feature()
Expand Down

0 comments on commit a483ffd

Please sign in to comment.