1use crate::{CHART_HEIGHT, CHART_WIDTH, Chart, Drawing, Error, Limits, Result, Series, Table, render};
3use serde_json::{Map, Value};
4
5fn bad<T>(m: impl Into<String>) -> Result<T> {
6 Err(Error::Invalid(m.into()))
7}
8
9pub(crate) fn cell(v: &Value) -> String {
12 match v {
13 Value::Null => String::new(),
14 Value::String(s) => s.clone(),
15 o => o.to_string(),
16 }
17}
18
19fn rows_of(rows: &[Value], n: usize, l: &Limits) -> Result<Vec<Vec<String>>> {
20 if rows.len() > l.max_rows {
21 return Err(Error::TooLarge(format!("{} rows (at most {})", rows.len(), l.max_rows)));
22 }
23 rows.iter()
24 .enumerate()
25 .map(|(i, r)| match r {
26 Value::Array(cells) if cells.len() == n => Ok(cells.iter().map(cell).collect()),
27 Value::Array(cells) => bad(format!("row {} has {} cells for {n} columns", i + 1, cells.len())),
28 _ => bad(format!("row {} is not a list of cells", i + 1)),
29 })
30 .collect()
31}
32
33fn numbers(v: Option<&Value>, what: &str, l: &Limits) -> Result<Vec<Option<f64>>> {
34 let Some(v) = v else { return Ok(vec![]) };
35 let Value::Array(a) = v else { return bad(format!("{what} is not a list of numbers")) };
36 if a.len() > l.max_points {
37 return Err(Error::TooLarge(format!("{} points (at most {})", a.len(), l.max_points)));
38 }
39 a.iter()
40 .enumerate()
41 .map(|(i, v)| match v {
42 Value::Null => Ok(None),
43 Value::Number(n) => Ok(n.as_f64()),
44 _ => bad(format!("{what}: value {} is not a number or null", i + 1)),
45 })
46 .collect()
47}
48
49fn size(o: &Map<String, Value>, key: &str, default: usize) -> Result<usize> {
50 match o.get(key) {
51 None | Some(Value::Null) => Ok(default),
52 Some(v) => match v.as_u64() {
53 Some(n) if n > 0 => Ok(usize::try_from(n).unwrap_or(usize::MAX)),
54 _ => bad(format!("{key} is not a positive whole number")),
55 },
56 }
57}
58
59fn chart(o: &Map<String, Value>, l: &Limits) -> Result<Drawing> {
60 let Some(Value::Array(ss)) = o.get("series") else { return bad("series is not a list") };
61 if ss.len() > l.max_series {
62 return Err(Error::TooLarge(format!("{} series (at most {})", ss.len(), l.max_series)));
63 }
64 let series = ss
65 .iter()
66 .enumerate()
67 .map(|(i, s)| {
68 let Value::Object(s) = s else { return bad(format!("series {} is not an object", i + 1)) };
69 let name = match s.get("name") {
70 None | Some(Value::Null) => String::new(),
71 Some(Value::String(n)) => n.clone(),
72 Some(_) => return bad(format!("series {}: name is not text", i + 1)),
73 };
74 let values = numbers(Some(s.get("values").unwrap_or(&Value::Null)).filter(|v| !v.is_null()), &format!("series {:?}", name), l)?;
75 Ok(Series { name, values })
76 })
77 .collect::<Result<Vec<_>>>()?;
78 let x = match o.get("x") {
79 None | Some(Value::Null) => (0..series.iter().map(|s| s.values.len()).max().unwrap_or(0)).map(|i| Some(i as f64)).collect(),
80 v => numbers(v, "x", l)?,
81 };
82 Ok(Drawing::Chart { chart: Chart { x, series }, width: size(o, "width", CHART_WIDTH)?, height: size(o, "height", CHART_HEIGHT)? })
83}
84
85pub(crate) fn drawing(v: &Value, l: &Limits) -> Result<Drawing> {
86 match v {
87 Value::Object(o) if o.contains_key("columns") => {
88 let Some(Value::Array(cols)) = o.get("columns") else { return bad("columns is not a list") };
89 let columns = cols.iter().map(|c| c.as_str().map(str::to_string).ok_or(())).collect::<std::result::Result<Vec<_>, _>>();
90 let Ok(columns) = columns else { return bad("columns are not all text") };
91 if columns.len() > l.max_columns {
92 return Err(Error::TooLarge(format!("{} columns (at most {})", columns.len(), l.max_columns)));
93 }
94 let rows = match o.get("rows") {
95 None | Some(Value::Null) => vec![],
96 Some(Value::Array(r)) => rows_of(r, columns.len(), l)?,
97 Some(_) => return bad("rows is not a list"),
98 };
99 Ok(Drawing::Table(Table { columns, rows }))
100 }
101 Value::Object(o) if o.contains_key("series") => chart(o, l),
102 Value::Array(items) if !items.is_empty() && items.iter().all(Value::is_object) => {
103 if items.len() > l.max_rows {
104 return Err(Error::TooLarge(format!("{} rows (at most {})", items.len(), l.max_rows)));
105 }
106 let mut columns: Vec<String> = items.iter().flat_map(|i| i.as_object().into_iter().flat_map(|o| o.keys().cloned())).collect();
107 columns.sort();
108 columns.dedup();
109 if columns.len() > l.max_columns {
110 return Err(Error::TooLarge(format!("{} columns (at most {})", columns.len(), l.max_columns)));
111 }
112 let rows = items.iter().map(|i| columns.iter().map(|c| i.get(c).map(cell).unwrap_or_default()).collect()).collect();
113 Ok(Drawing::Table(Table { columns, rows }))
114 }
115 Value::Array(items) if !items.is_empty() && items.iter().all(Value::is_array) => {
116 let n = items[0].as_array().map_or(0, Vec::len);
117 if n > l.max_columns {
118 return Err(Error::TooLarge(format!("{n} columns (at most {})", l.max_columns)));
119 }
120 let rows = rows_of(items, n, l)?;
121 Ok(Drawing::Table(Table { columns: (1..=n).map(|i| i.to_string()).collect(), rows }))
122 }
123 _ => bad("expected a table (columns and rows, a list of objects or a list of rows) or a chart (series)"),
124 }
125}
126
127pub fn json_png(text: &str, limits: &Limits) -> Result<Vec<u8>> {
129 if text.len() > limits.max_json_bytes {
130 return Err(Error::TooLarge(format!("{} bytes of JSON (at most {})", text.len(), limits.max_json_bytes)));
131 }
132 let v: Value = serde_json::from_str(text).map_err(|e| Error::Invalid(format!("not JSON: {e}")))?;
133 render(&drawing(&v, limits)?, limits)
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use crate::{chart_png, table_png};
140 use serde_json::json;
141
142 fn d(v: Value) -> Result<Drawing> {
143 drawing(&v, &Limits::default())
144 }
145
146 fn table(cols: &[&str], rows: &[&[&str]]) -> Drawing {
147 Drawing::Table(Table {
148 columns: cols.iter().map(|s| s.to_string()).collect(),
149 rows: rows.iter().map(|r| r.iter().map(|s| s.to_string()).collect()).collect(),
150 })
151 }
152
153 #[test]
154 fn json_tables_in_three_shapes() {
155 assert_eq!(
156 d(json!({"columns": ["a", "b"], "rows": [[1.5, "x"], [null, true]]})).unwrap(),
157 table(&["a", "b"], &[&["1.5", "x"], &["", "true"]])
158 );
159 assert_eq!(d(json!({"columns": ["a"]})).unwrap(), table(&["a"], &[]));
160 assert_eq!(d(json!([{"b": 1, "a": "x"}, {"c": [1, 2]}])).unwrap(), table(&["a", "b", "c"], &[&["x", "1", ""], &["", "", "[1,2]"]]));
161 assert_eq!(d(json!([[1, 2], [3, 4]])).unwrap(), table(&["1", "2"], &[&["1", "2"], &["3", "4"]]));
162 }
163
164 #[test]
165 fn json_charts() {
166 let Drawing::Chart { chart, width, height } = d(json!({"series": [{"name": "t", "values": [1, null, 3]}]})).unwrap() else { panic!() };
167 assert_eq!((width, height), (CHART_WIDTH, CHART_HEIGHT));
168 assert_eq!(chart.x, vec![Some(0.0), Some(1.0), Some(2.0)]);
169 assert_eq!(chart.series, vec![Series { name: "t".into(), values: vec![Some(1.0), None, Some(3.0)] }]);
170 let Drawing::Chart { chart, width, height } = d(json!({"x": [10, 20], "series": [], "width": 64, "height": 32})).unwrap() else { panic!() };
171 assert_eq!((chart.x, width, height), (vec![Some(10.0), Some(20.0)], 64, 32));
172 }
173
174 #[test]
175 fn json_draws_the_same_as_the_data() {
176 let l = Limits::default();
177 let t = Table { columns: vec!["a".into()], rows: vec![vec!["1".into()]] };
178 assert_eq!(json_png(r#"{"columns":["a"],"rows":[[1]]}"#, &l).unwrap(), table_png(&t, &l).unwrap());
179 let c = Chart { x: vec![Some(0.0), Some(1.0)], series: vec![Series { name: "s".into(), values: vec![Some(1.0), Some(2.0)] }] };
180 assert_eq!(
181 json_png(r#"{"x":[0,1],"series":[{"name":"s","values":[1,2]}],"width":40,"height":30}"#, &l).unwrap(),
182 chart_png(&c, 40, 30, &l).unwrap()
183 );
184 }
185
186 #[test]
187 fn malformed_json_is_an_error() {
188 let l = Limits::default();
189 let inv = |s: &str| matches!(json_png(s, &l), Err(Error::Invalid(_)));
190 assert!(inv("{"), "not JSON");
191 assert!(inv("42") && inv("\"x\"") && inv("[]") && inv("{}") && inv("[1, 2]") && inv("[{}, 1]"));
192 assert!(inv(r#"{"columns": "a"}"#) && inv(r#"{"columns": [1]}"#) && inv(r#"{"columns": ["a"], "rows": [1]}"#));
193 assert!(inv(r#"{"columns": ["a"], "rows": [[1, 2]]}"#) && inv(r#"{"columns": ["a"], "rows": {}}"#));
194 assert!(inv(r#"[[1, 2], [3]]"#));
195 assert!(inv(r#"{"series": 1}"#) && inv(r#"{"series": [1]}"#) && inv(r#"{"series": [{"name": 1}]}"#));
196 assert!(inv(r#"{"series": [{"values": ["x"]}]}"#) && inv(r#"{"x": "a", "series": []}"#));
197 assert!(inv(r#"{"series": [], "width": 0}"#) && inv(r#"{"series": [], "width": -1}"#) && inv(r#"{"series": [], "height": 1.5}"#));
198 assert!(inv(r#"{"columns": []}"#), "no columns");
199 assert_eq!(json_png(r#"{"series": [{"values": [null]}]}"#, &l), Err(Error::Empty));
200 assert!(inv(&format!("{}{}", "[".repeat(10_000), "]".repeat(10_000))));
202 }
203
204 #[test]
205 fn json_limits() {
206 let l = Limits::default();
207 let big = |s: &str, l: &Limits| matches!(json_png(s, l), Err(Error::TooLarge(_)));
208 assert!(big(r#"{"columns":["a"]}"#, &Limits { max_json_bytes: 5, ..l }));
209 assert!(big(r#"{"columns":["a","b"]}"#, &Limits { max_columns: 1, ..l }));
210 assert!(big(r#"[{"a":1,"b":2}]"#, &Limits { max_columns: 1, ..l }));
211 assert!(big(r#"[[1,2]]"#, &Limits { max_columns: 1, ..l }));
212 assert!(big(r#"{"columns":["a"],"rows":[[1],[2]]}"#, &Limits { max_rows: 1, ..l }));
213 assert!(big(r#"[{"a":1},{"a":2}]"#, &Limits { max_rows: 1, ..l }));
214 assert!(big(r#"{"series":[{"values":[1,2,3]}]}"#, &Limits { max_points: 2, ..l }));
215 assert!(big(r#"{"series":[{"values":[1]},{"values":[2]}]}"#, &Limits { max_series: 1, ..l }));
216 assert!(big(r#"{"series":[{"values":[1]}],"width":100000}"#, &l));
217 assert!(big(r#"{"series":[{"values":[1]}],"width":5000,"height":4000}"#, &l), "over the pixel limit");
218 }
219}