Skip to main content

dre_core/
run_results.rs

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