mockforge_tracing/
exporter.rs1use std::time::Duration;
4use thiserror::Error;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum ExporterType {
9 Jaeger,
10 Otlp,
11}
12
13#[derive(Debug, Clone)]
15pub struct JaegerExporter {
16 pub endpoint: String,
18 pub max_batch_size: usize,
20 pub max_queue_size: usize,
22 pub batch_timeout: Duration,
24}
25
26impl Default for JaegerExporter {
27 fn default() -> Self {
28 Self {
29 endpoint: "http://localhost:14268/api/traces".to_string(),
30 max_batch_size: 512,
31 max_queue_size: 2048,
32 batch_timeout: Duration::from_secs(5),
33 }
34 }
35}
36
37impl JaegerExporter {
38 pub fn new(endpoint: String) -> Self {
40 Self {
41 endpoint,
42 ..Default::default()
43 }
44 }
45
46 pub fn with_max_batch_size(mut self, size: usize) -> Self {
48 self.max_batch_size = size;
49 self
50 }
51
52 pub fn with_max_queue_size(mut self, size: usize) -> Self {
54 self.max_queue_size = size;
55 self
56 }
57
58 pub fn with_batch_timeout(mut self, timeout: Duration) -> Self {
60 self.batch_timeout = timeout;
61 self
62 }
63
64 pub fn validate(&self) -> Result<(), ExporterError> {
66 if self.endpoint.is_empty() {
67 return Err(ExporterError::InvalidEndpoint("Endpoint cannot be empty".to_string()));
68 }
69
70 if self.max_batch_size == 0 {
71 return Err(ExporterError::InvalidConfig(
72 "Max batch size must be greater than 0".to_string(),
73 ));
74 }
75
76 if self.max_queue_size < self.max_batch_size {
77 return Err(ExporterError::InvalidConfig(
78 "Max queue size must be >= max batch size".to_string(),
79 ));
80 }
81
82 Ok(())
83 }
84}
85
86#[derive(Debug, Clone)]
88pub struct OtlpExporter {
89 pub endpoint: String,
91 pub protocol: OtlpProtocol,
93 pub headers: Vec<(String, String)>,
95 pub timeout: Duration,
97 pub compression: Option<OtlpCompression>,
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum OtlpProtocol {
104 Grpc,
105 HttpProtobuf,
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub enum OtlpCompression {
111 Gzip,
112}
113
114impl Default for OtlpExporter {
115 fn default() -> Self {
116 Self {
117 endpoint: "http://localhost:4317".to_string(),
118 protocol: OtlpProtocol::Grpc,
119 headers: Vec::new(),
120 timeout: Duration::from_secs(10),
121 compression: None,
122 }
123 }
124}
125
126impl OtlpExporter {
127 pub fn new(endpoint: String) -> Self {
129 Self {
130 endpoint,
131 ..Default::default()
132 }
133 }
134
135 pub fn with_protocol(mut self, protocol: OtlpProtocol) -> Self {
137 self.protocol = protocol;
138 self
139 }
140
141 pub fn with_header(mut self, key: String, value: String) -> Self {
143 self.headers.push((key, value));
144 self
145 }
146
147 pub fn with_timeout(mut self, timeout: Duration) -> Self {
149 self.timeout = timeout;
150 self
151 }
152
153 pub fn with_compression(mut self, compression: OtlpCompression) -> Self {
155 self.compression = Some(compression);
156 self
157 }
158
159 pub fn validate(&self) -> Result<(), ExporterError> {
161 if self.endpoint.is_empty() {
162 return Err(ExporterError::InvalidEndpoint("Endpoint cannot be empty".to_string()));
163 }
164
165 if !self.endpoint.starts_with("http://") && !self.endpoint.starts_with("https://") {
167 return Err(ExporterError::InvalidEndpoint(
168 "Endpoint must start with http:// or https://".to_string(),
169 ));
170 }
171
172 Ok(())
173 }
174}
175
176#[derive(Error, Debug)]
178pub enum ExporterError {
179 #[error("Invalid endpoint: {0}")]
180 InvalidEndpoint(String),
181
182 #[error("Invalid configuration: {0}")]
183 InvalidConfig(String),
184
185 #[error("Export failed: {0}")]
186 ExportFailed(String),
187}
188
189pub type JaegerExporterError = ExporterError;
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn test_jaeger_default_config() {
198 let exporter = JaegerExporter::default();
199 assert_eq!(exporter.endpoint, "http://localhost:14268/api/traces");
200 assert_eq!(exporter.max_batch_size, 512);
201 assert_eq!(exporter.max_queue_size, 2048);
202 assert!(exporter.validate().is_ok());
203 }
204
205 #[test]
206 fn test_jaeger_custom_config() {
207 let exporter = JaegerExporter::new("http://custom:14268/api/traces".to_string())
208 .with_max_batch_size(256)
209 .with_max_queue_size(1024)
210 .with_batch_timeout(Duration::from_secs(10));
211
212 assert_eq!(exporter.endpoint, "http://custom:14268/api/traces");
213 assert_eq!(exporter.max_batch_size, 256);
214 assert_eq!(exporter.max_queue_size, 1024);
215 assert_eq!(exporter.batch_timeout, Duration::from_secs(10));
216 assert!(exporter.validate().is_ok());
217 }
218
219 #[test]
220 fn test_jaeger_invalid_config() {
221 let exporter = JaegerExporter::new("http://localhost:14268".to_string())
222 .with_max_batch_size(1024)
223 .with_max_queue_size(512); assert!(exporter.validate().is_err());
226 }
227
228 #[test]
229 fn test_jaeger_empty_endpoint() {
230 let exporter = JaegerExporter::new("".to_string());
231 assert!(exporter.validate().is_err());
232 }
233
234 #[test]
235 fn test_otlp_default_config() {
236 let exporter = OtlpExporter::default();
237 assert_eq!(exporter.endpoint, "http://localhost:4317");
238 assert_eq!(exporter.protocol, OtlpProtocol::Grpc);
239 assert!(exporter.headers.is_empty());
240 assert_eq!(exporter.timeout, Duration::from_secs(10));
241 assert!(exporter.compression.is_none());
242 assert!(exporter.validate().is_ok());
243 }
244
245 #[test]
246 fn test_otlp_custom_config() {
247 let exporter = OtlpExporter::new("https://otel-collector:4317".to_string())
248 .with_protocol(OtlpProtocol::HttpProtobuf)
249 .with_header("Authorization".to_string(), "Bearer token123".to_string())
250 .with_timeout(Duration::from_secs(30))
251 .with_compression(OtlpCompression::Gzip);
252
253 assert_eq!(exporter.endpoint, "https://otel-collector:4317");
254 assert_eq!(exporter.protocol, OtlpProtocol::HttpProtobuf);
255 assert_eq!(exporter.headers.len(), 1);
256 assert_eq!(exporter.timeout, Duration::from_secs(30));
257 assert_eq!(exporter.compression, Some(OtlpCompression::Gzip));
258 assert!(exporter.validate().is_ok());
259 }
260
261 #[test]
262 fn test_otlp_empty_endpoint() {
263 let exporter = OtlpExporter::new("".to_string());
264 assert!(exporter.validate().is_err());
265 }
266
267 #[test]
268 fn test_otlp_invalid_endpoint_protocol() {
269 let exporter = OtlpExporter::new("ftp://localhost:4317".to_string());
270 assert!(exporter.validate().is_err());
271 }
272
273 #[test]
274 fn test_otlp_multiple_headers() {
275 let exporter = OtlpExporter::new("http://localhost:4317".to_string())
276 .with_header("X-API-Key".to_string(), "key123".to_string())
277 .with_header("X-Tenant-ID".to_string(), "tenant1".to_string());
278
279 assert_eq!(exporter.headers.len(), 2);
280 }
281}