The single boundary helper for client-supplied text that is echoed back to the client or written to the log.
Three separate hazards, one function:
- Type. Interpolating a non-binary —
protocolVersion: {}, arequestIdobject — raises inside the handler, and the surroundingrescueturns a client mistake into a misleading "Internal server error". - Size. A 1 MB string sits comfortably inside the transports'
length: 1_000_000parser cap, and an unclamped echo reflects it in full into both the error message and the log line. - Control characters. An unfiltered echo of
"tools/call\x00\x01\x02"reaches the operator's log with terminal escapes and NULs intact.
text/2 applies to_string/1, strips control characters (C0/C1, DEL,
zero-width and bidi controls, and the Unicode line separators — keeping
nothing, not even tabs or newlines, which is what lets a value forge log
lines), and clamps both the byte length and the grapheme length. Both clamps
are needed: a grapheme cluster is unbounded in size, so one base character
plus 400 000 combining marks is a single grapheme and ~800 KB of output.
Summary
Functions
Renders value as text that is safe to put in an error message or a log
line: coerced to a string, control characters removed, clamped to max
graphemes and to a hard byte ceiling (default 200).
Functions
@spec text(term(), pos_integer()) :: String.t()
Renders value as text that is safe to put in an error message or a log
line: coerced to a string, control characters removed, clamped to max
graphemes and to a hard byte ceiling (default 200).
Examples
iex> ConduitMcp.Reflect.text("tools/call")
"tools/call"
iex> ConduitMcp.Reflect.text("tools/call\x00\x01")
"tools/call"
iex> ConduitMcp.Reflect.text(%{})
"%{}"
iex> ConduitMcp.Reflect.text(String.duplicate("a", 500)) |> String.length()
200