Skip to main content

zenith_cli/
json_types.rs

1//! Serialisable DTO types for JSON output.
2//!
3//! These types are defined in the CLI crate — we do NOT add serde to
4//! zenith-core.  Each type maps from zenith-core/zenith-scene types to a
5//! schema-versioned JSON shape.
6
7use serde::Serialize;
8
9/// JSON representation of a [`zenith_core::Diagnostic`].
10#[derive(Debug, Serialize)]
11pub struct DiagnosticJson {
12    pub code: String,
13    pub severity: String,
14    pub message: String,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub subject_id: Option<String>,
17}
18
19impl From<&zenith_core::Diagnostic> for DiagnosticJson {
20    fn from(d: &zenith_core::Diagnostic) -> Self {
21        Self {
22            code: d.code.clone(),
23            severity: severity_str(&d.severity).to_owned(),
24            message: d.message.clone(),
25            subject_id: d.subject_id.clone(),
26        }
27    }
28}
29
30pub(crate) fn severity_str(s: &zenith_core::Severity) -> &'static str {
31    match s {
32        zenith_core::Severity::Error => "error",
33        zenith_core::Severity::Warning => "warning",
34        zenith_core::Severity::Advisory => "advisory",
35    }
36}
37
38/// Top-level JSON envelope for `validate`.
39#[derive(Debug, Serialize)]
40pub struct ValidateOutput {
41    pub schema: &'static str,
42    pub valid: bool,
43    pub diagnostics: Vec<DiagnosticJson>,
44}
45
46/// Top-level JSON envelope for `fmt`.
47#[derive(Debug, Serialize)]
48pub struct FmtOutput {
49    pub schema: &'static str,
50    pub changed: bool,
51    pub hash: String,
52}
53
54/// A single token entry for `tokens` output.
55#[derive(Debug, Serialize)]
56pub struct TokenEntry {
57    pub id: String,
58    pub token_type: String,
59    pub resolved_value: String,
60}
61
62/// Top-level JSON envelope for `tokens`.
63#[derive(Debug, Serialize)]
64pub struct TokensOutput {
65    pub schema: &'static str,
66    pub tokens: Vec<TokenEntry>,
67    pub diagnostics: Vec<DiagnosticJson>,
68}
69
70/// Top-level JSON envelope for `render`.
71#[derive(Debug, Serialize)]
72pub struct RenderOutput {
73    pub schema: &'static str,
74    pub diagnostics: Vec<DiagnosticJson>,
75}
76
77/// Top-level JSON envelope for `tx`.
78#[derive(Debug, Serialize)]
79pub struct TxOutputJson {
80    pub schema: &'static str,
81    pub status: String,
82    pub affected: Vec<String>,
83    pub diagnostics: Vec<DiagnosticJson>,
84    pub changed: bool,
85}
86
87/// Per-row result in the `merge --json` batch report.
88#[derive(Debug, Serialize)]
89pub struct MergeRowResult {
90    pub row: usize,
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub key: Option<String>,
93    pub status: &'static str,
94    pub outputs: Vec<String>,
95    pub diagnostics: Vec<DiagnosticJson>,
96}
97
98/// Top-level JSON envelope for `merge --json`.
99#[derive(Debug, Serialize)]
100pub struct MergeOutput {
101    pub schema: &'static str,
102    pub total_rows: usize,
103    pub written: usize,
104    pub failed: usize,
105    pub rows: Vec<MergeRowResult>,
106}
107
108/// One row entry in the generation manifest (successful rows only).
109#[derive(Debug, Serialize)]
110pub struct ManifestRow {
111    pub row: usize,
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub key: Option<String>,
114    pub outputs: Vec<String>,
115}
116
117/// Deterministic generation manifest for `zenith merge --manifest`.
118#[derive(Debug, Serialize)]
119pub struct MergeManifest {
120    pub schema: &'static str,
121    /// Manifest format version. Bumped only when the manifest structure changes
122    /// (never on a routine crate release), so identical inputs stay byte-identical.
123    pub generator: &'static str,
124    pub source_sha256: String,
125    pub data_sha256: String,
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub name_by: Option<String>,
128    pub rows: Vec<ManifestRow>,
129}
130
131// ── Variant JSON types ────────────────────────────────────────────────────────
132
133/// Per-variant result in the `variant --json` envelope.
134#[derive(Debug, Serialize)]
135pub struct VariantResultJson {
136    pub id: String,
137    pub source: String,
138    pub status: &'static str,
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub outputs_zen: Option<String>,
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub outputs_png: Option<String>,
143    pub diagnostics: Vec<DiagnosticJson>,
144}
145
146/// Top-level JSON envelope for `variant --json`.
147#[derive(Debug, Serialize)]
148pub struct VariantOutput {
149    pub schema: &'static str,
150    pub total_variants: usize,
151    pub generated: usize,
152    pub failed: usize,
153    pub variants: Vec<VariantResultJson>,
154}
155
156/// One target entry in the variant generation manifest (successful variants only).
157#[derive(Debug, Serialize)]
158pub struct VariantManifestTarget {
159    pub id: String,
160    pub source: String,
161    pub outputs_zen: String,
162    pub outputs_png: String,
163}
164
165/// Deterministic generation manifest for `zenith variant --manifest`.
166#[derive(Debug, Serialize)]
167pub struct VariantManifest {
168    pub schema: &'static str,
169    /// Manifest format version. Bumped only when the manifest structure changes
170    /// (never on a routine crate release), so identical inputs stays byte-identical.
171    pub generator: &'static str,
172    pub source_sha256: String,
173    pub targets: Vec<VariantManifestTarget>,
174}
175
176// ── Schema JSON types ─────────────────────────────────────────────────────────
177
178/// A single node-kind entry in the `schema nodes` JSON output.
179#[derive(Debug, Serialize)]
180pub struct SchemaNodeEntry {
181    pub kind: String,
182    pub summary: String,
183}
184
185/// A single attribute entry in the `schema node <kind>` and `schema page/asset/document` JSON output.
186#[derive(Debug, Serialize)]
187pub struct SchemaAttr {
188    pub name: String,
189    pub ty: String,
190}
191
192/// A single node-kind detail entry in the `schema node <kind>` JSON output.
193#[derive(Debug, Serialize)]
194pub struct SchemaNodeDetail {
195    pub kind: String,
196    pub summary: String,
197    pub attributes: Vec<SchemaAttr>,
198    /// Minimal full-node authoring example, when one is available.
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub example: Option<String>,
201    /// Child-content descriptor for kinds that accept authorable children.
202    /// Absent from JSON for kinds with no authorable child content.
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub content: Option<SchemaNodeContent>,
205}
206
207/// Child-content descriptor embedded in [`SchemaNodeDetail`].
208#[derive(Debug, Serialize)]
209pub struct SchemaNodeContent {
210    pub description: String,
211    pub example: String,
212}
213
214/// A single op entry in the `schema ops` JSON output.
215#[derive(Debug, Serialize)]
216pub struct SchemaOpEntry {
217    pub op: String,
218    pub summary: String,
219}
220
221/// A single op field entry in the `schema op <name>` JSON output.
222#[derive(Debug, Serialize)]
223pub struct SchemaOpFieldEntry {
224    pub name: String,
225    pub ty: String,
226    pub required: bool,
227}
228
229/// Full detail entry in the `schema op <name>` JSON output.
230#[derive(Debug, Serialize)]
231pub struct SchemaOpDetail {
232    pub op: String,
233    pub summary: String,
234    pub fields: Vec<SchemaOpFieldEntry>,
235    pub example: String,
236}
237
238/// Top-level JSON envelope for `schema nodes`.
239#[derive(Debug, Serialize)]
240pub struct SchemaNodesOutput {
241    pub schema: &'static str,
242    pub nodes: Vec<SchemaNodeEntry>,
243}
244
245/// Top-level JSON envelope for `schema node <kind>`.
246#[derive(Debug, Serialize)]
247pub struct SchemaNodeOutput {
248    pub schema: &'static str,
249    pub node: SchemaNodeDetail,
250}
251
252/// Top-level JSON envelope for `schema ops`.
253#[derive(Debug, Serialize)]
254pub struct SchemaOpsOutput {
255    pub schema: &'static str,
256    pub ops: Vec<SchemaOpEntry>,
257}
258
259/// Top-level JSON envelope for `schema op <name>`.
260#[derive(Debug, Serialize)]
261pub struct SchemaOpOutput {
262    pub schema: &'static str,
263    pub op: SchemaOpDetail,
264}
265
266/// Top-level JSON envelope for bare `zenith schema` (overview).
267#[derive(Debug, Serialize)]
268pub struct SchemaOverviewOutput {
269    pub schema: &'static str,
270    pub node_kinds: usize,
271    pub tx_ops: usize,
272    pub token_types: usize,
273}
274
275/// A single token-type entry in the `schema tokens` JSON output.
276#[derive(Debug, Serialize)]
277pub struct SchemaTokenEntry {
278    pub ty: String,
279    pub summary: String,
280}
281
282/// Full detail for one token type in the `schema token <type>` JSON output.
283#[derive(Debug, Serialize)]
284pub struct SchemaTokenDetail {
285    pub ty: String,
286    pub summary: String,
287    pub value_form: String,
288    pub child_nodes: String,
289    pub example: String,
290}
291
292/// Top-level JSON envelope for `schema tokens`.
293#[derive(Debug, Serialize)]
294pub struct SchemaTokensOutput {
295    pub schema: &'static str,
296    pub token_types: Vec<SchemaTokenEntry>,
297}
298
299/// Top-level JSON envelope for `schema token <type>`.
300#[derive(Debug, Serialize)]
301pub struct SchemaTokenOutput {
302    pub schema: &'static str,
303    pub token: SchemaTokenDetail,
304}
305
306/// Top-level JSON envelope for `schema page`, `schema asset`, `schema document`.
307#[derive(Debug, Serialize)]
308pub struct SchemaSurfaceOutput {
309    pub schema: &'static str,
310    /// Which non-node surface this describes: `"page"`, `"asset"`, or `"document"`.
311    pub surface: &'static str,
312    pub summary: String,
313    pub attributes: Vec<SchemaAttr>,
314}
315
316/// A single governable diagnostic-code entry in the `schema diagnostics` JSON.
317#[derive(Debug, Serialize)]
318pub struct SchemaDiagnosticCode {
319    pub code: String,
320    /// `"error"`, `"warning"`, or `"advisory"`.
321    pub severity: String,
322    pub summary: String,
323    /// True when an `allow`/`deny`/`warn` entry can adjust this code (Warning /
324    /// Advisory). Error-severity codes are immutable and report `false`.
325    pub governable: bool,
326}
327
328/// Top-level JSON envelope for `schema diagnostics`.
329#[derive(Debug, Serialize)]
330pub struct SchemaDiagnosticsOutput {
331    pub schema: &'static str,
332    pub summary: String,
333    /// The policy verbs: `allow`, `deny`, `warn`.
334    pub verbs: Vec<String>,
335    /// Canonical KDL policy-entry forms.
336    pub syntax: Vec<String>,
337    /// Precedence note: in-file `diagnostics { … }` now; CLI flags/config later.
338    pub precedence: &'static str,
339    /// Every diagnostic code in the catalog (governable and always-Error).
340    pub codes: Vec<SchemaDiagnosticCode>,
341}
342
343/// One override property entry in the `schema variant` JSON output.
344#[derive(Debug, Serialize)]
345pub struct SchemaOverridePropEntry {
346    pub name: String,
347    pub ty: String,
348    pub required: bool,
349}
350
351/// Top-level JSON envelope for `schema variant`.
352#[derive(Debug, Serialize)]
353pub struct SchemaVariantOutput {
354    pub schema: &'static str,
355    pub summary: String,
356    pub block_structure: String,
357    pub variant_node: String,
358    pub override_entry: String,
359    pub override_props: Vec<SchemaOverridePropEntry>,
360    pub example: String,
361}
362
363/// Top-level JSON envelope for `schema brand`.
364#[derive(Debug, Serialize)]
365pub struct SchemaBrandOutput {
366    pub schema: &'static str,
367    pub summary: String,
368    pub placement: &'static str,
369    pub child_nodes: Vec<SchemaBrandChildNode>,
370    pub absent_means: &'static str,
371    pub diagnostic_codes: Vec<SchemaBrandDiagCode>,
372    pub example: &'static str,
373}
374
375/// One child-node descriptor in the `schema brand` JSON output.
376#[derive(Debug, Serialize)]
377pub struct SchemaBrandChildNode {
378    pub node: &'static str,
379    pub syntax: &'static str,
380    pub description: &'static str,
381}
382
383/// One diagnostic code entry in the `schema brand` JSON output.
384#[derive(Debug, Serialize)]
385pub struct SchemaBrandDiagCode {
386    pub code: &'static str,
387    pub severity: &'static str,
388    pub summary: &'static str,
389}
390
391// ── Fonts JSON types ──────────────────────────────────────────────────────────
392
393/// Top-level JSON envelope for `zenith fonts --json`.
394#[derive(Debug, Serialize)]
395pub struct FontsOutput {
396    pub schema: &'static str,
397    /// Family names bundled in the binary (lowercase, sorted). These are portable:
398    /// any machine with this Zenith binary will resolve them identically.
399    pub bundled: Vec<String>,
400    /// Family names found on this machine only (lowercase, sorted), after excluding
401    /// any family already in `bundled`. Using these trips a `font.local` advisory
402    /// and renders may differ on another machine.
403    pub local: Vec<String>,
404}
405
406// ── Recipe inspect JSON types ─────────────────────────────────────────────────
407
408/// A single `param` entry within a [`RecipeInspectJson`].
409#[derive(Debug, Serialize)]
410pub struct RecipeParamInspectJson {
411    pub name: String,
412    /// Canonical string representation of the parameter value: a token ref is
413    /// rendered as `"<id>"`, a literal as its raw string, a dimension as
414    /// `"(<unit>)<value>"`.
415    pub value: String,
416}
417
418/// A single recipe entry in the `recipes` array of [`crate::commands::inspect::InspectOutput`].
419#[derive(Debug, Serialize)]
420pub struct RecipeInspectJson {
421    pub id: String,
422    pub kind: String,
423    #[serde(skip_serializing_if = "Option::is_none")]
424    pub seed: Option<i64>,
425    #[serde(skip_serializing_if = "Option::is_none")]
426    pub generator: Option<String>,
427    #[serde(skip_serializing_if = "Option::is_none")]
428    pub bounds: Option<String>,
429    #[serde(skip_serializing_if = "Option::is_none")]
430    pub detached: Option<bool>,
431    pub params: Vec<RecipeParamInspectJson>,
432    pub palette: Vec<String>,
433    pub expanded: Vec<String>,
434}