1use crate::data::*;
5use serde::Serialize;
6
7#[derive(Serialize, Debug)]
8#[serde(tag = "request_type", content = "payload")]
9#[serde(rename_all = "kebab-case")]
10pub enum Payload {
11 AppStarted(AppStarted),
12 AppDependenciesLoaded(AppDependenciesLoaded),
13 AppIntegrationsChange(AppIntegrationsChange),
14 AppClientConfigurationChange(AppClientConfigurationChange),
15 AppEndpoints(AppEndpoints),
16 AppHeartbeat(#[serde(skip_serializing)] ()),
17 AppClosing(#[serde(skip_serializing)] ()),
18 GenerateMetrics(GenerateMetrics),
19 Sketches(Distributions),
20 Logs(Logs),
21 MessageBatch(Vec<Payload>),
22 AppExtendedHeartbeat(AppStarted),
23}
24
25impl Payload {
26 pub fn request_type(&self) -> &'static str {
27 use Payload::*;
28 match self {
29 AppStarted(_) => "app-started",
30 AppDependenciesLoaded(_) => "app-dependencies-loaded",
31 AppIntegrationsChange(_) => "app-integrations-change",
32 AppClientConfigurationChange(_) => "app-client-configuration-change",
33 AppEndpoints(_) => "app-endpoints",
34 AppHeartbeat(_) => "app-heartbeat",
35 AppClosing(_) => "app-closing",
36 GenerateMetrics(_) => "generate-metrics",
37 Sketches(_) => "sketches",
38 Logs(_) => "logs",
39 MessageBatch(_) => "message-batch",
40 AppExtendedHeartbeat(_) => "app-extended-heartbeat",
41 }
42 }
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48 use serde_json::json;
49
50 #[test]
51 fn test_app_started_serialization() {
52 let payload = Payload::AppStarted(AppStarted {
53 configuration: vec![
54 Configuration {
55 name: "sampling_rate".to_string(),
56 value: "0.5".to_string(),
57 origin: ConfigurationOrigin::EnvVar,
58 config_id: Some("config-123".to_string()),
59 seq_id: Some(42),
60 },
61 Configuration {
62 name: "log_level".to_string(),
63 value: "debug".to_string(),
64 origin: ConfigurationOrigin::Code,
65 config_id: None,
66 seq_id: None,
67 },
68 ],
69 dependencies: Vec::new(),
70 integrations: Vec::new(),
71 });
72
73 let serialized = serde_json::to_value(&payload).unwrap();
74
75 let expected = json!({
76 "request_type": "app-started",
77 "payload": {
78 "configuration": [
79 {
80 "name": "sampling_rate",
81 "value": "0.5",
82 "origin": "env_var",
83 "config_id": "config-123",
84 "seq_id": 42
85 },
86 {
87 "name": "log_level",
88 "value": "debug",
89 "origin": "code",
90 "config_id": null,
91 "seq_id": null
92 }
93 ],
94 "dependencies": [],
95 "integrations": []
96 }
97 });
98
99 assert_eq!(serialized, expected);
100 }
101
102 #[test]
103 fn test_app_dependencies_loaded_serialization() {
104 let payload = Payload::AppDependenciesLoaded(AppDependenciesLoaded {
105 dependencies: vec![
106 Dependency {
107 name: "tokio".to_string(),
108 version: Some("1.32.0".to_string()),
109 },
110 Dependency {
111 name: "serde".to_string(),
112 version: None,
113 },
114 ],
115 });
116
117 let serialized = serde_json::to_value(&payload).unwrap();
118
119 let expected = json!({
120 "request_type": "app-dependencies-loaded",
121 "payload": {
122 "dependencies": [
123 {
124 "name": "tokio",
125 "version": "1.32.0"
126 },
127 {
128 "name": "serde",
129 "version": null
130 }
131 ]
132 }
133 });
134
135 assert_eq!(serialized, expected);
136 }
137
138 #[test]
139 fn test_app_integrations_change_serialization() {
140 let payload = Payload::AppIntegrationsChange(AppIntegrationsChange {
141 integrations: vec![
142 Integration {
143 name: "postgres".to_string(),
144 enabled: true,
145 version: Some("0.19.0".to_string()),
146 compatible: Some(true),
147 auto_enabled: Some(false),
148 },
149 Integration {
150 name: "redis".to_string(),
151 enabled: false,
152 version: None,
153 compatible: None,
154 auto_enabled: None,
155 },
156 ],
157 });
158
159 let serialized = serde_json::to_value(&payload).unwrap();
160
161 let expected = json!({
162 "request_type": "app-integrations-change",
163 "payload": {
164 "integrations": [
165 {
166 "name": "postgres",
167 "enabled": true,
168 "version": "0.19.0",
169 "compatible": true,
170 "auto_enabled": false
171 },
172 {
173 "name": "redis",
174 "enabled": false,
175 "version": null,
176 "compatible": null,
177 "auto_enabled": null
178 }
179 ]
180 }
181 });
182
183 assert_eq!(serialized, expected);
184 }
185
186 #[test]
187 fn test_app_client_configuration_change_serialization() {
188 let payload = Payload::AppClientConfigurationChange(AppClientConfigurationChange {
189 configuration: vec![Configuration {
190 name: "timeout".to_string(),
191 value: "30s".to_string(),
192 origin: ConfigurationOrigin::RemoteConfig,
193 config_id: Some("remote-1".to_string()),
194 seq_id: Some(10),
195 }],
196 });
197
198 let serialized = serde_json::to_value(&payload).unwrap();
199
200 let expected = json!({
201 "request_type": "app-client-configuration-change",
202 "payload": {
203 "configuration": [
204 {
205 "name": "timeout",
206 "value": "30s",
207 "origin": "remote_config",
208 "config_id": "remote-1",
209 "seq_id": 10
210 }
211 ]
212 }
213 });
214
215 assert_eq!(serialized, expected);
216 }
217
218 #[test]
219 fn test_app_endpoints_serialization() {
220 let payload = Payload::AppEndpoints(AppEndpoints {
221 is_first: true,
222 endpoints: vec![
223 json!({
224 "method": "GET",
225 "path": "/api/users",
226 "operation_name": "get_users",
227 "resource_name": "users"
228 }),
229 json!({
230 "method": "POST",
231 "path": "/api/users",
232 "operation_name": "create_user",
233 "resource_name": "users"
234 }),
235 ],
236 });
237
238 let serialized = serde_json::to_value(&payload).unwrap();
239
240 let expected = json!({
241 "request_type": "app-endpoints",
242 "payload": {
243 "is_first": true,
244 "endpoints": [
245 {
246 "method": "GET",
247 "path": "/api/users",
248 "operation_name": "get_users",
249 "resource_name": "users"
250 },
251 {
252 "method": "POST",
253 "path": "/api/users",
254 "operation_name": "create_user",
255 "resource_name": "users"
256 }
257 ]
258 }
259 });
260
261 assert_eq!(serialized, expected);
262 }
263
264 #[test]
265 fn test_app_heartbeat_serialization() {
266 let payload = Payload::AppHeartbeat(());
267
268 let serialized = serde_json::to_value(&payload).unwrap();
269
270 let expected = json!({
271 "request_type": "app-heartbeat"
272 });
273
274 assert_eq!(serialized, expected);
275 }
276
277 #[test]
278 fn test_app_closing_serialization() {
279 let payload = Payload::AppClosing(());
280
281 let serialized = serde_json::to_value(&payload).unwrap();
282
283 let expected = json!({
284 "request_type": "app-closing"
285 });
286
287 assert_eq!(serialized, expected);
288 }
289
290 #[test]
291 fn test_generate_metrics_serialization() {
292 let payload = Payload::GenerateMetrics(GenerateMetrics {
293 series: vec![
294 metrics::Serie {
295 namespace: metrics::MetricNamespace::Tracers,
296 metric: "spans_created".to_string(),
297 points: vec![(1234567890, 42.0), (1234567900, 43.0)],
298 tags: vec![],
299 common: true,
300 _type: metrics::MetricType::Count,
301 interval: 10,
302 },
303 metrics::Serie {
304 namespace: metrics::MetricNamespace::Profilers,
305 metric: "cpu_time".to_string(),
306 points: vec![(1234567890, 0.75)],
307 tags: vec![],
308 common: false,
309 _type: metrics::MetricType::Gauge,
310 interval: 60,
311 },
312 ],
313 });
314
315 let serialized = serde_json::to_value(&payload).unwrap();
316
317 let expected = json!({
318 "request_type": "generate-metrics",
319 "payload": {
320 "series": [
321 {
322 "namespace": "tracers",
323 "metric": "spans_created",
324 "points": [[1234567890, 42.0], [1234567900, 43.0]],
325 "tags": [],
326 "common": true,
327 "type": "count",
328 "interval": 10
329 },
330 {
331 "namespace": "profilers",
332 "metric": "cpu_time",
333 "points": [[1234567890, 0.75]],
334 "tags": [],
335 "common": false,
336 "type": "gauge",
337 "interval": 60
338 }
339 ]
340 }
341 });
342
343 assert_eq!(serialized, expected);
344 }
345
346 #[test]
347 fn test_sketches_serialization() {
348 let payload = Payload::Sketches(Distributions {
349 series: vec![metrics::Distribution {
350 namespace: metrics::MetricNamespace::Tracers,
351 metric: "request_duration".to_string(),
352 tags: vec![],
353 sketch: metrics::SerializedSketch::B64 {
354 sketch_b64: "base64encodeddata".to_string(),
355 },
356 common: true,
357 interval: 10,
358 _type: metrics::MetricType::Distribution,
359 }],
360 });
361
362 let serialized = serde_json::to_value(&payload).unwrap();
363
364 let expected = json!({
365 "request_type": "sketches",
366 "payload": {
367 "series": [
368 {
369 "namespace": "tracers",
370 "metric": "request_duration",
371 "tags": [],
372 "sketch_b64": "base64encodeddata",
373 "common": true,
374 "interval": 10,
375 "type": "distribution"
376 }
377 ]
378 }
379 });
380
381 assert_eq!(serialized, expected);
382 }
383
384 #[test]
385 fn test_logs_serialization() {
386 let payload = Payload::Logs(Logs {
387 logs: vec![
388 Log {
389 message: "Connection error".to_string(),
390 level: LogLevel::Error,
391 count: 1,
392 stack_trace: Some("at main.rs:42".to_string()),
393 tags: "env:prod".to_string(),
394 is_sensitive: false,
395 is_crash: false,
396 },
397 Log {
398 message: "Deprecated function used".to_string(),
399 level: LogLevel::Warn,
400 count: 5,
401 stack_trace: None,
402 tags: String::new(),
403 is_sensitive: false,
404 is_crash: false,
405 },
406 ],
407 });
408
409 let serialized = serde_json::to_value(&payload).unwrap();
410
411 let expected = json!({
412 "request_type": "logs",
413 "payload": {
414 "logs": [
415 {
416 "message": "Connection error",
417 "level": "ERROR",
418 "count": 1,
419 "stack_trace": "at main.rs:42",
420 "tags": "env:prod",
421 "is_sensitive": false,
422 "is_crash": false
423 },
424 {
425 "message": "Deprecated function used",
426 "level": "WARN",
427 "count": 5,
428 "stack_trace": null,
429 "tags": "",
430 "is_sensitive": false,
431 "is_crash": false
432 }
433 ]
434 }
435 });
436
437 assert_eq!(serialized, expected);
438 }
439
440 #[test]
441 fn test_message_batch_serialization() {
442 let payload = Payload::MessageBatch(vec![
443 Payload::AppHeartbeat(()),
444 Payload::Logs(Logs {
445 logs: vec![Log {
446 message: "Test log".to_string(),
447 level: LogLevel::Debug,
448 count: 1,
449 stack_trace: None,
450 tags: String::new(),
451 is_sensitive: false,
452 is_crash: false,
453 }],
454 }),
455 ]);
456
457 let serialized = serde_json::to_value(&payload).unwrap();
458
459 let expected = json!({
460 "request_type": "message-batch",
461 "payload": [
462 {
463 "request_type": "app-heartbeat"
464 },
465 {
466 "request_type": "logs",
467 "payload": {
468 "logs": [
469 {
470 "message": "Test log",
471 "level": "DEBUG",
472 "count": 1,
473 "stack_trace": null,
474 "tags": "",
475 "is_sensitive": false,
476 "is_crash": false
477 }
478 ]
479 }
480 }
481 ]
482 });
483
484 assert_eq!(serialized, expected);
485 }
486
487 #[test]
488 fn test_app_extended_heartbeat_serialization() {
489 let payload = Payload::AppExtendedHeartbeat(AppStarted {
490 configuration: vec![Configuration {
491 name: "feature_flag".to_string(),
492 value: "enabled".to_string(),
493 origin: ConfigurationOrigin::Default,
494 config_id: None,
495 seq_id: None,
496 }],
497 dependencies: Vec::new(),
498 integrations: Vec::new(),
499 });
500
501 let serialized = serde_json::to_value(&payload).unwrap();
502
503 let expected = json!({
504 "request_type": "app-extended-heartbeat",
505 "payload": {
506 "configuration": [
507 {
508 "name": "feature_flag",
509 "value": "enabled",
510 "origin": "default",
511 "config_id": null,
512 "seq_id": null
513 }
514 ],
515 "dependencies": [],
516 "integrations": []
517 }
518 });
519
520 assert_eq!(serialized, expected);
521 }
522}