diff --git a/src/main/kotlin/cli/App.kt b/src/main/kotlin/cli/App.kt index 6a727e2..421eca1 100644 --- a/src/main/kotlin/cli/App.kt +++ b/src/main/kotlin/cli/App.kt @@ -5,30 +5,25 @@ import arrow.core.left import arrow.core.right import cli.readInput import config.appConfig -import domain.AllowedSenders import domain.ApplicationErrors +import domain.CreateValidatedEmailRoute import domain.EmailRoute import domain.InterruptedError -import domain.ReceiveEmailConsents suspend fun main() = either { val (allowedSenders, receiverEmailConsent) = appConfig().bind() - with(allowedSenders) { - with(receiverEmailConsent) { - while (true) { - runProgram().bind() - } - } + val createValidatedEmailRoute = EmailRoute.factoryWithContext(allowedSenders, receiverEmailConsent) + while (true) { + runProgram(createValidatedEmailRoute).bind() } }.getOrHandle { errors -> errors.log() } -context(AllowedSenders, ReceiveEmailConsents) -private suspend fun runProgram() = either { +private suspend fun runProgram(createValidatedEmailRoute: CreateValidatedEmailRoute) = either { val (from, to, cc, bcc) = readInput().leftNel().bind() - val emailRoute = EmailRoute.validated(from, to, cc, bcc).bind() + val emailRoute = createValidatedEmailRoute(from, to, cc, bcc).bind() println("Sending email to $emailRoute") }.handleErrorWith { errors -> diff --git a/src/main/kotlin/domain/EmailRoute.kt b/src/main/kotlin/domain/EmailRoute.kt index fe0cb3e..2dbcec6 100644 --- a/src/main/kotlin/domain/EmailRoute.kt +++ b/src/main/kotlin/domain/EmailRoute.kt @@ -7,6 +7,8 @@ import arrow.core.traverseValidated import arrow.core.valid import validate +typealias CreateValidatedEmailRoute = (String, String, List, List) -> Validated + data class EmailRoute private constructor( val from: Email, val to: Email, @@ -14,29 +16,26 @@ data class EmailRoute private constructor( val bcc: List = emptyList() ) { companion object { - context(AllowedSenders, ReceiveEmailConsents) - fun validated( - from: String, - to: String, - cc: List = emptyList(), - bcc: List = emptyList() - ): Validated = - validate( - Email.valueOf(from), - Email.valueOf(to), - cc.traverseValidated { Email.valueOf(it) }, - bcc.traverseValidated { Email.valueOf(it) } - ) { validFrom, validTo, validCc, validBcc -> - EmailRoute(validFrom, validTo, validCc, validBcc) - }.andThen { emailRoute -> - when { - emailRoute.cc.isEmpty() && emailRoute.bcc.isEmpty() -> - ValidationError("Both cc and bcc are empty").invalidNel() - emailRoute.from !in this@AllowedSenders -> - ValidationError("'${emailRoute.from}' is not in the list of allowed senders").invalidNel() - emailRoute.to !in this@ReceiveEmailConsents -> - ValidationError("'${emailRoute.to}' does not consent receiving emails").invalidNel() - else -> emailRoute.valid() + fun factoryWithContext(allowedSenders: AllowedSenders, receiveEmailConsents: ReceiveEmailConsents): + CreateValidatedEmailRoute = + { from, to, cc, bcc -> + validate( + Email.valueOf(from), + Email.valueOf(to), + cc.traverseValidated { Email.valueOf(it) }, + bcc.traverseValidated { Email.valueOf(it) } + ) { validFrom, validTo, validCc, validBcc -> + EmailRoute(validFrom, validTo, validCc, validBcc) + }.andThen { emailRoute -> + when { + emailRoute.cc.isEmpty() && emailRoute.bcc.isEmpty() -> + ValidationError("Both cc and bcc are empty").invalidNel() + emailRoute.from !in allowedSenders -> + ValidationError("'${emailRoute.from.value}' is not in the list of allowed senders").invalidNel() + emailRoute.to !in receiveEmailConsents -> + ValidationError("'${emailRoute.to.value}' does not consent receiving emails").invalidNel() + else -> emailRoute.valid() + } } } } diff --git a/src/test/kotlin/domain/EmailRouteSpec.kt b/src/test/kotlin/domain/EmailRouteSpec.kt index 68abed5..acdb297 100644 --- a/src/test/kotlin/domain/EmailRouteSpec.kt +++ b/src/test/kotlin/domain/EmailRouteSpec.kt @@ -14,78 +14,79 @@ class EmailRouteSpec : FreeSpec({ val allReceiversConsent = ReceiveEmailConsents { true } val noReceiversConsent = ReceiveEmailConsents { false } - with(allSendersAllowed) { - with(allReceiversConsent) { - "should validate when all validation rules pass" { - val validatedEmailRoute = EmailRoute.validated( - "sender@localhost", - "receiver@localhost", - listOf("cc@localhost") - ) - - with(validatedEmailRoute.shouldBeValid()) { - from shouldBe validEmail("sender@localhost") - to shouldBe validEmail("receiver@localhost") - cc shouldBe listOf(validEmail("cc@localhost")) - bcc shouldBe emptyList() - } - } + "given all senders allowed and all receivers consent" - { + val createValidatedEmailRoute = EmailRoute.factoryWithContext(allSendersAllowed, allReceiversConsent) - "should invalidate both empty cc and bcc" { - val emailRoute = EmailRoute.validated( - "sender@localhost", - "receiver@localhost" - ) + "should validate when all validation rules pass" { + val validatedEmailRoute = createValidatedEmailRoute( + "sender@localhost", + "receiver@localhost", + listOf("cc@localhost"), + emptyList() + ) - emailRoute shouldBe ValidationError("Both cc and bcc are empty").invalidNel() + with(validatedEmailRoute.shouldBeValid()) { + from shouldBe validEmail("sender@localhost") + to shouldBe validEmail("receiver@localhost") + cc shouldBe listOf(validEmail("cc@localhost")) + bcc shouldBe emptyList() } + } - "should accumulate errors" { - val emailRoute = EmailRoute.validated( - "invalid-from", - "invalid-to", - listOf("invalid-cc"), - listOf("invalid-bcc") - ) - - emailRoute shouldBe nonEmptyListOf( - ValidationError("'invalid-from' should be a valid email address"), - ValidationError("'invalid-to' should be a valid email address"), - ValidationError("'invalid-cc' should be a valid email address"), - ValidationError("'invalid-bcc' should be a valid email address") - ).invalid() - } + "should invalidate both empty cc and bcc" { + val emailRoute = createValidatedEmailRoute( + "sender@localhost", + "receiver@localhost", + emptyList(), + emptyList() + ) + + emailRoute shouldBe ValidationError("Both cc and bcc are empty").invalidNel() + } + + "should accumulate errors" { + val emailRoute = createValidatedEmailRoute( + "invalid-from", + "invalid-to", + listOf("invalid-cc"), + listOf("invalid-bcc") + ) + + emailRoute shouldBe nonEmptyListOf( + ValidationError("'invalid-from' should be a valid email address"), + ValidationError("'invalid-to' should be a valid email address"), + ValidationError("'invalid-cc' should be a valid email address"), + ValidationError("'invalid-bcc' should be a valid email address") + ).invalid() } } "should invalidate when the sender is not allowed" { - with(noSendersAllowed) { - with(allReceiversConsent) { - val validatedEmailRoute = EmailRoute.validated( - "sender@localhost", - "receiver@localhost", - listOf("cc@localhost") - ) - - validatedEmailRoute shouldBe - ValidationError("'sender@localhost' is not in the list of allowed senders").invalidNel() - } - } + val createValidatedEmailRoute = EmailRoute.factoryWithContext(noSendersAllowed, allReceiversConsent) + + val validatedEmailRoute = createValidatedEmailRoute( + "sender@localhost", + "receiver@localhost", + listOf("cc@localhost"), + emptyList() + ) + + validatedEmailRoute shouldBe + ValidationError("'sender@localhost' is not in the list of allowed senders").invalidNel() } "should invalidate when the receiver does not consent receiving emails" { - with(allSendersAllowed) { - with(noReceiversConsent) { - val validatedEmailRoute = EmailRoute.validated( - "sender@localhost", - "receiver@localhost", - listOf("cc@localhost") - ) - - validatedEmailRoute shouldBe - ValidationError("'receiver@localhost' does not consent receiving emails").invalidNel() - } - } + val createValidatedEmailRoute = EmailRoute.factoryWithContext(allSendersAllowed, noReceiversConsent) + + val validatedEmailRoute = createValidatedEmailRoute( + "sender@localhost", + "receiver@localhost", + listOf("cc@localhost"), + emptyList() + ) + + validatedEmailRoute shouldBe + ValidationError("'receiver@localhost' does not consent receiving emails").invalidNel() } })