kotools / types

Explicit types for Kotlin Multiplatform.
https://types.kotools.org
MIT License
90 stars 6 forks source link

✨ New `EmailAddress` type in `org.kotools.types` package #635

Closed LVMVRQUXL closed 7 months ago

LVMVRQUXL commented 7 months ago

📝 Description

We want to introduce an EmailAddress experimental type, in the org.kotools.types package of the types Gradle subproject, with an improved API for deprecating the same type from the kotools.types.web package.

interface EmailAddress {
    override fun equals(other: Any?): Boolean
    override fun hashCode(): Int
    override fun toString(): String

    companion object {
        const val PATTERN: String

        fun fromString(value: Any): EmailAddress
        fun fromString(value: Any, pattern: Any): EmailAddress

        fun fromStringOrNull(value: Any): EmailAddress?
        fun fromStringOrNull(value: Any, pattern: Any): EmailAddress?
    }
}

Like the type from the kotools.types.web package, this new one should be serializable as String. Finally, we want to deprecate the type from the kotools.types.web with a warning level, and its factory functions with an error level and replacement suggestions, for removal in v4.7.

✅ Checklist

LVMVRQUXL commented 7 months ago

Also related to issue #602.

It may be a good idea to extract serializers to a new kotlinx-serialization subproject while making this type serializable. Consumers will be able to import the new subproject and use it like the following:

val format = Json { serializersModule = KotoolsTypesSerializersModule.all }
val emailAddress = format.decodeFromString<EmailAddress>("\"contact@kotools.org\"")
println(emailAddress) // contact@kotools.org
LVMVRQUXL commented 7 months ago

A better design for creating instances of the EmailAddress type is to provide constructors that should throw exceptions in case of invalid arguments, and orNull factory functions that should return null in case of invalid arguments. Also, arguments should be of type String instead of Any for avoiding confusions.

fun EmailAddress(value: String): EmailAddress
fun EmailAddress(value: String, pattern: String): EmailAddress

fun EmailAddress.Companion.orNull(value: String): EmailAddress?
fun EmailAddress.Companion.orNull(value: String, pattern: String): EmailAddress?

Here are some examples for calling these functions from Kotlin code:

EmailAddress("contact@kotools.org")
EmailAddress(" @kotools.org") // throws an exception

EmailAddress(value = "contact@kotools.org", pattern = "^[a-z]+@[a-z]+\\.[a-z]+\$")
EmailAddress(value = " @kotools.org", pattern = "^[a-z]+@[a-z]+\\.[a-z]+\$") // throws an exception
EmailAddress(value = "contact@kotools.org", pattern = "^[a-z]+\$") // throws an exception

EmailAddress.orNull("contact@kotools.org")
EmailAddress.orNull(" @kotools.org") // returns null

EmailAddress.orNull(value = "contact@kotools.org", pattern = "^[a-z]+@[a-z]+\\.[a-z]+\$")
EmailAddress.orNull(value = " @kotools.org", pattern = "^[a-z]+@[a-z]+\\.[a-z]+\$") // returns null
EmailAddress.orNull(value = "contact@kotools.org", pattern = "^[a-z]+\$") // returns null

Finally, the EmailAddress(String) function should be the primary constructor.

LVMVRQUXL commented 7 months ago

It is not possible to define another constructor without directly calling the primary one, including the check of the value against the default pattern, which is not the intended behavior when calling the factory function of EmailAddress with a custom pattern. So we will go back to the original factory functions.