Error Handling for App Authors¶
The conformance error codes tell you what went wrong at a template-fixture level. This page is for the other side of that: what your app actually catches, what it can read off the exception, and a worked recipe for turning that into a message an end user (not a kBars maintainer) can act on — the case that matters most if your app lets users author templates, such as a CMS.
The exception hierarchy¶
KbTemplate.render throws exactly two public exception types, both extending the sealed-ish base
KbException:
KbException (abstract)
├── KbParseException — the template source could not be parsed
└── KbRenderException — the template parsed fine, but a fatal error occurred while rendering it
Both carry the same three things, inherited from KbException:
message(String, nevernull) — the underlying error's message, with the template position folded in when one was captured (see below).location(KbSourceLocation?) — where in the template source the failure occurred, ornullwhen no position could be attributed.errorCode(String) — the stable conformance code for this failure (for example,"KB-2001"), or"KB-0000"for an unclassified failure. This is the identifier to key any lookup table on — the human-readable message is not guaranteed stable across kBars versions, but the code is.
Catch KbException when your app doesn't care which phase failed; catch KbParseException and
KbRenderException separately when it does — for example, a CMS's "preview" step might want to
report parse errors inline in an editor gutter, while a render error surfaces only when the template
is actually invoked against live data.
try {
val template = KbTemplate.create(source)
template.render(env)
} catch (e: KbParseException) {
// source itself is malformed — surface at edit time
} catch (e: KbRenderException) {
// source is fine; something about this render failed (unknown transform, malformed assign target, …)
}
KbTemplate.create itself never throws — parsing is deferred until the first render call, so both
exception types are only ever raised from render.
What's not an exception: soft failures¶
Not every problem during a render is fatal. A soft failure — a type mismatch fed into a transform, a
reference to a name the context doesn't have, a search transform that found nothing — is recoverable:
the expression in question evaluates to nothing and the render completes normally with no exception at
all. If you want visibility into these, register a KbEventListener:
val env = KbEnvironment.create(context) {
eventListener(
object : KbEventListener() {
override fun onSoftFail(event: KbSoftFailEvent) {
logger.warn("kBars soft failure ${event.category.code}: ${event.problem}")
}
}
)
}
Each KbSoftFailEvent carries its own category (with a stable code, mirroring errorCode on the
exception types), a problem description, the offending input as a string (when there was a single
one), the transformName it happened inside (when applicable), and its own location. See
Custom Transforms for the full soft-fail
vocabulary and how a custom transform reports one.
The practical split: a KbEventListener is how you observe problems without interrupting the
render; KbRenderException/KbParseException are how you find out the render didn't complete.
Reading a KbSourceLocation¶
When location is non-null, it pinpoints the failure precisely enough to show a user their own
template, not a stack trace:
line— 1-based line number.column— 0-based, counted in Unicode code points (an emoji is one column, not two, even though it's two UTF-16 chars).sourceText— the verbatim failing expression, e.g."price | divided_by: qty".templateStack— the chain of templates active at the failure, outermost first. Index 0 is alwaysKbTemplateOrigin.Root; each following entry is aKbTemplateOrigin.Partialnaming the partial key entered next. A failure inside a partial three levels deep has a four-element stack; a failure in the root template alone has the single-element stack[Root].
KbException.message already folds this into a human string for you ("... (at partial 'row' line
3:12: price | divided_by: qty)"), which is enough for a log line. Building a user-facing message
usually means re-deriving the pieces yourself, since the log format is meant for a developer, not an
end user:
fun describeLocation(location: KbSourceLocation): String {
val where = when (val origin = location.templateStack.last()) {
KbTemplateOrigin.Root -> "your template"
is KbTemplateOrigin.Partial -> "the \"${origin.key}\" partial"
}
return "$where, line ${location.line}"
}
Recipe: a CMS-style user-facing message¶
Say end users author templates in a CMS, and you want a preview pane to show something better than a raw stack trace. The shape is the same for a parse error (caught at "save"/"preview" time) and a render error (caught when the template runs against real content):
fun renderForPreview(source: String, env: KbEnvironment): PreviewResult =
try {
PreviewResult.Success(KbTemplate.create(source).render(env))
} catch (e: KbParseException) {
PreviewResult.Failure(userMessage(e, phase = "This template has a syntax error"))
} catch (e: KbRenderException) {
PreviewResult.Failure(userMessage(e, phase = "This template failed to render"))
}
private fun userMessage(e: KbException, phase: String): String {
val location = e.location ?: return "$phase. (${e.errorCode})"
val where = describeLocation(location)
return "$phase in $where: ${location.sourceText}"
}
sealed interface PreviewResult {
data class Success(val output: String) : PreviewResult
data class Failure(val message: String) : PreviewResult
}
Two things worth building in from the start, not bolting on later:
- Key off
errorCode, notmessage. If you ever want to localize these messages, or offer different wording per failure kind ("unknown transform" vs. "unterminated block"), branch onerrorCode— it's the part of the contract that's stable across kBars releases. - Don't show
e.causeto the end user. The underlyingThrowable(an ANTLR parse error, anIllegalArgumentExceptionfrom deep in the renderer) is a developer-facing detail. Log it; don't put it in the CMS preview pane.
Where to next¶
- Custom Transforms — the soft-fail vs. throw decision from inside a transform you write yourself, which is the other half of this contract.
- Environment Properties — the builder DSL
eventListeneris configured through. - API Reference — the full Dokka reference for
KbException,KbSourceLocation, andKbSoftFailEvent.