/
RailwayTest.kt
117 lines (99 loc) · 3.16 KB
/
RailwayTest.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package com.github.hadilq.happy.sample
import com.github.hadilq.happy.annotation.Happy
import org.junit.Test
import java.io.IOException
import java.util.concurrent.TimeoutException
/**
* This is a modified version of https://gist.github.com/antonyharfield/1928d02a1163cf115d701deca5b99f63
*/
class RailwayTest {
@Test
fun happyPathTest() {
val result = doWork()
println("result: $result")
assert(result == Success(Unit))
}
}
// The sample method that needs to compose the processes
fun doWork(): Result<Unit> {
return input() into
::parse elseIf
{
return Failure("Cannot parse email")
} into
::validate elseIf
{
InvalidEmailAddress { message -> return Failure(message) }
EmptySubject { message -> return Failure(message) }
EmptyBody { message -> return Failure(message) }
} into
::send elseIf
{
IOFailure { error -> return Failure(error.message ?: "") }
Timeout { error -> return Failure(error.message ?: "") }
UnAuthorized { error -> return Failure(error.message ?: "") }
} into
{ Success(Unit) }
}
inline infix fun <T, R> T.into(block: (T) -> R): R {
return block(this)
}
data class Email(
val to: String,
val subject: String,
val body: String
)
fun input() = listOf("@sampleTo", "sampleSubject", "sampleBody")
// Parse the lines of input to an Email object
fun parse(inputs: List<String>): Result<Email> =
if (inputs.size == 3)
Success(Email(to = inputs[0], subject = inputs[1], body = inputs[2]))
else
Failure("Unexpected end of input")
// Validate Email
fun validate(input: Success<Email>): ValidationResult {
val email = input.value
return when {
!email.to.contains("@") -> {
InvalidEmailAddress("Invalid email address! `To` is ${email.to}")
}
email.subject.isBlank() -> {
EmptySubject("Subject must not be blank")
}
email.body.isBlank() -> {
EmptyBody("Body must not be blank")
}
else -> ValidateSuccess(email)
}
}
// Send the email (typically this would have an unhappy path too)
fun send(input: ValidateSuccess): SendResult {
val email = input.email
return try {
println("Sent to ${email.to}. Whoosh!")
SendSuccess(email)
} catch (exception: IOException) {
IOFailure(exception)
} catch (exception: TimeoutException) {
Timeout(exception)
} catch (exception: UnAuthorizedException) {
UnAuthorized(exception)
}
}
sealed class Result<T>
@Happy
data class Success<T>(val value: T) : Result<T>()
data class Failure<T>(val errorMessage: String) : Result<T>()
sealed class ValidationResult
@Happy
data class ValidateSuccess(val email: Email) : ValidationResult()
data class InvalidEmailAddress(val errorMessage: String) : ValidationResult()
data class EmptySubject(val errorMessage: String) : ValidationResult()
data class EmptyBody(val errorMessage: String) : ValidationResult()
sealed class SendResult
@Happy
data class SendSuccess(val email: Email) : SendResult()
data class IOFailure(val error: IOException) : SendResult()
data class Timeout(val error: TimeoutException) : SendResult()
data class UnAuthorized(val error: UnAuthorizedException) : SendResult()
class UnAuthorizedException : Throwable()