Skip to content

feature: Compile-check the Book's code examples (and fix 3 doc bugs they caught)#603

Merged
xerial merged 2 commits into
mainfrom
docs/book-examples
Jun 22, 2026
Merged

feature: Compile-check the Book's code examples (and fix 3 doc bugs they caught)#603
xerial merged 2 commits into
mainfrom
docs/book-examples

Conversation

@xerial

@xerial xerial commented Jun 22, 2026

Copy link
Copy Markdown
Member

The Book's authoring guide says every non-illustrative snippet must compile against the current uni — but nothing enforced it, so the examples could silently drift as the API evolves. This adds a book-examples module that makes the book self-verifying, and fixes the drift that compiling immediately surfaced.

The module

A JVM-only book-examples project (mirrors the existing netty project pattern; publish / skip, aggregated into the JVM build so CI compiles it). One source file per JVM-runnable chapter — Design, Logging, Data, Rx, Control, HTTP, RPC — using the same APIs as the chapter's snippets. Examples are compiled, not run (servers configured but not started); Ch04TestingTest is a real UniTest suite that also runs.

./sbt bookExamples/Test/compile   # compiles every example
./sbt bookExamples/test           # 3/3 pass

(Chapters 12–15 — Scala.js/Vite and Scala Native FFI — need the JS/Native toolchains and external bundlers, so they're verified against their reference sources instead, as the README notes.)

Doc bugs it caught — all fixed here

  1. Resource.withResource does not exist. The real API is Control.withResource / Control.withResources. Fixed in the book (ch08) and control/resource.md (was wrong in 10 places).
  2. The JSON access DSL needs import wvlet.uni.json.*. json("user")("name").toStringValue and json / "k" are implicit-class extensions in package object json; import wvlet.uni.json.JSON doesn't bring them into scope. Fixed in the book (ch06) and core/json.md.

These are exactly the kind of copy-paste-breaking errors the module now prevents.

pnpm docs:build passes.

🤖 Generated with Claude Code

…hey caught

Adds a `book-examples` JVM module (not published, aggregated into the JVM
build so CI compiles it) with one source per JVM-runnable book chapter —
Design, Logging, Data, Rx, Control, HTTP, RPC — plus a real UniTest suite
for the Testing chapter. The book's authoring guide requires runnable
snippets to compile; nothing enforced it until now, so they could drift
as the API evolved.

Compiling them immediately surfaced three real doc bugs, all fixed here:

- `Resource.withResource` does not exist — the API is `Control.withResource`
  / `Control.withResources`. Fixed in both the book (ch08) and the
  `control/resource.md` reference.
- The JSON access DSL (`json("user")("name").toStringValue`, `json / "k"`)
  lives in `package object json` as implicit classes, so it needs
  `import wvlet.uni.json.*`, not `import wvlet.uni.json.JSON`. Fixed in both
  the book (ch06) and `core/json.md`.

`./sbt bookExamples/test` passes (3/3); `pnpm docs:build` passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the doc Improvements or additions to documentation label Jun 22, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new book-examples module containing compile-checked Scala examples corresponding to chapters in "The Uni Book" to prevent API drift, along with documentation updates to reflect API changes (such as replacing Resource with Control.withResource). Feedback on the changes highlights two issues: first, helper classes in Ch04TestingTest.scala are defined as inner classes, which will cause dependency injection to fail at runtime due to the missing outer class instance; second, the CircuitBreaker in Ch08Control.scala is instantiated inside the combined method, preventing it from maintaining state across invocations.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +8 to +37
class Ch04TestingTest extends UniTest:
test("adds two numbers") {
val result = 2 + 3
result shouldBe 5
}

test("a list contains a value") {
Seq("a", "b", "c") shouldContain "b"
}

// Substitute, don't mock: take the real wiring and override one binding.
class Database:
def lookup(id: String): Option[String] = None
class FakeDatabase extends Database:
override def lookup(id: String): Option[String] = Some("Alice")
class UserService(db: Database):
def findUser(id: String): Option[String] = db.lookup(id)

test("looks up a user via a Design override") {
val appDesign = Design.newDesign
.bindSingleton[Database]
.bindSingleton[UserService]

val testDesign = appDesign +
Design.newDesign.bindInstance[Database](FakeDatabase())

testDesign.build[UserService] { users =>
users.findUser("123") shouldBe Some("Alice")
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Defining Database, FakeDatabase, and UserService as inner classes of Ch04TestingTest makes them non-static. Since UserService requires an outer instance of Ch04TestingTest for instantiation, Design.build[UserService] will fail at runtime because the DI container cannot satisfy the outer class dependency. Moving these helper classes to a companion object makes them static nested classes, resolving the DI instantiation issue.

class Ch04TestingTest extends UniTest:
  import Ch04TestingTest.*

  test("adds two numbers") {
    val result = 2 + 3
    result shouldBe 5
  }

  test("a list contains a value") {
    Seq("a", "b", "c") shouldContain "b"
  }

  test("looks up a user via a Design override") {
    val appDesign = Design.newDesign
      .bindSingleton[Database]
      .bindSingleton[UserService]

    val testDesign = appDesign + 
      Design.newDesign.bindInstance[Database](FakeDatabase())

    testDesign.build[UserService] { users =>
      users.findUser("123") shouldBe Some("Alice")
    }
  }

object Ch04TestingTest:
  class Database:
    def lookup(id: String): Option[String] = None
  class FakeDatabase extends Database:
    override def lookup(id: String): Option[String] = Some("Alice")
  class UserService(db: Database):
    def findUser(id: String): Option[String] = db.lookup(id)

Comment on lines +40 to +48
def combined(url: String): String =
val breaker = CircuitBreaker.withConsecutiveFailures(5)
breaker.run {
Retry.withBackOff(maxRetry = 3).run {
Control.withResource(openConnection()) { conn =>
conn.get(url)
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Defining the CircuitBreaker inside the combined method creates a new instance of the breaker on every invocation. A circuit breaker must be shared across multiple calls to maintain its state (failure count, open/closed status) and function correctly. Defining it as a member field of the Ch08Control object ensures the state is preserved across calls, matching the pattern described in the book.

  private val combinedBreaker = CircuitBreaker.withConsecutiveFailures(5)

  def combined(url: String): String =
    combinedBreaker.run {
      Retry.withBackOff(maxRetry = 3).run {
        Control.withResource(openConnection()) { conn =>
          conn.get(url)
        }
      }
    }

@xerial xerial enabled auto-merge (squash) June 22, 2026 01:44
xerial added a commit that referenced this pull request Jun 22, 2026
`docs/uni-walkthrough.md` was a 566-line single-page tour that is now
**orphaned** (not referenced in any nav, sidebar, or index — reachable
only by direct URL or site search) and fully **superseded** by The Uni
Book (narrative) plus the module references (API). It also still used
the old "dependency injection" wording and contained 566 lines of
unverified code at risk of the same drift the [book-examples
PR](#603) just found.

Rather than delete it (which would 404 any old bookmark or external
link), this replaces its content with a concise **router page**: a short
"this has moved" note and a table mapping each former section to its new
home — the relevant Book chapter and the relevant reference page. The
URL stays alive; the stale code is gone.

`pnpm docs:build` passes (all the `/book/*` and `/core/*` targets
resolve).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Code format CI check flagged the new book-examples sources and the
build.sbt edit. Apply scalafmtAll + scalafmtSbt. No behavior change;
bookExamples/Test/compile still passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xerial xerial merged commit 31eafaa into main Jun 22, 2026
13 checks passed
@xerial xerial deleted the docs/book-examples branch June 22, 2026 02:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

doc Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant