Skip to main content

benday_core/
spec.rs

1//! The chart specification: a strict subset of Vega-Lite's JSON grammar.
2//!
3//! Unknown fields are rejected at parse time (`deny_unknown_fields`) so that
4//! callers emitting full Vega-Lite get a correctable error instead of a
5//! silently wrong chart.
6
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Deserialize)]
10#[serde(deny_unknown_fields)]
11pub struct Spec {
12    /// Optional: rows may instead arrive on stdin. `ingest::resolve` enforces
13    /// that data is present in exactly one place.
14    #[serde(default)]
15    pub data: Option<Data>,
16    pub mark: Mark,
17    pub encoding: Encoding,
18    #[serde(default)]
19    pub title: Option<String>,
20    /// Plot area width in terminal cells (not total output width).
21    #[serde(default)]
22    pub width: Option<usize>,
23    /// Plot area height in terminal cells.
24    #[serde(default)]
25    pub height: Option<usize>,
26}
27
28/// Inline data: tidy row objects, OR columnar `columns` + `rows`. Exactly one
29/// form — `ingest::resolve` enforces it (serde can't express either/or here
30/// without wrecking error paths).
31#[derive(Debug, Deserialize)]
32#[serde(deny_unknown_fields)]
33pub struct Data {
34    /// Inline tidy data: one JSON object per row.
35    #[serde(default)]
36    pub values: Option<Vec<serde_json::Map<String, serde_json::Value>>>,
37    #[serde(default)]
38    pub columns: Option<Vec<Column>>,
39    #[serde(default)]
40    pub rows: Option<Vec<Vec<serde_json::Value>>>,
41}
42
43/// Strict twin of `ingest::EnvColumn`: the spec is agent-authored, so unknown
44/// keys are rejected here.
45#[derive(Debug, Deserialize)]
46#[serde(deny_unknown_fields)]
47pub struct Column {
48    pub name: String,
49    #[serde(default, rename = "type")]
50    pub ty: Option<String>,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
54#[serde(rename_all = "lowercase")]
55pub enum Mark {
56    Bar,
57    Line,
58    Point,
59    Area,
60}
61
62#[derive(Debug, Deserialize)]
63#[serde(deny_unknown_fields)]
64pub struct Encoding {
65    pub x: Channel,
66    pub y: Channel,
67    #[serde(default)]
68    pub color: Option<Channel>,
69    /// Accepted by the grammar only so validation can reject it with a helpful
70    /// redirect (grouping is expressed with `color`). Typed as a raw `Value`,
71    /// not a `Channel`: Vega-Lite emits several xOffset shapes (`{"field": …}`,
72    /// `{"value": …}`, band configs) and a strict `Channel` would bounce them
73    /// into serde's generic unknown-field error before validation could help.
74    #[serde(default, rename = "xOffset")]
75    pub x_offset: Option<serde_json::Value>,
76}
77
78#[derive(Debug, Deserialize)]
79#[serde(deny_unknown_fields)]
80pub struct Channel {
81    pub field: String,
82    /// Inferred from the data when omitted.
83    #[serde(default, rename = "type")]
84    pub ty: Option<FieldType>,
85    #[serde(default)]
86    pub aggregate: Option<Aggregate>,
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
90#[serde(rename_all = "lowercase")]
91pub enum FieldType {
92    Quantitative,
93    Nominal,
94    Ordinal,
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
98#[serde(rename_all = "lowercase")]
99pub enum Aggregate {
100    Sum,
101    Mean,
102    Median,
103    Min,
104    Max,
105    Count,
106}