Skip to main content

drasi_bootstrap_postgres/
descriptor.rs

1// Copyright 2025 The Drasi Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Plugin descriptor for the PostgreSQL bootstrap provider.
16
17use drasi_lib::bootstrap::BootstrapProvider;
18use drasi_plugin_sdk::prelude::*;
19use utoipa::OpenApi;
20
21use crate::config::{SslMode, TableKeyConfig};
22use crate::PostgresBootstrapConfig;
23use crate::PostgresBootstrapProvider;
24
25// ── DTO types ────────────────────────────────────────────────────────────────
26
27/// SSL mode DTO (mirrors [`SslMode`]).
28#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, utoipa::ToSchema)]
29#[schema(as = bootstrap::postgres::SslMode)]
30#[serde(rename_all = "lowercase")]
31#[derive(Default)]
32pub enum SslModeDto {
33    Disable,
34    #[default]
35    Prefer,
36    Require,
37}
38
39/// Table key configuration DTO (mirrors [`TableKeyConfig`]).
40#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)]
41#[schema(as = bootstrap::postgres::TableKeyConfig)]
42#[serde(rename_all = "camelCase", deny_unknown_fields)]
43pub struct TableKeyConfigDto {
44    pub table: String,
45    pub key_columns: Vec<String>,
46}
47
48fn default_host() -> ConfigValue<String> {
49    ConfigValue::Static("localhost".to_string()) // DevSkim: ignore DS162092
50}
51
52fn default_port() -> ConfigValue<u16> {
53    ConfigValue::Static(5432)
54}
55
56fn default_password() -> ConfigValue<String> {
57    ConfigValue::Static(String::new())
58}
59
60fn default_slot_name() -> String {
61    "drasi_slot".to_string()
62}
63
64fn default_publication_name() -> String {
65    "drasi_publication".to_string()
66}
67
68fn default_ssl_mode() -> ConfigValue<SslModeDto> {
69    ConfigValue::Static(SslModeDto::default())
70}
71
72/// Configuration DTO for the PostgreSQL bootstrap provider.
73#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)]
74#[schema(as = bootstrap::postgres::PostgresBootstrapConfig)]
75#[serde(rename_all = "camelCase", deny_unknown_fields)]
76pub struct PostgresBootstrapConfigDto {
77    #[serde(default = "default_host")]
78    #[schema(value_type = ConfigValueString)]
79    pub host: ConfigValue<String>,
80
81    #[serde(default = "default_port")]
82    #[schema(value_type = ConfigValueU16)]
83    pub port: ConfigValue<u16>,
84
85    #[schema(value_type = ConfigValueString)]
86    pub database: ConfigValue<String>,
87
88    #[schema(value_type = ConfigValueString)]
89    pub user: ConfigValue<String>,
90
91    #[serde(default = "default_password")]
92    #[schema(value_type = ConfigValueString)]
93    pub password: ConfigValue<String>,
94
95    #[serde(default)]
96    pub tables: Vec<String>,
97
98    #[serde(default = "default_slot_name")]
99    pub slot_name: String,
100
101    #[serde(default = "default_publication_name")]
102    pub publication_name: String,
103
104    #[serde(default = "default_ssl_mode")]
105    #[schema(value_type = ConfigValue<bootstrap::postgres::SslMode>)]
106    pub ssl_mode: ConfigValue<SslModeDto>,
107
108    #[serde(default)]
109    #[schema(value_type = Vec<bootstrap::postgres::TableKeyConfig>)]
110    pub table_keys: Vec<TableKeyConfigDto>,
111}
112
113// ── Descriptor ───────────────────────────────────────────────────────────────
114
115#[derive(OpenApi)]
116#[openapi(components(schemas(PostgresBootstrapConfigDto, SslModeDto, TableKeyConfigDto,)))]
117struct PostgresBootstrapSchemas;
118
119/// Plugin descriptor for the PostgreSQL bootstrap provider.
120pub struct PostgresBootstrapDescriptor;
121
122#[async_trait]
123impl BootstrapPluginDescriptor for PostgresBootstrapDescriptor {
124    fn kind(&self) -> &str {
125        "postgres"
126    }
127
128    fn config_version(&self) -> &str {
129        "1.0.0"
130    }
131
132    fn config_schema_name(&self) -> &str {
133        "bootstrap.postgres.PostgresBootstrapConfig"
134    }
135
136    fn config_schema_json(&self) -> String {
137        let api = PostgresBootstrapSchemas::openapi();
138        serde_json::to_string(
139            &api.components
140                .as_ref()
141                .expect("OpenAPI components missing")
142                .schemas,
143        )
144        .expect("Failed to serialize config schema")
145    }
146
147    async fn create_bootstrap_provider(
148        &self,
149        config_json: &serde_json::Value,
150        _source_config_json: &serde_json::Value,
151    ) -> anyhow::Result<Box<dyn BootstrapProvider>> {
152        let dto: PostgresBootstrapConfigDto = serde_json::from_value(config_json.clone())?;
153        let mapper = DtoMapper::new();
154
155        let ssl_mode = match dto.ssl_mode {
156            ConfigValue::Static(v) => match v {
157                SslModeDto::Disable => SslMode::Disable,
158                SslModeDto::Prefer => SslMode::Prefer,
159                SslModeDto::Require => SslMode::Require,
160            },
161            _ => SslMode::default(),
162        };
163
164        let table_keys = dto
165            .table_keys
166            .into_iter()
167            .map(|tk| TableKeyConfig {
168                table: tk.table,
169                key_columns: tk.key_columns,
170            })
171            .collect();
172
173        let config = PostgresBootstrapConfig {
174            host: mapper.resolve_string(&dto.host)?,
175            port: mapper.resolve_typed(&dto.port)?,
176            database: mapper.resolve_string(&dto.database)?,
177            user: mapper.resolve_string(&dto.user)?,
178            password: mapper.resolve_string(&dto.password)?,
179            tables: dto.tables,
180            slot_name: dto.slot_name,
181            publication_name: dto.publication_name,
182            ssl_mode,
183            table_keys,
184        };
185
186        config.validate()?;
187        Ok(Box::new(PostgresBootstrapProvider::new(config)))
188    }
189}