scalaadvanced

DSL Builder Pattern

Create type-safe domain-specific languages with Scala's builder pattern using apply and infix notation.

scala
import scala.collection.mutable.ListBuffer

// SQL-like DSL
case class Query(
  table: String,
  conditions: List[String] = Nil,
  columns: List[String] = List("*"),
  ordering: Option[String] = None,
  limit: Option[Int] = None
):
  def select(cols: String*): Query = copy(columns = cols.toList)
  def where(condition: String): Query = copy(conditions = conditions :+ condition)
  def orderBy(col: String): Query = copy(ordering = Some(col))
  def limit(n: Int): Query = copy(limit = Some(n))

  def toSQL: String =
    val cols = columns.mkString(", ")
    val base = s"SELECT $cols FROM $table"
    val where_ = if conditions.nonEmpty then s" WHERE ${conditions.mkString(" AND ")}" else ""
    val order = ordering.map(o => s" ORDER BY $o").getOrElse("")
    val lim = limit.map(l => s" LIMIT $l").getOrElse("")
    base + where_ + order + lim

def from(table: String): Query = Query(table)

// HTML DSL
class Element(val tag: String):
  private val children = ListBuffer[String]()
  private val attrs = ListBuffer[(String, String)]()

  def attr(key: String, value: String): Element =
    attrs += ((key, value)); this

  def content(text: String): Element =
    children += text; this

  def child(elem: Element): Element =
    children += elem.render; this

  def render: String =
    val attrStr = if attrs.nonEmpty then
      " " + attrs.map((k, v) => s"""$k="$v"""").mkString(" ")
    else ""
    s"<$tag$attrStr>${children.mkString}</$tag>"

def div = Element("div")
def p = Element("p")
def h1 = Element("h1")
def a = Element("a")
def ul = Element("ul")
def li = Element("li")

// Config DSL
case class ServerConfig(
  host: String = "localhost",
  port: Int = 8080,
  maxConnections: Int = 100,
  ssl: Boolean = false,
  routes: List[(String, String)] = Nil
)

class ServerBuilder:
  private var config = ServerConfig()

  def host(h: String): ServerBuilder = { config = config.copy(host = h); this }
  def port(p: Int): ServerBuilder = { config = config.copy(port = p); this }
  def maxConnections(n: Int): ServerBuilder = { config = config.copy(maxConnections = n); this }
  def enableSSL: ServerBuilder = { config = config.copy(ssl = true); this }
  def route(path: String, handler: String): ServerBuilder =
    config = config.copy(routes = config.routes :+ (path, handler)); this
  def build: ServerConfig = config

def server: ServerBuilder = ServerBuilder()

@main def run(): Unit =
  // SQL DSL
  val query = from("users")
    .select("name", "email", "age")
    .where("age > 18")
    .where("active = true")
    .orderBy("name ASC")
    .limit(50)
  println(query.toSQL)

  // HTML DSL
  val html = div.attr("class", "container").child(
    h1.content("Welcome")
  ).child(
    p.content("Hello, World!")
  ).child(
    ul.child(
      li.content("Item 1")
    ).child(
      li.content("Item 2")
    )
  )
  println(html.render)

  // Config DSL
  val cfg = server
    .host("0.0.0.0")
    .port(443)
    .maxConnections(1000)
    .enableSSL
    .route("/api/users", "UserController")
    .route("/api/orders", "OrderController")
    .build
  println(s"Server: ${cfg.host}:${cfg.port} (SSL=${cfg.ssl})")
  cfg.routes.foreach((path, handler) => println(s"  $path -> $handler"))

Use Cases

  • Type-safe query builders
  • Configuration DSLs
  • HTML/markup generation

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.