Skip to main content

libdd_telemetry/data/
payload.rs

1// Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/
2// SPDX-License-Identifier: Apache-2.0
3
4use 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}