1use schemars::JsonSchema;
8use serde::Deserialize;
9use serde::de::IgnoredAny;
10
11use super::de::{Located, Loose, Map, Maybe, OneOf, UnknownKeys};
12
13type JsonMap = serde_json::Map<String, serde_json::Value>;
14
15pub type QueryItem = Located<Loose<OneOf<String, QueryEntry>>>;
17
18#[derive(Default, Deserialize, JsonSchema)]
20#[schemars(title = "DRE report", deny_unknown_fields)]
21pub struct ReportFile {
22 #[schemars(length(min = 1))]
24 pub name: Option<Located<Loose<String>>>,
25 pub description: Option<Located<Loose<String>>>,
27 pub meta: Option<Located<Loose<JsonMap>>>,
29 pub tags: Option<Located<Loose<Vec<String>>>>,
31 #[serde(default)]
33 #[schemars(schema_with = "queries")]
34 pub queries: Option<Located<Loose<Vec<QueryItem>>>>,
35 #[serde(default)]
37 #[schemars(schema_with = "output_or_list")]
38 pub output: Option<IgnoredAny>,
39 pub profile: Option<Located<Loose<String>>>,
41 pub sets: Option<Located<Loose<Vec<SetItem>>>>,
43 pub default_set: Option<Located<Loose<String>>>,
45 pub vars: Option<Located<Loose<JsonMap>>>,
47 pub var_schema: Option<Located<Loose<crate::variables::Definitions>>>,
50 pub timezone: Option<Located<Loose<String>>>,
52 pub locale: Option<Located<Loose<String>>>,
54 #[serde(default)]
56 #[schemars(schema_with = "super::dependencies::plugins")]
57 pub plugins: Option<IgnoredAny>,
58 #[serde(default)]
60 #[schemars(schema_with = "super::project::sources_ref")]
61 pub sources: Option<IgnoredAny>,
62 #[schemars(skip)]
64 pub schedule: Option<Located<IgnoredAny>>,
65 #[serde(rename = "$unknown", default)]
66 #[schemars(skip)]
67 pub unknown: UnknownKeys,
68}
69
70#[derive(Deserialize, JsonSchema)]
72#[schemars(rename = "queryEntry", deny_unknown_fields)]
73pub struct QueryEntry {
74 #[schemars(required)]
76 pub query: Option<Loose<String>>,
77 pub profile: Option<Loose<String>>,
79 #[schemars(extend("default" = true))]
81 pub tab: Option<Loose<bool>>,
82 pub tab_name: Option<Loose<String>>,
84 #[schemars(regex(pattern = "^[A-Za-z]{1,3}[0-9]+$"))]
86 pub anchor: Option<Loose<String>>,
87 pub header: Option<Loose<bool>>,
89 #[serde(default)]
91 #[schemars(schema_with = "columns")]
92 pub columns: Option<serde_json::Value>,
93 pub autofit: Option<Loose<bool>>,
95 #[serde(default)]
97 #[schemars(schema_with = "sheet_style")]
98 pub style: Option<serde_json::Value>,
99 #[serde(rename = "$unknown", default)]
100 #[schemars(skip)]
101 pub unknown: UnknownKeys,
102}
103
104fn sheet_style(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
105 schemars::json_schema!({
106 "type": "object",
107 "description": "How an xlsx tab looks. Cell keys apply to every data cell; `header` and `totals` take cell keys for those rows.",
108 "properties": {
109 "header": {"type": "object", "description": "Cell keys for the header row (default: bold)."},
110 "totals": {"type": "object", "description": "Cell keys for the totals row (default: bold, a thin top border)."},
111 "banded_rows": {"oneOf": [{"type": "string", "pattern": "^#?[0-9A-Fa-f]{6}$"}, {"const": false}], "description": "The fill of every other data row, `\"#RRGGBB\"`, or `false`."},
112 "borders": {"enum": ["none", "thin", "medium"], "description": "A border around every cell of the table."},
113 "bold": {"type": "boolean", "description": "Bold text in every data cell."},
114 "italic": {"type": "boolean", "description": "Italic text in every data cell."},
115 "underline": {"type": "boolean", "description": "Underlined text in every data cell."},
116 "font": {"type": "object", "description": "The font: `{name: Calibri, size: 11}`.", "properties": {"name": {"type": "string", "description": "The font's name."}, "size": {"type": "number", "description": "The size in points."}}, "additionalProperties": false},
117 "font_color": {"type": "string", "pattern": "^#?[0-9A-Fa-f]{6}$", "description": "The text colour, `\"#RRGGBB\"`."},
118 "fill": {"type": "string", "pattern": "^#?[0-9A-Fa-f]{6}$", "description": "The background colour, `\"#RRGGBB\"`."},
119 "align": {"enum": ["left", "center", "right"], "description": "Horizontal alignment."},
120 "border": {"enum": ["none", "thin", "medium"], "description": "A border around each data cell."}
121 },
122 "additionalProperties": false
123 })
124}
125
126fn columns(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
127 schemars::json_schema!({
128 "type": "object",
129 "additionalProperties": {
130 "type": "object",
131 "description": "Settings for one column of an xlsx tab.",
132 "properties": {
133 "format": {"type": "string", "description": "The Excel number format of the column, e.g. `#,##0.00` or `dd/mm/yyyy`. See the xlsx column formats in the plugins reference."},
134 "formula": {"type": "string", "description": "An Excel formula for each row of this column; `{name}` stands for that column's cell on the same row, e.g. `=ROUND({qty}*{unit_price},2)`. The SQL selects a placeholder column where the formula goes."},
135 "total": {"type": "string", "description": "Puts a total under the column: one of `sum`, `count`, `average`, `min`, `max`, or a formula such as `=SUM({net:*})`."},
136 "style": {"type": "object", "description": "How the column's data cells look: `bold`, `italic`, `underline`, `font` (`{name, size}`), `font_color`, `fill` (`\"#RRGGBB\"`), `align` (`left`, `center`, `right`), `border` (`none`, `thin`, `medium`). Over the tab's and output's `style`."},
137 "width": {"oneOf": [{"const": "auto"}, {"type": "number", "exclusiveMinimum": 0, "maximum": 255}], "description": "The column's width: `auto` (sized from its content, at most 60 characters) or a number of characters. Over the tab's and output's `autofit`."}
138 },
139 "additionalProperties": false
140 },
141 "propertyNames": {"minLength": 1}
142 })
143}
144
145pub type SetItem = Located<Loose<OneOf<String, SetEntry>>>;
147
148#[derive(Deserialize, JsonSchema)]
150#[schemars(rename = "setEntry", deny_unknown_fields)]
151pub struct SetEntry {
152 #[schemars(required)]
154 pub name: Option<Loose<String>>,
155 pub profile: Option<Located<Loose<String>>>,
157 #[serde(default)]
159 #[schemars(schema_with = "any_map")]
160 pub vars: Option<Loose<JsonMap>>,
161 pub exclude: Option<Located<Loose<Vec<String>>>>,
163 #[serde(default)]
165 #[schemars(schema_with = "set_queries")]
166 pub queries: Option<Located<Loose<Vec<SetQueryItem>>>>,
167 #[serde(default)]
169 #[schemars(schema_with = "tab_names")]
170 pub tab_names: Option<Loose<JsonMap>>,
171 #[serde(default)]
173 #[schemars(schema_with = "output_or_list")]
174 pub output: Option<serde_json::Value>,
175 pub locale: Option<Loose<String>>,
177 #[schemars(skip)]
179 pub schedule: Option<IgnoredAny>,
180 #[serde(rename = "$unknown", default)]
181 #[schemars(skip)]
182 pub unknown: UnknownKeys,
183}
184
185pub type SetQueryItem = Loose<OneOf<String, SetQuery>>;
187
188#[derive(Deserialize)]
190pub struct SetQuery {
191 pub query: Option<Loose<String>>,
192 pub tab: Option<Loose<bool>>,
193 pub tab_name: Option<Loose<String>>,
194}
195
196fn any_map(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
197 schemars::json_schema!({"type": "object", "additionalProperties": true})
198}
199
200fn tab_names(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
201 schemars::json_schema!({"type": "object", "additionalProperties": {"type": "string"}})
202}
203
204fn output_or_list(g: &mut schemars::SchemaGenerator) -> schemars::Schema {
205 let output = g.subschema_for::<Output>();
206 schemars::json_schema!({
207 "oneOf": [output, {"type": "array", "minItems": 1, "items": output}]
208 })
209}
210
211fn set_queries(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
212 schemars::json_schema!({
213 "type": "array",
214 "items": {
215 "description": "A query of this Set, by name, or with its own settings.",
216 "oneOf": [
217 {"type": "string"},
218 {
219 "type": "object",
220 "description": "A query with settings.",
221 "properties": {
222 "query": {"type": "string", "description": "The query's name."},
223 "tab": {"type": "boolean", "description": "Whether the query makes a tab."},
224 "tab_name": {"type": "string", "description": "The tab name."}
225 },
226 "required": ["query"],
227 "additionalProperties": true
228 }
229 ]
230 }
231 })
232}
233
234#[derive(Deserialize, JsonSchema)]
238#[schemars(rename = "output", extend("additionalProperties" = true))]
239pub struct Output {
240 #[schemars(regex(pattern = "^[A-Za-z_][A-Za-z0-9_]*$"))]
242 pub name: Option<Loose<String>>,
243 pub format: Option<Loose<String>>,
245 #[schemars(length(min = 1))]
247 pub queries: Option<Loose<Vec<String>>>,
248 #[serde(default)]
250 #[schemars(schema_with = "string_or_bool")]
251 pub when: Option<Loose<OneOf<String, bool>>>,
252 #[serde(default)]
254 #[schemars(schema_with = "destination_or_list")]
255 pub destination: Option<Loose<OneOf<Destination, Vec<Loose<Destination>>>>>,
256 pub template: Option<Loose<Template>>,
257 #[serde(default)]
259 #[schemars(schema_with = "extension")]
260 pub extension: Maybe<Loose<Option<OneOf<String, bool>>>>,
261 #[serde(rename = "$unknown", default)]
263 #[schemars(skip)]
264 pub options: Map<serde_json::Value>,
265}
266
267#[derive(Deserialize, JsonSchema)]
269#[schemars(rename = "destination", extend("additionalProperties" = true))]
270pub struct Destination {
271 #[schemars(required)]
273 pub profile: Option<Loose<String>>,
274 pub path: Option<Loose<String>>,
276 #[serde(default)]
278 #[schemars(schema_with = "attach")]
279 pub attach: Option<Loose<OneOf<String, Vec<String>>>>,
280 #[serde(rename = "$unknown", default)]
282 #[schemars(skip)]
283 pub options: Map<serde_json::Value>,
284}
285
286#[derive(Deserialize, JsonSchema)]
288#[schemars(rename = "template", deny_unknown_fields)]
289pub struct Template {
290 #[schemars(required)]
292 pub file: Option<Loose<String>>,
293 pub bindings: Option<Loose<Vec<Loose<TemplateBinding>>>>,
295}
296
297#[derive(Deserialize, JsonSchema)]
299#[schemars(deny_unknown_fields, inline)]
300pub struct TemplateBinding {
301 #[schemars(required)]
303 pub sheet: Option<Loose<String>>,
304 pub query: Option<Loose<String>>,
306 #[schemars(range(min = 1))]
308 pub result_index: Option<Loose<u64>>,
309 #[schemars(regex(pattern = "^[A-Za-z]{1,3}[0-9]+$"))]
311 pub anchor: Option<Loose<String>>,
312 pub header: Option<Loose<bool>>,
314 pub columns: Option<Loose<Vec<String>>>,
316 #[schemars(regex(pattern = "^[A-Za-z]{1,3}[0-9]+$"))]
318 pub cell: Option<Loose<String>>,
319 pub value: Option<Loose<String>>,
321 pub column: Option<Loose<String>>,
323 #[serde(rename = "$unknown", default)]
324 #[schemars(skip)]
325 pub unknown: UnknownKeys,
326}
327
328fn string_or_bool(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
329 schemars::json_schema!({"type": ["string", "boolean"]})
330}
331
332fn extension(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
333 schemars::json_schema!({"type": ["string", "boolean", "null"], "x-dre-null": true})
335}
336
337fn destination_or_list(g: &mut schemars::SchemaGenerator) -> schemars::Schema {
338 let destination = g.subschema_for::<Destination>();
339 schemars::json_schema!({
340 "oneOf": [destination, {"type": "array", "minItems": 1, "items": destination}]
341 })
342}
343
344fn attach(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
345 schemars::json_schema!({
346 "oneOf": [
347 {"type": "string"},
348 {"type": "array", "items": {"type": "string"}, "minItems": 1}
349 ]
350 })
351}
352
353fn queries(g: &mut schemars::SchemaGenerator) -> schemars::Schema {
354 let entry = g.subschema_for::<QueryEntry>();
355 schemars::json_schema!({
356 "type": "array",
357 "items": {
358 "oneOf": [
359 {"type": "string", "description": "The name of a `.sql` file under `reports/`, without extension."},
360 entry
361 ]
362 }
363 })
364}