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