Skip to content

Instantly share code, notes, and snippets.

@danneu
Last active July 15, 2017 16:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danneu/19422e9b21c723b4732f4476f0a4ec9a to your computer and use it in GitHub Desktop.
Save danneu/19422e9b21c723b4732f4476f0a4ec9a to your computer and use it in GitHub Desktop.
package com.danneu.result
class State <T> (var target: T, var tip: String? = null) {
override fun toString() = "[State tip=$tip]"
}
typealias ResultState <T> = Result<State<T>, String>
abstract class Reducer <T> {
abstract val step: (ResultState<T>) -> ResultState<T>
var tip: String? = null
fun withTip(newTip: String) = apply {
this.tip = newTip
}
}
object V/*alidators*/ {
fun <T> tip(tip: String) = object : Reducer<T>() {
override val step: (ResultState<T>) -> ResultState<T> = { result ->
result.map { state ->
state.apply {
this.tip = tip
}
}
}
}
fun isLength (min: Int, max: Int) = object : Reducer<CharSequence>() {
override val step: (ResultState<CharSequence>) -> ResultState<CharSequence> = { result ->
notEmpty().step(result).flatMap { state ->
when (state.target.length in min..max) {
true ->
result
false ->
Result.err(state.tip ?: "Value must be $min-$max chars")
}
}
}
}
fun notEmpty () = object : Reducer<CharSequence>() {
override val step: (ResultState<CharSequence>) -> ResultState<CharSequence> = { result ->
result.flatMap { state ->
when (state.target.isNotEmpty()) {
true ->
result
false ->
Result.err(state.tip ?: "Value must not be empty")
}
}
}
}
fun isEmpty () = object : Reducer<CharSequence>() {
override val step: (ResultState<CharSequence>) -> ResultState<CharSequence> = { result ->
result.flatMap { state ->
when (state.target.isEmpty()) {
true ->
result
false ->
Result.err(this.tip ?: state.tip ?: "Value must not be empty")
}
}
}
}
fun <T> equal (other: Any?) = object : Reducer<T>() {
override val step: (ResultState<T>) -> ResultState<T> = { result -> result.flatMap { state ->
when (state.target == other) {
true ->
result
false ->
Result.err(state.tip ?: "The expected values did not match")
}
}}
}
fun <T> check (predicate: (State<T>) -> Boolean) = object : Reducer<T>() {
override val step: (ResultState<T>) -> ResultState<T> = { result -> result.flatMap { state ->
when (predicate(state)) {
true ->
result
false ->
Result.err(state.tip ?: "The validation check failed")
}
}}
}
fun <T> checkNot (predicate: (State<T>) -> Boolean) = object : Reducer<T>() {
override val step: (ResultState<T>) -> ResultState<T> = { result -> result.flatMap { state ->
when (!predicate(state)) {
true ->
result
false ->
Result.err(state.tip ?: "The validation check failed")
}
}}
}
}
fun <T> validate (target: T, reducers: List<Reducer<T>>): Result<State<T>, String> {
val initResult: Result<State<T>, String> = Result.ok(State(target))
return reducers.fold(initResult, { acc, reducer ->
reducer.step(acc)
})
}
data class User(val uname: String, val password1: String, val password2: String)
fun main(args: Array<String>) {
val user = User("dan", "secret1", "secret1")
val uname = validate(user.uname, listOf(
V.tip("Username is required"),
V.isLength(3, 15)
)).apply(::println).getOrThrow()
val password1 = validate(user.password1, listOf(
V.tip("Password is required"),
V.isLength(6, 150)
)).apply(::println).getOrThrow()
val password2 = validate(user.password2, listOf(
V.tip("Password confirmation is required"),
V.isLength(6, 150),
V.tip("Password must match the confirmation"),
V.check { state ->
false
},
V.isEmpty().withTip("new tip") // <-- I can at least call .withTip() on the string methods now, tho not the others
)).apply(::println).getOrThrow()
println("Finished! $uname, $password1, $password2")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment