1use serde::{Deserialize, Serialize};
5use serde_json::{Map, Value};
6
7pub use crate::options::OptionField;
8use crate::{Kind, PluginId};
9
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
15#[schemars(rename = "{T}")]
17pub struct Envelope<T> {
18 #[serde(default, skip_serializing_if = "Option::is_none")]
19 pub id: Option<u64>,
20 #[serde(flatten)]
21 pub body: T,
22}
23
24impl<T> Envelope<T> {
25 pub fn new(id: Option<u64>, body: T) -> Envelope<T> {
26 Envelope { id, body }
27 }
28}
29
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
32#[serde(tag = "type", rename_all = "snake_case")]
33pub enum Request {
34 Hello {
36 min_version: u32,
37 max_version: u32,
38 core_version: String,
39 #[serde(default, skip_serializing_if = "Option::is_none")]
42 plugin: Option<PluginId>,
43 },
44 Describe {},
47 Validate {
50 options: Map<String, Value>,
51 #[serde(default, skip_serializing_if = "Option::is_none")]
55 template: Option<Value>,
56 },
57 ValidateConnection {
62 connection: Map<String, Value>,
63 #[serde(default, skip_serializing_if = "Vec::is_empty")]
64 unresolved: Vec<String>,
65 },
66 Open {
68 connection: Map<String, Value>,
69 read_only: bool,
70 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
73 sandbox: bool,
74 },
75 Execute {
78 sql: String,
79 #[serde(default, skip_serializing_if = "Option::is_none")]
80 row_limit: Option<u64>,
81 },
82 Check {
84 sql: String,
85 },
86 Write {
89 path: String,
90 format: String,
91 options: Map<String, Value>,
92 result_sets: Vec<ResultSetMeta>,
93 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
95 font_requests: bool,
96 #[serde(default, skip_serializing_if = "Option::is_none")]
97 template: Option<Value>,
98 },
99 Fonts {
103 paths: Vec<String>,
104 error: Option<String>,
105 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
106 repeated: bool,
107 },
108 ResultSetEnd {},
109 Finish {},
110 Deliver {
116 #[serde(default, skip_serializing_if = "Option::is_none")]
117 local_path: Option<String>,
118 #[serde(default, skip_serializing_if = "Option::is_none")]
119 remote_path: Option<String>,
120 #[serde(default, skip_serializing_if = "Vec::is_empty")]
121 files: Vec<DeliveryFile>,
122 connection: Map<String, Value>,
123 #[serde(default)]
124 options: Map<String, Value>,
125 #[serde(default, skip_serializing_if = "Option::is_none")]
126 message: Option<Message>,
127 },
128 Load {
132 name: String,
133 },
134 Close {},
136 Cancel {},
140}
141
142#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
144#[serde(tag = "type", rename_all = "snake_case")]
145pub enum Response {
146 Hello {
147 protocol_version: u32,
148 kind: Kind,
149 name: String,
150 version: String,
151 #[serde(default)]
152 capabilities: Vec<String>,
153 #[serde(default, skip_serializing_if = "Vec::is_empty")]
155 provides: Vec<PluginId>,
156 },
157 VersionMismatch {
158 min_version: u32,
159 max_version: u32,
160 },
161 Describe {
162 connection_fields: Vec<ConnectionField>,
163 #[serde(default, skip_serializing_if = "Vec::is_empty")]
164 option_fields: Vec<OptionField>,
165 #[serde(default, skip_serializing_if = "Option::is_none")]
169 identifier_quote: Option<String>,
170 #[serde(default, skip_serializing_if = "Option::is_none")]
174 string_literal: Option<String>,
175 #[serde(default, skip_serializing_if = "Option::is_none")]
178 message_limit: Option<u64>,
179 #[serde(default, skip_serializing_if = "Option::is_none")]
183 single_writer: Option<SingleWriter>,
184 },
185 Validated {
187 errors: Vec<String>,
188 #[serde(default, skip_serializing_if = "Vec::is_empty")]
190 warnings: Vec<String>,
191 },
192 Ok {},
193 Result {
195 columns: Vec<String>,
196 },
197 ResultEnd {
198 rows: u64,
199 },
200 NoResult {
201 #[serde(default, skip_serializing_if = "Option::is_none")]
202 rows_affected: Option<u64>,
203 },
204 NeedFonts {
206 font: FontRequest,
207 },
208 Written {
209 files: Vec<String>,
210 #[serde(default, skip_serializing_if = "Vec::is_empty")]
212 warnings: Vec<String>,
213 },
214 Delivered {
215 location: String,
216 #[serde(default, skip_serializing_if = "Option::is_none")]
218 attempts: Option<u32>,
219 },
220 Loaded {
223 relation: String,
224 rows: u64,
225 #[serde(default, skip_serializing_if = "Option::is_none")]
226 warning: Option<String>,
227 },
228 Error {
233 message: String,
234 #[serde(default, skip_serializing_if = "Option::is_none")]
235 kind: Option<String>,
236 #[serde(default, skip_serializing_if = "Option::is_none")]
237 code: Option<String>,
238 },
239 Log {
241 level: LogLevel,
242 message: String,
243 #[serde(default, skip_serializing_if = "Map::is_empty")]
245 fields: Map<String, Value>,
246 },
247 Progress {
249 #[serde(default, skip_serializing_if = "Option::is_none")]
250 message: Option<String>,
251 #[serde(default, skip_serializing_if = "Option::is_none")]
252 done: Option<u64>,
253 #[serde(default, skip_serializing_if = "Option::is_none")]
254 total: Option<u64>,
255 },
256}
257
258impl Response {
259 pub fn error(message: impl Into<String>) -> Response {
261 Response::Error {
262 message: message.into(),
263 kind: None,
264 code: None,
265 }
266 }
267}
268
269pub const ERROR_KINDS: &[&str] = &[
271 "config",
272 "plugin",
273 "refused",
274 "connection",
275 "auth",
276 "query",
277 "delivery",
278 "internal",
279 "cancelled",
280 "timed_out",
281];
282
283#[derive(
286 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, schemars::JsonSchema,
287)]
288#[serde(rename_all = "lowercase")]
289pub enum LogLevel {
290 Error,
291 Warn,
292 Info,
293 Debug,
294 Trace,
295}
296
297#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
299pub struct Message {
300 pub title: String,
302 pub text: String,
304 #[serde(default, skip_serializing_if = "Option::is_none")]
307 pub html: Option<String>,
308 pub path: String,
310}
311
312#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
314pub struct DeliveryFile {
315 pub local_path: String,
316 #[serde(default, skip_serializing_if = "Option::is_none")]
317 pub remote_path: Option<String>,
318}
319
320#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
322pub struct ResultSetMeta {
323 pub name: String,
325 pub query: String,
327 pub result_index: usize,
329 #[serde(default, skip_serializing_if = "Option::is_none")]
330 pub anchor: Option<String>,
331 #[serde(default, skip_serializing_if = "Option::is_none")]
332 pub header: Option<bool>,
333 #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
335 pub columns: std::collections::BTreeMap<String, ColumnOptions>,
336 #[serde(default, skip_serializing_if = "Option::is_none")]
339 pub autofit: Option<bool>,
340 #[serde(default, skip_serializing_if = "Option::is_none")]
342 pub style: Option<crate::style::SheetStyle>,
343}
344
345#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
347pub struct ColumnOptions {
348 #[serde(default, skip_serializing_if = "Option::is_none")]
350 pub format: Option<String>,
351 #[serde(default, skip_serializing_if = "Option::is_none")]
354 pub formula: Option<String>,
355 #[serde(default, skip_serializing_if = "Option::is_none")]
358 pub total: Option<String>,
359 #[serde(default, skip_serializing_if = "Option::is_none")]
361 pub width: Option<ColumnWidth>,
362 #[serde(default, skip_serializing_if = "Option::is_none")]
364 pub style: Option<crate::style::CellStyle>,
365}
366
367#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
369#[serde(untagged)]
370pub enum ColumnWidth {
371 Chars(f64),
372 Auto(AutoWidth),
373}
374
375#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
377#[serde(rename_all = "lowercase")]
378pub enum AutoWidth {
379 Auto,
380}
381
382#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
384pub struct SingleWriter {
385 pub field: String,
387 #[serde(default, skip_serializing_if = "Vec::is_empty")]
389 pub except: Vec<String>,
390}
391
392impl SingleWriter {
393 pub fn applies(&self, connection: &Map<String, Value>) -> bool {
395 connection
396 .get(&self.field)
397 .and_then(Value::as_str)
398 .is_some_and(|v| !self.except.iter().any(|e| e == v))
399 }
400}
401
402#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
403pub struct ConnectionField {
404 pub name: String,
405 #[serde(default)]
406 pub description: String,
407 #[serde(default)]
408 pub required: bool,
409 #[serde(default)]
411 pub secret: bool,
412 #[serde(default, skip_serializing_if = "Option::is_none")]
413 pub default: Option<Value>,
414 #[serde(default, skip_serializing_if = "Option::is_none")]
417 pub same_as_source: Option<String>,
418 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
421 pub manual: bool,
422 #[serde(default, skip_serializing_if = "Option::is_none")]
424 pub kind: Option<FieldKind>,
425 #[serde(default, skip_serializing_if = "Vec::is_empty")]
427 pub choices: Vec<String>,
428}
429
430#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
432#[serde(rename_all = "snake_case")]
433pub enum FieldKind {
434 String,
435 Integer,
437 Boolean,
438 Path,
440 Duration,
442 Map,
444}
445
446impl ConnectionField {
447 pub fn new(name: &str, description: &str) -> Self {
448 ConnectionField {
449 name: name.into(),
450 description: description.into(),
451 required: false,
452 secret: false,
453 default: None,
454 same_as_source: None,
455 manual: false,
456 kind: None,
457 choices: Vec::new(),
458 }
459 }
460 #[must_use]
461 pub fn kind(mut self, kind: FieldKind) -> Self {
462 self.kind = Some(kind);
463 self
464 }
465 #[must_use]
466 pub fn choices(mut self, choices: &[&str]) -> Self {
467 self.choices = choices.iter().map(|c| c.to_string()).collect();
468 self.kind.get_or_insert(FieldKind::String);
469 self
470 }
471 #[must_use]
472 pub fn required(mut self) -> Self {
473 self.required = true;
474 self
475 }
476 #[must_use]
477 pub fn secret(mut self) -> Self {
478 self.secret = true;
479 self
480 }
481 #[must_use]
482 pub fn default(mut self, v: impl Into<Value>) -> Self {
483 self.default = Some(v.into());
484 self
485 }
486 #[must_use]
487 pub fn same_as_source(mut self, source_type: &str) -> Self {
488 self.same_as_source = Some(source_type.into());
489 self
490 }
491 #[must_use]
492 pub fn manual(mut self) -> Self {
493 self.manual = true;
494 self
495 }
496}
497
498#[cfg(test)]
499mod font_tests {
500 use super::*;
501
502 #[test]
503 fn old_write_requests_do_not_enable_font_requests() {
504 let request: Request = serde_json::from_value(serde_json::json!({
505 "type": "write", "path": "out.pdf", "format": "pdf", "options": {}, "result_sets": []
506 }))
507 .unwrap();
508 assert!(matches!(
509 request,
510 Request::Write {
511 font_requests: false,
512 ..
513 }
514 ));
515 assert!(
516 serde_json::to_value(request)
517 .unwrap()
518 .get("font_requests")
519 .is_none()
520 );
521 }
522}
523
524#[derive(Debug, Clone, PartialEq, Eq)]
527pub struct FontError {
528 pub message: String,
529 pub repeated: bool,
530}
531
532impl From<String> for FontError {
533 fn from(message: String) -> FontError {
534 FontError {
535 message,
536 repeated: false,
537 }
538 }
539}
540
541impl From<&str> for FontError {
542 fn from(message: &str) -> FontError {
543 message.to_string().into()
544 }
545}
546
547impl std::fmt::Display for FontError {
548 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
549 f.write_str(&self.message)
550 }
551}
552
553#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
555pub struct FontRequest {
556 pub name: String,
557 pub version: String,
558}