「つくってあそぼ Kotlin DSL ~拡張編~」を発表しました

2018.11.05

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

【11/2(金) 大阪】第2弾クラスメソッドのモバイル開発を知る!第2回〜Android編で「つくってあそぼ Kotlin DSL ~拡張編~」を発表しました。

モバイルメソッド大阪 第3回 #mobilemethodでは、DSLの作り方を発表し、今回はDSLの利用の仕方にフォーカスしました。

発表資料

ソースコード

object Validations {

    inline fun <reified T> define(init: ValidationBuilder<T>.() -> Unit) {
        val validation = ValidationBuilder<T>().apply(init).build()
        val key = (T::class.java).run {
            "${`package`.name}.$simpleName"
        }
        map[key] = validation
    }

    inline fun <reified T> key(): String {
        val key = (T::class.java).run {
            "${`package`.name}.$simpleName"
        }
        return key
    }

    val map = mutableMapOf<String, Validation<*>>()

    operator fun invoke(init: Validations.() -> Unit) {
        init()
    }

    fun get(key: String): Any? {
        return map[key]
    }

    inline fun <reified T> get(): Validation<T> {
        val key = (T::class.java).run {
            "${`package`.name}.$simpleName"
        }

        val validation = map.getOrElse(key) {
            throw IllegalArgumentException("not found validation: $key")
        }
        return (validation as Validation<T>)

    }

    inline fun <reified T> validate(entity: T): Map<String, List<String>> {
        val validation = get<T>()
        return validation.validate(entity)
    }

}

interface Validable<T> {
    val key: String

    fun validate(): Map<String, List<String>> =
            (Validations.get(key) as Validation<T>).validate(this as T)
}

inline fun <reified T> Validable<T>.key() = Validations.key<T>()

object ValidableDelegate {

    inline fun <reified T> get() = Validable<T>(Validations.key<T>())

    class Validable<T>(private val key: String) : ReadOnlyProperty<Any, Validation<T>> {

        override fun getValue(thisRef: Any, property: KProperty<*>): Validation<T> {
            return Validations.get(key) as Validation<T>
        }

    }
}


class Validation<T>(val validations: Map<String, ChildValidation<T>>) {

    companion object {
        inline operator fun <T> invoke(init: ValidationBuilder<T>.() -> Unit): Validation<T> {
            val builder = ValidationBuilder<T>().apply(init)
            return builder.build()
        }
    }

    fun validate(value: T): Map<String, List<String>> {
        val messages = mutableMapOf<String, List<String>>()
        validations.forEach { map ->
            val errors = map.value.validations
                    .asSequence()
                    .filter { !it.first.invoke(value) }
                    .map { it.second }
                    .filter { it.isNotEmpty() }
                    .toList()
            if (errors.isNotEmpty()) {
                messages.put(map.key, errors)
            }
        }
        return messages
    }
}

class ValidationBuilder<T> {
    val validations = mutableMapOf<String, ChildValidation<T>>()

    operator fun String.invoke(init: ChildValidation<T>.() -> Unit) {
        validations.put(this, ChildValidation<T>().apply(init))
    }

    fun build(): Validation<T> {
        return Validation(validations)
    }

}

class ChildValidation<T> {
    val validations = mutableListOf<Pair<T.() -> Boolean, String>>()
    fun be(f: T.() -> Boolean) = f

    class ErrorMessageSyntax<T>(val validation: T.() -> Boolean)
    class WithCallbackSyntax<T>(val validation: T.() -> Boolean)

    infix fun (T.() -> Boolean).not(syntax: error) = ErrorMessageSyntax(this)

    infix fun (T.() -> Boolean).not(syntax: with) = WithCallbackSyntax(this)

    infix fun WithCallbackSyntax<T>.callback(pair: Pair<() -> Unit, String>) {
    }

    operator fun String.invoke(f: () -> Unit): Pair<() -> Unit, String> {
        return f to this
    }


    infix fun ErrorMessageSyntax<T>.message(message: String) {
        validations.add(Pair(this.validation, message))
    }

    infix fun (T.() -> Boolean).not(message: String) {
        validations.add(Pair(this, message))
    }


}

object error
object with

あとがき

前回に引き続き前日にボードゲーム会も開催し、満喫した大阪でした。

これでKotlin DSL系の話は一段落つきました。、Kotlin 1.3の新機能を楽しみしたいと思います。