Skip to main content

dre_protocol/
msg.rs

1//! Control messages. Every JSON frame is an object with a `type` field.
2
3use serde::{Deserialize, Serialize};
4use serde_json::{Map, Value};
5
6pub use crate::options::OptionField;
7use crate::{Kind, PluginId};
8
9/// Core → plugin.
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11#[serde(tag = "type", rename_all = "snake_case")]
12pub enum Request {
13    /// Always first. The plugin picks a version in both ranges or replies `version_mismatch`.
14    Hello {
15        min_version: u32,
16        max_version: u32,
17        core_version: String,
18        /// The plugin core wants served, for an executable that provides several. A package
19        /// that doesn't provide it replies `error`; without it, the package serves its first.
20        #[serde(default, skip_serializing_if = "Option::is_none")]
21        plugin: Option<PluginId>,
22    },
23    /// Describe the connection fields a profile output for this plugin takes (used by `dre init`)
24    /// and the options a report's config block for it takes.
25    Describe {},
26    /// Check one config block of options (a format's `output:` keys, a destination entry's keys)
27    /// without doing anything. Replies `validated`.
28    Validate {
29        options: Map<String, Value>,
30    },
31    /// Source: open a session. All later `execute`/`check` requests run on it.
32    Open {
33        connection: Map<String, Value>,
34        read_only: bool,
35    },
36    /// Source: run one statement. Replies `result` (then Arrow frames, then `result_end`) or
37    /// `no_result`.
38    Execute {
39        sql: String,
40        #[serde(default, skip_serializing_if = "Option::is_none")]
41        row_limit: Option<u64>,
42    },
43    /// Source: verify one statement without executing it. Replies `ok` or `error`.
44    Check {
45        sql: String,
46    },
47    /// Format: write result sets to `path`. Followed, per result set, by Arrow frames (at least one,
48    /// carrying the schema) and a `result_set_end`, then `finish`. Replies `written`.
49    Write {
50        path: String,
51        format: String,
52        options: Map<String, Value>,
53        result_sets: Vec<ResultSetMeta>,
54        #[serde(default, skip_serializing_if = "Option::is_none")]
55        template: Option<Value>,
56    },
57    ResultSetEnd {},
58    Finish {},
59    /// Destination: deliver a local file, or with `files` (only to a plugin advertising
60    /// `multi_file`) every file of one output at once. Exactly one of `local_path`/`files` is set.
61    /// `options` are the destination entry's plugin options, rendered by core. Replies `delivered`.
62    Deliver {
63        #[serde(default, skip_serializing_if = "Option::is_none")]
64        local_path: Option<String>,
65        #[serde(default, skip_serializing_if = "Option::is_none")]
66        remote_path: Option<String>,
67        #[serde(default, skip_serializing_if = "Vec::is_empty")]
68        files: Vec<DeliveryFile>,
69        connection: Map<String, Value>,
70        #[serde(default)]
71        options: Map<String, Value>,
72    },
73    /// Source: load rows into a temporary table on the session, named after `name`. Followed by
74    /// Arrow frames (at least one, carrying the schema) and a `result_set_end`. Replies `loaded`.
75    /// Only sent to plugins advertising `load`.
76    Load {
77        name: String,
78    },
79    /// End the conversation; the plugin exits 0.
80    Close {},
81}
82
83/// Plugin → core.
84#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
85#[serde(tag = "type", rename_all = "snake_case")]
86pub enum Response {
87    Hello {
88        protocol_version: u32,
89        kind: Kind,
90        name: String,
91        version: String,
92        #[serde(default)]
93        capabilities: Vec<String>,
94        /// Every plugin the executable serves; empty for one that serves only `kind`/`name`.
95        #[serde(default, skip_serializing_if = "Vec::is_empty")]
96        provides: Vec<PluginId>,
97    },
98    VersionMismatch {
99        min_version: u32,
100        max_version: u32,
101    },
102    Describe {
103        connection_fields: Vec<ConnectionField>,
104        #[serde(default, skip_serializing_if = "Vec::is_empty")]
105        option_fields: Vec<OptionField>,
106    },
107    /// Every problem with the options, each a sentence naming the key; empty when they're fine.
108    Validated {
109        errors: Vec<String>,
110    },
111    Ok {},
112    /// A result set follows as Arrow frames, ended by `result_end`.
113    Result {
114        columns: Vec<String>,
115    },
116    ResultEnd {
117        rows: u64,
118    },
119    NoResult {
120        #[serde(default, skip_serializing_if = "Option::is_none")]
121        rows_affected: Option<u64>,
122    },
123    Written {
124        files: Vec<String>,
125        /// Things the person should know about the files (a value written as text, say).
126        #[serde(default, skip_serializing_if = "Vec::is_empty")]
127        warnings: Vec<String>,
128    },
129    Delivered {
130        location: String,
131    },
132    /// `relation` is what SQL uses to read the loaded rows. `warning`, when set, is shown to the
133    /// user (e.g. the database has no bulk path, so a load this size is slow).
134    Loaded {
135        relation: String,
136        rows: u64,
137        #[serde(default, skip_serializing_if = "Option::is_none")]
138        warning: Option<String>,
139    },
140    Error {
141        message: String,
142    },
143}
144
145/// One file of a multi-file `deliver`.
146#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
147pub struct DeliveryFile {
148    pub local_path: String,
149    #[serde(default, skip_serializing_if = "Option::is_none")]
150    pub remote_path: Option<String>,
151}
152
153/// Per result set, what a format plugin needs to lay it out.
154#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
155pub struct ResultSetMeta {
156    /// Sheet name (xlsx) or file suffix (single-table formats).
157    pub name: String,
158    /// The query (`.sql` basename) that produced it.
159    pub query: String,
160    /// 1-based index among that query's result sets.
161    pub result_index: usize,
162    #[serde(default, skip_serializing_if = "Option::is_none")]
163    pub anchor: Option<String>,
164    #[serde(default, skip_serializing_if = "Option::is_none")]
165    pub header: Option<bool>,
166    /// The query entry's `columns:` map: per result column, how to show it.
167    #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
168    pub columns: std::collections::BTreeMap<String, ColumnOptions>,
169}
170
171/// One entry of a `columns:` map.
172#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
173pub struct ColumnOptions {
174    /// An Excel number format code.
175    #[serde(default, skip_serializing_if = "Option::is_none")]
176    pub format: Option<String>,
177    /// A row formula (`={qty}*{price}`), written in place of the column's value, which becomes
178    /// its cached result.
179    #[serde(default, skip_serializing_if = "Option::is_none")]
180    pub formula: Option<String>,
181    /// A totals row entry under the data: `sum`, `average`, `count`, `min`, `max`, or a formula
182    /// over whole columns (`=SUM({amount:*})/COUNT({qty:*})`).
183    #[serde(default, skip_serializing_if = "Option::is_none")]
184    pub total: Option<String>,
185}
186
187#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
188pub struct ConnectionField {
189    pub name: String,
190    #[serde(default)]
191    pub description: String,
192    #[serde(default)]
193    pub required: bool,
194    /// Offered as an `env_var()` reference by default in `dre init`.
195    #[serde(default)]
196    pub secret: bool,
197    #[serde(default, skip_serializing_if = "Option::is_none")]
198    pub default: Option<Value>,
199    /// A source type whose profile has the same field: `dre init` offers the value entered for
200    /// that source as this field's default (e.g. one Databricks host for source and destination).
201    #[serde(default, skip_serializing_if = "Option::is_none")]
202    pub same_as_source: Option<String>,
203}
204
205impl ConnectionField {
206    pub fn new(name: &str, description: &str) -> Self {
207        ConnectionField {
208            name: name.into(),
209            description: description.into(),
210            required: false,
211            secret: false,
212            default: None,
213            same_as_source: None,
214        }
215    }
216    pub fn required(mut self) -> Self {
217        self.required = true;
218        self
219    }
220    pub fn secret(mut self) -> Self {
221        self.secret = true;
222        self
223    }
224    pub fn default(mut self, v: impl Into<Value>) -> Self {
225        self.default = Some(v.into());
226        self
227    }
228    pub fn same_as_source(mut self, source_type: &str) -> Self {
229        self.same_as_source = Some(source_type.into());
230        self
231    }
232}