Repository: serpro69/kotlin-faker (kotlin)

Issue:
When creating class instance I would like to prepend string values with the name of the property (feature request)
I have a huge amount of domain models + 30 and I can't maintain all the functions that would create meaningful data for all of the domain models... Because of that I have joust one generic function that is responsible for generation of any domain model...

```
        inline fun <reified T : Any> random(): T {
            val obj = fake.randomProvider.randomClassInstance<T> {
                this.typeGenerator<Id<T>> { newId() }
                this.typeGenerator<LocalDate> { LocalDate.today() }
                this.typeGenerator<LocalDateTime> { LocalDateTime.now() }
            }
            return obj
        }
```

Currently if I want to seed database with this function I get something like this...

```
Person(
  name=MPTWYZW1YYGuZT9KRDt8QoaY,
  surname=WLAs3cATX1T1IO2sVcgls2ci,
  phone=RAi0SGkEPRGthp6hrPWJEdIk,
  email=1pUR0bfFLHOaOTmKrqvM9fFs,
  password=HWFgWWZ1iywXknlynoq6aH43,
  username=HVa3H3hL68BkbW7mGxPcA8nT
)
```

It would be SOO NICE if there could be option to prepend those string values it would make my job on fullstack testing so much easier!

```
Person(
  name=name_MPTWYZW1YYGuZT9KRDt8QoaY,
  surname=surname_WLAs3cATX1T1IO2sVcgls2ci,
  phone=phone_RAi0SGkEPRGthp6hrPWJEdIk,
  email=email_1pUR0bfFLHOaOTmKrqvM9fFs,
  password=password_HWFgWWZ1iywXknlynoq6aH43,
  username=username_HVa3H3hL68BkbW7mGxPcA8nT
)
```

Pull request description:
<img src="https://img.devrant.com/devrant/rant/r_2880659_Vum14.jpg" height="250"></img>

# Changes for issue: #177 

- Created `ParameterInfo` data class for storing parameter informations.
- Created extension function that maps `KParameter` to `ParameterInfo`.
- Generators: `typeGenerator`, `namedParameterGenerator` now use lambda function that accepts `ParameterInfo` instance.

# Breaking changes?
There are not breaking changes in this PR. :heart: :heart_eyes: 

# Code snipets
```kotlin
class TestClass(
    val bar: String
)
val t0: TestClass = randomProvider.randomClassInstance {
    typeGenerator<String> { pInfo -> pInfo.toString() }
}
val t1: TestClass = randomProvider.randomClassInstance {
    namedParameterGenerator("bar") { pInfo -> pInfo.toString() }
}
assert t0.bar == "ParameterInfo(index=0, name=bar, isOptional=false, isVararg=false)"
assert t1.bar == "ParameterInfo(index=0, name=bar, isOptional=false, isVararg=false)"
```

Patch:
diff --git a/core/build.gradle.kts b/core/build.gradle.kts
index 083aaf2388..288e4187f9 100644
--- a/core/build.gradle.kts
+++ b/core/build.gradle.kts
@@ -25,8 +25,9 @@ dependencies {
 
 apply<Yaml2JsonPlugin>() // this shouldn't really be needed since the plugin is supposed to be applied in the plugins{} block
 configure<Yaml2JsonPluginExtension> {
-    input.set(File("core/src/main/resources/locales"))
-    output.set(File("core/build/generated/src/main/resources"))
+    val cwd = project.projectDir.getAbsolutePath()
+    input.set(File("$cwd/src/main/resources/locales"))
+    output.set(File("$cwd/build/generated/src/main/resources"))
 }
 
 tasks.processResources.get().dependsOn(tasks["yaml2json"])
diff --git a/core/src/integration/kotlin/io/github/serpro69/kfaker/docs/Extras.kt b/core/src/integration/kotlin/io/github/serpro69/kfaker/docs/Extras.kt
index fd721496b7..f7f0b37831 100644
--- a/core/src/integration/kotlin/io/github/serpro69/kfaker/docs/Extras.kt
+++ b/core/src/integration/kotlin/io/github/serpro69/kfaker/docs/Extras.kt
@@ -25,17 +25,21 @@ class Extras : DescribeSpec({
 
         context("configurable constructor arg type generation") {
             it("should generate pre-configured constructor params") {
+                fun randomString() = "X3a8s813dcb";
+
                 // START extras_random_instance_two
-                class Baz(val id: Int, val uuid: UUID, val relatedUuid: UUID)
+                class Baz(val id: Int, val uuid: UUID, val relatedUuid: UUID, val user: String)
 
                 val baz: Baz = faker.randomProvider.randomClassInstance {
                     typeGenerator<UUID> { UUID.fromString("00000000-0000-0000-0000-000000000000") }
                     typeGenerator<Int> { 0 }
+                    typeGenerator<String> { parameterInfo -> "${parameterInfo.name}_${randomString()}" }
                     namedParameterGenerator("relatedUuid") { UUID.fromString("11111111-1111-1111-1111-111111111111") }
                 }
                 // END extras_random_instance_two
 
                 assertEquals(baz.id, 0)
+                assertEquals(baz.user, "user_X3a8s813dcb")
                 assertEquals(baz.uuid, UUID.fromString("00000000-0000-0000-0000-000000000000"))
                 assertEquals(baz.relatedUuid, UUID.fromString("11111111-1111-1111-1111-111111111111"))
             }
@@ -371,4 +375,3 @@ enum class Foo {
     FORTY_TWO
 }
 // END extras_random_everything_two
-
diff --git a/core/src/integration/kotlin/io/github/serpro69/kfaker/docs/Homepage.kt b/core/src/integration/kotlin/io/github/serpro69/kfaker/docs/Homepage.kt
index 32b8236384..42f9d870fc 100644
--- a/core/src/integration/kotlin/io/github/serpro69/kfaker/docs/Homepage.kt
+++ b/core/src/integration/kotlin/io/github/serpro69/kfaker/docs/Homepage.kt
@@ -129,14 +129,17 @@ class Homepage : DescribeSpec({
                 // END random_class_instance_one
             }
             it("should generate types by configuration") {
+                fun randomString() = "X3a8s813dcb";
                 // START random_class_instance_two
-                class Baz(val id: Int, val uuid: UUID, val name: String)
+                class Baz(val id: Int, val uuid: UUID, val username: String)
 
                 val baz: Baz = faker.randomProvider.randomClassInstance {
+                    // ヽ(^o^)丿 ᕕ(ᐛ)ᕗ Prepend string type parameter values with parameter name!
+                    typeGenerator<String> { parameterInfo -> "${parameterInfo.name}_${randomString()}" }
                     typeGenerator<UUID> { UUID.fromString("00000000-0000-0000-0000-000000000000") }
                     typeGenerator<Int> { faker.random.nextInt(min = 0, max = 9) }
-                    typeGenerator<String> { faker.name.name() } // Random name generated by Faker
                 }
+                assertEquals(baz.username, "username_X3a8s813dcb")
                 assertEquals(baz.uuid, UUID.fromString("00000000-0000-0000-0000-000000000000"))
                 assertTrue(baz.id in 0..9)
                 // END random_class_instance_two
diff --git a/core/src/main/kotlin/io/github/serpro69/kfaker/provider/misc/ParameterInfo.kt b/core/src/main/kotlin/io/github/serpro69/kfaker/provider/misc/ParameterInfo.kt
new file mode 100644
index 0000000000..299772161c
--- /dev/null
+++ b/core/src/main/kotlin/io/github/serpro69/kfaker/provider/misc/ParameterInfo.kt
@@ -0,0 +1,25 @@
+package io.github.serpro69.kfaker.provider.misc
+
+import kotlin.reflect.KParameter
+
+/**
+ * Provides additional informations about Class parameter to custom defined generators.
+ * The reason why is not used KParameter is that you will want to provide
+ * additional informations about parameter that is not available in KParameter class.
+ */
+data class ParameterInfo(
+    val index: Int,
+    val name: String,
+    val isOptional: Boolean,
+    val isVararg: Boolean
+)
+
+/**
+ * Extension function that maps KParameter to ParameterInfo dataclass.
+ */
+fun KParameter.toParameterInfo() = ParameterInfo(
+    index = index,
+    name = name.toString(),
+    isOptional = isOptional,
+    isVararg = isVararg
+)
diff --git a/core/src/main/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProvider.kt b/core/src/main/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProvider.kt
index b49a836aef..4fd0446589 100644
--- a/core/src/main/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProvider.kt
+++ b/core/src/main/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProvider.kt
@@ -131,16 +131,19 @@ class RandomClassProvider {
 
             val params = constructor.parameters
                 .map {
+
+                    val pInfo = it.toParameterInfo()
+
                     val klass = it.type.classifier as KClass<*>
                     when {
                         config.namedParameterGenerators.containsKey(it.name) -> {
-                            config.namedParameterGenerators[it.name]?.invoke()
+                            config.namedParameterGenerators[it.name]?.invoke(pInfo)
                         }
                         it.type.isMarkedNullable && config.nullableGenerators.containsKey(klass) -> {
-                            config.nullableGenerators[klass]?.invoke()
+                            config.nullableGenerators[klass]?.invoke(pInfo)
                         }
                         else -> {
-                            klass.predefinedTypeOrNull(config)
+                            klass.predefinedTypeOrNull(config, pInfo)
                                 ?: klass.randomPrimitiveOrNull()
                                 ?: klass.objectInstance
                                 ?: klass.randomEnumOrNull()
@@ -156,8 +159,8 @@ class RandomClassProvider {
         }
     }
 
-    private fun <T : Any> KClass<T>.predefinedTypeOrNull(config: RandomProviderConfig): Any? {
-        return config.predefinedGenerators[this]?.invoke()
+    private fun <T : Any> KClass<T>.predefinedTypeOrNull(config: RandomProviderConfig, pInfo: ParameterInfo): Any? {
+        return config.predefinedGenerators[this]?.invoke(pInfo)
     }
 
     /**
@@ -234,32 +237,32 @@ class RandomProviderConfig @PublishedApi internal constructor() {
     var fallbackStrategy: FallbackStrategy = FallbackStrategy.USE_MIN_NUM_OF_ARGS
 
     @PublishedApi
-    internal val namedParameterGenerators = mutableMapOf<String, () -> Any?>()
+    internal val namedParameterGenerators = mutableMapOf<String, (pInfo: ParameterInfo) -> Any?>()
 
     @PublishedApi
-    internal val predefinedGenerators = mutableMapOf<KClass<*>, () -> Any>()
+    internal val predefinedGenerators = mutableMapOf<KClass<*>, (pInfo: ParameterInfo) -> Any>()
 
     @PublishedApi
-    internal val nullableGenerators = mutableMapOf<KClass<*>, () -> Any?>()
+    internal val nullableGenerators = mutableMapOf<KClass<*>, (pInfo: ParameterInfo) -> Any?>()
 
     /**
      * Configures generation for a specific named parameter. Overrides all other generators
      */
-    inline fun <reified K : Any> namedParameterGenerator(parameterName: String, noinline generator: () -> K?) {
+    inline fun <reified K : Any> namedParameterGenerator(parameterName: String, noinline generator: (pInfo: ParameterInfo) -> K?) {
         namedParameterGenerators[parameterName] = generator
     }
 
     /**
      * Configures generation for a specific type. It can override internal generators (for primitives, for example)
      */
-    inline fun <reified K : Any> typeGenerator(noinline generator: () -> K) {
+    inline fun <reified K : Any> typeGenerator(noinline generator: (pInfo: ParameterInfo) -> K) {
         predefinedGenerators[K::class] = generator
     }
 
     /**
      * Configures generation for a specific nullable type. It can override internal generators (for primitives, for example)
      */
-    inline fun <reified K : Any?> nullableTypeGenerator(noinline generator: () -> K?) {
+    inline fun <reified K : Any?> nullableTypeGenerator(noinline generator: (pInfo: ParameterInfo) -> K?) {
         nullableGenerators[K::class] = generator
     }
 
@@ -280,9 +283,9 @@ private fun RandomProviderConfig.copy(
     constructorParamSize: Int? = null,
     constructorFilterStrategy: ConstructorFilterStrategy? = null,
     fallbackStrategy: FallbackStrategy? = null,
-    namedParameterGenerators: Map<String, () -> Any?>? = null,
-    predefinedGenerators: Map<KClass<*>, () -> Any>? = null,
-    nullableGenerators: Map<KClass<*>, () -> Any?>? = null
+    namedParameterGenerators: Map<String, (pInfo: ParameterInfo) -> Any?>? = null,
+    predefinedGenerators: Map<KClass<*>, (pInfo: ParameterInfo) -> Any>? = null,
+    nullableGenerators: Map<KClass<*>, (pInfo: ParameterInfo) -> Any?>? = null
 ): RandomProviderConfig = RandomProviderConfig().apply {
     this@apply.collectionsSize = collectionsSize ?: this@copy.collectionsSize
     this@apply.constructorParamSize = constructorParamSize ?: this@copy.constructorParamSize
diff --git a/docs/src/orchid/resources/wiki/extras.md b/docs/src/orchid/resources/wiki/extras.md
index 4bebef78ff..64c73a9158 100644
--- a/docs/src/orchid/resources/wiki/extras.md
+++ b/docs/src/orchid/resources/wiki/extras.md
@@ -141,6 +141,7 @@ So for each instance of `Baz` the following will be true:
 
 ```kotlin
 baz.id == 0
+baz.user == "user_X3a8s813dcb"
 baz.uuid == UUID.fromString("00000000-0000-0000-0000-000000000000")
 baz.relatedUuid == UUID.fromString("11111111-1111-1111-1111-111111111111")
 ```
@@ -504,8 +505,3 @@ The following functions are available withing the `Faker().string`:
 {% btc %}{% endbtc %}
 
 <br>
-
-
-
-
-


Test patch:
diff --git a/core/src/test/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProviderTest.kt b/core/src/test/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProviderTest.kt
index 68f899197a..294bf669a3 100644
--- a/core/src/test/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProviderTest.kt
+++ b/core/src/test/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProviderTest.kt
@@ -164,6 +164,7 @@ class RandomClassProviderTest : DescribeSpec({
     describe("a TestClass with non-empty constructor for custom type generators") {
         class Foo(val int: Int)
         class TestClass(
+            val bar: String,
             val id: UUID,
             val int: Int,
             val long: Long,
@@ -178,6 +179,8 @@ class RandomClassProviderTest : DescribeSpec({
                 typeGenerator<UUID> { givenUuid }
                 @Suppress("RemoveExplicitTypeArguments")
                 typeGenerator<Int> { givenInt }
+                @Suppress("RemoveExplicitTypeArguments")
+                typeGenerator<String> { pInfo -> pInfo.toString() }
             }
 
             it("it should be a predefined UUID and primitives") {
@@ -186,6 +189,7 @@ class RandomClassProviderTest : DescribeSpec({
                     testClass.id shouldBe givenUuid
                     testClass.int shouldBe givenInt
                     testClass.foo.int shouldBe givenInt
+                    testClass.bar shouldBe "ParameterInfo(index=0, name=bar, isOptional=false, isVararg=false)"
                 }
             }
         }
@@ -196,7 +200,8 @@ class RandomClassProviderTest : DescribeSpec({
             val id: UUID,
             val id2: UUID,
             val nullableId: UUID?,
-            val nullableId2: UUID?
+            val nullableId2: UUID?,
+            val foo: String? = " "
         )
 
         val typeGeneratedId = UUID.fromString("00000000-0000-0000-0000-000000000000")
@@ -209,6 +214,7 @@ class RandomClassProviderTest : DescribeSpec({
                 namedParameterGenerator("nullableId") { nullableNamedParameterGeneratedId }
                 namedParameterGenerator("nullableId2") { null }
                 typeGenerator<UUID> { typeGeneratedId }
+                namedParameterGenerator("foo") { pInfo -> pInfo.toString() }
             }
 
             it("should use predefined UUIDs with parameter generators taking precedence over type generators") {
@@ -218,6 +224,7 @@ class RandomClassProviderTest : DescribeSpec({
                     testClass.id2 shouldBe typeGeneratedId
                     testClass.nullableId shouldBe nullableNamedParameterGeneratedId
                     testClass.nullableId2 shouldBe null
+                    testClass.foo shouldBe "ParameterInfo(index=4, name=foo, isOptional=true, isVararg=false)"
                 }
             }
         }