Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support generators with parameters #31

Merged
merged 3 commits into from
Jun 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 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 @@
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 @@
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,38 +78,38 @@
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)
{
var categoryGenerator = generatorProperty.GetValue(faker);
var generatorMethod = categoryGenerator.GetType().GetMethod(generationMethod,

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

View workflow job for this annotation

GitHub Actions / main

Dereference of a possibly null reference.

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

View workflow job for this annotation

GitHub Actions / main

Dereference of a possibly null reference.

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

View workflow job for this annotation

GitHub Actions / main

Dereference of a possibly null reference.
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)
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-###-###-####";

Check warning on line 213 in DbSeeder/Data/Bogus/ParamsGenerator.cs

View workflow job for this annotation

GitHub Actions / main

Remove this method and declare a constant for this value. (https://rules.sonarsource.com/csharp/RSPEC-3400)
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";

Check warning on line 217 in DbSeeder/Data/Bogus/ParamsGenerator.cs

View workflow job for this annotation

GitHub Actions / main

Remove this method and declare a constant for this value. (https://rules.sonarsource.com/csharp/RSPEC-3400)
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 @@ -11,15 +10,15 @@
private static readonly HashSet<string> NumeralTypes =
["bigint", "int", "smallint", "tinyint", "bit", "decimal", "money", "smallmoney", "float", "real"];

private static readonly HashSet<string> BinaryTypes = ["binary", "varbinary", "image"];

Check warning on line 13 in DbSeeder/Data/GeneratorFactory.cs

View workflow job for this annotation

GitHub Actions / main

Remove the unused private field 'BinaryTypes'. (https://rules.sonarsource.com/csharp/RSPEC-1144)

private static readonly HashSet<string> DateTimeTypes =

Check warning on line 15 in DbSeeder/Data/GeneratorFactory.cs

View workflow job for this annotation

GitHub Actions / main

Remove the unused private field 'DateTimeTypes'. (https://rules.sonarsource.com/csharp/RSPEC-1144)
["datetime", "smalldatetime", "date", "time", "datetime2", "datetimeoffset"];

private static readonly HashSet<string> OtherTypes =

Check warning on line 18 in DbSeeder/Data/GeneratorFactory.cs

View workflow job for this annotation

GitHub Actions / main

Remove the unused private field 'OtherTypes'. (https://rules.sonarsource.com/csharp/RSPEC-1144)
["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 @@
}

// 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();

Check warning on line 38 in DbSeeder/Data/GeneratorFactory.cs

View workflow job for this annotation

GitHub Actions / main

Indexing at 0 should be used instead of the "Enumerable" extension method "First" (https://rules.sonarsource.com/csharp/RSPEC-6608)
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");
}
}
Loading