Hi, I am learning how to work with Validated as it...
# arrow
k
Hi, I am learning how to work with Validated as it seems very nice, I have also been playing around with context receivers. Does this look good?
Copy code
data class Ssn(val ssn: String) {
    companion object {

        suspend fun create(ssn: String): ValidatedNel<ValidationError, Ssn> = Validated.fromEither(
            either {
                val validLengthAndAllNumerical = validateLengthAndAllNumerical(ssn).bind()
                Ssn(validateControlNumbers(validLengthAndAllNumerical).bind())
            })


        private fun validateLengthAndAllNumerical(ssn: String): ValidatedNel<ValidationError, String> = with(ssn) {
                length().zip(allNumbers()) { _, _-> ssn }
            }

        context(String)
        private fun length(): ValidatedNel<ValidationError, Unit> =
            if (length == 11) Unit.valid()
            else ValidationError.IncorrectLength.invalidNel()

        context(String)
        private fun allNumbers(): ValidatedNel<ValidationError, Unit> =
            if (filter { it.isDigit() }.length == length) Unit.valid()
            else ValidationError.NotAllDigits.invalidNel()

        private fun validateControlNumbers(ssn: String): ValidatedNel<ValidationError, String> {
            val firstCtrl = listOf(3, 7, 6, 1, 8, 9, 4, 5, 2, 1)
            val secondCtrl = listOf(5, 4, 3, 2, 7, 6, 5, 4, 3, 2, 1)
            val ssnNumeric = ssn.map { it.digitToInt() }

            val firstValid = ssnNumeric.dropLast(1).reduceIndexed { index, acc, i ->
                acc + (i * firstCtrl[index]) } % 11 == 0

            val secondValid = ssnNumeric.reduceIndexed { index, acc, i ->
                acc + (i * secondCtrl[index]) } % 11 == 0

            return if (firstValid && secondValid) ssn.valid()
            else ValidationError.RuleViolation.invalidNel()
        }
    }
}

sealed class ValidationError {
    data object Error: ValidationError()
    data object IncorrectLength: ValidationError()
    data object NotAllDigits: ValidationError()
    data object RuleViolation: ValidationError()
}
m
I don't know about the Arrow part of things (I'm not that familiar with
Validated
) - but your digit check could be simpler -
"1234".all(Character::isDigit)
does what you're after with early break if anything isn't a digit
k
I also see that Ssn should be a value class
s
I think in all these cases it would be simpler to just use extension functions rather than context receivers. Context receivers are better used for behavior, or capabilities, rather than extending logic on data such as
String
.