Repository: serpro69/kotlin-faker (kotlin)

Issue:
Add top-level randomClassInstance function
Not everyone needs the whole set of functionality provided by faker and some use-cases are limited to just generating random POJO instances. Having a top-level `randomClassInstance` function might be useful for this case, so one does not need to instantiate and configure faker to be able to access random class instance functionality.

> Suggested by @timrijckaert

Pull request description:
Closes #265

Patch:
diff --git a/core/api/core.api b/core/api/core.api
index 5423e4aeed..c1bec8f445 100644
--- a/core/api/core.api
+++ b/core/api/core.api
@@ -79,6 +79,13 @@ public final class io/github/serpro69/kfaker/FakerConfigBuilder {
 public abstract interface annotation class io/github/serpro69/kfaker/FakerDsl : java/lang/annotation/Annotation {
 }
 
+public final class io/github/serpro69/kfaker/FakerExtKt {
+	public static final fun getRcp ()Lio/github/serpro69/kfaker/provider/misc/RandomClassProvider;
+	public static final fun randomClassInstance (Lkotlin/reflect/KClass;Lio/github/serpro69/kfaker/FakerConfig;)Ljava/lang/Object;
+	public static final fun randomClassInstance (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
+	public static synthetic fun randomClassInstance$default (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object;
+}
+
 public final class io/github/serpro69/kfaker/FakerKt {
 	public static final fun faker (Lkotlin/jvm/functions/Function1;)Lio/github/serpro69/kfaker/Faker;
 }
@@ -690,6 +697,7 @@ public final class io/github/serpro69/kfaker/provider/misc/ParameterInfo {
 }
 
 public final class io/github/serpro69/kfaker/provider/misc/RandomClassProvider {
+	public fun <init> (Lio/github/serpro69/kfaker/FakerConfig;)V
 	public final fun configure (Lkotlin/jvm/functions/Function1;)V
 	public final fun copy ()Lio/github/serpro69/kfaker/provider/misc/RandomClassProvider;
 	public final fun getConfig ()Lio/github/serpro69/kfaker/provider/misc/RandomProviderConfig;
diff --git a/core/src/main/kotlin/io/github/serpro69/kfaker/FakerExt.kt b/core/src/main/kotlin/io/github/serpro69/kfaker/FakerExt.kt
new file mode 100644
index 0000000000..bfc1080a95
--- /dev/null
+++ b/core/src/main/kotlin/io/github/serpro69/kfaker/FakerExt.kt
@@ -0,0 +1,62 @@
+package io.github.serpro69.kfaker
+
+import io.github.serpro69.kfaker.provider.misc.RandomClassProvider
+import io.github.serpro69.kfaker.provider.misc.RandomProviderConfig
+import kotlin.reflect.KClass
+
+@PublishedApi
+internal val rcp = RandomClassProvider(fakerConfig { })
+
+/**
+ * Creates an instance of [T]. If [T] has a parameterless public/internal constructor then it will be used to create an instance of this class,
+ * otherwise a constructor with minimal number of parameters will be used with randomly-generated values.
+ *
+ * @param configurator configure random class instance creation
+ *
+ * @throws NoSuchElementException if [T] has no public or internal constructor.
+ */
+inline fun <reified T : Any> randomClassInstance(
+    configurator: RandomProviderConfig.() -> Unit = {}
+): T = rcp.randomClassInstance<T>(configurator)
+
+/**
+ * Creates an instance of [T]. If [T] has a parameterless public/internal constructor then it will be used to create an instance of this class,
+ * otherwise a constructor with minimal number of parameters will be used with randomly-generated values.
+ *
+ * @param fakerConfig configure [RandomClassProvider] via the [FakerConfig]
+ *
+ * @throws NoSuchElementException if [T] has no public or internal constructor.
+ */
+inline fun <reified T : Any> randomClassInstance(
+    fakerConfig: FakerConfig,
+): T = RandomClassProvider(fakerConfig).randomClassInstance<T>()
+
+/**
+ * Creates an instance of [T] from the [KClass] input.
+ * If [T] has a parameterless public/internal constructor then it will be used to create an instance of this class,
+ * otherwise a constructor with minimal number of parameters will be used with randomly-generated values.
+ *
+ * @param kClass a [KClass] of [T]
+ * @param configurator configure random class instance creation
+ *
+ * @throws NoSuchElementException if [T] has no public or internal constructor.
+ */
+fun <T : Any> randomClassInstance(
+    kClass: KClass<T>,
+    configurator: RandomProviderConfig.() -> Unit = {}
+): T = rcp.randomClassInstance(kClass, configurator)
+
+/**
+ * Creates an instance of [T] from the [KClass] input.
+ * If [T] has a parameterless public/internal constructor then it will be used to create an instance of this class,
+ * otherwise a constructor with minimal number of parameters will be used with randomly-generated values.
+ *
+ * @param kClass a [KClass] of [T]
+ * @param fakerConfig configure [RandomClassProvider] via the [FakerConfig]
+ *
+ * @throws NoSuchElementException if [T] has no public or internal constructor.
+ */
+fun <T : Any> randomClassInstance(
+    kClass: KClass<T>,
+    fakerConfig: FakerConfig,
+): T = RandomClassProvider(fakerConfig).randomClassInstance(kClass)
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 2bbf6fc749..38bed6d4ca 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
@@ -38,6 +38,7 @@ class RandomClassProvider {
     /**
      * Creates an instance of this [RandomClassProvider] with the given [fakerConfig].
      */
+    @PublishedApi
     internal constructor(fakerConfig: FakerConfig) {
         this.fakerConfig = fakerConfig
         randomService = RandomService(fakerConfig)
diff --git a/docs/src/orchid/resources/wiki/extras.md b/docs/src/orchid/resources/wiki/extras.md
index a14e931d75..baf1e02381 100644
--- a/docs/src/orchid/resources/wiki/extras.md
+++ b/docs/src/orchid/resources/wiki/extras.md
@@ -20,6 +20,7 @@
     * [New Instance](#new-instance)
     * [Instance Copy](#instance-copy)
   * [Dealing with Generic Types](#dealing-with-generic-types)
+  * [Top-level functions](#top-level-functions)
 * [Random Everything](#random-everything)
 * [Random Strings from Templates](#random-strings-from-templates)
 
@@ -580,6 +581,56 @@ Generic parameter types are not fully supported at this moment due to type-erasu
 
 <br>
 
+### Top-level functions
+
+Sometimes you just want to generate random POJO instances and don't need the whole range of kotlin-faker functionality?
+For these cases you can import `randomClassInstance` top-level function and use it directly w/o the need to instantiate an instance of `Faker`
+
+{% tabs %}
+
+{% kotlin "Kotlin" %}
+{% filter compileAs('md') %}
+```kotlin
+import io.github.serpro69.kfaker.randomClassInstance
+
+val randomClass = randomClassInstance<MyClass>()
+```
+{% endfilter %}
+{% endkotlin %}
+
+{% endtabs %}
+
+It can also be configured via `configurator` lambda parameter, or via `FakerConfig` instance:
+
+{% tabs %}
+
+{% kotlin "Kotlin" %}
+{% filter compileAs('md') %}
+```kotlin
+import io.github.serpro69.kfaker.fakerConfig
+import io.github.serpro69.kfaker.randomClassInstance
+import java.time.Instant
+
+val randomClass = randomClassInstance<MyClass>() {
+  typeGenerator<Instant> { Instant.now() }
+}
+
+val cfg = fakerConfig {
+  randomClassInstance {
+    typeGenerator<Instant> { Instant.MIN }
+  }
+}
+val another = randomClassInstance<MyClass>(cfg)
+```
+{% endfilter %}
+{% endkotlin %}
+
+{% endtabs %}
+
+{% btc %}{% endbtc %}
+
+<br>
+
 ## Random Everything
 
 Faker provides its wrapper functions around `java.util.Random` (with some additional functionality that is not covered by `java.util.Random`) through `Faker().random` property.


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 24fa1b69ce..f4def90fc4 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
@@ -5,6 +5,7 @@ import io.github.serpro69.kfaker.fakerConfig
 import io.github.serpro69.kfaker.provider.misc.DefaultValuesStrategy.PICK_RANDOMLY
 import io.github.serpro69.kfaker.provider.misc.DefaultValuesStrategy.USE_DEFAULTS
 import io.github.serpro69.kfaker.random
+import io.github.serpro69.kfaker.randomClassInstance
 import io.kotest.assertions.throwables.shouldNotThrow
 import io.kotest.assertions.throwables.shouldThrow
 import io.kotest.core.spec.style.DescribeSpec
@@ -23,14 +24,13 @@ import io.kotest.matchers.types.instanceOf
 import io.kotest.matchers.types.shouldNotBeSameInstanceAs
 import org.junit.jupiter.api.assertThrows
 import java.util.UUID
-import kotlin.random.Random
 import kotlin.reflect.full.declaredMemberProperties
 
 @Suppress("unused")
 class RandomClassProviderTest : DescribeSpec({
     assertSoftly = true
 
-    val config = fakerConfig { random = Random.Default }
+    val config = fakerConfig {}
     val randomProvider = RandomClassProvider(config)
 
     describe("a TestClass with an empty constructor") {
@@ -696,14 +696,14 @@ class RandomClassProviderTest : DescribeSpec({
             with(RandomClassProvider(config)) {
                 configure {
                     typeGenerator { Foo(42) }
-                    nullableTypeGenerator { "nn" }
+                    nullableTypeGenerator { "non-null" }
                     namedParameterGenerator("test") { 32167 }
                 }
                 randomClassInstance<Bar>().foo.int shouldBe 42
                 randomClassInstance<Baz>().foo.int shouldBe 42
                 randomClassInstance<Baz>().bar.foo.int shouldBe 42
                 randomClassInstance<Baz>().test shouldBe 32167
-                randomClassInstance<Baz>().nullable shouldBe "nn"
+                randomClassInstance<Baz>().nullable shouldBe "non-null"
             }
         }
 
@@ -932,6 +932,53 @@ class RandomClassProviderTest : DescribeSpec({
             }
         }
     }
+
+    context("randomClassProvider top-level functions") {
+        class Foo(val int: Int)
+        class Bar(val foo: Foo)
+        class Baz(val foo: Foo, val bar: Bar, val test: Int, val nullable: String?)
+
+        it("should generate random class instance") {
+            randomClassInstance<Foo>() shouldBe instanceOf<Foo>()
+        }
+
+        it("should configure all random instances via configurator") {
+            val bar = randomClassInstance<Bar> {
+                typeGenerator { Foo(42) }
+                nullableTypeGenerator { "non-null" }
+                namedParameterGenerator("test") { 32167 }
+            }
+            val baz = randomClassInstance<Baz> {
+                typeGenerator { Foo(42) }
+                nullableTypeGenerator { "non-null" }
+                namedParameterGenerator("test") { 32167 }
+            }
+            bar.foo.int shouldBe 42
+            baz.foo.int shouldBe 42
+            baz.bar.foo.int shouldBe 42
+            baz.test shouldBe 32167
+            baz.nullable shouldBe "non-null"
+        }
+
+        it("should configure all random instances via fakerConfig") {
+            val cfg: () -> FakerConfig = {
+                fakerConfig {
+                    randomClassInstance {
+                        typeGenerator { Foo(0) }
+                        nullableTypeGenerator { "nn" }
+                        namedParameterGenerator("test") { 1 }
+                    }
+                }
+            }
+            val bar = randomClassInstance<Bar>(cfg())
+            val baz = randomClassInstance<Baz>(cfg())
+            bar.foo.int shouldBe 0
+            baz.foo.int shouldBe 0
+            baz.bar.foo.int shouldBe 0
+            baz.test shouldBe 1
+            baz.nullable shouldBe "nn"
+        }
+    }
 })
 
 enum class TestEnumWithParams(val id: Int) {