|
| 1 | +@file:Suppress("PackageDirectoryMismatch") |
| 2 | + |
| 3 | +package playground.konad |
| 4 | + |
| 5 | +import io.konad.* |
| 6 | +import io.konad.Maybe.Companion.maybe |
| 7 | +import io.konad.Maybe.Companion.nullable |
| 8 | +import io.konad.applicative.builders.on |
| 9 | +import playground.shouldBe |
| 10 | + |
| 11 | +/** |
| 12 | + * lucapiccinelli/konad |
| 13 | + * |
| 14 | + * [GitHub](https://github.com/lucapiccinelli/konad) |
| 15 | + */ |
| 16 | + |
| 17 | +fun main() { |
| 18 | + println() |
| 19 | + println("# lucapiccinelli/konad") |
| 20 | + println("Simple Kotlin monads for the everyday error handling. Brings simple nulls composition") |
| 21 | + println() |
| 22 | + |
| 23 | + println("Deal with nullables. How to compose nullables") |
| 24 | + |
| 25 | + var foo: Int? = 1 |
| 26 | + var bar: String? = "2" |
| 27 | + var baz: Float? = 3.0f |
| 28 | + |
| 29 | + fun useThem(x: Int, y: String, z: Float): Int = x + y.toInt() + z.toInt() |
| 30 | + |
| 31 | + val result1: Int? = ::useThem.curry() |
| 32 | + .on(foo.maybe) |
| 33 | + .on(bar.maybe) |
| 34 | + .on(baz.maybe) |
| 35 | + .nullable |
| 36 | + |
| 37 | + result1 shouldBe 6 |
| 38 | + |
| 39 | + println("How to provide error messages when a null is not an acceptable value") |
| 40 | + foo = null |
| 41 | + bar = null |
| 42 | + baz = null |
| 43 | + val result2: Result<Int> = ::useThem.curry() |
| 44 | + .on(foo.ifNull("Foo should not be null")) |
| 45 | + .on(bar.ifNull("Bar should not be null")) |
| 46 | + .on(baz.ifNull("Baz should not be null")) |
| 47 | + .result |
| 48 | + |
| 49 | + println("How to access the content of a Result") |
| 50 | + val expectedResultMessage = "Foo should not be null,Bar should not be null,Baz should not be null" |
| 51 | + |
| 52 | + println("When style") |
| 53 | + when (result2) { |
| 54 | + is Result.Ok -> result2.toString() |
| 55 | + is Result.Errors -> result2.description(",") |
| 56 | + } shouldBe expectedResultMessage |
| 57 | + |
| 58 | + println("Fold style") |
| 59 | + result2.fold( |
| 60 | + { it.toString() }, |
| 61 | + { it.description(",") }) shouldBe expectedResultMessage |
| 62 | + |
| 63 | + println("Map style") |
| 64 | + result2 |
| 65 | + .map { it.toString() } |
| 66 | + .ifError { it.description(",") } shouldBe expectedResultMessage |
| 67 | + |
| 68 | + |
| 69 | + println() |
| 70 | + println("How to create a Type-system with Result. See below the defitions of User, Email and PhoneNumber") |
| 71 | + val user: Result<User> = ::User.curry() |
| 72 | + .on("foo.bar") |
| 73 | + .on(Email.of("foo.bar")) // This email is invalid -> returns Result.Errors |
| 74 | + .on(PhoneNumber.of("xxx")) // This phone number is invalid -> returns Result.Errors |
| 75 | + .on("Foo") |
| 76 | + .result |
| 77 | + |
| 78 | + when (user) { |
| 79 | + is Result.Ok -> user.toString() |
| 80 | + is Result.Errors -> user.description(" - ") |
| 81 | + } shouldBe "foo.bar doesn't match an email format - xxx should match a valid phone number, but it doesn't" |
| 82 | + |
| 83 | + |
| 84 | + println() |
| 85 | + println("How to deal with collection of nullables") |
| 86 | + val collectionOfNullables: List<Int?> = listOf(1, 2, null, 4) |
| 87 | + val nullableCollection: Collection<Int>? = collectionOfNullables.flatten() |
| 88 | + |
| 89 | + nullableCollection shouldBe null |
| 90 | + |
| 91 | + println("How to deal with collection of Results") |
| 92 | + val collectionOfResult: List<Result<Int>> = listOf("error1".error(), 2.ok(), "error3".error(), 4.ok()) |
| 93 | + val resultCollection: Result<Collection<Int>> = collectionOfResult.flatten() |
| 94 | + |
| 95 | + resultCollection.fold( |
| 96 | + { it.toString() }, |
| 97 | + { it.description(",") }) shouldBe "error1,error3" |
| 98 | +} |
| 99 | + |
| 100 | +const val EMAIL_REGEX = |
| 101 | + "(?:[a-z0-9!#\$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#\$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)*[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])" |
| 102 | +const val PHONE_NUMBER_REGEX = "^(\\+\\d{1,3})?\\s?(\\d\\s?)+\$" |
| 103 | + |
| 104 | +data class User(val username: String, val email: Email, val phoneNumber: PhoneNumber, val firstname: String) |
| 105 | + |
| 106 | +data class Email private constructor(val value: String) { |
| 107 | + companion object { |
| 108 | + fun of(emailValue: String): Result<Email> = if (Regex(EMAIL_REGEX).matches(emailValue)) |
| 109 | + Email(emailValue).ok() |
| 110 | + else "$emailValue doesn't match an email format".error() |
| 111 | + } |
| 112 | +} |
| 113 | + |
| 114 | +data class PhoneNumber private constructor(val value: String) { |
| 115 | + companion object { |
| 116 | + fun of(phoneNumberValue: String): Result<PhoneNumber> = if (Regex(PHONE_NUMBER_REGEX).matches(phoneNumberValue)) |
| 117 | + PhoneNumber(phoneNumberValue).ok() |
| 118 | + else "$phoneNumberValue should match a valid phone number, but it doesn't".error() |
| 119 | + } |
| 120 | +} |
0 commit comments