1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
//! `run_results.json`: what one run of one Binding did. A public, versioned contract
//! (`docs/run-results.schema.json` is generated from these types): adding an optional field
//! keeps [`SCHEMA_VERSION`]; removing, renaming or re-typing one, or changing what it means,
//! bumps it (a minor DRE release before 1.0, a major one after).
use std::collections::BTreeMap;
use std::path::PathBuf;
use schemars::JsonSchema;
use serde::Serialize;
use crate::codes::{Code, ErrorCode, Kind};
use crate::settings::Settings;
type JsonMap = serde_json::Map<String, serde_json::Value>;
/// The format's version.
pub const SCHEMA_VERSION: &str = "dre/run-results/v1";
/// `<target path>/run/<report>/<set or default>/runs/<run id>/run_results.json`: what one run of one Binding did. Written by `dre run` for each Binding it runs. See docs/manifest.md.
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[schemars(title = "DRE run results")]
pub struct RunResults {
/// The format's version: `dre/run-results/v1`.
#[schemars(schema_with = "schema_version")]
pub schema_version: &'static str,
/// This run's id (`20261009T060000Z-k3f9`: its UTC start and four random characters), the name of its folder under `runs/`.
pub run_id: String,
pub report: String,
/// Null for a report without Sets.
pub set: Option<String>,
/// The Binding's folder name: the Set's, or `default`.
pub binding: String,
/// False for a bare .sql under reports/ (an unmanaged report).
pub managed: bool,
/// The inherited connection (Set, report, folder `+profile`, `default_profile`), rendered.
pub profile: Option<String>,
/// Every connection the Binding's queries ran on.
pub connections: Vec<String>,
/// The run's target (environment): `--target`, else `DRE_TARGET`, else `dev`.
pub target: String,
/// The schedule it ran under (`--schedule`).
pub schedule: Option<String>,
/// The vars that schedule layered in.
pub schedule_vars: Option<JsonMap>,
/// Every var the Binding rendered with: its own, the schedule's, then `--var`.
pub vars: JsonMap,
/// `run.date`, `YYYY-MM-DD`.
pub run_date: String,
/// The instant it was scheduled for (`DRE_RUN_AT`), RFC 3339.
pub scheduled_at: Option<String>,
/// The run's timezone (IANA name).
pub timezone: String,
/// What the command was asked to do.
pub params: Params,
pub status: Status,
/// Why it failed.
pub error: Option<String>,
/// `error`'s code (see the error codes reference).
pub error_code: Option<ErrorCode>,
/// `error_code`'s kind.
pub error_kind: Option<Kind>,
/// Whether it was a `--preview`: never delivered.
pub preview: bool,
/// `--preview`'s row limit per query.
pub row_limit: Option<u64>,
/// When the Binding started, RFC 3339.
pub started_at: String,
pub duration_ms: u64,
/// Each result set its queries produced, in order.
pub result_sets: Vec<ResultSet>,
/// Every file every output wrote.
pub outputs: Vec<OutputFile>,
/// One entry per output, in declared order.
pub output_results: Vec<OutputResult>,
/// Why outputs weren't delivered, when that's by design (no destination, `deliver: false`, a preview).
pub delivery: Option<String>,
/// Every destination, in delivery order: file outputs, then messages.
pub deliveries: Vec<Delivery>,
/// How the result sets' columns changed since the last run, if they did.
pub schema_drift: Vec<String>,
/// The resolved target path.
pub target_path: PathBuf,
/// Every setting and where its value came from.
#[schemars(schema_with = "settings")]
pub settings: Settings,
/// The SHA-256 of the manifest.json bytes this run wrote.
pub manifest_checksum: Option<String>,
/// `dre run --retry-failed <run id>`: the run whose failed and never-started rows this one redid.
#[serde(skip_serializing_if = "Option::is_none")]
pub retry_of: Option<String>,
/// `dre release`: the run whose held deliveries this one delivered.
#[serde(skip_serializing_if = "Option::is_none")]
pub release_of: Option<String>,
/// `dre release --by` (default `$USER`): who released them.
#[serde(skip_serializing_if = "Option::is_none")]
pub released_by: Option<String>,
/// Each held delivery a release delivered (or refused), with its result.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub released: Vec<Released>,
}
/// One held delivery, as `dre release` delivered it.
#[derive(Debug, Clone, Serialize, serde::Deserialize, JsonSchema)]
pub struct Released {
/// The held entry's id in the run's `held.json`.
pub id: String,
pub output: Option<String>,
/// A per-row output's row key.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub key: Option<String>,
pub profile: String,
/// `delivered`, `failed`, `refused` (a file changed since it was rendered) or `not_delivered`.
pub status: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub location: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
/// How a Binding ended.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum Status {
Success,
Error,
/// `--dry-run`: rendered, nothing executed.
DryRun,
/// `dre validate --live`: checked on the database, nothing executed.
Checked,
/// Stopped by Ctrl-C or a termination signal while it ran; nothing was delivered after.
Cancelled,
/// Stopped because the run took longer than its timeout; nothing was delivered after.
TimedOut,
}
/// The command's parameters.
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct Params {
pub selector: Option<String>,
pub set: Option<String>,
pub schedule: Option<String>,
/// `--target` as given.
pub target: Option<String>,
/// `--profile`.
pub profile: Option<String>,
/// `--var`.
pub vars: BTreeMap<String, String>,
pub run_date: String,
pub scheduled_at: Option<String>,
/// `--timezone` or `DRE_TIMEZONE`.
pub timezone: Option<String>,
pub output_name: Option<String>,
pub output_path: Option<String>,
pub dry_run: bool,
pub preview: Option<u64>,
pub accept_schema_change: bool,
}
/// One result set.
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct ResultSet {
/// Its name: the tab name.
pub name: String,
pub query: String,
/// The connection it came from.
pub connection: String,
pub rows: u64,
pub columns: Vec<String>,
}
/// One file an output wrote.
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct OutputFile {
/// Relative to the project root, or to the target path when that's outside the project.
pub path: PathBuf,
pub size: u64,
/// Where it was delivered, when it was.
pub delivered_to: Option<String>,
/// The output's `name:`.
pub output: Option<String>,
}
/// How one output ended.
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct OutputResult {
pub name: Option<String>,
pub format: String,
/// The queries it formatted.
pub queries: Vec<String>,
#[schemars(schema_with = "output_status")]
pub status: &'static str,
pub error: Option<String>,
pub files: Vec<OutputFileRef>,
pub delivery: Option<String>,
pub deliveries: Vec<Delivery>,
/// Its `when:`, evaluated.
#[serde(skip_serializing_if = "Option::is_none")]
pub when: Option<bool>,
/// Why it was skipped or failed, when that's by design: `no_data` (`on_empty`).
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
/// Its queries that returned no rows (a `no_data` warning when the output still went out).
#[serde(skip_serializing_if = "Vec::is_empty")]
pub no_data: Vec<String>,
/// Queries whose tab (or file) was left out because they were empty (`on_empty: skip`).
#[serde(skip_serializing_if = "Vec::is_empty")]
pub dropped: Vec<String>,
/// A per-row output's summary; its rows are in the `sidecar`, not in `files` or `deliveries`.
#[serde(skip_serializing_if = "Option::is_none")]
pub for_each: Option<ForEachResult>,
/// A message output's rendered title and text, as sent.
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<Message>,
}
/// A per-row output's summary: `output_results[].for_each`. Each row's detail is a line of the
/// `<output>.rows.jsonl` sidecar in the run folder.
#[derive(Debug, Clone, Default, Serialize, serde::Deserialize, JsonSchema)]
pub struct ForEachResult {
/// The driver query.
pub query: String,
/// The key column(s).
pub key: Vec<String>,
/// Driver rows this run was asked to make (all, or the ones `--retry-failed`, `--only-keys` or `--limit-rows` chose).
pub rows: u64,
pub succeeded: u64,
/// Rows with nothing to send (`on_empty`), or whose attachment failed.
pub skipped: u64,
pub failed: u64,
/// Rows with deliveries held for `dre release`.
pub held: u64,
/// Rows that never started (the run was cancelled or timed out).
#[serde(default)]
pub not_started: u64,
/// Rows of the output's queries whose key isn't in the driver.
pub orphan_rows: u64,
/// Each failed row: its key, error code and message (masked).
pub failed_keys: Vec<FailedKey>,
/// The sidecar with every row's result, relative to the project root (or the target path).
pub sidecar: PathBuf,
/// True when this run made only some rows (`--retry-failed`, `--only-keys`, `--limit-rows`).
#[serde(default)]
pub partial: bool,
}
/// A failed row.
#[derive(Debug, Clone, Serialize, serde::Deserialize, JsonSchema)]
pub struct FailedKey {
pub key: String,
pub code: String,
pub message: String,
}
/// One line of `<output>.rows.jsonl`: one row of a per-row output.
#[derive(Debug, Clone, Serialize, serde::Deserialize, JsonSchema)]
pub struct RowRecord {
pub key: String,
/// `succeeded`, `skipped`, `failed` or `held`.
pub status: String,
/// Why a row was skipped: `no_data` or `attachment_failed`.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
/// The files made for the row, with their SHA-256.
#[serde(default)]
pub files: Vec<RowFile>,
/// Each destination of the row.
#[serde(default)]
pub deliveries: Vec<RowDelivery>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<FailedKey>,
}
/// One file of a row.
#[derive(Debug, Clone, Serialize, serde::Deserialize, JsonSchema)]
pub struct RowFile {
pub path: PathBuf,
pub sha256: String,
}
/// One delivery of a row. Values rendered from `each.*` are masked unless `mask_row_values: false`.
#[derive(Debug, Clone, Serialize, serde::Deserialize, JsonSchema)]
pub struct RowDelivery {
pub profile: String,
/// The rendered path, or the destination's address options (`to`, `channel`).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub target: Option<String>,
/// `delivered`, `failed`, `held`, `kept` (not delivered by design) or `not_delivered`.
pub status: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub location: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
/// One file of an output.
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct OutputFileRef {
pub path: PathBuf,
pub size: u64,
pub delivered_to: Option<String>,
}
/// A message output's rendered title and text.
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct Message {
pub title: String,
pub text: String,
}
/// One delivery to one destination.
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct Delivery {
/// The destination profile.
pub profile: String,
/// Its plugin type; null for an entry that delivers nowhere.
#[serde(rename = "type")]
pub kind: Option<String>,
/// The profile's entry for the run.
pub target: String,
#[schemars(schema_with = "delivery_status")]
pub status: &'static str,
/// Where it went.
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<String>,
/// How many tries it took, when the plugin had to try again after a temporary error.
#[serde(skip_serializing_if = "Option::is_none")]
pub attempts: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error_code: Option<ErrorCode>,
}
fn schema_version(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({"const": SCHEMA_VERSION})
}
fn output_status(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({"enum": ["delivered", "kept", "skipped", "failed", "held"]})
}
fn delivery_status(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({"enum": ["delivered", "not_delivered", "failed", "held"]})
}
fn settings(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"value": {"type": ["string", "null"]},
"source": {"type": "string", "description": "A flag (`--target`), an environment variable (`DRE_TARGET`), a dre_project.yml key, how it was found, or `default`."}
},
"required": ["name", "value", "source"]
},
"x-dre-null": true
})
}
impl JsonSchema for Code {
fn schema_name() -> std::borrow::Cow<'static, str> {
"Code".into()
}
fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({"type": "string", "description": "A code from the error codes reference, e.g. `query-failed`."})
}
fn inline_schema() -> bool {
true
}
}
impl JsonSchema for ErrorCode {
fn schema_name() -> std::borrow::Cow<'static, str> {
"ErrorCode".into()
}
fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({"type": "string", "description": "A code from the error codes reference, e.g. `query-failed`, or a plugin's own, namespaced by the plugin, e.g. `sftp/host-key-mismatch`."})
}
fn inline_schema() -> bool {
true
}
}
impl JsonSchema for Kind {
fn schema_name() -> std::borrow::Cow<'static, str> {
"Kind".into()
}
fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({"enum": ["usage", "config", "plugin", "refused", "connection", "auth", "query", "delivery", "internal", "cancelled", "timed_out"]})
}
fn inline_schema() -> bool {
true
}
}