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 tags: Option<Located<Loose<Vec<String>>>>,
27 #[serde(default)]
29 #[schemars(schema_with = "queries")]
30 pub queries: Option<Located<Loose<Vec<QueryItem>>>>,
31 #[serde(default)]
33 #[schemars(schema_with = "output_or_list")]
34 pub output: Option<IgnoredAny>,
35 pub profile: Option<Located<Loose<String>>>,
37 pub sets: Option<Located<Loose<Vec<SetItem>>>>,
39 pub default_set: Option<Located<Loose<String>>>,
41 pub vars: Option<Located<Loose<JsonMap>>>,
43 pub timezone: Option<Located<Loose<String>>>,
45 pub locale: Option<Located<Loose<String>>>,
47 #[serde(default)]
49 #[schemars(schema_with = "super::dependencies::plugins")]
50 pub plugins: Option<IgnoredAny>,
51 #[serde(default)]
53 #[schemars(schema_with = "super::project::sources_ref")]
54 pub sources: Option<IgnoredAny>,
55 #[schemars(skip)]
57 pub schedule: Option<Located<IgnoredAny>>,
58 #[serde(rename = "$unknown", default)]
59 #[schemars(skip)]
60 pub unknown: UnknownKeys,
61}
62
63#[derive(Deserialize, JsonSchema)]
65#[schemars(rename = "queryEntry", deny_unknown_fields)]
66pub struct QueryEntry {
67 #[schemars(required)]
69 pub query: Option<Loose<String>>,
70 pub profile: Option<Loose<String>>,
72 #[schemars(extend("default" = true))]
74 pub tab: Option<Loose<bool>>,
75 pub tab_name: Option<Loose<String>>,
77 #[schemars(regex(pattern = "^[A-Za-z]{1,3}[0-9]+$"))]
79 pub anchor: Option<Loose<String>>,
80 pub header: Option<Loose<bool>>,
82 #[serde(default)]
84 #[schemars(schema_with = "columns")]
85 pub columns: Option<serde_json::Value>,
86 pub autofit: Option<Loose<bool>>,
88 #[serde(default)]
90 #[schemars(schema_with = "sheet_style")]
91 pub style: Option<serde_json::Value>,
92 #[serde(rename = "$unknown", default)]
93 #[schemars(skip)]
94 pub unknown: UnknownKeys,
95}
96
97fn sheet_style(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
98 schemars::json_schema!({
99 "type": "object",
100 "description": "How an xlsx tab looks. Cell keys apply to every data cell; `header` and `totals` take cell keys for those rows.",
101 "properties": {
102 "header": {"type": "object", "description": "Cell keys for the header row (default: bold)."},
103 "totals": {"type": "object", "description": "Cell keys for the totals row (default: bold, a thin top border)."},
104 "banded_rows": {"oneOf": [{"type": "string", "pattern": "^#?[0-9A-Fa-f]{6}$"}, {"const": false}], "description": "The fill of every other data row, `\"#RRGGBB\"`, or `false`."},
105 "borders": {"enum": ["none", "thin", "medium"], "description": "A border around every cell of the table."},
106 "bold": {"type": "boolean", "description": "Bold text in every data cell."},
107 "italic": {"type": "boolean", "description": "Italic text in every data cell."},
108 "underline": {"type": "boolean", "description": "Underlined text in every data cell."},
109 "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},
110 "font_color": {"type": "string", "pattern": "^#?[0-9A-Fa-f]{6}$", "description": "The text colour, `\"#RRGGBB\"`."},
111 "fill": {"type": "string", "pattern": "^#?[0-9A-Fa-f]{6}$", "description": "The background colour, `\"#RRGGBB\"`."},
112 "align": {"enum": ["left", "center", "right"], "description": "Horizontal alignment."},
113 "border": {"enum": ["none", "thin", "medium"], "description": "A border around each data cell."}
114 },
115 "additionalProperties": false
116 })
117}
118
119fn columns(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
120 schemars::json_schema!({
121 "type": "object",
122 "additionalProperties": {
123 "type": "object",
124 "description": "Settings for one column of an xlsx tab.",
125 "properties": {
126 "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."},
127 "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."},
128 "total": {"type": "string", "description": "Puts a total under the column: one of `sum`, `count`, `average`, `min`, `max`, or a formula such as `=SUM({net:*})`."},
129 "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`."},
130 "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`."}
131 },
132 "additionalProperties": false
133 },
134 "propertyNames": {"minLength": 1}
135 })
136}
137
138pub type SetItem = Located<Loose<OneOf<String, SetEntry>>>;
140
141#[derive(Deserialize, JsonSchema)]
143#[schemars(rename = "setEntry", deny_unknown_fields)]
144pub struct SetEntry {
145 #[schemars(required)]
147 pub name: Option<Loose<String>>,
148 pub profile: Option<Located<Loose<String>>>,
150 #[serde(default)]
152 #[schemars(schema_with = "any_map")]
153 pub vars: Option<Loose<JsonMap>>,
154 pub exclude: Option<Located<Loose<Vec<String>>>>,
156 #[serde(default)]
158 #[schemars(schema_with = "set_queries")]
159 pub queries: Option<Located<Loose<Vec<SetQueryItem>>>>,
160 #[serde(default)]
162 #[schemars(schema_with = "tab_names")]
163 pub tab_names: Option<Loose<JsonMap>>,
164 #[serde(default)]
166 #[schemars(schema_with = "output_or_list")]
167 pub output: Option<serde_json::Value>,
168 pub locale: Option<Loose<String>>,
170 #[schemars(skip)]
172 pub schedule: Option<IgnoredAny>,
173 #[serde(rename = "$unknown", default)]
174 #[schemars(skip)]
175 pub unknown: UnknownKeys,
176}
177
178pub type SetQueryItem = Loose<OneOf<String, SetQuery>>;
180
181#[derive(Deserialize)]
183pub struct SetQuery {
184 pub query: Option<Loose<String>>,
185 pub tab: Option<Loose<bool>>,
186 pub tab_name: Option<Loose<String>>,
187}
188
189fn any_map(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
190 schemars::json_schema!({"type": "object", "additionalProperties": true})
191}
192
193fn tab_names(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
194 schemars::json_schema!({"type": "object", "additionalProperties": {"type": "string"}})
195}
196
197fn output_or_list(g: &mut schemars::SchemaGenerator) -> schemars::Schema {
198 let output = g.subschema_for::<Output>();
199 schemars::json_schema!({
200 "oneOf": [output, {"type": "array", "minItems": 1, "items": output}]
201 })
202}
203
204fn set_queries(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
205 schemars::json_schema!({
206 "type": "array",
207 "items": {
208 "description": "A query of this Set, by name, or with its own settings.",
209 "oneOf": [
210 {"type": "string"},
211 {
212 "type": "object",
213 "description": "A query with settings.",
214 "properties": {
215 "query": {"type": "string", "description": "The query's name."},
216 "tab": {"type": "boolean", "description": "Whether the query makes a tab."},
217 "tab_name": {"type": "string", "description": "The tab name."}
218 },
219 "required": ["query"],
220 "additionalProperties": true
221 }
222 ]
223 }
224 })
225}
226
227#[derive(Deserialize, JsonSchema)]
231#[schemars(rename = "output", extend("additionalProperties" = true))]
232pub struct Output {
233 #[schemars(regex(pattern = "^[A-Za-z_][A-Za-z0-9_]*$"))]
235 pub name: Option<Loose<String>>,
236 pub format: Option<Loose<String>>,
238 #[schemars(length(min = 1))]
240 pub queries: Option<Loose<Vec<String>>>,
241 #[serde(default)]
243 #[schemars(schema_with = "string_or_bool")]
244 pub when: Option<Loose<OneOf<String, bool>>>,
245 #[serde(default)]
247 #[schemars(schema_with = "destination_or_list")]
248 pub destination: Option<Loose<OneOf<Destination, Vec<Loose<Destination>>>>>,
249 pub template: Option<Loose<Template>>,
250 #[serde(default)]
252 #[schemars(schema_with = "extension")]
253 pub extension: Maybe<Loose<Option<OneOf<String, bool>>>>,
254 #[serde(rename = "$unknown", default)]
256 #[schemars(skip)]
257 pub options: Map<serde_json::Value>,
258}
259
260#[derive(Deserialize, JsonSchema)]
262#[schemars(rename = "destination", extend("additionalProperties" = true))]
263pub struct Destination {
264 #[schemars(required)]
266 pub profile: Option<Loose<String>>,
267 pub path: Option<Loose<String>>,
269 #[serde(default)]
271 #[schemars(schema_with = "attach")]
272 pub attach: Option<Loose<OneOf<String, Vec<String>>>>,
273 #[serde(rename = "$unknown", default)]
275 #[schemars(skip)]
276 pub options: Map<serde_json::Value>,
277}
278
279#[derive(Deserialize, JsonSchema)]
281#[schemars(rename = "template", deny_unknown_fields)]
282pub struct Template {
283 #[schemars(required)]
285 pub file: Option<Loose<String>>,
286 pub bindings: Option<Loose<Vec<Loose<TemplateBinding>>>>,
288}
289
290#[derive(Deserialize, JsonSchema)]
292#[schemars(deny_unknown_fields, inline)]
293pub struct TemplateBinding {
294 #[schemars(required)]
296 pub sheet: Option<Loose<String>>,
297 pub query: Option<Loose<String>>,
299 #[schemars(range(min = 1))]
301 pub result_index: Option<Loose<u64>>,
302 #[schemars(regex(pattern = "^[A-Za-z]{1,3}[0-9]+$"))]
304 pub anchor: Option<Loose<String>>,
305 pub header: Option<Loose<bool>>,
307 pub columns: Option<Loose<Vec<String>>>,
309 #[schemars(regex(pattern = "^[A-Za-z]{1,3}[0-9]+$"))]
311 pub cell: Option<Loose<String>>,
312 pub value: Option<Loose<String>>,
314 pub column: Option<Loose<String>>,
316 #[serde(rename = "$unknown", default)]
317 #[schemars(skip)]
318 pub unknown: UnknownKeys,
319}
320
321fn string_or_bool(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
322 schemars::json_schema!({"type": ["string", "boolean"]})
323}
324
325fn extension(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
326 schemars::json_schema!({"type": ["string", "boolean", "null"], "x-dre-null": true})
328}
329
330fn destination_or_list(g: &mut schemars::SchemaGenerator) -> schemars::Schema {
331 let destination = g.subschema_for::<Destination>();
332 schemars::json_schema!({
333 "oneOf": [destination, {"type": "array", "minItems": 1, "items": destination}]
334 })
335}
336
337fn attach(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
338 schemars::json_schema!({
339 "oneOf": [
340 {"type": "string"},
341 {"type": "array", "items": {"type": "string"}, "minItems": 1}
342 ]
343 })
344}
345
346fn queries(g: &mut schemars::SchemaGenerator) -> schemars::Schema {
347 let entry = g.subschema_for::<QueryEntry>();
348 schemars::json_schema!({
349 "type": "array",
350 "items": {
351 "oneOf": [
352 {"type": "string", "description": "The name of a `.sql` file under `reports/`, without extension."},
353 entry
354 ]
355 }
356 })
357}