1use agent_first_data::LogFilters;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use std::collections::HashMap;
5
6mod log_filters_serde {
10 use agent_first_data::{LogFilters, cli_parse_log_filters};
11 use serde::{Deserialize, Deserializer, Serialize, Serializer};
12
13 pub fn serialize<S: Serializer>(
14 filters: &LogFilters,
15 serializer: S,
16 ) -> Result<S::Ok, S::Error> {
17 filters.as_slice().serialize(serializer)
18 }
19
20 pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<LogFilters, D::Error> {
21 let raw = Vec::<String>::deserialize(deserializer)?;
22 Ok(cli_parse_log_filters(&raw))
23 }
24}
25
26#[derive(Debug, Deserialize)]
27#[serde(tag = "code", deny_unknown_fields)]
28pub enum Input {
29 #[serde(rename = "query")]
30 Query {
31 id: String,
32 #[serde(default)]
33 session: Option<String>,
34 sql: String,
35 #[serde(default)]
36 params: Vec<Value>,
37 #[serde(default)]
38 options: QueryOptions,
39 },
40 #[serde(rename = "config")]
41 Config(ConfigPatch),
42 #[serde(rename = "cancel")]
43 Cancel { id: String },
44 #[serde(rename = "ping")]
45 Ping,
46 #[serde(rename = "close")]
47 Close,
48 #[serde(rename = "session_info")]
49 SessionInfo {
50 #[serde(default)]
51 id: Option<String>,
52 #[serde(default)]
53 session: Option<String>,
54 },
55 #[serde(rename = "begin")]
59 Begin {
60 #[serde(default)]
61 id: Option<String>,
62 #[serde(default)]
63 session: Option<String>,
64 #[serde(default)]
67 read_only: bool,
68 #[serde(default)]
72 permission: Option<Permission>,
73 },
74 #[serde(rename = "commit")]
75 Commit {
76 #[serde(default)]
77 id: Option<String>,
78 #[serde(default)]
79 session: Option<String>,
80 },
81 #[serde(rename = "rollback")]
82 Rollback {
83 #[serde(default)]
84 id: Option<String>,
85 #[serde(default)]
86 session: Option<String>,
87 },
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, clap::ValueEnum)]
91pub enum Permission {
92 #[serde(rename = "read")]
93 Read,
94 #[serde(rename = "write")]
95 Write,
96 #[serde(rename = "ssh-read")]
97 SshRead,
98 #[serde(rename = "ssh-write")]
99 SshWrite,
100 #[serde(rename = "container-read")]
101 ContainerRead,
102 #[serde(rename = "container-write")]
103 ContainerWrite,
104}
105
106impl Permission {
107 pub fn as_str(self) -> &'static str {
108 match self {
109 Self::Read => "read",
110 Self::Write => "write",
111 Self::SshRead => "ssh-read",
112 Self::SshWrite => "ssh-write",
113 Self::ContainerRead => "container-read",
114 Self::ContainerWrite => "container-write",
115 }
116 }
117
118 pub fn is_read_only(self) -> bool {
119 matches!(self, Self::Read | Self::SshRead | Self::ContainerRead)
120 }
121
122 pub fn allows_ssh(self) -> bool {
123 matches!(self, Self::SshRead | Self::SshWrite)
124 }
125
126 pub fn allows_container(self) -> bool {
127 matches!(self, Self::ContainerRead | Self::ContainerWrite)
128 }
129}
130
131impl std::str::FromStr for Permission {
132 type Err = String;
133
134 fn from_str(value: &str) -> Result<Self, Self::Err> {
135 match value {
136 "read" => Ok(Self::Read),
137 "write" => Ok(Self::Write),
138 "ssh-read" => Ok(Self::SshRead),
139 "ssh-write" => Ok(Self::SshWrite),
140 "container-read" => Ok(Self::ContainerRead),
141 "container-write" => Ok(Self::ContainerWrite),
142 _ => Err(format!(
143 "invalid permission `{value}`; expected read, write, ssh-read, ssh-write, container-read, or container-write"
144 )),
145 }
146 }
147}
148
149#[derive(Debug, Deserialize, Default, Clone)]
150#[serde(deny_unknown_fields)]
151#[allow(dead_code)]
152pub struct QueryOptions {
153 #[serde(default)]
154 pub stream_rows: bool,
155 pub batch_rows: Option<usize>,
156 pub batch_bytes: Option<usize>,
157 pub statement_timeout_ms: Option<u64>,
158 pub lock_timeout_ms: Option<u64>,
159 pub permission: Option<Permission>,
160 pub inline_max_rows: Option<usize>,
161 pub inline_max_bytes: Option<usize>,
162}
163
164#[derive(Debug, Serialize)]
165#[serde(tag = "code")]
166pub enum Output {
167 #[serde(rename = "result")]
168 Result {
169 #[serde(skip_serializing_if = "Option::is_none")]
170 id: Option<String>,
171 #[serde(skip_serializing_if = "Option::is_none")]
172 session: Option<String>,
173 command_tag: String,
174 columns: Vec<ColumnInfo>,
175 rows: Vec<Value>,
176 row_count: usize,
177 #[serde(skip_serializing_if = "is_false", default)]
180 truncated: bool,
181 #[serde(skip_serializing_if = "Option::is_none")]
183 truncated_at_rows: Option<usize>,
184 #[serde(skip_serializing_if = "Option::is_none")]
186 truncated_at_bytes: Option<usize>,
187 trace: Trace,
188 },
189 #[serde(rename = "result_start")]
190 ResultStart {
191 id: String,
192 #[serde(skip_serializing_if = "Option::is_none")]
193 session: Option<String>,
194 columns: Vec<ColumnInfo>,
195 },
196 #[serde(rename = "result_rows")]
197 ResultRows {
198 id: String,
199 rows: Vec<Value>,
200 rows_batch_count: usize,
201 },
202 #[serde(rename = "result_end")]
203 ResultEnd {
204 id: String,
205 #[serde(skip_serializing_if = "Option::is_none")]
206 session: Option<String>,
207 command_tag: String,
208 trace: Trace,
209 },
210 #[serde(rename = "sql_error")]
211 SqlError {
212 #[serde(skip_serializing_if = "Option::is_none")]
213 id: Option<String>,
214 #[serde(skip_serializing_if = "Option::is_none")]
215 session: Option<String>,
216 sqlstate: String,
217 message: String,
218 #[serde(skip_serializing_if = "Option::is_none")]
219 detail: Option<String>,
220 #[serde(skip_serializing_if = "Option::is_none")]
221 hint: Option<String>,
222 #[serde(skip_serializing_if = "Option::is_none")]
223 position: Option<String>,
224 trace: Trace,
225 },
226 #[serde(rename = "error")]
227 Error {
228 #[serde(skip_serializing_if = "Option::is_none")]
229 id: Option<String>,
230 error_code: String,
231 error: String,
232 #[serde(skip_serializing_if = "Option::is_none")]
233 sqlstate: Option<String>,
234 #[serde(skip_serializing_if = "Option::is_none")]
235 message: Option<String>,
236 #[serde(skip_serializing_if = "Option::is_none")]
237 detail: Option<String>,
238 #[serde(skip_serializing_if = "Option::is_none")]
239 hint: Option<String>,
240 retryable: bool,
241 trace: Trace,
242 },
243 #[serde(rename = "dry_run")]
244 DryRun {
245 #[serde(skip_serializing_if = "Option::is_none")]
246 id: Option<String>,
247 sql: String,
248 params: Vec<String>,
249 #[serde(skip_serializing_if = "Option::is_none")]
250 session: Option<String>,
251 #[serde(skip_serializing_if = "Vec::is_empty", default)]
254 param_types: Vec<String>,
255 #[serde(skip_serializing_if = "Vec::is_empty", default)]
258 columns: Vec<ColumnInfo>,
259 trace: Trace,
260 },
261 #[serde(rename = "config")]
262 Config(RuntimeConfig),
263 #[serde(rename = "pong")]
264 Pong { trace: PongTrace },
265 #[serde(rename = "close")]
266 Close { message: String, trace: CloseTrace },
267 #[serde(rename = "session_info")]
268 SessionInfo {
269 #[serde(skip_serializing_if = "Option::is_none")]
270 id: Option<String>,
271 session: String,
272 transport_kind: String,
273 permission_default: String,
274 stream_rows_default: bool,
275 batch_rows: usize,
276 batch_bytes: usize,
277 inline_max_rows: usize,
278 inline_max_bytes: usize,
279 statement_timeout_ms: u64,
280 lock_timeout_ms: u64,
281 #[serde(skip_serializing_if = "Option::is_none")]
282 database: Option<String>,
283 #[serde(skip_serializing_if = "Option::is_none")]
284 user: Option<String>,
285 #[serde(skip_serializing_if = "Option::is_none")]
286 host: Option<String>,
287 #[serde(skip_serializing_if = "Option::is_none")]
288 port: Option<u16>,
289 #[serde(skip_serializing_if = "Option::is_none")]
290 server_version: Option<String>,
291 trace: Trace,
292 },
293 #[serde(rename = "log")]
294 Log {
295 event: String,
296 #[serde(skip_serializing_if = "Option::is_none")]
297 request_id: Option<String>,
298 #[serde(skip_serializing_if = "Option::is_none")]
299 session: Option<String>,
300 #[serde(skip_serializing_if = "Option::is_none")]
301 error_code: Option<String>,
302 #[serde(skip_serializing_if = "Option::is_none")]
303 command_tag: Option<String>,
304 #[serde(skip_serializing_if = "Option::is_none")]
305 version: Option<String>,
306 #[serde(skip_serializing_if = "Option::is_none")]
307 config: Option<Value>,
308 #[serde(skip_serializing_if = "Option::is_none")]
309 args: Option<Value>,
310 #[serde(skip_serializing_if = "Option::is_none")]
311 env: Option<Value>,
312 #[serde(skip_serializing_if = "Option::is_none")]
313 chain: Option<String>,
314 trace: Trace,
315 },
316}
317
318fn is_false(b: &bool) -> bool {
319 !*b
320}
321
322#[derive(Debug, Serialize, Clone)]
323pub struct ColumnInfo {
324 pub name: String,
325 #[serde(rename = "type")]
326 pub type_name: String,
327}
328
329#[derive(Debug, Serialize, Clone)]
330pub struct Trace {
331 pub duration_ms: u64,
332 #[serde(skip_serializing_if = "Option::is_none")]
333 pub row_count: Option<usize>,
334 #[serde(skip_serializing_if = "Option::is_none")]
335 pub payload_bytes: Option<usize>,
336}
337
338impl Trace {
339 pub fn only_duration(duration_ms: u64) -> Self {
340 Self {
341 duration_ms,
342 row_count: None,
343 payload_bytes: None,
344 }
345 }
346}
347
348#[derive(Debug, Serialize)]
349pub struct PongTrace {
350 pub uptime_s: u64,
351 pub requests_total: u64,
352 pub in_flight: usize,
353}
354
355#[derive(Debug, Serialize)]
356pub struct CloseTrace {
357 pub uptime_s: u64,
358 pub requests_total: u64,
359}
360
361#[derive(Debug, Clone, Default)]
362pub struct SessionConfig {
363 pub dsn_secret: Option<String>,
364 pub conninfo_secret: Option<String>,
365 pub host: Option<String>,
366 pub port: Option<u16>,
367 pub user: Option<String>,
368 pub dbname: Option<String>,
369 pub password_secret: Option<String>,
370 pub ssh: SshConfig,
371 pub container: ContainerConfig,
372}
373
374#[derive(Serialize)]
375struct SafeSessionConfig<'a> {
376 #[serde(skip_serializing_if = "Option::is_none")]
377 dsn_secret: Option<&'static str>,
378 #[serde(skip_serializing_if = "Option::is_none")]
379 conninfo_secret: Option<&'static str>,
380 #[serde(skip_serializing_if = "Option::is_none")]
381 host: Option<&'a str>,
382 #[serde(skip_serializing_if = "Option::is_none")]
383 port: Option<u16>,
384 #[serde(skip_serializing_if = "Option::is_none")]
385 user: Option<&'a str>,
386 #[serde(skip_serializing_if = "Option::is_none")]
387 dbname: Option<&'a str>,
388 #[serde(skip_serializing_if = "Option::is_none")]
389 password_secret: Option<&'static str>,
390 #[serde(flatten)]
391 ssh: &'a SshConfig,
392 #[serde(flatten)]
393 container: &'a ContainerConfig,
394}
395
396impl Serialize for SessionConfig {
397 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
398 where
399 S: serde::Serializer,
400 {
401 SafeSessionConfig {
402 dsn_secret: self.dsn_secret.as_ref().map(|_| "***"),
403 conninfo_secret: self.conninfo_secret.as_ref().map(|_| "***"),
404 host: self.host.as_deref(),
405 port: self.port,
406 user: self.user.as_deref(),
407 dbname: self.dbname.as_deref(),
408 password_secret: self.password_secret.as_ref().map(|_| "***"),
409 ssh: &self.ssh,
410 container: &self.container,
411 }
412 .serialize(serializer)
413 }
414}
415
416#[derive(Debug, Serialize, Deserialize, Clone, Default)]
417pub struct SshConfig {
418 #[serde(rename = "ssh", skip_serializing_if = "Option::is_none")]
419 pub destination: Option<String>,
420 #[serde(rename = "ssh_via", default, skip_serializing_if = "Vec::is_empty")]
421 pub via: Vec<String>,
422 #[serde(rename = "ssh_options", default, skip_serializing_if = "Vec::is_empty")]
423 pub options: Vec<String>,
424 #[serde(rename = "ssh_local_host", skip_serializing_if = "Option::is_none")]
425 pub local_host: Option<String>,
426 #[serde(rename = "ssh_local_port", skip_serializing_if = "Option::is_none")]
427 pub local_port: Option<u16>,
428 #[serde(rename = "ssh_remote_socket", skip_serializing_if = "Option::is_none")]
429 pub remote_socket: Option<String>,
430 #[serde(rename = "ssh_sudo_user", skip_serializing_if = "Option::is_none")]
431 pub sudo_user: Option<String>,
432}
433
434impl SshConfig {
435 pub fn has_transport_fields(&self) -> bool {
436 self.destination.is_some()
437 || !self.via.is_empty()
438 || !self.options.is_empty()
439 || self.local_host.is_some()
440 || self.local_port.is_some()
441 || self.remote_socket.is_some()
442 || self.sudo_user.is_some()
443 }
444
445 pub fn has_tunnel_or_bridge_options(&self) -> bool {
446 self.local_host.is_some()
447 || self.local_port.is_some()
448 || !self.via.is_empty()
449 || self.remote_socket.is_some()
450 || self.sudo_user.is_some()
451 }
452}
453
454#[derive(Debug, Serialize, Deserialize, Clone, Default)]
455pub struct ContainerConfig {
456 #[serde(rename = "container", skip_serializing_if = "Option::is_none")]
457 pub target: Option<String>,
458 #[serde(rename = "container_driver", skip_serializing_if = "Option::is_none")]
459 pub driver: Option<String>,
460 #[serde(rename = "container_runtime", skip_serializing_if = "Option::is_none")]
461 pub runtime: Option<String>,
462 #[serde(rename = "container_user", skip_serializing_if = "Option::is_none")]
463 pub user: Option<String>,
464 #[serde(
465 rename = "container_namespace",
466 skip_serializing_if = "Option::is_none"
467 )]
468 pub namespace: Option<String>,
469 #[serde(rename = "container_context", skip_serializing_if = "Option::is_none")]
470 pub context: Option<String>,
471 #[serde(
472 rename = "container_compose_files",
473 default,
474 skip_serializing_if = "Vec::is_empty"
475 )]
476 pub compose_files: Vec<String>,
477 #[serde(
478 rename = "container_compose_project",
479 skip_serializing_if = "Option::is_none"
480 )]
481 pub compose_project: Option<String>,
482 #[serde(
483 rename = "container_pod_container",
484 skip_serializing_if = "Option::is_none"
485 )]
486 pub pod_container: Option<String>,
487}
488
489impl ContainerConfig {
490 pub fn has_transport_fields(&self) -> bool {
491 self.target.is_some()
492 || self.driver.is_some()
493 || self.runtime.is_some()
494 || self.user.is_some()
495 || self.namespace.is_some()
496 || self.context.is_some()
497 || !self.compose_files.is_empty()
498 || self.compose_project.is_some()
499 || self.pod_container.is_some()
500 }
501}
502
503#[derive(Debug, Deserialize, Default)]
504#[serde(deny_unknown_fields)]
505struct SessionConfigFlat {
506 #[serde(default)]
507 dsn_secret: Option<String>,
508 #[serde(default)]
509 conninfo_secret: Option<String>,
510 #[serde(default)]
511 host: Option<String>,
512 #[serde(default)]
513 port: Option<u16>,
514 #[serde(default)]
515 user: Option<String>,
516 #[serde(default)]
517 dbname: Option<String>,
518 #[serde(default)]
519 password_secret: Option<String>,
520 #[serde(default)]
521 ssh: Option<String>,
522 #[serde(default)]
523 ssh_via: Vec<String>,
524 #[serde(default)]
525 ssh_options: Vec<String>,
526 #[serde(default)]
527 ssh_local_host: Option<String>,
528 #[serde(default)]
529 ssh_local_port: Option<u16>,
530 #[serde(default)]
531 ssh_remote_socket: Option<String>,
532 #[serde(default)]
533 ssh_sudo_user: Option<String>,
534 #[serde(default)]
535 container: Option<String>,
536 #[serde(default)]
537 container_driver: Option<String>,
538 #[serde(default)]
539 container_runtime: Option<String>,
540 #[serde(default)]
541 container_user: Option<String>,
542 #[serde(default)]
543 container_namespace: Option<String>,
544 #[serde(default)]
545 container_context: Option<String>,
546 #[serde(default)]
547 container_compose_files: Vec<String>,
548 #[serde(default)]
549 container_compose_project: Option<String>,
550 #[serde(default)]
551 container_pod_container: Option<String>,
552}
553
554impl From<SessionConfigFlat> for SessionConfig {
555 fn from(flat: SessionConfigFlat) -> Self {
556 Self {
557 dsn_secret: flat.dsn_secret,
558 conninfo_secret: flat.conninfo_secret,
559 host: flat.host,
560 port: flat.port,
561 user: flat.user,
562 dbname: flat.dbname,
563 password_secret: flat.password_secret,
564 ssh: SshConfig {
565 destination: flat.ssh,
566 via: flat.ssh_via,
567 options: flat.ssh_options,
568 local_host: flat.ssh_local_host,
569 local_port: flat.ssh_local_port,
570 remote_socket: flat.ssh_remote_socket,
571 sudo_user: flat.ssh_sudo_user,
572 },
573 container: ContainerConfig {
574 target: flat.container,
575 driver: flat.container_driver,
576 runtime: flat.container_runtime,
577 user: flat.container_user,
578 namespace: flat.container_namespace,
579 context: flat.container_context,
580 compose_files: flat.container_compose_files,
581 compose_project: flat.container_compose_project,
582 pod_container: flat.container_pod_container,
583 },
584 }
585 }
586}
587
588impl<'de> Deserialize<'de> for SessionConfig {
589 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
590 where
591 D: serde::Deserializer<'de>,
592 {
593 SessionConfigFlat::deserialize(deserializer).map(Self::from)
594 }
595}
596
597impl SessionConfig {
598 pub fn uses_ssh_transport(&self) -> bool {
599 self.ssh.has_transport_fields()
600 }
601
602 pub fn uses_container_transport(&self) -> bool {
603 self.container.has_transport_fields()
604 }
605
606 pub fn transport_kind(&self) -> Result<TransportKind, String> {
607 let uses_ssh = self.uses_ssh_transport();
608 let uses_container = self.uses_container_transport();
609 match (uses_ssh, uses_container) {
610 (false, false) => Ok(TransportKind::Direct),
611 (true, false) => Ok(TransportKind::Ssh),
612 (false, true) => Ok(TransportKind::Container),
613 (true, true) => Ok(TransportKind::Container),
616 }
617 }
618}
619
620#[derive(Debug, Clone, Copy, PartialEq, Eq)]
621pub enum TransportKind {
622 Direct,
623 Ssh,
624 Container,
625}
626
627impl TransportKind {
628 pub fn as_str(self) -> &'static str {
629 match self {
630 Self::Direct => "direct",
631 Self::Ssh => "ssh",
632 Self::Container => "container",
633 }
634 }
635}
636
637#[derive(Debug, Serialize, Deserialize, Clone)]
638pub struct RuntimeConfig {
639 pub default_session: String,
640 #[serde(default)]
641 pub sessions: HashMap<String, SessionConfig>,
642 pub inline_max_rows: usize,
643 pub inline_max_bytes: usize,
644 pub statement_timeout_ms: u64,
645 pub lock_timeout_ms: u64,
646 #[serde(default, with = "log_filters_serde")]
647 pub log: LogFilters,
648}
649
650impl Default for RuntimeConfig {
651 fn default() -> Self {
652 let mut sessions = HashMap::new();
653 sessions.insert("default".to_string(), SessionConfig::default());
654 Self {
655 default_session: "default".to_string(),
656 sessions,
657 inline_max_rows: 1000,
658 inline_max_bytes: 1_048_576,
659 statement_timeout_ms: 30_000,
660 lock_timeout_ms: 5_000,
661 log: LogFilters::default(),
662 }
663 }
664}
665
666#[derive(Debug, Deserialize, Default)]
667#[serde(deny_unknown_fields)]
668pub struct ConfigPatch {
669 pub default_session: Option<String>,
670 pub sessions: Option<HashMap<String, SessionConfigPatch>>,
671 pub inline_max_rows: Option<usize>,
672 pub inline_max_bytes: Option<usize>,
673 pub statement_timeout_ms: Option<u64>,
674 pub lock_timeout_ms: Option<u64>,
675 pub log: Option<Vec<String>>,
676}
677
678#[derive(Debug, Default)]
679pub enum PatchField<T> {
680 #[default]
681 Missing,
682 Null,
683 Value(T),
684}
685
686impl<'de, T> Deserialize<'de> for PatchField<T>
687where
688 T: Deserialize<'de>,
689{
690 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
691 where
692 D: serde::Deserializer<'de>,
693 {
694 let value = Option::<T>::deserialize(deserializer)?;
695 match value {
696 Some(value) => Ok(Self::Value(value)),
697 None => Ok(Self::Null),
698 }
699 }
700}
701
702impl<T> PatchField<T> {
703 pub fn into_update(self) -> Option<Option<T>> {
704 match self {
705 Self::Missing => None,
706 Self::Null => Some(None),
707 Self::Value(value) => Some(Some(value)),
708 }
709 }
710}
711
712#[derive(Debug, Default)]
713pub struct SessionConfigPatch {
714 pub dsn_secret: PatchField<String>,
715 pub conninfo_secret: PatchField<String>,
716 pub host: PatchField<String>,
717 pub port: PatchField<u16>,
718 pub user: PatchField<String>,
719 pub dbname: PatchField<String>,
720 pub password_secret: PatchField<String>,
721 pub ssh: SshConfigPatch,
722 pub container: ContainerConfigPatch,
723}
724
725#[derive(Debug, Default)]
726pub struct SshConfigPatch {
727 pub destination: PatchField<String>,
728 pub via: PatchField<Vec<String>>,
729 pub options: PatchField<Vec<String>>,
730 pub local_host: PatchField<String>,
731 pub local_port: PatchField<u16>,
732 pub remote_socket: PatchField<String>,
733 pub sudo_user: PatchField<String>,
734}
735
736#[derive(Debug, Default)]
737pub struct ContainerConfigPatch {
738 pub target: PatchField<String>,
739 pub driver: PatchField<String>,
740 pub runtime: PatchField<String>,
741 pub user: PatchField<String>,
742 pub namespace: PatchField<String>,
743 pub context: PatchField<String>,
744 pub compose_files: PatchField<Vec<String>>,
745 pub compose_project: PatchField<String>,
746 pub pod_container: PatchField<String>,
747}
748
749#[derive(Debug, Deserialize, Default)]
750#[serde(deny_unknown_fields)]
751struct SessionConfigPatchFlat {
752 #[serde(default)]
753 dsn_secret: PatchField<String>,
754 #[serde(default)]
755 conninfo_secret: PatchField<String>,
756 #[serde(default)]
757 host: PatchField<String>,
758 #[serde(default)]
759 port: PatchField<u16>,
760 #[serde(default)]
761 user: PatchField<String>,
762 #[serde(default)]
763 dbname: PatchField<String>,
764 #[serde(default)]
765 password_secret: PatchField<String>,
766 #[serde(default)]
767 ssh: PatchField<String>,
768 #[serde(default)]
769 ssh_via: PatchField<Vec<String>>,
770 #[serde(default)]
771 ssh_options: PatchField<Vec<String>>,
772 #[serde(default)]
773 ssh_local_host: PatchField<String>,
774 #[serde(default)]
775 ssh_local_port: PatchField<u16>,
776 #[serde(default)]
777 ssh_remote_socket: PatchField<String>,
778 #[serde(default)]
779 ssh_sudo_user: PatchField<String>,
780 #[serde(default)]
781 container: PatchField<String>,
782 #[serde(default)]
783 container_driver: PatchField<String>,
784 #[serde(default)]
785 container_runtime: PatchField<String>,
786 #[serde(default)]
787 container_user: PatchField<String>,
788 #[serde(default)]
789 container_namespace: PatchField<String>,
790 #[serde(default)]
791 container_context: PatchField<String>,
792 #[serde(default)]
793 container_compose_files: PatchField<Vec<String>>,
794 #[serde(default)]
795 container_compose_project: PatchField<String>,
796 #[serde(default)]
797 container_pod_container: PatchField<String>,
798}
799
800impl From<SessionConfigPatchFlat> for SessionConfigPatch {
801 fn from(flat: SessionConfigPatchFlat) -> Self {
802 Self {
803 dsn_secret: flat.dsn_secret,
804 conninfo_secret: flat.conninfo_secret,
805 host: flat.host,
806 port: flat.port,
807 user: flat.user,
808 dbname: flat.dbname,
809 password_secret: flat.password_secret,
810 ssh: SshConfigPatch {
811 destination: flat.ssh,
812 via: flat.ssh_via,
813 options: flat.ssh_options,
814 local_host: flat.ssh_local_host,
815 local_port: flat.ssh_local_port,
816 remote_socket: flat.ssh_remote_socket,
817 sudo_user: flat.ssh_sudo_user,
818 },
819 container: ContainerConfigPatch {
820 target: flat.container,
821 driver: flat.container_driver,
822 runtime: flat.container_runtime,
823 user: flat.container_user,
824 namespace: flat.container_namespace,
825 context: flat.container_context,
826 compose_files: flat.container_compose_files,
827 compose_project: flat.container_compose_project,
828 pod_container: flat.container_pod_container,
829 },
830 }
831 }
832}
833
834impl<'de> Deserialize<'de> for SessionConfigPatch {
835 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
836 where
837 D: serde::Deserializer<'de>,
838 {
839 SessionConfigPatchFlat::deserialize(deserializer).map(Self::from)
840 }
841}
842
843#[derive(Debug, Clone)]
844#[allow(dead_code)]
845pub struct ResolvedOptions {
846 pub stream_rows: bool,
847 pub batch_rows: usize,
848 pub batch_bytes: usize,
849 pub statement_timeout_ms: u64,
850 pub lock_timeout_ms: u64,
851 pub read_only: bool,
852 pub inline_max_rows: usize,
853 pub inline_max_bytes: usize,
854}
855
856#[cfg(test)]
857#[path = "../tests/support/unit_types.rs"]
858mod tests;