Skip to content

Commit

Permalink
Support generators with parameters (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
Bardin08 authored Jun 22, 2024
2 parents 9228498 + 9c55bc2 commit b06f8b9
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 45 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace DbSeeder.Data.Bogus;

public record BogusGenerator(
public record BogusGeneratorDescriptor(
string Category,
string GeneratorIdentifier,
Type ReturnType,
Expand Down
30 changes: 15 additions & 15 deletions DbSeeder/Data/Bogus/BogusUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ public static class BogusUtilities
}
};

public static Dictionary<string, List<BogusGenerator>> GetBogusGenerators()
public static Dictionary<string, List<BogusGeneratorDescriptor>> GetBogusGenerators()
{
var generators = new Dictionary<string, List<BogusGenerator>>();
var generators = new Dictionary<string, List<BogusGeneratorDescriptor>>();

var fakerType = typeof(Faker);

Expand All @@ -44,7 +44,7 @@ public static Dictionary<string, List<BogusGenerator>> GetBogusGenerators()
foreach (var m in methods)
{
var methodParams = m.GetParameters().ToDictionary(x => x.Name!, x => x.ParameterType);
var generator = new BogusGenerator(
var generator = new BogusGeneratorDescriptor(
generatorCategory,
generatorCategory + m.Name.ToLower(),
m.ReturnType,
Expand All @@ -57,12 +57,12 @@ public static Dictionary<string, List<BogusGenerator>> GetBogusGenerators()
return generators;
}

public static Dictionary<string, List<BogusGenerator>> GetFiltersForReturnType(
this Dictionary<string, List<BogusGenerator>> src, string returnType)
public static Dictionary<string, List<BogusGeneratorDescriptor>> GetFiltersForReturnType(
this Dictionary<string, List<BogusGeneratorDescriptor>> src, string returnType)
{
var allowed = AllowedTypes[returnType];

var allowedGenerators = new Dictionary<string, List<BogusGenerator>>();
var allowedGenerators = new Dictionary<string, List<BogusGeneratorDescriptor>>();
foreach (var (category, generators) in src)
{
allowedGenerators.Add(category, []);
Expand All @@ -78,12 +78,12 @@ public static Dictionary<string, List<BogusGenerator>> GetFiltersForReturnType(
return allowedGenerators;
}

public static dynamic? Generate(BogusGenerator generator)
public static dynamic? Generate(BogusGeneratorDescriptor generatorDescriptor)
{
var faker = new Faker();
var generationMethod = generator.GeneratorIdentifier[generator.Category.Length..];
var generationMethod = generatorDescriptor.GeneratorIdentifier[generatorDescriptor.Category.Length..];

var generatorProperty = faker.GetType().GetProperty(generator.Category,
var generatorProperty = faker.GetType().GetProperty(generatorDescriptor.Category,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
if (generatorProperty != null)
{
Expand All @@ -92,24 +92,24 @@ public static Dictionary<string, List<BogusGenerator>> GetFiltersForReturnType(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
if (generatorMethod != null)
{
var @params = (object[])null!;

var parameters = generatorMethod.GetParameters();
if (parameters.Length > 0)
{
// TODO[#26]: Implement generators with params
throw new NotImplementedException("This feature is currently not implemented. " +
"Only parameterless generators can be used");
@params = ParamsGenerator.GetParams(generatorDescriptor.GeneratorIdentifier);
}

// TODO[#27]: Implement constraints handling

Check warning on line 103 in DbSeeder/Data/Bogus/BogusUtilities.cs

View workflow job for this annotation

GitHub Actions / main

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)

Check warning on line 103 in DbSeeder/Data/Bogus/BogusUtilities.cs

View workflow job for this annotation

GitHub Actions / main

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)

Check warning on line 103 in DbSeeder/Data/Bogus/BogusUtilities.cs

View workflow job for this annotation

GitHub Actions / main

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)

Check warning on line 103 in DbSeeder/Data/Bogus/BogusUtilities.cs

View workflow job for this annotation

GitHub Actions / main

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)

Check warning on line 103 in DbSeeder/Data/Bogus/BogusUtilities.cs

View workflow job for this annotation

GitHub Actions / main

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)

Check warning on line 103 in DbSeeder/Data/Bogus/BogusUtilities.cs

View workflow job for this annotation

GitHub Actions / main

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)

Check warning on line 103 in DbSeeder/Data/Bogus/BogusUtilities.cs

View workflow job for this annotation

GitHub Actions / main

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)

Check warning on line 103 in DbSeeder/Data/Bogus/BogusUtilities.cs

View workflow job for this annotation

GitHub Actions / main

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)

Check warning on line 103 in DbSeeder/Data/Bogus/BogusUtilities.cs

View workflow job for this annotation

GitHub Actions / main

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)

Check warning on line 103 in DbSeeder/Data/Bogus/BogusUtilities.cs

View workflow job for this annotation

GitHub Actions / main

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)

Check warning on line 103 in DbSeeder/Data/Bogus/BogusUtilities.cs

View workflow job for this annotation

GitHub Actions / main

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)

Check warning on line 103 in DbSeeder/Data/Bogus/BogusUtilities.cs

View workflow job for this annotation

GitHub Actions / main

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)

Check warning on line 103 in DbSeeder/Data/Bogus/BogusUtilities.cs

View workflow job for this annotation

GitHub Actions / main

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)
var result = generatorMethod.Invoke(categoryGenerator, null);
var result = generatorMethod.Invoke(categoryGenerator, @params);
return Convert.ChangeType(result, generatorMethod.ReturnType)!;
}

Console.WriteLine($"Method '{generationMethod}' not found in '{generator.Category}' category");
Console.WriteLine($"Method '{generationMethod}' not found in '{generatorDescriptor.Category}' category");
}
else
{
Console.WriteLine($"Category '{generator.Category}' not found on Faker object");
Console.WriteLine($"Category '{generatorDescriptor.Category}' not found on Faker object");
}

return null;
Expand Down
12 changes: 3 additions & 9 deletions DbSeeder/Data/Bogus/DataGeneratorsEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,17 @@ namespace DbSeeder.Data.Bogus;

public static class DataGeneratorsEvaluator
{
public static List<BogusGenerator> FindBestNGenerators(
this Dictionary<string, List<BogusGenerator>> allGenerators,
public static List<BogusGeneratorDescriptor> FindBestNGenerators(
this Dictionary<string, List<BogusGeneratorDescriptor>> allGenerators,
Column column,
int n = 1)
{
var weights = new Dictionary<int, List<BogusGenerator>>();
var weights = new Dictionary<int, List<BogusGeneratorDescriptor>>();

foreach (var (_, generatorsCategory) in allGenerators)
{
foreach (var generator in generatorsCategory)
{
if (generator.Params.Count != 0)
{
// TODO[#26]: Implement generators with params
continue;
}

var weight = CalculateLevenshteinDistance(column.Name, generator.GeneratorIdentifier);
if (!weights.ContainsKey(weight))
{
Expand Down
226 changes: 226 additions & 0 deletions DbSeeder/Data/Bogus/ParamsGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
using Bogus;
using Bogus.DataSets;

namespace DbSeeder.Data.Bogus;

public static class ParamsGenerator
{
private const int MinNegativeNumber = int.MinValue;
private const int MaxNegativeNumber = -1;
private const int MinPositiveNumber = 0;
private const int MaxPositiveNumber = int.MaxValue;

private static readonly Faker Faker = new();

public static object[] GetParams(string generatorId)
{
return GetRandomGeneratorParams(generatorId);
}

private static object[] GetRandomGeneratorParams(string generatorId)
{
var minMaxInt = GetMinMaxIntValues();
var minMaxDouble = GetMinMaxDoubleValues();

return generatorId switch
{
"randomodd" => [0, 100],
"randomeven" => [0, 100],
"randomnumber" => [minMaxInt[1]],
"randomdigits" => [GetRandomInt(1, 10), 0, 9],
"randomdecimal" => [minMaxDouble.Min, minMaxDouble.Max],
"randomfloat" => [minMaxDouble.Min, minMaxDouble.Max],
"randombytes" => [GetRandomInt(1, 100)],
"randomsbyte" => [MinNegativeNumber, MaxNegativeNumber],
"randomuint" => [MinPositiveNumber, MaxPositiveNumber],
"randomulong" => [0, ulong.MaxValue],
"randomlong" => [0, long.MaxValue],
"randomshort" => [short.MinValue, short.MaxValue],
"randomushort" => [ushort.MinValue, ushort.MaxValue],
"randomchar" => ['a', 'z'],
"randomchars" => ['a', 'z', GetRandomInt(1, 10)],
"randomstring" => [GetRandomInt(1, 100), 'a', 'z'],
"randomstring2" => [GetRandomInt(1, 100), "abcdef"],
"randomutf16string" => [GetRandomInt(1, 100), GetRandomInt(1, 100), true],
"randomhash" => [GetRandomInt(1, 100), true],
"randombool" => [0.5],
"randomarrayelement" => [GetRandomArray()],
"randomarrayelements" => [GetRandomArray(), GetRandomInt(1, 10)],
"randomlistitem" => [GetRandomList()],
"randomlistitems" => [GetRandomList(), GetRandomInt(1, 10)],
"randomcollectionitem" => [GetRandomCollection()],
"randomreplacenumbers" => ["###-##-####", '#'],
"randomreplacesymbols" => ["###-##-####", '#', (Func<char, char>)(_ => '*')],
"randomreplace" => ["###-##-####"],
"randomclampstring" => ["sample", minMaxInt[0], minMaxInt[1]],
"randomenum" => [Enum.GetValues(typeof(DayOfWeek)).Cast<DayOfWeek>().ToArray()],
"randomenumvalues" => [GetRandomInt(1, 7), Enum.GetValues(typeof(DayOfWeek)).Cast<DayOfWeek>().ToArray()],
"randomshuffle" => [GetRandomList()],
"randomwords" => [GetRandomInt(1, 10)],
"randomwordsarray" => [GetRandomInt(1, 10)],
"randomalphanumeric" => [GetRandomInt(1, 100)],
"randomhexadecimal" => [GetRandomInt(1, 100), true],
"randomweightedrandom" => [GetRandomList(), GetRandomWeights()],
"phonephonenumber" => [GetPhoneNumberFormat()],
"phonephonenumberformat" => [GetRandomInt(0, 5)],
"namefirstname" => [GetRandomGender()],
"namelastname" => [GetRandomGender()],
"namefullname" => [GetRandomGender()],
"nameprefix" => [GetRandomGender()],
"namefindname" => [GetRandomFirstName(), GetRandomLastName(), true, false, GetRandomGender()],
"loremwords" => [GetRandomInt(1, 10)],
"loremletter" => [GetRandomInt(1, 26)],
"loremsentence" => [GetRandomInt(1, 20), GetRandomInt(1, 5)],
"loremsentences" => [GetRandomInt(1, 10), " "],
"loremparagraph" => [GetRandomInt(1, 5)],
"loremparagraphs" => [GetRandomInt(1, 10), " "],
"loremparagraphsminmax" => [GetRandomInt(1, 5), GetRandomInt(5, 10), " "],
"loremlines" => [GetRandomInt(1, 10), "\n"],
"loremslug" => [GetRandomInt(1, 10)],
"imagedatauri" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomColor()],
"imageplaceimgurl" =>
[
GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomCategory(), GetRandomFilter()
],
"imagepicsumurl" =>
[
GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomInt(1, 10),
GetRandomInt(1, 1000)
],
"imageplaceholderurl" =>
[
GetRandomInt(100, 1000), GetRandomInt(100, 1000), "Placeholder", GetRandomColor(), GetRandomColor(),
"png"
],
"imageloremflickrurl" =>
[
GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomKeywords(), GetRandomBool(), GetRandomBool(),
GetRandomInt(1, 1000)
],
"imagelorempixelurl" =>
[
GetRandomCategory(), GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()
],
"imageimage" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()],
"imageabstract" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()],
"imageanimals" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()],
"imagebusiness" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()],
"imagecats" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()],
"imagecity" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()],
"imagefood" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()],
"imagenightlife" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()],
"imagefashion" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()],
"imagepeople" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()],
"imagenature" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()],
"imagesports" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()],
"imagetechnics" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()],
"imagetransport" => [GetRandomInt(100, 1000), GetRandomInt(100, 1000), GetRandomBool(), GetRandomBool()],
"financeaccount" => [GetRandomInt(1, 20)],
"financeamount" => [minMaxDouble.Min, minMaxDouble.Max, GetRandomInt(0, 5)],
"financecurrency" => [GetRandomBool()],
"financecreditcardnumber" => [GetRandomProvider()],
"financeiban" => [GetRandomBool(), GetRandomCountryCode()],
"addresszipcode" => [GetRandomFormat()],
"addressstreetaddress" => [GetRandomBool()],
"addresscountrycode" => [GetRandomFormat()],
"addresslatitude" => [minMaxDouble.Min, minMaxDouble.Max],
"addresslongitude" => [minMaxDouble.Min, minMaxDouble.Max],
"addressdirection" => [GetRandomBool()],
"addresscardinaldirection" => [GetRandomBool()],
"addressordinaldirection" => [GetRandomBool()],
"datepast" => [GetRandomInt(1, 100), DateTime.Now],
"datepastoffset" => [GetRandomInt(1, 100), DateTimeOffset.Now],
"datesoon" => [GetRandomInt(1, 100), DateTime.Now],
"datesoonoffset" => [GetRandomInt(1, 100), DateTimeOffset.Now],
"datefuture" => [GetRandomInt(1, 100), DateTime.Now],
"datefutureoffset" => [GetRandomInt(1, 100), DateTimeOffset.Now],
"datebetween" => [DateTime.Now.AddYears(-10), DateTime.Now.AddYears(10)],
"datebetweenoffset" => [DateTimeOffset.Now.AddYears(-10), DateTimeOffset.Now.AddYears(10)],
"daterecent" => [GetRandomInt(1, 10), DateTime.Now],
"daterecentoffset" => [GetRandomInt(1, 10), DateTimeOffset.Now],
"datetimespan" => [GetRandomInt(1, 100)],
"datemonth" => [GetRandomBool(), GetRandomBool()],
"dateweekday" => [GetRandomBool(), GetRandomBool()],
"datebetweendateonly" => [DateTime.Now.AddYears(-10).Date, DateTime.Now.AddYears(10).Date],
"datepastdateonly" => [GetRandomInt(1, 100), DateTime.Now.Date],
"datesoondateonly" => [GetRandomInt(1, 100), DateTime.Now.Date],
"datefuturedateonly" => [GetRandomInt(1, 100), DateTime.Now.Date],
"daterecentdateonly" => [GetRandomInt(1, 10), DateTime.Now.Date],
"datebetweentimeonly" => [DateTime.Now.TimeOfDay, DateTime.Now.AddHours(10).TimeOfDay],
"datesoontimeonly" => [GetRandomInt(1, 60), DateTime.Now.TimeOfDay],
"daterecenttimeonly" => [GetRandomInt(1, 60), DateTime.Now.TimeOfDay],
"companycompanyname" => [GetRandomInt(0, 5)],
"companycompanynameformat" => [GetRandomInt(0, 5)],
"internetemail" => [GetRandomFirstName(), GetRandomLastName(), GetRandomProvider(), GetRandomBool()],
"internetexampleemail" => [GetRandomFirstName(), GetRandomLastName()],
"internetusername" => [GetRandomFirstName(), GetRandomLastName()],
"internetusernameunicode" => [GetRandomFirstName(), GetRandomLastName()],
"internetmac" => [GetRandomSeparator()],
"internetpassword" => [GetRandomInt(6, 20), GetRandomBool(), "^[a-zA-Z0-9]+$", "prefix"],
"internetcolor" =>
[
GetRandomInt(0, 255), GetRandomInt(0, 255), GetRandomInt(0, 255), GetRandomBool(), "hex"
],
"interneturlwithpath" => ["https", "example.com", "jpg"],
"interneturlrootedpath" => ["jpg"],
"commercedepartment" => [GetRandomInt(1, 10), GetRandomBool()],
"commerceprice" => [minMaxDouble.Min, minMaxDouble.Max, GetRandomInt(0, 5), "$"],
"commercecategories" => [GetRandomInt(1, 10)],
"systemfilename" => ["txt"],
"systemcommonfilename" => ["txt"],
"systemfileext" => ["text/plain"],
"rantreview" => ["product"],
"rantreviews" => ["product", GetRandomInt(1, 10)],
"vehiclevin" => [GetRandomBool()],
_ => throw new ArgumentException($"Unknown generatorId: {generatorId}")
};
}

private static int[] GetMinMaxIntValues()
{
var random = Random.Shared;
var num1 = random.Next(MinPositiveNumber, MaxPositiveNumber);
var num2 = random.Next(MinPositiveNumber, MaxPositiveNumber);

var min = Math.Min(num1, num2);
var max = Math.Max(num1, num2);

return [min, max];
}

private static (double Min, double Max) GetMinMaxDoubleValues()
{
var random = Random.Shared;
var num1 = random.NextDouble() * MaxPositiveNumber;
var num2 = random.NextDouble() * MaxPositiveNumber;

var min = Math.Min(num1, num2);
var max = Math.Max(num1, num2);

return (min, max);
}

private static int GetRandomInt(int min, int max) => Faker.Random.Number(min, max);
private static object[] GetRandomArray() => GetRandomCollection().ToArray();
private static List<object> GetRandomList() => GetRandomCollection().ToList();

private static ICollection<object> GetRandomCollection()
=> Enumerable.Range(0, Faker.Random.Number(100))
.Select(_ => (object)Guid.NewGuid().ToString())
.ToList();

private static List<int> GetRandomWeights() => Faker.Random.Digits(5).ToList();
private static string GetPhoneNumberFormat() => "+1-###-###-####";
private static Name.Gender GetRandomGender() => Faker.Random.ArrayElement([Name.Gender.Male, Name.Gender.Female]);
private static string GetRandomFirstName() => Faker.Person.FirstName;
private static string GetRandomLastName() => Faker.Person.LastName;
private static string GetRandomColor() => "#FF5733";
private static string GetRandomCategory() => "nature";
private static string GetRandomFilter() => "grayscale";
private static bool GetRandomBool() => Faker.Random.Bool(0.5f);
private static string GetRandomProvider() => "Visa";
private static string GetRandomCountryCode() => "US";
private static string GetRandomFormat() => "#####";
private static string[] GetRandomKeywords() => Faker.Lorem.Words();
private static string GetRandomSeparator() => "-";
}
25 changes: 5 additions & 20 deletions DbSeeder/Data/GeneratorFactory.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Data;
using DbSeeder.Data.Bogus;
using DbSeeder.Schema;

Expand All @@ -17,9 +16,9 @@ internal static class GeneratorFactory
["datetime", "smalldatetime", "date", "time", "datetime2", "datetimeoffset"];

private static readonly HashSet<string> OtherTypes =
["uniqueidentifier", "timestamp", "xml", "udt", "structured", "variant"];
["uniqueidentifier", "timestamp", "xml", "udt", "structured", "variant"];

private static readonly Dictionary<string, List<BogusGenerator>> Generators = BogusUtilities.GetBogusGenerators();
private static readonly Dictionary<string, List<BogusGeneratorDescriptor>> Generators = BogusUtilities.GetBogusGenerators();

public static object? GetGeneratorByColumnV2(Column col)
{
Expand All @@ -35,23 +34,9 @@ internal static class GeneratorFactory
}

// semantic filter
var generator = generators.FindBestNGenerators(col, n: 1).First();
var generatedValue = (object?)BogusUtilities.Generate(generator);
var selectedGenerators = generators.FindBestNGenerators(col, n: 3);
var firstGenerator = selectedGenerators.First();
var generatedValue = (object?)BogusUtilities.Generate(firstGenerator);
return generatedValue;
}

public static (Type, Func<object>) GetGeneratorByColumn(Column col)
{
if (StringTypes.Contains(col.DataType, StringComparer.OrdinalIgnoreCase))
{
return (typeof(string), () => Guid.NewGuid().ToString("N"));
}

if (NumeralTypes.Contains(col.DataType, StringComparer.OrdinalIgnoreCase)) // add type limits check
{
return (typeof(long), () => Random.Shared.NextInt64() * 17);
}

throw new NotImplementedException($"{col.DataType} is not currently supported");
}
}

0 comments on commit b06f8b9

Please sign in to comment.