arizona_utf8 (arizona v0.4.0)

View Source

The 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_transform warns 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 ?native value already proved the bytes bad; refuse/3 renames its {invalid_byte, _} / unexpected_end to {non_utf8_render, Byte, Offset}, naming the value.
  • Dev mode -- when the dev reloader is wired, arizona_http renders 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_socket locates a refused frame (locate_live/3) by rendering the live views again from the bindings they hold. Both check through an internal wrap/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.

Renders the child mount/1 stood in for through its own render/1, every value checked.

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

format_error(Reason, Stacktrace)

-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).

locate_live(Reason, Stacktrace, Pid)

-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.

mount(Props)

-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.

refuse(Term, Reason, Stacktrace)

-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.

render(Bindings)

-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.

render_view(Handler, Opts)

-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.