kotlinadvanced

Type-Safe DSL Builder Pattern

Create type-safe DSLs in Kotlin with receiver lambdas, @DslMarker, and nested builder scopes.

kotlin
@DslMarker
annotation class HtmlDsl

@HtmlDsl
class HTML {
    private val children = mutableListOf<Element>()
    fun head(block: Head.() -> Unit) { children.add(Head().apply(block)) }
    fun body(block: Body.() -> Unit) { children.add(Body().apply(block)) }
    override fun toString() =
        "<html>\n${children.joinToString("\n") { "  $it" }}\n</html>"
}

@HtmlDsl
open class Element(private val tag: String) {
    protected val children = mutableListOf<Any>()
    protected val attributes = mutableMapOf<String, String>()
    fun attr(key: String, value: String) { attributes[key] = value }
    override fun toString(): String {
        val attrs = if (attributes.isEmpty()) ""
            else " " + attributes.entries.joinToString(" ") { "${it.key}=\"${it.value}\"" }
        val content = children.joinToString("\n") { "  $it" }
        return if (children.isEmpty()) "<$tag$attrs />"
        else "<$tag$attrs>\n$content\n</$tag>"
    }
}

class Head : Element("head") {
    fun title(text: String) { children.add("<title>$text</title>") }
    fun meta(name: String, content: String) {
        children.add("<meta name=\"$name\" content=\"$content\" />")
    }
}

@HtmlDsl
class Body : Element("body") {
    fun h1(text: String) { children.add(Element("h1").apply { children.add(text) }) }
    fun p(text: String) { children.add(Element("p").apply { children.add(text) }) }
    fun div(block: Body.() -> Unit) {
        val el = Body(); el.block()
        children.add(Element("div").apply { this.children.addAll(el.children) })
    }
    fun ul(block: ListBuilder.() -> Unit) { children.add(ListBuilder().apply(block)) }
    fun a(href: String, text: String) {
        children.add(Element("a").apply { attr("href", href); children.add(text) })
    }
}

class ListBuilder : Element("ul") {
    fun li(text: String) { children.add(Element("li").apply { children.add(text) }) }
}

fun html(block: HTML.() -> Unit): HTML = HTML().apply(block)

fun main() {
    val page = html {
        head {
            title("My Page")
            meta("description", "A type-safe HTML DSL")
        }
        body {
            h1("Welcome!")
            p("This is built with a Kotlin DSL.")
            div {
                p("Inside a div")
                ul {
                    li("First item")
                    li("Second item")
                    li("Third item")
                }
            }
            a("https://kotlinlang.org", "Learn Kotlin")
        }
    }
    println(page)
}

Use Cases

  • HTML/XML document generation
  • Configuration DSLs for frameworks
  • Type-safe query builders

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.