Skip to content

Commit

Permalink
Allows for better binding control (skipping private fields, etc)
Browse files Browse the repository at this point in the history
Consolidates some CacheService, removes some usage of static CacheService
Exposes ReflectionCacheOptions
Better setup for Config on AutoFaker, Initialize added for external use (mostly just tests I imagine)
Tests for ReflectionCacheOptions
  • Loading branch information
soenneker committed Jun 1, 2024
1 parent c04651b commit e724a45
Show file tree
Hide file tree
Showing 20 changed files with 195 additions and 72 deletions.
11 changes: 9 additions & 2 deletions src/Abstract/IAutoFaker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Bogus;
using Soenneker.Utils.AutoBogus.Config;
using Soenneker.Utils.AutoBogus.Config.Abstract;
using Soenneker.Utils.AutoBogus.Services;

namespace Soenneker.Utils.AutoBogus.Abstract;

Expand All @@ -11,12 +12,18 @@ namespace Soenneker.Utils.AutoBogus.Abstract;
/// </summary>
public interface IAutoFaker
{
AutoFakerConfig Config { get; set; }
AutoFakerConfig Config { get; }

AutoFakerBinder Binder { get; set; }
AutoFakerBinder? Binder { get; set; }

Faker Faker { get; set; }

/// <summary>
/// This forces the final initialization that happens right before <see cref="Generate(Type)"/>. It typically does not need to be called!<para/>
/// It's available however when the underlying <see cref="AutoFakerBinder"/> or <see cref="CacheService"/> needs accessing.
/// </summary>
void Initialize();

/// <summary>
/// Generates an instance of type <typeparamref name="TType"/>.
/// </summary>
Expand Down
24 changes: 15 additions & 9 deletions src/AutoFaker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ public sealed class AutoFaker : IAutoFaker
{
public AutoFakerConfig Config { get; set; }

public AutoFakerBinder Binder { get; set; }
public AutoFakerBinder? Binder { get; set; }

public Faker Faker { get; set; }

internal CacheService CacheService { get; set; }
internal CacheService? CacheService { get; private set; }

private readonly Lazy<MethodInfo> _nonTypeParameterMethod = new(() =>
{
Expand All @@ -40,9 +40,6 @@ public AutoFaker(AutoFakerConfig? autoFakerConfig = null)
Config = new AutoFakerConfig();
else
Config = autoFakerConfig;

Binder = new AutoFakerBinder(Config);
CacheService = new CacheService();
}

public AutoFaker(Action<IAutoGenerateConfigBuilder>? configure)
Expand All @@ -57,25 +54,34 @@ public AutoFaker(Action<IAutoGenerateConfigBuilder>? configure)

configure.Invoke(builder);
}
}

Binder = new AutoFakerBinder(Config);
CacheService = new CacheService();
public void Initialize()
{
CacheService ??= new CacheService(Config.ReflectionCacheOptions);
Binder ??= new AutoFakerBinder(Config, CacheService);
}

public TType Generate<TType>()
{
Initialize();

var context = new AutoFakerContext(this);
return context.Generate<TType>()!;
}

public List<TType> Generate<TType>(int count)
{
Initialize();

var context = new AutoFakerContext(this);
return context.GenerateMany<TType>(count);
}

public object Generate(Type type)
{
Initialize();

// TODO: Optimize
MethodInfo method = _nonTypeParameterMethod.Value.MakeGenericMethod(type);

Expand All @@ -95,7 +101,7 @@ public void Configure(Action<IAutoFakerDefaultConfigBuilder> configure)

/// <summary>
/// Generates an instance of type <typeparamref name="TType"/>. <para/>
/// ⚠️ This creates a new Bogus.Faker on each call (expensive); use one AutoFaker across your context.
/// ⚠️ This creates a new Bogus.Faker on each call (expensive); use one AutoFaker across your context if possible.
/// </summary>
/// <typeparam name="TType">The type of instance to generate.</typeparam>
/// <param name="configure">A handler to build the generate request configuration.</param>
Expand All @@ -108,7 +114,7 @@ public static TType GenerateStatic<TType>(Action<IAutoGenerateConfigBuilder>? co

/// <summary>
/// Generates a collection of instances of type <typeparamref name="TType"/>. <para/>
/// ⚠️ This creates a new Bogus.Faker on each call (expensive); use one AutoFaker across your context.
/// ⚠️ This creates a new Bogus.Faker on each call (expensive); use one AutoFaker across your context if possible.
/// </summary>
/// <typeparam name="TType">The type of instance to generate.</typeparam>
/// <param name="count">The number of instances to generate.</param>
Expand Down
8 changes: 6 additions & 2 deletions src/AutoFakerBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,14 @@ public AutoFakerBinder(AutoFakerConfig autoFakerConfig, CacheService? cacheServi
_autoFakerConfig = autoFakerConfig;

if (cacheService != null)
{
_cacheService = cacheService;
}
else
_cacheService = new CacheService();
{
// This should only happen in tests
_cacheService = new CacheService(autoFakerConfig.ReflectionCacheOptions);
}

GeneratorService = new GeneratorService();
}
Expand All @@ -57,7 +62,6 @@ public AutoFakerBinder(AutoFakerConfig autoFakerConfig, CacheService? cacheServi

CachedConstructor? constructor = GetConstructor(context.CachedType);


if (context.RecursiveConstructorStack.Count(c => c == context.CachedType.CacheKey) >= _autoFakerConfig.RecursiveDepth)
{
context.RecursiveConstructorStack.Pop();
Expand Down
19 changes: 13 additions & 6 deletions src/AutoFaker{T}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,20 @@ namespace Soenneker.Utils.AutoBogus;
/// <typeparam name="TType">The type of instance to generate.</typeparam>
public class AutoFaker<TType> : Faker<TType> where TType : class
{
public AutoFakerConfig? Config { get; set; }
public AutoFakerConfig Config { get; set; }

/// <summary>
/// The <see cref="AutoFakerBinder"/> instance to use for the generation request.
/// </summary>
public AutoFakerBinder Binder { get; set; }
public AutoFakerBinder? Binder { get; set; }

private bool _createInitialized;

private bool _finishInitialized;

private readonly Func<Faker, TType> _defaultCreateAction;

private readonly CacheService _cacheService;
private CacheService? _cacheService;

public AutoFaker(AutoFakerConfig? autoFakerConfig = null)
{
Expand All @@ -39,9 +39,6 @@ public AutoFaker(AutoFakerConfig? autoFakerConfig = null)
else
Config = autoFakerConfig;

Binder = new AutoFakerBinder(Config);
_cacheService = new CacheService();

Locale = Config.Locale;
binder = new Binder();

Expand All @@ -51,13 +48,21 @@ public AutoFaker(AutoFakerConfig? autoFakerConfig = null)
CreateActions[currentRuleSet] = null;
}

public void Initialize()
{
_cacheService ??= new CacheService(Config.ReflectionCacheOptions);
Binder ??= new AutoFakerBinder(Config, _cacheService);
}

/// <summary>
/// Generates an instance of type <typeparamref name="TType"/>.
/// </summary>
/// <param name="ruleSets">An optional list of delimited rule sets to use for the generate request.</param>
/// <returns>The generated instance of type <typeparamref name="TType"/>.</returns>
public override TType Generate(string? ruleSets = null)
{
Initialize();

AutoFakerContext context = CreateContext(ruleSets);

PrepareCreate(context);
Expand All @@ -74,6 +79,8 @@ public override TType Generate(string? ruleSets = null)
/// <returns>The collection of generated instances of type <typeparamref name="TType"/>.</returns>
public override List<TType> Generate(int count, string? ruleSets = null)
{
Initialize();

AutoFakerContext context = CreateContext(ruleSets);

PrepareCreate(context);
Expand Down
3 changes: 3 additions & 0 deletions src/Config/AutoFakerConfig.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Soenneker.Reflection.Cache.Options;
using Soenneker.Utils.AutoBogus.Generators;

namespace Soenneker.Utils.AutoBogus.Config;
Expand Down Expand Up @@ -31,6 +32,8 @@ public sealed class AutoFakerConfig
// ReSharper disable once FieldCanBeMadeReadOnly.Global
public DateTimeKind DateTimeKind;

public ReflectionCacheOptions? ReflectionCacheOptions;

public AutoFakerConfig()
{
Locale = AutoFakerDefaultConfigOptions.Locale;
Expand Down
4 changes: 2 additions & 2 deletions src/Generators/AutoFakerGeneratorFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,12 @@ internal static IAutoFakerGenerator CreateGenerator(AutoFakerContext context)
}
}

if (BaseDataTableGenerator.TryCreateGenerator(context.CachedType, out BaseDataTableGenerator dataTableGenerator))
if (BaseDataTableGenerator.TryCreateGenerator(context, context.CachedType, out BaseDataTableGenerator dataTableGenerator))
{
return dataTableGenerator;
}

if (BaseDataSetGenerator.TryCreateGenerator(context.CachedType, out BaseDataSetGenerator dataSetGenerator))
if (BaseDataSetGenerator.TryCreateGenerator(context, context.CachedType, out BaseDataSetGenerator dataSetGenerator))
{
return dataSetGenerator;
}
Expand Down
4 changes: 2 additions & 2 deletions src/Generators/Types/DataSets/Base/BaseDataSetGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ namespace Soenneker.Utils.AutoBogus.Generators.Types.DataSets.Base;

internal abstract class BaseDataSetGenerator : IAutoFakerGenerator
{
public static bool TryCreateGenerator(CachedType dataSetType, out BaseDataSetGenerator? generator)
public static bool TryCreateGenerator(AutoFakerContext? context, CachedType dataSetType, out BaseDataSetGenerator? generator)
{
generator = default;

CachedType cachedDataSetType = StaticCacheService.Cache.GetCachedType(typeof(DataSet));
CachedType cachedDataSetType = context.CacheService.Cache.GetCachedType(typeof(DataSet));

if (dataSetType.Type == cachedDataSetType.Type)
generator = new UntypedDataSetGenerator();
Expand Down
2 changes: 1 addition & 1 deletion src/Generators/Types/DataSets/TypedDataSetGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public override object Generate(AutoFakerContext context)

CachedType cachedType = context.CacheService.Cache.GetCachedType(table.GetType());

if (!BaseDataTableGenerator.TryCreateGenerator(cachedType, out BaseDataTableGenerator? tableGenerator))
if (!BaseDataTableGenerator.TryCreateGenerator(context,cachedType, out BaseDataTableGenerator? tableGenerator))
throw new Exception($"Couldn't create generator for typed table type {table.GetType()}");

populatedTables.Add(table);
Expand Down
2 changes: 1 addition & 1 deletion src/Generators/Types/DataSets/UntypedDataSetGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public override object Generate(AutoFakerContext context)

CachedType cachedDataTableType = context.CacheService.Cache.GetCachedType(typeof(DataTable));

if (!BaseDataTableGenerator.TryCreateGenerator(cachedDataTableType, out BaseDataTableGenerator? tableGenerator))
if (!BaseDataTableGenerator.TryCreateGenerator(context, cachedDataTableType, out BaseDataTableGenerator? tableGenerator))
throw new Exception("Internal error: Couldn't create generator for DataTable");

for (int tableCount = context.Faker.Random.Int(2, 6); tableCount > 0; tableCount--)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public object Generate(AutoFakerContext context)
return table;
}

public static bool IsTypedDataTableType(CachedType cachedType, out Type rowType)
public static bool IsTypedDataTableType(AutoFakerContext context, CachedType cachedType, out Type rowType)
{
rowType = default;

Expand All @@ -39,19 +39,19 @@ public static bool IsTypedDataTableType(CachedType cachedType, out Type rowType)
if (cachedType.Type.BaseType == null)
break;

cachedType = StaticCacheService.Cache.GetCachedType(cachedType.Type.BaseType);
cachedType = context.CacheService.Cache.GetCachedType(cachedType.Type.BaseType);
}

return false;
}

public static bool TryCreateGenerator(CachedType tableType, out BaseDataTableGenerator generator)
public static bool TryCreateGenerator(AutoFakerContext context, CachedType tableType, out BaseDataTableGenerator generator)
{
generator = default;

if (tableType.Type == typeof(DataTable))
generator = new UntypedDataTableGenerator();
else if (IsTypedDataTableType(tableType, out Type? rowType))
else if (IsTypedDataTableType(context, tableType, out Type? rowType))
{
Type generatorType = typeof(TypedDataTableGenerator<,>).MakeGenericType(tableType.Type, rowType);

Expand Down
6 changes: 3 additions & 3 deletions src/Generators/Types/TypeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ namespace Soenneker.Utils.AutoBogus.Generators.Types;

internal sealed class TypeGenerator<TType> : IAutoFakerGenerator
{
object IAutoFakerGenerator.Generate(AutoFakerContext context)
object? IAutoFakerGenerator.Generate(AutoFakerContext context)
{
// Note that all instances are converted to object to cater for boxing and struct population
// When setting a value via reflection on a struct a copy is made
// This means the changes are applied to a different instance to the one created here
CachedType cachedType = context.CacheService.Cache.GetCachedType(typeof(TType));

object instance = context.Binder.CreateInstance<TType>(context, cachedType);
object? instance = context.Binder.CreateInstance<TType>(context, cachedType);

if (instance == null)
return null!;
return null;

// Populate the generated instance
context.Binder.PopulateInstance<TType>(instance, context, cachedType);
Expand Down
15 changes: 12 additions & 3 deletions src/Services/CacheService.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
using System;
using Soenneker.Reflection.Cache;
using Soenneker.Reflection.Cache.Options;

namespace Soenneker.Utils.AutoBogus.Services;

public class CacheService
public sealed class CacheService
{
internal ReflectionCache Cache => _cacheLazy.Value;

private Lazy<ReflectionCache> _cacheLazy = new(() => new ReflectionCache(), true);
private Lazy<ReflectionCache> _cacheLazy;

private readonly ReflectionCacheOptions? _reflectionCacheOptions;

public CacheService(ReflectionCacheOptions? reflectionCacheOptions = null)
{
_reflectionCacheOptions = reflectionCacheOptions;
_cacheLazy = new Lazy<ReflectionCache>(() => new ReflectionCache(_reflectionCacheOptions, true), true);
}

internal void ClearCache()
{
_cacheLazy = new Lazy<ReflectionCache>(() => new ReflectionCache(), true);
_cacheLazy = new Lazy<ReflectionCache>(() => new ReflectionCache(_reflectionCacheOptions, true), true);
}
}
Loading

0 comments on commit e724a45

Please sign in to comment.