Created
November 27, 2020 14:49
-
-
Save dmcg/934263fb0fd5ca6d9708939b90b9d599 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.math.BigDecimal | |
fun main() { | |
val s: StringValue = StringValue.of("hello") | |
StringValue("") // ok as no validation | |
val nes: NonEmptyStringValue? = NonEmptyStringValue.of("hello") // nullable as validated | |
// private as validated, would throw if not private | |
// NonEmptyStringValue("") | |
val accountNumber: AccountNumber? = AccountNumber.of("dd") | |
// private as @Hidden, would not throw if not private | |
// AccountNumber("") | |
} | |
data class StringValue constructor(val value: String) { | |
companion object : Factory<StringValue, String> | |
by factory(::StringValue) | |
} | |
data class MaskedStringValue constructor(val value: String) { | |
companion object : Factory<StringValue, String> | |
by factory(::StringValue, renderer = ::masked) | |
override fun toString() = render(value) | |
} | |
data class NonEmptyStringValue private constructor(val value: String) { | |
companion object : ValidatingFactory<NonEmptyStringValue?, String> | |
by factory(::NonEmptyStringValue).validatedBy(String::isNotBlank) | |
init { validateOrThrow(value) } | |
} | |
data class PositiveIntValue private constructor(val value: Int) { | |
companion object : ValidatingFactory<PositiveIntValue?, Int> | |
by factory(::PositiveIntValue, String::toInt).validatedBy( { it > 0 } ) | |
init { validateOrThrow(value) } | |
} | |
inline class AccountNumber @Hidden constructor(val value: String) { | |
@OptIn(Hidden::class) | |
companion object : ValidatingFactory<AccountNumber?, String> | |
by factory(::AccountNumber, renderer = ::masked).validatedBy(String::isNotBlank) | |
override fun toString() = render(value) | |
} | |
class MaskedPositiveBigDecimalValue(value: BigDecimal) : Value<BigDecimal>(value, renderer = ::masked) { | |
companion object : ValidatingFactory<MaskedPositiveBigDecimalValue?, BigDecimal> | |
by factory(::MaskedPositiveBigDecimalValue, String::toBigDecimal) | |
.validatedBy( { it > BigDecimal.ZERO } ) | |
} | |
fun <PRIMITIVE> masked(v: PRIMITIVE) = "*".repeat(v.toString().length) | |
open class Value<PRIMITIVE>( | |
val value: PRIMITIVE, | |
val renderer: (PRIMITIVE) -> String = defaultRenderer | |
) { | |
override fun equals(other: Any?): Boolean { | |
if (this === other) return true | |
if (javaClass != other?.javaClass) return false | |
other as Value<*> | |
if (value != other.value) return false | |
return true | |
} | |
override fun hashCode(): Int = value?.hashCode() ?: 0 | |
override fun toString(): String = renderer(value) | |
} | |
interface Factory<DOMAIN, PRIMITIVE> { | |
fun of(v: PRIMITIVE): DOMAIN | |
fun parse(s: String): DOMAIN? | |
fun render(v: PRIMITIVE): String | |
} | |
interface ValidatingFactory<DOMAIN, PRIMITIVE> : Factory<DOMAIN, PRIMITIVE> { | |
fun validate(v: PRIMITIVE): Boolean | |
} | |
fun <PRIMITIVE> ValidatingFactory<*, PRIMITIVE>.validateOrThrow(v: PRIMITIVE) = require(validate(v)) | |
fun <DOMAIN, PRIMITIVE> Factory<DOMAIN, PRIMITIVE>.validatedBy( | |
validation: (PRIMITIVE) -> Boolean | |
): ValidatingFactory<DOMAIN?, PRIMITIVE> = | |
object: | |
ValidatingFactory<DOMAIN?, PRIMITIVE>, | |
Factory<DOMAIN?, PRIMITIVE> by this@validatedBy as Factory<DOMAIN?, PRIMITIVE> { | |
override fun of(v: PRIMITIVE): DOMAIN? = if (validate(v)) [email protected](v) else null | |
override fun validate(v: PRIMITIVE) = validation(v) | |
} | |
fun <DOMAIN, PRIMITIVE> factory( | |
f: (PRIMITIVE) -> DOMAIN, | |
parser: (String) -> PRIMITIVE, | |
renderer: (PRIMITIVE) -> String = defaultRenderer | |
): Factory<DOMAIN, PRIMITIVE> = | |
object : Factory<DOMAIN, PRIMITIVE> { | |
override fun of(v: PRIMITIVE) = f(v) | |
override fun parse(s: String): DOMAIN? = | |
try { | |
of(parser(s)) | |
} catch (e: Exception) { | |
null | |
} | |
override fun render(v: PRIMITIVE) = renderer(v) | |
} | |
fun <DOMAIN> factory( | |
f: (String) -> DOMAIN, | |
renderer: (String) -> String = { it } | |
): Factory<DOMAIN, String> = factory(f, renderer){ it } | |
val defaultRenderer = Any?::toString | |
@RequiresOptIn | |
annotation class Hidden |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment