arizona_utf8 (arizona v0.4.0)
View SourceThe UTF-8 output contract: naming a rendered value that is not UTF-8.
Every render target writes UTF-8 -- an HTML response is served as
text/html; charset=utf-8, and a WebSocket frame and a ?native document are
JSON. A rendered binary must therefore hold UTF-8. Lists need nothing from this
module: arizona_template:to_bin/1 decodes them as chardata and refuses one that
does not decode. A binary is emitted as the bytes it holds, and checking every
one of them on the render path was measured too expensive (see
docs/performance.md), so the contract is enforced where it costs nothing:
- Compile time --
arizona_parse_transformwarns about a non-ASCII<<"...">>literal that is not folded into a template static, the usual source of Latin-1 bytes. - Failure paths -- a JSON encoder that refuses a frame or a
?nativevalue already proved the bytes bad;refuse/3renames its{invalid_byte, _}/unexpected_endto{non_utf8_render, Byte, Offset}, naming the value. - Dev mode -- when the dev reloader is wired,
arizona_httprenders each page with every value checked (render_view/2), so a bad one raises at its template line, and checks the finished document once;arizona_socketlocates a refused frame (locate_live/3) by rendering the live views again from the bindings they hold. Both check through an internalwrap/2.
Summary
Functions
Formats non_utf8_render, bare or wrapped in arizona_loc. Picked up by
erl_error:format_exception/3 through the error_info annotation, so the dev
error page and the crash log name the byte and the fix.
Locates a non_utf8_render raised for a WebSocket frame. The frame left the
live process's values behind, so it names the bytes but not the template line.
In dev mode the views Pid holds are rendered again from their current bindings
with every value checked; the first one that holds the bytes returns
{{arizona_loc, {Module, Line}, Reason}, Stacktrace}. Outside dev mode, or when
nothing is found, the input pair comes back unchanged.
Stands in for a ?stateful child's handler in a dev-mode page (see
render_view/2): runs the child's own mount/1 -- once -- and keeps its handler
in the bindings for render/1.
Re-raises a JSON encoder failure on Term as {non_utf8_render, Byte, Offset}
when Reason is the encoder's invalid-UTF-8 error and Term holds a binary that
is not UTF-8 (the offset is within that binary). Any other failure is re-raised
unchanged. Called only after the encoder raised, so it costs nothing otherwise.
The dev-mode render of an HTTP page: arizona_render:render_view/2 with every
value checked as it renders, inside its slot's located evaluation, so a value that
is not UTF-8 raises {arizona_loc, {Module, Line}, {non_utf8_render, Byte, Offset}}
at the template line that produced it -- in the page, a nested template, an ?each
item or a ?stateless or ?stateful child alike. The page and its children mount
once, as they do in production. The finished document is then checked once for
what no slot produced (a layout's values), raising {non_utf8_render, Byte, Offset} with the offset in the document. The output is the same bytes
render_view/2 returns.
Functions
-spec format_error(Reason, Stacktrace) -> ErrorInfo when Reason :: term(), Stacktrace :: [tuple()], ErrorInfo :: #{general := iolist()}.
Formats non_utf8_render, bare or wrapped in arizona_loc. Picked up by
erl_error:format_exception/3 through the error_info annotation, so the dev
error page and the crash log name the byte and the fix.
Offset counts from the start of what was refused: the offending binary (an
encoder refusal, the dev-mode per-value check -- the preview shows that binary),
the decoded output of a chardata list (arizona_template:to_bin/1 -- the raising
call's argument is the list, so there is no preview), or the finished document
(the dev-mode document check -- the preview is the 32 bytes around it).
-spec locate_live(Reason, Stacktrace, Pid) -> {Reason1, Stacktrace1} when Reason :: term(), Stacktrace :: erlang:stacktrace(), Pid :: pid() | undefined, Reason1 :: term(), Stacktrace1 :: erlang:stacktrace().
Locates a non_utf8_render raised for a WebSocket frame. The frame left the
live process's values behind, so it names the bytes but not the template line.
In dev mode the views Pid holds are rendered again from their current bindings
with every value checked; the first one that holds the bytes returns
{{arizona_loc, {Module, Line}, Reason}, Stacktrace}. Outside dev mode, or when
nothing is found, the input pair comes back unchanged.
-spec mount(Props) -> {Bindings, Resets} when Props :: arizona_template:bindings(), Bindings :: arizona_template:bindings(), Resets :: map().
Stands in for a ?stateful child's handler in a dev-mode page (see
render_view/2): runs the child's own mount/1 -- once -- and keeps its handler
in the bindings for render/1.
-spec refuse(Term, Reason, Stacktrace) -> no_return() when Term :: term(), Reason :: term(), Stacktrace :: erlang:stacktrace().
Re-raises a JSON encoder failure on Term as {non_utf8_render, Byte, Offset}
when Reason is the encoder's invalid-UTF-8 error and Term holds a binary that
is not UTF-8 (the offset is within that binary). Any other failure is re-raised
unchanged. Called only after the encoder raised, so it costs nothing otherwise.
-spec render(Bindings) -> arizona_template:template() when Bindings :: arizona_template:bindings().
Renders the child mount/1 stood in for through its own render/1, every value
checked.
-spec render_view(Handler, Opts) -> {ContentType, iolist()} when Handler :: module(), Opts :: arizona_live:route_opts(), ContentType :: binary().
The dev-mode render of an HTTP page: arizona_render:render_view/2 with every
value checked as it renders, inside its slot's located evaluation, so a value that
is not UTF-8 raises {arizona_loc, {Module, Line}, {non_utf8_render, Byte, Offset}}
at the template line that produced it -- in the page, a nested template, an ?each
item or a ?stateless or ?stateful child alike. The page and its children mount
once, as they do in production. The finished document is then checked once for
what no slot produced (a layout's values), raising {non_utf8_render, Byte, Offset} with the offset in the document. The output is the same bytes
render_view/2 returns.