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 pub profile_pinned: bool,
380}
381
382#[derive(Serialize)]
383struct SafeSessionConfig<'a> {
384 #[serde(skip_serializing_if = "Option::is_none")]
385 dsn_secret: Option<&'static str>,
386 #[serde(skip_serializing_if = "Option::is_none")]
387 conninfo_secret: Option<&'static str>,
388 #[serde(skip_serializing_if = "Option::is_none")]
389 host: Option<&'a str>,
390 #[serde(skip_serializing_if = "Option::is_none")]
391 port: Option<u16>,
392 #[serde(skip_serializing_if = "Option::is_none")]
393 user: Option<&'a str>,
394 #[serde(skip_serializing_if = "Option::is_none")]
395 dbname: Option<&'a str>,
396 #[serde(skip_serializing_if = "Option::is_none")]
397 password_secret: Option<&'static str>,
398 #[serde(flatten)]
399 ssh: &'a SshConfig,
400 #[serde(flatten)]
401 container: &'a ContainerConfig,
402}
403
404impl Serialize for SessionConfig {
405 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
406 where
407 S: serde::Serializer,
408 {
409 SafeSessionConfig {
410 dsn_secret: self.dsn_secret.as_ref().map(|_| "***"),
411 conninfo_secret: self.conninfo_secret.as_ref().map(|_| "***"),
412 host: self.host.as_deref(),
413 port: self.port,
414 user: self.user.as_deref(),
415 dbname: self.dbname.as_deref(),
416 password_secret: self.password_secret.as_ref().map(|_| "***"),
417 ssh: &self.ssh,
418 container: &self.container,
419 }
420 .serialize(serializer)
421 }
422}
423
424#[derive(Debug, Serialize, Deserialize, Clone, Default)]
425pub struct SshConfig {
426 #[serde(rename = "ssh", skip_serializing_if = "Option::is_none")]
427 pub destination: Option<String>,
428 #[serde(rename = "ssh_via", default, skip_serializing_if = "Vec::is_empty")]
429 pub via: Vec<String>,
430 #[serde(rename = "ssh_options", default, skip_serializing_if = "Vec::is_empty")]
431 pub options: Vec<String>,
432 #[serde(rename = "ssh_local_host", skip_serializing_if = "Option::is_none")]
433 pub local_host: Option<String>,
434 #[serde(rename = "ssh_local_port", skip_serializing_if = "Option::is_none")]
435 pub local_port: Option<u16>,
436 #[serde(rename = "ssh_remote_socket", skip_serializing_if = "Option::is_none")]
437 pub remote_socket: Option<String>,
438 #[serde(rename = "ssh_sudo_user", skip_serializing_if = "Option::is_none")]
439 pub sudo_user: Option<String>,
440}
441
442impl SshConfig {
443 pub fn has_transport_fields(&self) -> bool {
444 self.destination.is_some()
445 || !self.via.is_empty()
446 || !self.options.is_empty()
447 || self.local_host.is_some()
448 || self.local_port.is_some()
449 || self.remote_socket.is_some()
450 || self.sudo_user.is_some()
451 }
452
453 pub fn has_tunnel_or_bridge_options(&self) -> bool {
454 self.local_host.is_some()
455 || self.local_port.is_some()
456 || !self.via.is_empty()
457 || self.remote_socket.is_some()
458 || self.sudo_user.is_some()
459 }
460}
461
462#[derive(Debug, Serialize, Deserialize, Clone, Default)]
463pub struct ContainerConfig {
464 #[serde(rename = "container", skip_serializing_if = "Option::is_none")]
465 pub target: Option<String>,
466 #[serde(rename = "container_driver", skip_serializing_if = "Option::is_none")]
467 pub driver: Option<String>,
468 #[serde(rename = "container_runtime", skip_serializing_if = "Option::is_none")]
469 pub runtime: Option<String>,
470 #[serde(rename = "container_user", skip_serializing_if = "Option::is_none")]
471 pub user: Option<String>,
472 #[serde(
473 rename = "container_namespace",
474 skip_serializing_if = "Option::is_none"
475 )]
476 pub namespace: Option<String>,
477 #[serde(rename = "container_context", skip_serializing_if = "Option::is_none")]
478 pub context: Option<String>,
479 #[serde(
480 rename = "container_compose_files",
481 default,
482 skip_serializing_if = "Vec::is_empty"
483 )]
484 pub compose_files: Vec<String>,
485 #[serde(
486 rename = "container_compose_project",
487 skip_serializing_if = "Option::is_none"
488 )]
489 pub compose_project: Option<String>,
490 #[serde(
491 rename = "container_pod_container",
492 skip_serializing_if = "Option::is_none"
493 )]
494 pub pod_container: Option<String>,
495}
496
497impl ContainerConfig {
498 pub fn has_transport_fields(&self) -> bool {
499 self.target.is_some()
500 || self.driver.is_some()
501 || self.runtime.is_some()
502 || self.user.is_some()
503 || self.namespace.is_some()
504 || self.context.is_some()
505 || !self.compose_files.is_empty()
506 || self.compose_project.is_some()
507 || self.pod_container.is_some()
508 }
509}
510
511#[derive(Debug, Deserialize, Default)]
512#[serde(deny_unknown_fields)]
513struct SessionConfigFlat {
514 #[serde(default)]
515 dsn_secret: Option<String>,
516 #[serde(default)]
517 conninfo_secret: Option<String>,
518 #[serde(default)]
519 host: Option<String>,
520 #[serde(default)]
521 port: Option<u16>,
522 #[serde(default)]
523 user: Option<String>,
524 #[serde(default)]
525 dbname: Option<String>,
526 #[serde(default)]
527 password_secret: Option<String>,
528 #[serde(default)]
529 ssh: Option<String>,
530 #[serde(default)]
531 ssh_via: Vec<String>,
532 #[serde(default)]
533 ssh_options: Vec<String>,
534 #[serde(default)]
535 ssh_local_host: Option<String>,
536 #[serde(default)]
537 ssh_local_port: Option<u16>,
538 #[serde(default)]
539 ssh_remote_socket: Option<String>,
540 #[serde(default)]
541 ssh_sudo_user: Option<String>,
542 #[serde(default)]
543 container: Option<String>,
544 #[serde(default)]
545 container_driver: Option<String>,
546 #[serde(default)]
547 container_runtime: Option<String>,
548 #[serde(default)]
549 container_user: Option<String>,
550 #[serde(default)]
551 container_namespace: Option<String>,
552 #[serde(default)]
553 container_context: Option<String>,
554 #[serde(default)]
555 container_compose_files: Vec<String>,
556 #[serde(default)]
557 container_compose_project: Option<String>,
558 #[serde(default)]
559 container_pod_container: Option<String>,
560}
561
562impl From<SessionConfigFlat> for SessionConfig {
563 fn from(flat: SessionConfigFlat) -> Self {
564 Self {
565 profile_pinned: false,
566 dsn_secret: flat.dsn_secret,
567 conninfo_secret: flat.conninfo_secret,
568 host: flat.host,
569 port: flat.port,
570 user: flat.user,
571 dbname: flat.dbname,
572 password_secret: flat.password_secret,
573 ssh: SshConfig {
574 destination: flat.ssh,
575 via: flat.ssh_via,
576 options: flat.ssh_options,
577 local_host: flat.ssh_local_host,
578 local_port: flat.ssh_local_port,
579 remote_socket: flat.ssh_remote_socket,
580 sudo_user: flat.ssh_sudo_user,
581 },
582 container: ContainerConfig {
583 target: flat.container,
584 driver: flat.container_driver,
585 runtime: flat.container_runtime,
586 user: flat.container_user,
587 namespace: flat.container_namespace,
588 context: flat.container_context,
589 compose_files: flat.container_compose_files,
590 compose_project: flat.container_compose_project,
591 pod_container: flat.container_pod_container,
592 },
593 }
594 }
595}
596
597impl<'de> Deserialize<'de> for SessionConfig {
598 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
599 where
600 D: serde::Deserializer<'de>,
601 {
602 SessionConfigFlat::deserialize(deserializer).map(Self::from)
603 }
604}
605
606impl SessionConfig {
607 pub fn uses_ssh_transport(&self) -> bool {
608 self.ssh.has_transport_fields()
609 }
610
611 pub fn uses_container_transport(&self) -> bool {
612 self.container.has_transport_fields()
613 }
614
615 pub fn transport_kind(&self) -> Result<TransportKind, String> {
616 let uses_ssh = self.uses_ssh_transport();
617 let uses_container = self.uses_container_transport();
618 match (uses_ssh, uses_container) {
619 (false, false) => Ok(TransportKind::Direct),
620 (true, false) => Ok(TransportKind::Ssh),
621 (false, true) => Ok(TransportKind::Container),
622 (true, true) => Ok(TransportKind::Container),
625 }
626 }
627}
628
629#[derive(Debug, Clone, Copy, PartialEq, Eq)]
630pub enum TransportKind {
631 Direct,
632 Ssh,
633 Container,
634}
635
636impl TransportKind {
637 pub fn as_str(self) -> &'static str {
638 match self {
639 Self::Direct => "direct",
640 Self::Ssh => "ssh",
641 Self::Container => "container",
642 }
643 }
644}
645
646#[derive(Debug, Serialize, Deserialize, Clone)]
647pub struct RuntimeConfig {
648 pub default_session: String,
649 #[serde(default)]
650 pub sessions: HashMap<String, SessionConfig>,
651 pub inline_max_rows: usize,
652 pub inline_max_bytes: usize,
653 pub statement_timeout_ms: u64,
654 pub lock_timeout_ms: u64,
655 #[serde(default, with = "log_filters_serde")]
656 pub log: LogFilters,
657}
658
659impl Default for RuntimeConfig {
660 fn default() -> Self {
661 let mut sessions = HashMap::new();
662 sessions.insert("default".to_string(), SessionConfig::default());
663 Self {
664 default_session: "default".to_string(),
665 sessions,
666 inline_max_rows: 1000,
667 inline_max_bytes: 1_048_576,
668 statement_timeout_ms: 30_000,
669 lock_timeout_ms: 5_000,
670 log: LogFilters::default(),
671 }
672 }
673}
674
675#[derive(Debug, Deserialize, Default)]
676#[serde(deny_unknown_fields)]
677pub struct ConfigPatch {
678 pub default_session: Option<String>,
679 pub sessions: Option<HashMap<String, SessionConfigPatch>>,
680 pub inline_max_rows: Option<usize>,
681 pub inline_max_bytes: Option<usize>,
682 pub statement_timeout_ms: Option<u64>,
683 pub lock_timeout_ms: Option<u64>,
684 pub log: Option<Vec<String>>,
685}
686
687#[derive(Debug, Default)]
688pub enum PatchField<T> {
689 #[default]
690 Missing,
691 Null,
692 Value(T),
693}
694
695impl<'de, T> Deserialize<'de> for PatchField<T>
696where
697 T: Deserialize<'de>,
698{
699 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
700 where
701 D: serde::Deserializer<'de>,
702 {
703 let value = Option::<T>::deserialize(deserializer)?;
704 match value {
705 Some(value) => Ok(Self::Value(value)),
706 None => Ok(Self::Null),
707 }
708 }
709}
710
711impl<T> PatchField<T> {
712 pub fn into_update(self) -> Option<Option<T>> {
713 match self {
714 Self::Missing => None,
715 Self::Null => Some(None),
716 Self::Value(value) => Some(Some(value)),
717 }
718 }
719}
720
721#[derive(Debug, Default)]
722pub struct SessionConfigPatch {
723 pub dsn_secret: PatchField<String>,
724 pub conninfo_secret: PatchField<String>,
725 pub host: PatchField<String>,
726 pub port: PatchField<u16>,
727 pub user: PatchField<String>,
728 pub dbname: PatchField<String>,
729 pub password_secret: PatchField<String>,
730 pub ssh: SshConfigPatch,
731 pub container: ContainerConfigPatch,
732}
733
734#[derive(Debug, Default)]
735pub struct SshConfigPatch {
736 pub destination: PatchField<String>,
737 pub via: PatchField<Vec<String>>,
738 pub options: PatchField<Vec<String>>,
739 pub local_host: PatchField<String>,
740 pub local_port: PatchField<u16>,
741 pub remote_socket: PatchField<String>,
742 pub sudo_user: PatchField<String>,
743}
744
745#[derive(Debug, Default)]
746pub struct ContainerConfigPatch {
747 pub target: PatchField<String>,
748 pub driver: PatchField<String>,
749 pub runtime: PatchField<String>,
750 pub user: PatchField<String>,
751 pub namespace: PatchField<String>,
752 pub context: PatchField<String>,
753 pub compose_files: PatchField<Vec<String>>,
754 pub compose_project: PatchField<String>,
755 pub pod_container: PatchField<String>,
756}
757
758#[derive(Debug, Deserialize, Default)]
759#[serde(deny_unknown_fields)]
760struct SessionConfigPatchFlat {
761 #[serde(default)]
762 dsn_secret: PatchField<String>,
763 #[serde(default)]
764 conninfo_secret: PatchField<String>,
765 #[serde(default)]
766 host: PatchField<String>,
767 #[serde(default)]
768 port: PatchField<u16>,
769 #[serde(default)]
770 user: PatchField<String>,
771 #[serde(default)]
772 dbname: PatchField<String>,
773 #[serde(default)]
774 password_secret: PatchField<String>,
775 #[serde(default)]
776 ssh: PatchField<String>,
777 #[serde(default)]
778 ssh_via: PatchField<Vec<String>>,
779 #[serde(default)]
780 ssh_options: PatchField<Vec<String>>,
781 #[serde(default)]
782 ssh_local_host: PatchField<String>,
783 #[serde(default)]
784 ssh_local_port: PatchField<u16>,
785 #[serde(default)]
786 ssh_remote_socket: PatchField<String>,
787 #[serde(default)]
788 ssh_sudo_user: PatchField<String>,
789 #[serde(default)]
790 container: PatchField<String>,
791 #[serde(default)]
792 container_driver: PatchField<String>,
793 #[serde(default)]
794 container_runtime: PatchField<String>,
795 #[serde(default)]
796 container_user: PatchField<String>,
797 #[serde(default)]
798 container_namespace: PatchField<String>,
799 #[serde(default)]
800 container_context: PatchField<String>,
801 #[serde(default)]
802 container_compose_files: PatchField<Vec<String>>,
803 #[serde(default)]
804 container_compose_project: PatchField<String>,
805 #[serde(default)]
806 container_pod_container: PatchField<String>,
807}
808
809impl From<SessionConfigPatchFlat> for SessionConfigPatch {
810 fn from(flat: SessionConfigPatchFlat) -> Self {
811 Self {
812 dsn_secret: flat.dsn_secret,
813 conninfo_secret: flat.conninfo_secret,
814 host: flat.host,
815 port: flat.port,
816 user: flat.user,
817 dbname: flat.dbname,
818 password_secret: flat.password_secret,
819 ssh: SshConfigPatch {
820 destination: flat.ssh,
821 via: flat.ssh_via,
822 options: flat.ssh_options,
823 local_host: flat.ssh_local_host,
824 local_port: flat.ssh_local_port,
825 remote_socket: flat.ssh_remote_socket,
826 sudo_user: flat.ssh_sudo_user,
827 },
828 container: ContainerConfigPatch {
829 target: flat.container,
830 driver: flat.container_driver,
831 runtime: flat.container_runtime,
832 user: flat.container_user,
833 namespace: flat.container_namespace,
834 context: flat.container_context,
835 compose_files: flat.container_compose_files,
836 compose_project: flat.container_compose_project,
837 pod_container: flat.container_pod_container,
838 },
839 }
840 }
841}
842
843impl<'de> Deserialize<'de> for SessionConfigPatch {
844 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
845 where
846 D: serde::Deserializer<'de>,
847 {
848 SessionConfigPatchFlat::deserialize(deserializer).map(Self::from)
849 }
850}
851
852#[derive(Debug, Clone)]
853#[allow(dead_code)]
854pub struct ResolvedOptions {
855 pub stream_rows: bool,
856 pub batch_rows: usize,
857 pub batch_bytes: usize,
858 pub statement_timeout_ms: u64,
859 pub lock_timeout_ms: u64,
860 pub read_only: bool,
861 pub inline_max_rows: usize,
862 pub inline_max_bytes: usize,
863}
864
865#[cfg(test)]
866#[path = "../tests/support/unit_types.rs"]
867mod tests;