Kristian Nedrevold
10/13/2022, 9:05 PMdata 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()
}
Marc Plano-Lesay
10/13/2022, 9:19 PMValidated
) - but your digit check could be simpler - "1234".all(Character::isDigit)
does what you're after with early break if anything isn't a digitKristian Nedrevold
10/13/2022, 9:33 PMsimon.vergauwen
10/14/2022, 7:47 AMString
.