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