1pub mod config;
6pub mod metrics;
7mod setup;
8pub mod sink;
9pub mod source;
10
11pub use metrics::{NatsSinkMetrics, NatsSourceMetrics};
12pub use sink::NatsSink;
13pub use source::NatsSource;
14
15use std::sync::Arc;
16
17use arrow_schema::Schema;
18
19use crate::config::{ConfigKeySpec, ConnectorInfo};
20use crate::registry::ConnectorRegistry;
21
22pub fn register_nats_source(
28 registry: &ConnectorRegistry,
29) -> Result<(), crate::error::ConnectorError> {
30 let info = ConnectorInfo {
31 name: "nats".to_string(),
32 display_name: "NATS Source".to_string(),
33 version: env!("CARGO_PKG_VERSION").to_string(),
34 is_source: true,
35 is_sink: false,
36 config_keys: source_config_keys(),
37 };
38 registry.register_source(
39 "nats",
40 info,
41 Arc::new(|registry| {
42 Ok(Box::new(NatsSource::new(
43 Arc::new(Schema::empty()),
44 registry.map(Arc::as_ref),
45 )))
46 }),
47 )
48}
49
50pub fn register_nats_sink(
56 registry: &ConnectorRegistry,
57) -> Result<(), crate::error::ConnectorError> {
58 let info = ConnectorInfo {
59 name: "nats".to_string(),
60 display_name: "NATS Sink".to_string(),
61 version: env!("CARGO_PKG_VERSION").to_string(),
62 is_source: false,
63 is_sink: true,
64 config_keys: sink_config_keys(),
65 };
66 registry.register_sink(
67 "nats",
68 info,
69 Arc::new(|_config, registry| {
70 Ok(Box::new(NatsSink::new(
71 Arc::new(Schema::empty()),
72 registry.map(Arc::as_ref),
73 )))
74 }),
75 )
76}
77
78fn auth_and_tls_keys() -> Vec<ConfigKeySpec> {
79 use ConfigKeySpec as K;
80 vec![
81 K::optional("auth.mode", "none | user_pass | token | creds_file", "none"),
82 K::optional("user", "Username (auth.mode=user_pass)", ""),
83 K::optional("password", "Password (auth.mode=user_pass)", ""),
84 K::optional("token", "Bearer token (auth.mode=token)", ""),
85 K::optional(
86 "creds.file",
87 "Path to a NATS credentials file (auth.mode=creds_file)",
88 "",
89 ),
90 K::optional("tls.enabled", "Require TLS on the connection", "false"),
91 K::optional(
92 "tls.ca.location",
93 "PEM CA certificate for server verification",
94 "",
95 ),
96 K::optional(
97 "tls.cert.location",
98 "Client certificate for mutual TLS (pairs with tls.key.location)",
99 "",
100 ),
101 K::optional("tls.key.location", "Client private key for mutual TLS", ""),
102 ]
103}
104
105fn source_config_keys() -> Vec<ConfigKeySpec> {
106 use ConfigKeySpec as K;
107 let mut keys = vec![
108 K::required("servers", "NATS server URLs, comma-separated"),
109 K::optional("mode", "core | jetstream", "jetstream"),
110 K::optional(
112 "stream",
113 "JetStream stream name (required in jetstream mode)",
114 "",
115 ),
116 K::optional(
117 "consumer",
118 "Durable consumer name (required in jetstream mode)",
119 "",
120 ),
121 K::optional("subject", "Single subject or wildcard (e.g., orders.>)", ""),
122 K::optional(
123 "subject.filters",
124 "Comma-separated filter subjects (JS 2.10+)",
125 "",
126 ),
127 K::optional(
128 "deliver.policy",
129 "all | new | by_start_sequence | by_start_time",
130 "all",
131 ),
132 K::optional(
133 "start.sequence",
134 "Stream sequence for by_start_sequence",
135 "",
136 ),
137 K::optional("start.time", "RFC3339 timestamp for by_start_time", ""),
138 K::optional("ack.policy", "explicit | none", "explicit"),
139 K::optional(
140 "ack.wait.ms",
141 "Per-message ack wait in milliseconds",
142 "60000",
143 ),
144 K::optional(
145 "max.deliver",
146 "Max delivery attempts before poison action",
147 "5",
148 ),
149 K::optional(
150 "max.ack.pending",
151 "Max unacked messages (server-side flow control)",
152 "10000",
153 ),
154 K::optional("fetch.batch", "Messages per pull fetch", "500"),
155 K::optional("fetch.max.wait.ms", "Max wait per fetch", "500"),
156 K::optional(
157 "lag.poll.interval.ms",
158 "Interval between consumer.info() polls for the lag gauge (0 disables)",
159 "10000",
160 ),
161 K::optional(
162 "queue.group",
163 "Queue group for load balancing (core mode only)",
164 "",
165 ),
166 K::optional("format", "json | csv | raw", "json"),
167 ];
168 keys.extend(auth_and_tls_keys());
169 keys
170}
171
172fn sink_config_keys() -> Vec<ConfigKeySpec> {
173 use ConfigKeySpec as K;
174 let mut keys = vec![
175 K::required("servers", "NATS server URLs, comma-separated"),
176 K::optional("mode", "core | jetstream", "jetstream"),
177 K::optional(
178 "stream",
179 "Required JetStream target; validated and sent as Nats-Expected-Stream",
180 "",
181 ),
182 K::optional("subject", "Literal subject for every row", ""),
183 K::optional(
184 "subject.column",
185 "Column name whose value is the subject",
186 "",
187 ),
188 K::optional(
189 "dedup.id.column",
190 "Unique row column used as Nats-Msg-Id for bounded broker deduplication",
191 "",
192 ),
193 K::optional(
194 "min.duplicate.window.ms",
195 "Minimum stream duplicate_window accepted when deduplication is enabled",
196 "120000",
197 ),
198 K::optional("max.pending", "Max outstanding PubAck futures", "4096"),
199 K::optional("ack.timeout.ms", "Per-publish ack timeout", "30000"),
200 K::optional("format", "json | csv | raw", "json"),
201 K::optional(
202 "header.columns",
203 "Comma-separated columns projected to NATS headers",
204 "",
205 ),
206 ];
207 keys.extend(auth_and_tls_keys());
208 keys
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn register_source_appears_in_registry() {
217 let registry = ConnectorRegistry::new();
218 register_nats_source(®istry).unwrap();
219 assert!(registry.list_sources().contains(&"nats".to_string()));
220 }
221
222 #[test]
223 fn register_sink_appears_in_registry() {
224 let registry = ConnectorRegistry::new();
225 register_nats_sink(®istry).unwrap();
226 assert!(registry.list_sinks().contains(&"nats".to_string()));
227 }
228}