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        });
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}