serpro69 / kotlin-faker

Port of a popular ruby faker gem written in kotlin. Generate realistically looking fake data such as names, addresses, banking details, and many more, that can be used for testing and data anonymization purposes.
https://serpro69.github.io/kotlin-faker/
MIT License
472 stars 44 forks source link

When creating class instance I would like to prepend string values with the name of the property (feature request) #177

Closed urosjarc closed 1 year ago

urosjarc commented 1 year ago

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
)
serpro69 commented 1 year ago

Hi @urosjarc , thanks for opening this issue.

To understand it better, do you want to prepend string values with the property name, or was that just an example?

urosjarc commented 1 year ago

I would like that faker prepends all string type property values with property name automatically if using randomClassInstance on some random class.

I suggest adding a configuration option to faker prependWithPropertyName: Boolean and by default it would be false so that breaking changes will not happen in the next version.

I would create PR myself if I would understand the architecture of this library.

urosjarc commented 1 year ago

In the CONTRIBUTING it writes only how to add a new provider but I could not see any manuals that could help me to structure PR myself. :sweat: I usually don't want to bother people with my wishes but in this case, I could not do things differently.

If you explain which files I have to look for, I will create PR myself no problem.

serpro69 commented 1 year ago

PRs are very much welcome, but I can take a look at it myself also if you don't feel like making a PR :) (Not promising a super fast solution though, but I will try to find time this month.) The implementation for random class instance is here - https://github.com/serpro69/kotlin-faker/blob/master/core/src/main/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProvider.kt It's not mentioned in the "contributing" because it's a bit "specific", but the file has kdocs which hopefully makes it easier to understand what's what.

Anyways, I can see how something like this can be useful, but I would also like it to be more configurable so to speak. Prepending a property name seems like a very specific use-case. What I envision doing here is to add another function similar to typeGenerator or namedParameterGenerator (propertyGenerator, for example), which would give access to the KProperty, for example? There you could do something like this:

faker.randomProvider.randomClassInstance<T> {
    propertyGenerator<String> { prop ->
        "${prop.name}_${faker.random.randomString()}"
    }
}

I need to think a bit if this makes sense at all and if so - how to implement it.

urosjarc commented 1 year ago

Look, now that you explained where to make a change I will be more than happy to create PR myself and show my appreciation for the work that you have done for this library.

I'm planning to add config with following options...

instanceProperties: {
     prepend: {
           active: Boolean = false,
           position: <start,end> = start,
           style: <snake_case, cammel_case, ...> = camel_case
           text: <leave, capitalize, lowercase, ...> = leave
           joinWith: String = "_"
           maxWidth: Int? = null
           forceWidth: Int? = null
           align: <left, right> = left
           emptySpaceFill: String = "_"
     },
}
serpro69 commented 1 year ago

Sounds good :)

I rescind my previous proposal on giving access to KProperty via a new function - I kind of thought it might be useful, but now that I've given it some thought, I don't really see how that would make things better in any way; and I wanted to get rid of reflection also for awhile now. So yeah, let's go for the configuration of instance properties instead. Not sure what some of these configs you mention are supposed to do, but I'm happy to look at the PR and we can continue discussing the implementation there :)

urosjarc commented 1 year ago

I will put some good tests displaying all possible configurations. :+1:

serpro69 commented 1 year ago

Available in 1.14.0-rc.1 ;)

urosjarc commented 1 year ago

@serpro69

urosjarc commented 1 year ago

@serpro69 Here is my example of how to use this feature in the best possible way...

The code so fine you need sunglasses :sunglasses:

val fake = Faker()
var str_counter = 0

inline fun <reified T : Any> random(): T = fake.randomProvider.randomClassInstance {
    this.typeGenerator<String> { pInfo ->
        str_counter++
        "${pInfo.name}_${str_counter}"
    }
}

data class Person(
    val name: String,
    val surname: String,
    val phone: String,
    val email: String,
    val password: String,
    val username: String,
)

fun main() {
    for (i in 0..10) {
        val oseba = random<Person>()
        println(oseba)
    }
}
Person(name=name_1, surname=surname_2, phone=phone_3, email=email_4, password=password_5, username=username_6)
Person(name=name_7, surname=surname_8, phone=phone_9, email=email_10, password=password_11, username=username_12)
Person(name=name_13, surname=surname_14, phone=phone_15, email=email_16, password=password_17, username=username_18)
Person(name=name_19, surname=surname_20, phone=phone_21, email=email_22, password=password_23, username=username_24)
Person(name=name_25, surname=surname_26, phone=phone_27, email=email_28, password=password_29, username=username_30)
Person(name=name_31, surname=surname_32, phone=phone_33, email=email_34, password=password_35, username=username_36)
Person(name=name_37, surname=surname_38, phone=phone_39, email=email_40, password=password_41, username=username_42)
Person(name=name_43, surname=surname_44, phone=phone_45, email=email_46, password=password_47, username=username_48)
Person(name=name_49, surname=surname_50, phone=phone_51, email=email_52, password=password_53, username=username_54)
Person(name=name_55, surname=surname_56, phone=phone_57, email=email_58, password=password_59, username=username_60)
Person(name=name_61, surname=surname_62, phone=phone_63, email=email_64, password=password_65, username=username_66)
urosjarc commented 1 year ago

Or event better...

val fake = Faker()
var counters = mutableMapOf<String, Int>()

inline fun <reified T : Any> random(): T = fake.randomProvider.randomClassInstance {
    this.typeGenerator<String> { pInfo ->
        val value = counters.getOrDefault(pInfo.name, -1) + 1
        counters[pInfo.name] = value
        "${pInfo.name}_${value}"
    }
}
Person(name=name_0, surname=surname_0, phone=phone_0, email=email_0, password=password_0, username=username_0)
Person(name=name_1, surname=surname_1, phone=phone_1, email=email_1, password=password_1, username=username_1)
Person(name=name_2, surname=surname_2, phone=phone_2, email=email_2, password=password_2, username=username_2)
Person(name=name_3, surname=surname_3, phone=phone_3, email=email_3, password=password_3, username=username_3)
Person(name=name_4, surname=surname_4, phone=phone_4, email=email_4, password=password_4, username=username_4)
Person(name=name_5, surname=surname_5, phone=phone_5, email=email_5, password=password_5, username=username_5)
Person(name=name_6, surname=surname_6, phone=phone_6, email=email_6, password=password_6, username=username_6)
Person(name=name_7, surname=surname_7, phone=phone_7, email=email_7, password=password_7, username=username_7)
Person(name=name_8, surname=surname_8, phone=phone_8, email=email_8, password=password_8, username=username_8)
Person(name=name_9, surname=surname_9, phone=phone_9, email=email_9, password=password_9, username=username_9)
Person(name=name_10, surname=surname_10, phone=phone_10, email=email_10, password=password_10, username=username_10)
serpro69 commented 1 year ago

Nice :) Glad it's doing exactly what you needed. I'm sure someone else will find this feature useful as well :)