Error Handling¶
T-string backends catch errors that f-strings silently ignore and block injection by construction.
Validation errors¶
The full example demonstrates three kinds of validation errors and injection prevention:
"""Validation and error handling: t-string safety vs f-string fragility.
Demonstrates how t-string backends detect errors that f-strings silently
ignore, plus injection prevention analogous to SQL parameterized queries.
"""
from __future__ import annotations
import json
from json_tstring import render_data, render_text
def main() -> None:
print("=== Error 1: Non-serializable value ===\n")
class DatabaseConnection:
def __init__(self, host: str):
self.host = host
conn = DatabaseConnection("db.example.com")
try:
render_text(t'{{"connection": {conn}}}')
except Exception as exc:
print(f" {type(exc).__name__}: {exc}")
print(" f-string would silently produce repr() junk.\n")
print("=== Error 2: Invalid key type ===\n")
key = 42
try:
render_text(t'{{{key}: "value"}}')
except Exception as exc:
print(f" {type(exc).__name__}: {exc}")
print(" JSON keys must be strings — t-strings enforce this.\n")
print("=== Error 3: Non-finite float ===\n")
value = float("inf")
try:
render_text(t'{{"metric": {value}}}')
except Exception as exc:
print(f" {type(exc).__name__}: {exc}")
print(" JSON forbids Infinity/NaN — t-strings reject them.\n")
print("=== Injection safety: f-string vs t-string ===\n")
# Malicious input that closes the string and injects a new key
user_input = 'admin", "role": "superuser'
# f-string: vulnerable — attacker overrides the role
fstring_result = f'{{"role": "viewer", "username": "{user_input}"}}'
parsed = json.loads(fstring_result)
print(f" f-string parsed role: {parsed.get('role')}")
print(" ^^^ Injection succeeded: attacker controls the role!\n")
# t-string: safe — value is properly escaped
tstring_text = render_text(
t'{{"role": "viewer", "username": {user_input}}}'
)
parsed = json.loads(tstring_text)
print(f" t-string parsed role: {parsed.get('role')}")
print(f" t-string parsed username: {parsed.get('username')}")
print(" ^^^ Injection blocked: value properly escaped.")
if __name__ == "__main__":
main()
Error hierarchy¶
All backends share the same error hierarchy from tstring-bindings:
TemplateError
├── TemplateParseError — template syntax is invalid
├── TemplateSemanticError — valid syntax but invalid semantics (e.g., wrong key type)
└── UnrepresentableValueError — Python value can't be represented in the target format
Errors carry Diagnostic objects with source spans and expression labels for precise error reporting.
Common errors¶
Non-serializable values¶
from json_tstring import render_text
conn = SomeObject()
render_text(t'{{"connection": {conn}}}')
# => UnrepresentableValueError
F-strings would silently produce repr() output — invalid JSON.
Invalid key types¶
from json_tstring import render_text
key = 42
render_text(t'{{{key}: "value"}}')
# => TemplateSemanticError
JSON keys must be strings. T-strings enforce this at render time.
Non-finite floats¶
from json_tstring import render_text
value = float("inf")
render_text(t'{{"metric": {value}}}')
# => UnrepresentableValueError
JSON (RFC 8259) forbids Infinity and NaN. This library also rejects them for YAML to keep output portable.
Injection prevention¶
T-strings prevent injection the same way SQL parameterized queries prevent SQL injection:
import json
from json_tstring import render_text
# Malicious input
user_input = 'admin", "role": "superuser'
# f-string: VULNERABLE — attacker overrides the role
fstring = f'{{"role": "viewer", "username": "{user_input}"}}'
json.loads(fstring).get("role") # => "superuser" (injected!)
# t-string: SAFE — value is properly escaped
tstring = render_text(t'{{"role": "viewer", "username": {user_input}}}')
json.loads(tstring).get("role") # => "viewer" (correct)
Values are inserted into the parsed AST, not concatenated into strings, so the attacker cannot break out of the value slot.