Skip to content

Report the reason, the expression and the location of runtime render errors - #89

Open
AlexKalnitskiy wants to merge 3 commits into
masterfrom
arithmetic-error-details
Open

Report the reason, the expression and the location of runtime render errors#89
AlexKalnitskiy wants to merge 3 commits into
masterfrom
arithmetic-error-details

Conversation

@AlexKalnitskiy

@AlexKalnitskiy AlexKalnitskiy commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Previously runtime render errors were hard to trace back to the template: an arithmetic failure was reported as a bare Arithmetic operation result could not be evaluated with no location at all, and the other runtime errors (a null value usage, a missing variable value, a failed function call) named the variable or the function but never the location.

Every runtime render error now reports the expression as it is written in the template and the location, in one message shape built in one place (the UnrenderableTemplateModelException base):

<error text>[: <details>][ in "<expression>"][ at <line:column>]
Arithmetic operation result could not be evaluated: division by zero in "cart.total / cart.itemCount" at 12:34
An attempt to use a null value in "cell.Value" at 4:14
Value for variable not found in "a" at 1:41
Function invocation resulted in error: Argument must be positive in "picky()" at 1:3

Arithmetic failures are thrown as ArithmeticOperationException with a typed Reason: DivisionByZero, NotANumber or ResultOutOfRange. The other error texts are exposed as constants on the base exception, and the failed expression is a typed Expression property on it.

The same parts are put into Exception.Data under the QuokkaExceptionData keys (ErrorText, Reason, Expression, Location, Line, Column), so that the calling code can build its own (e.g. localized) message for any runtime error without parsing the message text.

Details:

  • A runtime error raised while evaluating a function argument propagates as itself instead of being re-wrapped into a generic function failure, so e.g. a division by zero inside max(a / b, 1) keeps its reason and expression.
  • The captured expression text is template source only (variable values are never put into messages or Data) and is capped at 100 characters.
  • Location.Column is documented as 0-based, matching the values the engine has always produced.
  • ArithmeticOperationException derives from UnrenderableTemplateModelException and the other errors keep their exception type, so the existing catch blocks keep working.
  • Version bumped to 8.4.0: new public API and changed error message texts.

Tests: 494/494 green on net8.0 and net9.0 (14 new). Two existing tests were adjusted: the division-by-zero one now allows a derived exception type, and the null-cell-value one asserts the new message wording.

🤖 Generated with Claude Code

AlexKalnitskiy and others added 2 commits August 14, 2026 14:30
…etic operation

Previously any arithmetic result which could not be converted to a template value
was reported as a bare "Arithmetic operation result could not be evaluated" with
no location at all, which made it impossible to tell which template expression
had failed and why.

Arithmetic expressions now keep the place they were parsed from, and the failure
is thrown as ArithmeticOperationException, which carries:

- Reason: DivisionByZero, NotANumber or ResultOutOfRange;
- Expression: the failed expression as it is written in the template;
- Location: inherited from UnrenderableTemplateModelException, now filled in.

The same parts are put into Exception.Data under the QuokkaExceptionData keys,
so that the calling code can build its own (e.g. localized) message without
parsing the message text. UnrenderableTemplateModelException also puts its
location into Exception.Data, so every runtime error is uniform in that regard.

The resulting message reads:

Arithmetic operation result could not be evaluated: division by zero in "cart.total / cart.itemCount" at 12:34

ArithmeticOperationException derives from UnrenderableTemplateModelException,
so the existing catch blocks keep working.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Arithmetic errors were given the reason, the expression and the location
in the previous commit; the remaining runtime render errors — a null value
usage, a missing variable value and a failed function call — still reported
neither the expression nor the location in the message text.

All runtime render errors now share one message shape, built in one place
(the UnrenderableTemplateModelException base):

<error text>[: <details>][ in "<expression>"][ at <line:column>]

- An attempt to use a null value in "cell.Value" at 4:14
- Value for variable not found in "a" at 1:41
- Function invocation resulted in error: Argument must be positive in "picky()" at 1:3

The stable error texts are exposed as constants, the expression is exposed
as a typed property on the base exception, and Exception.Data is filled
uniformly with QuokkaExceptionData.ErrorText and Expression in addition to
the location entries, so the calling code can build its own (e.g. localized)
message for any runtime error without parsing the message text.

Function calls now keep their source text the same way arithmetic
expressions do, so the failed call is reported as written in the template,
e.g. "max(a, b)" rather than just the function name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AlexKalnitskiy AlexKalnitskiy changed the title Report the reason, the expression and the location of a failed arithmetic operation Report the reason, the expression and the location of runtime render errors Aug 14, 2026
- A runtime error raised while evaluating a function argument now propagates
  as itself: arguments are evaluated before the try block, so an arithmetic
  failure inside an argument is no longer re-wrapped into a generic
  "Function invocation resulted in error" losing its reason and expression.
- The captured expression source text is capped at 100 characters
  (an ellipsis is appended), so unbounded template expressions can't bloat
  messages, Data entries or the compiled template.
- Expression is normalized to null when blank, so Message and
  Data[Quokka.Expression] can't disagree about whether an expression exists.
- Location.Column is documented as 0-based, matching the values the engine
  has always produced (ANTLR CharPositionInLine).
- The DivisionByZero doc no longer claims infinity is only reachable by
  dividing by zero (a chain of multiplications can overflow to infinity),
  and the QuokkaExceptionData doc scopes the Data contract to runtime
  render errors.
- Version bumped to 8.4.0: new public API and changed error messages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Code Coverage

Package Line Rate Branch Rate Complexity Health
Mindbox.Quokka.Abstractions 92% 88% 106
Mindbox.Quokka 83% 78% 2184
Summary 83% (4874 / 5845) 78% (922 / 1181) 2290

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant