Render helpers¶
Two functions that turn a slice of rows into a string. They exist as a
pair so one --output text|json flag can pick between them without the
calling command formatting anything itself.
#[must_use]
pub fn render_table<R: Tabled>(rows: &[R]) -> String;
pub fn render_json<R: Serialize>(rows: &[R]) -> Result<String, RenderError>;
Both take a slice, so both accept &vec and &[..]. Neither prints
anything — they return the string and leave the writing to you.
render_table output shape¶
The style is tabled's Style::psql(), applied unconditionally. There
is no parameter, no builder and no configuration key that changes it —
see Why the table style is fixed.
For two rows of struct Row { name: &'static str, count: u32 }:
Columns are padded to the widest cell, so the rule width follows the data.
Column headers come from the field names of the Tabled derive, in
declaration order. Rename them with #[tabled(rename = "...")] on the
field; that is a tabled attribute, not an rtb-tui one.
The returned string always ends with a newline. If tabled did not
produce one, render_table appends it, so print! is the right macro
and println! will add a blank line.
render_table is infallible — there is no error type and no panic path
for a type that implements Tabled.
What render_table does with an empty slice¶
An empty slice still renders the header and the separator rule, because the headers come from the type rather than from the data:
It does not return an empty string, and it does not return a "no rows"
message. If you want one, check rows.is_empty() before calling.
render_json output shape¶
serde_json::to_string_pretty, which is a two-space indent, plus a
trailing newline appended by render_json. The top level is always a
JSON array, including for one row and for zero rows:
An empty slice renders as [] followed by a newline. Field names come
from serde, so #[serde(rename = "...")] and #[serde(skip)] apply
in the usual way — and note that serde and tabled have separate
rename attributes, so a field renamed for one is not renamed for the
other.
When render_json returns an error¶
render_json returns RenderError::Json(String) when
serde_json::to_string_pretty fails on the rows. The payload is the
underlying serde_json::Error already converted to a string.
In practice serde_json fails on very little. Measured against this
crate at v0.6.3:
| Input | Result |
|---|---|
HashMap<i32, _> — integer map keys |
Ok — keys are stringified, {"7": ...} |
f64::NAN, f64::INFINITY |
Ok — both serialise as null |
HashMap<(u8, u8), _> — tuple map keys |
Err(RenderError::Json("key must be a string")) |
A hand-written Serialize impl that returns an error |
Err(RenderError::Json(_)) with that impl's message |
So the realistic triggers are a map keyed by something that is not
string-like, and a hand-written Serialize impl that refuses a value.
Both are programmer mistakes in the row type rather than anything a
user typed, which is why the error carries a string rather than the
original serde_json::Error.
The rustdoc on render_json names non-finite floats and non-string map
keys as the typical causes. Half of that is wrong — see
Known documentation defects.
Using both from one row type¶
A type that feeds both helpers needs both derives, and they are independent:
use serde::Serialize;
use tabled::Tabled;
#[derive(Tabled, Serialize)]
struct Row {
name: &'static str,
count: u32,
}
Tabled renders each field through Display unless you tell it
otherwise, which rules out nesting: a field whose type is another
struct will not derive Tabled without a per-field tabled attribute,
even though Serialize handles it happily. A nested shape is the one
case where the text and JSON views of the same type genuinely diverge —
the table flattens or drops what the JSON keeps.