Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…utobogus into feat/add-dateonly-timeonly
  • Loading branch information
soenneker committed Jan 28, 2024
2 parents eee668b + f2d4eed commit b637197
Show file tree
Hide file tree
Showing 18 changed files with 202 additions and 53 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
[![](https://img.shields.io/nuget/dt/soenneker.utils.autobogus.svg?style=for-the-badge)](https://www.nuget.org/packages/soenneker.utils.autobogus/)

# ![](https://user-images.githubusercontent.com/4441470/224455560-91ed3ee7-f510-4041-a8d2-3fc093025112.png) Soenneker.Utils.AutoBogus
### The .NET Bogus autogenerator
### The .NET Autogenerator

This project is a replacement for the abandoned [AutoBogus](https://github.com/nickdodd79/AutoBogus) library. It's mostly plug and play. It aims to be fast, and support the latest types in .NET.
This project is an automatic creator and populator for the fake data generator [Bogus](https://github.com/bchavez/Bogus).

It's a replacement for the abandoned [AutoBogus](https://github.com/nickdodd79/AutoBogus) library. It's mostly plug and play.

The goals are to be *fast*, and support the latest types in .NET.

.NET Standard 2.1 is required.

Expand Down
5 changes: 5 additions & 0 deletions src/Abstract/IAutoFakerBinder.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Reflection;
using Bogus;
using Soenneker.Utils.AutoBogus.Context;
Expand All @@ -17,6 +18,8 @@ public interface IAutoFakerBinder : IBinder
/// <returns>The created instance.</returns>
TType? CreateInstance<TType>(AutoFakerContext context);

object? CreateInstance(AutoFakerContext context, Type type);

/// <summary>
/// Populates the provided instance with generated values.
/// </summary>
Expand All @@ -29,4 +32,6 @@ public interface IAutoFakerBinder : IBinder
/// values are applied to the provided instance and not a copy.
/// </remarks>
void PopulateInstance<TType>(object instance, AutoFakerContext context, MemberInfo[]? members = null);

void PopulateInstance(object instance, AutoFakerContext context, Type type, MemberInfo[]? members = null);
}
101 changes: 101 additions & 0 deletions src/AutoFakerBinder.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using Soenneker.Reflection.Cache.Constructors;
Expand Down Expand Up @@ -50,6 +51,49 @@ public class AutoFakerBinder : Binder, IAutoFakerBinder

}

public object? CreateInstance(AutoFakerContext? context, Type type)
{
if (context == null)
return default;

CachedConstructor? constructor = GetConstructor(context.CachedType);

if (constructor == null)
return default;

ParameterInfo[] parametersInfo = constructor.GetParameters();
var parameters = new object[parametersInfo.Length];

for (int i = 0; i < parameters.Length; i++)
{
parameters[i] = GetParameterGenerator(type, parametersInfo[i], context).Generate(context);
}

return constructor.Invoke(parameters);
}

public virtual TType? CreateInstance<TType>(AutoFakerContext? context, Type type)
{
if (context == null)
return default;

CachedConstructor? constructor = GetConstructor(context.CachedType);

if (constructor == null)
return default;

ParameterInfo[] parametersInfo = constructor.GetParameters();
var parameters = new object[parametersInfo.Length];

for (int i = 0; i < parameters.Length; i++)
{
parameters[i] = GetParameterGenerator(type, parametersInfo[i], context).Generate(context);
}

return (TType?)constructor.Invoke(parameters);

}

/// <summary>
/// Populates the provided instance with generated values.
/// </summary>
Expand Down Expand Up @@ -120,6 +164,63 @@ public virtual void PopulateInstance<TType>(object? instance, AutoFakerContext?
}
}

public void PopulateInstance(object instance, AutoFakerContext context, Type type, MemberInfo[]? members = null)
{
// We can only populate non-null instances
if (instance == null || context == null)
{
return;
}

CachedType cachedType = CacheService.Cache.GetCachedType(type);

// Iterate the members and bind a generated value
List<AutoMember> autoMembers = GetMembersToPopulate(cachedType, members);

foreach (AutoMember? member in autoMembers)
{
if (member.Type != null)
{
// Check if the member has a skip config or the type has already been generated as a parent
// If so skip this generation otherwise track it for use later in the object tree
if (ShouldSkip(member.Type, $"{type.FullName}.{member.Name}", context))
{
continue;
}

context.Setup(type, member.Type, member.Name);

context.TypesStack.Push(member.Type);

// Generate a random value and bind it to the instance
IAutoFakerGenerator generator = GeneratorFactory.GetGenerator(context);
object value = generator.Generate(context);

try
{
if (!member.IsReadOnly)
{
member.Setter.Invoke(instance, value);
}
else if (member.CachedType.IsDictionary())
{
PopulateDictionary(value, instance, member);
}
else if (member.CachedType.IsCollection())
{
PopulateCollection(value, instance, member);
}
}
catch
{
}

// Remove the current type from the type stack so siblings can be created
context.TypesStack.Pop();
}
}
}

private static bool ShouldSkip(Type type, string path, AutoFakerContext context)
{
// Skip if the type is found
Expand Down
10 changes: 8 additions & 2 deletions src/Config/AutoFakerConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ internal sealed class AutoFakerConfig

internal List<string> SkipPaths { get; set; }

internal List<GeneratorOverride> Overrides { get; set; }
internal List<GeneratorOverride>? Overrides { get; set; }

public Func<AutoFakerContext, int?> TreeDepth { get; set; }

Expand All @@ -45,7 +45,10 @@ internal AutoFakerConfig()
SkipPaths = [];
Overrides = [];

Faker ??= new Faker(Locale);
if (Faker != null)
return;

Faker = new Faker(Locale);
}

internal AutoFakerConfig(AutoFakerConfig fakerConfig)
Expand All @@ -60,6 +63,9 @@ internal AutoFakerConfig(AutoFakerConfig fakerConfig)
SkipPaths = fakerConfig.SkipPaths;
Overrides = fakerConfig.Overrides;

if (Faker != null)
return;

Faker = fakerConfig.Faker ?? new Faker(Locale);
}
}
2 changes: 1 addition & 1 deletion src/Context/AutoFakerContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public sealed class AutoFakerContext

internal IAutoFakerBinder FakerBinder => AutoFakerConfig.FakerBinder;

internal List<GeneratorOverride> Overrides => AutoFakerConfig.Overrides;
internal List<GeneratorOverride>? Overrides => AutoFakerConfig.Overrides;

internal AutoFakerContext(AutoFakerConfig autoFakerConfig, Type? type = null)
{
Expand Down
1 change: 0 additions & 1 deletion src/Extensions/TypeExtension.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Dynamic;
using Soenneker.Reflection.Cache.Types;
using Soenneker.Utils.AutoBogus.Services;

namespace Soenneker.Utils.AutoBogus.Extensions;

Expand Down
58 changes: 30 additions & 28 deletions src/Generators/GeneratorFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,25 @@ internal static IAutoFakerGenerator GetGenerator(AutoFakerContext context)
{
IAutoFakerGenerator generator = ResolveGenerator(context);

// Check if any overrides are available for this generate request
var overrides = new List<GeneratorOverride>();
List<GeneratorOverride>? overrides = null;

foreach (GeneratorOverride? o in context.Overrides)
if (context.Overrides != null)
{
if (o.CanOverride(context))
overrides = [];

for (var i = 0; i < context.Overrides.Count; i++)
{
overrides.Add(o);
GeneratorOverride? o = context.Overrides[i];

if (o.CanOverride(context))
overrides.Add(o);
}
}

if (overrides.Count > 0)
{
return new GeneratorOverrideInvoker(generator, overrides);
}
if (overrides == null || overrides.Count == 0)
return generator;

return generator;
return new GeneratorOverrideInvoker(generator, overrides);
}

internal static IAutoFakerGenerator ResolveGenerator(AutoFakerContext context)
Expand All @@ -71,28 +73,17 @@ internal static IAutoFakerGenerator ResolveGenerator(AutoFakerContext context)
type = type.GetElementType();
}

// Check if an expando object needs to generator
// This actually means an existing dictionary needs to be populated
if (context.CachedType.IsExpandoObject())
{
return new ExpandoObjectGenerator();
}

// Do some type -> generator mapping
if (type.IsArray)
{
type = type.GetElementType();
return CreateGenericGenerator(typeof(ArrayGenerator<>), type);
}

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

if (DataSetGenerator.TryCreateGenerator(context.CachedType, out DataSetGenerator dataSetGenerator))
// Resolve the generator from the type
if (Generators.TryGetValue(type, out IAutoFakerGenerator? generator))
{
return dataSetGenerator;
return generator;
}

if (context.CachedType.IsEnum)
Expand All @@ -106,12 +97,19 @@ internal static IAutoFakerGenerator ResolveGenerator(AutoFakerContext context)
return CreateGenericGenerator(typeof(NullableGenerator<>), type);
}

// Check if an expando object needs to generator
// This actually means an existing dictionary needs to be populated
if (context.CachedType.IsExpandoObject())
{
return new ExpandoObjectGenerator();
}

(CachedType? collectionType, GenericCollectionType? genericCollectionType) = GenericTypeUtil.GetGenericCollectionType(context.CachedType);

if (collectionType != null)
{
// For generic types we need to interrogate the inner types
Type[] generics = collectionType.GetGenericArguments();
Type[] generics = collectionType.GetGenericArguments()!;

switch (genericCollectionType!.Name)
{
Expand Down Expand Up @@ -156,10 +154,14 @@ internal static IAutoFakerGenerator ResolveGenerator(AutoFakerContext context)
}
}

// Resolve the generator from the type
if (Generators.TryGetValue(type, out IAutoFakerGenerator? generator))
if (DataTableGenerator.TryCreateGenerator(context.CachedType, out DataTableGenerator dataTableGenerator))
{
return generator;
return dataTableGenerator;
}

if (DataSetGenerator.TryCreateGenerator(context.CachedType, out DataSetGenerator dataSetGenerator))
{
return dataSetGenerator;
}

return CreateGenericGenerator(typeof(TypeGenerator<>), type);
Expand Down
5 changes: 2 additions & 3 deletions src/Generators/Types/DataSetGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@

namespace Soenneker.Utils.AutoBogus.Generators.Types;

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

Expand Down
2 changes: 1 addition & 1 deletion src/Generators/Types/ListGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ object IAutoFakerGenerator.Generate(AutoFakerContext context)
{
list = (List<TType>)Activator.CreateInstance(context.GenerateType);
}
catch (Exception ex)
catch
{
list = [];
}
Expand Down
1 change: 0 additions & 1 deletion src/Generators/Types/ReadOnlyDictionaryGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using Soenneker.Utils.AutoBogus.Context;
using Soenneker.Utils.AutoBogus.Extensions;
using Soenneker.Utils.AutoBogus.Generators.Abstract;

namespace Soenneker.Utils.AutoBogus.Generators.Types;
Expand Down
26 changes: 25 additions & 1 deletion src/Generators/Types/TypeGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Soenneker.Utils.AutoBogus.Context;
using System;
using Soenneker.Utils.AutoBogus.Context;
using Soenneker.Utils.AutoBogus.Generators.Abstract;

namespace Soenneker.Utils.AutoBogus.Generators.Types;
Expand All @@ -15,6 +16,29 @@ object IAutoFakerGenerator.Generate(AutoFakerContext context)
// Populate the generated instance
context.FakerBinder.PopulateInstance<TType>(instance, context);

return instance;
}
}

internal sealed class TypeGenerator: IAutoFakerGenerator
{
readonly Type _type;

public TypeGenerator(Type type)
{
_type = type;
}

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
object? instance = context.FakerBinder.CreateInstance(context, _type);

// Populate the generated instance
context.FakerBinder.PopulateInstance(instance, context, _type);

return instance;
}
}
2 changes: 1 addition & 1 deletion src/Soenneker - Backup.Utils.AutoBogus.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Bogus" Version="35.3.1" />
<PackageReference Include="Bogus" Version="35.4.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
</ItemGroup>

Expand Down
Loading

0 comments on commit b637197

Please sign in to comment.