1use std::{
2 borrow::Cow,
3 collections::BTreeMap,
4 convert::TryFrom,
5 marker::PhantomData,
6 num::{NonZero, TryFromIntError},
7};
8
9use lookup::{OwnedTargetPath, OwnedValuePath, PathPrefix, lookup_v2::OwnedSegment};
10use snafu::Snafu;
11use vrl::{
12 compiler::{ProgramInfo, SecretTarget, Target, value::VrlValueConvert},
13 prelude::Collection,
14 value::{Kind, ObjectMap, Value},
15};
16
17use super::{
18 Event, EventMetadata, LogEvent, Metric, MetricKind, TraceEvent,
19 metric::{MetricTags, TagValue, TagValueSet},
20};
21use crate::{
22 config::{LogNamespace, log_schema},
23 schema::Definition,
24};
25
26const VALID_METRIC_PATHS_SET: &str = ".name, .namespace, .interval_ms, .timestamp, .kind, .tags";
27
28const VALID_METRIC_PATHS_GET: &str =
30 ".name, .namespace, .interval_ms, .timestamp, .kind, .tags, .type";
31
32const MAX_METRIC_PATH_DEPTH: usize = 3;
37
38#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
40pub enum MetricTagMode {
41 #[default]
44 Single,
45 Full,
48 Auto,
58}
59
60#[allow(clippy::large_enum_variant)]
62#[derive(Debug, Clone)]
63pub enum VrlTarget {
64 LogEvent(Value, EventMetadata),
67 Metric {
68 metric: Metric,
69 value: Value,
70 tag_mode: MetricTagMode,
71 },
72 Trace(Value, EventMetadata),
73}
74
75pub enum TargetEvents {
76 One(Event),
77 Logs(TargetIter<LogEvent>),
78 Traces(TargetIter<TraceEvent>),
79}
80
81pub struct TargetIter<T> {
82 iter: std::vec::IntoIter<Value>,
83 metadata: EventMetadata,
84 _marker: PhantomData<T>,
85 log_namespace: LogNamespace,
86}
87
88fn create_log_event(value: Value, metadata: EventMetadata) -> LogEvent {
89 let mut log = LogEvent::new_with_metadata(metadata);
90 log.maybe_insert(log_schema().message_key_target_path(), value);
91 log
92}
93
94impl Iterator for TargetIter<LogEvent> {
95 type Item = Event;
96
97 fn next(&mut self) -> Option<Self::Item> {
98 self.iter.next().map(|v| {
99 match self.log_namespace {
100 LogNamespace::Legacy => match v {
101 value @ Value::Object(_) => LogEvent::from_parts(value, self.metadata.clone()),
102 value => create_log_event(value, self.metadata.clone()),
103 },
104 LogNamespace::Vector => LogEvent::from_parts(v, self.metadata.clone()),
105 }
106 .into()
107 })
108 }
109}
110
111impl Iterator for TargetIter<TraceEvent> {
112 type Item = Event;
113
114 fn next(&mut self) -> Option<Self::Item> {
115 self.iter.next().map(|v| {
116 match v {
117 value @ Value::Object(_) => {
118 TraceEvent::from(LogEvent::from_parts(value, self.metadata.clone()))
119 }
120 value => TraceEvent::from(create_log_event(value, self.metadata.clone())),
121 }
122 .into()
123 })
124 }
125}
126
127impl VrlTarget {
128 pub fn new(event: Event, info: &ProgramInfo, tag_mode: MetricTagMode) -> Self {
129 match event {
130 Event::Log(event) => {
131 let (value, metadata) = event.into_parts();
132 VrlTarget::LogEvent(value, metadata)
133 }
134 Event::Metric(metric) => {
135 let value = precompute_metric_value(&metric, info, tag_mode);
139
140 VrlTarget::Metric {
141 metric,
142 value,
143 tag_mode,
144 }
145 }
146 Event::Trace(event) => {
147 let (fields, metadata) = event.into_parts();
148 VrlTarget::Trace(Value::Object(fields), metadata)
149 }
150 }
151 }
152
153 pub fn modify_schema_definition_for_into_events(input: Definition) -> Definition {
155 let log_namespaces = input.log_namespaces().clone();
156
157 let merged_arrays = merge_array_definitions(input);
159 Definition::combine_log_namespaces(
160 &log_namespaces,
161 move_field_definitions_into_message(merged_arrays.clone()),
162 merged_arrays,
163 )
164 }
165
166 pub fn into_events(self, log_namespace: LogNamespace) -> TargetEvents {
171 match self {
172 VrlTarget::LogEvent(value, metadata) => match value {
173 value @ Value::Object(_) => {
174 TargetEvents::One(LogEvent::from_parts(value, metadata).into())
175 }
176
177 Value::Array(values) => TargetEvents::Logs(TargetIter {
178 iter: values.into_iter(),
179 metadata,
180 _marker: PhantomData,
181 log_namespace,
182 }),
183
184 v => match log_namespace {
185 LogNamespace::Vector => {
186 TargetEvents::One(LogEvent::from_parts(v, metadata).into())
187 }
188 LogNamespace::Legacy => TargetEvents::One(create_log_event(v, metadata).into()),
189 },
190 },
191 VrlTarget::Trace(value, metadata) => match value {
192 value @ Value::Object(_) => {
193 let log = LogEvent::from_parts(value, metadata);
194 TargetEvents::One(TraceEvent::from(log).into())
195 }
196
197 Value::Array(values) => TargetEvents::Traces(TargetIter {
198 iter: values.into_iter(),
199 metadata,
200 _marker: PhantomData,
201 log_namespace,
202 }),
203
204 v => TargetEvents::One(create_log_event(v, metadata).into()),
205 },
206 VrlTarget::Metric { metric, .. } => TargetEvents::One(Event::Metric(metric)),
207 }
208 }
209
210 fn metadata(&self) -> &EventMetadata {
211 match self {
212 VrlTarget::LogEvent(_, metadata) | VrlTarget::Trace(_, metadata) => metadata,
213 VrlTarget::Metric { metric, .. } => metric.metadata(),
214 }
215 }
216
217 fn metadata_mut(&mut self) -> &mut EventMetadata {
218 match self {
219 VrlTarget::LogEvent(_, metadata) | VrlTarget::Trace(_, metadata) => metadata,
220 VrlTarget::Metric { metric, .. } => metric.metadata_mut(),
221 }
222 }
223}
224
225fn move_field_definitions_into_message(mut definition: Definition) -> Definition {
228 let mut message = definition.event_kind().clone();
229 message.remove_object();
230 message.remove_array();
231
232 if !message.is_never()
233 && let Some(message_key) = log_schema().message_key()
234 {
235 let message = Kind::object(Collection::from(BTreeMap::from([(
238 message_key.to_string().into(),
239 message,
240 )])));
241
242 definition.event_kind_mut().remove_bytes();
243 definition.event_kind_mut().remove_integer();
244 definition.event_kind_mut().remove_float();
245 definition.event_kind_mut().remove_boolean();
246 definition.event_kind_mut().remove_timestamp();
247 definition.event_kind_mut().remove_regex();
248 definition.event_kind_mut().remove_null();
249
250 *definition.event_kind_mut() = definition.event_kind().union(message);
251 }
252
253 definition
254}
255
256fn merge_array_definitions(mut definition: Definition) -> Definition {
263 if let Some(array) = definition.event_kind().as_array() {
264 let array_kinds = array.reduced_kind();
265
266 let kind = definition.event_kind_mut();
267 kind.remove_array();
268 *kind = kind.union(array_kinds);
269 }
270
271 definition
272}
273
274fn set_metric_tag_values(
275 name: String,
276 value: &Value,
277 metric: &mut Metric,
278 tag_mode: MetricTagMode,
279) {
280 let multi_value = match tag_mode {
284 MetricTagMode::Single => false,
285 MetricTagMode::Full => true,
286 MetricTagMode::Auto => matches!(value, Value::Array(_)),
287 };
288
289 if multi_value {
290 let values = if let Value::Array(values) = value {
291 values.as_slice()
292 } else {
293 std::slice::from_ref(value)
294 };
295
296 let tag_values = values
297 .iter()
298 .filter_map(|value| match value {
299 Value::Bytes(bytes) => {
300 Some(TagValue::Value(String::from_utf8_lossy(bytes).to_string()))
301 }
302 Value::Null => Some(TagValue::Bare),
303 _ => None,
304 })
305 .collect::<Vec<_>>();
306
307 metric.set_multi_value_tag(name, tag_values);
308 } else {
309 if let Ok(tag_value) = value.try_bytes_utf8_lossy().map(Cow::into_owned) {
311 metric.replace_tag(name, tag_value);
312 } else if value.is_null() {
313 metric.set_multi_value_tag(name, vec![TagValue::Bare]);
314 }
315 }
316}
317
318impl Target for VrlTarget {
319 fn target_insert(&mut self, target_path: &OwnedTargetPath, value: Value) -> Result<(), String> {
320 let path = &target_path.path;
321 match target_path.prefix {
322 PathPrefix::Event => match self {
323 VrlTarget::LogEvent(log, _) | VrlTarget::Trace(log, _) => {
324 log.insert(path, value);
325 Ok(())
326 }
327 VrlTarget::Metric {
328 metric,
329 value: metric_value,
330 tag_mode,
331 } => {
332 if path.is_root() {
333 return Err(MetricPathError::SetPathError.to_string());
334 }
335
336 if let Some(paths) = path.to_alternative_components(MAX_METRIC_PATH_DEPTH) {
337 match paths.as_slice() {
338 ["tags"] => {
339 let value =
340 value.clone().try_object().map_err(|e| e.to_string())?;
341
342 metric.remove_tags();
343 for (field, value) in &value {
344 set_metric_tag_values(
345 field.as_str().into(),
346 value,
347 metric,
348 *tag_mode,
349 );
350 }
351 }
352 ["tags", field] => {
353 set_metric_tag_values(
354 (*field).to_owned(),
355 &value,
356 metric,
357 *tag_mode,
358 );
359 }
360 ["name"] => {
361 let value = value.clone().try_bytes().map_err(|e| e.to_string())?;
362 metric.series.name.name =
363 String::from_utf8_lossy(&value).into_owned();
364 }
365 ["namespace"] => {
366 let value = value.clone().try_bytes().map_err(|e| e.to_string())?;
367 metric.series.name.namespace =
368 Some(String::from_utf8_lossy(&value).into_owned());
369 }
370 ["interval_ms"] => {
371 let value: i64 =
372 value.clone().try_into_i64().map_err(|e| e.to_string())?;
373 let value: u32 = value
374 .try_into()
375 .map_err(|e: TryFromIntError| e.to_string())?;
376 let value = NonZero::try_from(value).map_err(|e| e.to_string())?;
377 metric.data.time.interval_ms = Some(value);
378 }
379 ["timestamp"] => {
380 let value =
381 value.clone().try_timestamp().map_err(|e| e.to_string())?;
382 metric.data.time.timestamp = Some(value);
383 }
384 ["kind"] => {
385 metric.data.kind = MetricKind::try_from(value.clone())?;
386 }
387 _ => {
388 return Err(MetricPathError::InvalidPath {
389 path: &path.to_string(),
390 expected: VALID_METRIC_PATHS_SET,
391 }
392 .to_string());
393 }
394 }
395
396 metric_value.insert(path, value);
397
398 return Ok(());
399 }
400
401 Err(MetricPathError::InvalidPath {
402 path: &path.to_string(),
403 expected: VALID_METRIC_PATHS_SET,
404 }
405 .to_string())
406 }
407 },
408 PathPrefix::Metadata => {
409 self.metadata_mut()
410 .value_mut()
411 .insert(&target_path.path, value);
412 Ok(())
413 }
414 }
415 }
416
417 #[allow(clippy::redundant_closure_for_method_calls)] fn target_get(&self, target_path: &OwnedTargetPath) -> Result<Option<&Value>, String> {
419 match target_path.prefix {
420 PathPrefix::Event => match self {
421 VrlTarget::LogEvent(log, _) | VrlTarget::Trace(log, _) => {
422 Ok(log.get(&target_path.path))
423 }
424 VrlTarget::Metric { value, .. } => target_get_metric(&target_path.path, value),
425 },
426 PathPrefix::Metadata => Ok(self.metadata().value().get(&target_path.path)),
427 }
428 }
429
430 fn target_get_mut(
431 &mut self,
432 target_path: &OwnedTargetPath,
433 ) -> Result<Option<&mut Value>, String> {
434 match target_path.prefix {
435 PathPrefix::Event => match self {
436 VrlTarget::LogEvent(log, _) | VrlTarget::Trace(log, _) => {
437 Ok(log.get_mut(&target_path.path))
438 }
439 VrlTarget::Metric { value, .. } => target_get_mut_metric(&target_path.path, value),
440 },
441 PathPrefix::Metadata => Ok(self.metadata_mut().value_mut().get_mut(&target_path.path)),
442 }
443 }
444
445 fn target_remove(
446 &mut self,
447 target_path: &OwnedTargetPath,
448 compact: bool,
449 ) -> Result<Option<vrl::value::Value>, String> {
450 match target_path.prefix {
451 PathPrefix::Event => match self {
452 VrlTarget::LogEvent(log, _) | VrlTarget::Trace(log, _) => {
453 Ok(log.remove(&target_path.path, compact))
454 }
455 VrlTarget::Metric {
456 metric,
457 value,
458 tag_mode,
459 } => {
460 if target_path.path.is_root() {
461 return Err(MetricPathError::SetPathError.to_string());
462 }
463
464 if let Some(paths) = target_path
465 .path
466 .to_alternative_components(MAX_METRIC_PATH_DEPTH)
467 {
468 let removed_value = match paths.as_slice() {
469 ["namespace"] => metric.series.name.namespace.take().map(Into::into),
470 ["timestamp"] => metric.data.time.timestamp.take().map(Into::into),
471 ["interval_ms"] => metric
472 .data
473 .time
474 .interval_ms
475 .take()
476 .map(u32::from)
477 .map(Into::into),
478 ["tags"] => metric
479 .series
480 .tags
481 .take()
482 .map(|tags| metric_tags_to_vrl_value(tags, *tag_mode)),
483 ["tags", field] => metric
484 .remove_tag_set(field)
485 .map(|tag_set| tag_value_set_to_vrl_value(&tag_set, *tag_mode)),
486 _ => {
487 return Err(MetricPathError::InvalidPath {
488 path: &target_path.path.to_string(),
489 expected: VALID_METRIC_PATHS_SET,
490 }
491 .to_string());
492 }
493 };
494
495 value.remove(&target_path.path, false);
496
497 Ok(removed_value)
498 } else {
499 Ok(None)
500 }
501 }
502 },
503 PathPrefix::Metadata => Ok(self
504 .metadata_mut()
505 .value_mut()
506 .remove(&target_path.path, compact)),
507 }
508 }
509}
510
511impl SecretTarget for VrlTarget {
512 fn get_secret(&self, key: &str) -> Option<&str> {
513 self.metadata().secrets().get_secret(key)
514 }
515
516 fn insert_secret(&mut self, key: &str, value: &str) {
517 self.metadata_mut().secrets_mut().insert_secret(key, value);
518 }
519
520 fn remove_secret(&mut self, key: &str) {
521 self.metadata_mut().secrets_mut().remove_secret(key);
522 }
523}
524
525fn target_get_metric<'a>(
538 path: &OwnedValuePath,
539 value: &'a Value,
540) -> Result<Option<&'a Value>, String> {
541 if path.is_root() {
542 return Ok(Some(value));
543 }
544
545 let value = value.get(path);
546
547 let Some(paths) = path.to_alternative_components(MAX_METRIC_PATH_DEPTH) else {
548 return Ok(None);
549 };
550
551 match paths.as_slice() {
552 ["name"]
553 | ["kind"]
554 | ["type"]
555 | ["tags", _]
556 | ["namespace"]
557 | ["timestamp"]
558 | ["interval_ms"]
559 | ["tags"] => Ok(value),
560 _ => Err(MetricPathError::InvalidPath {
561 path: &path.to_string(),
562 expected: VALID_METRIC_PATHS_GET,
563 }
564 .to_string()),
565 }
566}
567
568fn target_get_mut_metric<'a>(
569 path: &OwnedValuePath,
570 value: &'a mut Value,
571) -> Result<Option<&'a mut Value>, String> {
572 if path.is_root() {
573 return Ok(Some(value));
574 }
575
576 let value = value.get_mut(path);
577
578 let Some(paths) = path.to_alternative_components(MAX_METRIC_PATH_DEPTH) else {
579 return Ok(None);
580 };
581
582 match paths.as_slice() {
583 ["name"]
584 | ["kind"]
585 | ["tags", _]
586 | ["namespace"]
587 | ["timestamp"]
588 | ["interval_ms"]
589 | ["tags"] => Ok(value),
590 _ => Err(MetricPathError::InvalidPath {
591 path: &path.to_string(),
592 expected: VALID_METRIC_PATHS_SET,
593 }
594 .to_string()),
595 }
596}
597
598struct MetricProperty {
599 property: &'static str,
600 getter: fn(&Metric) -> Option<Value>,
601 set: bool,
602}
603
604impl MetricProperty {
605 fn new(property: &'static str, getter: fn(&Metric) -> Option<Value>) -> Self {
606 Self {
607 property,
608 getter,
609 set: false,
610 }
611 }
612
613 fn insert(&mut self, metric: &Metric, map: &mut ObjectMap) {
614 if self.set {
615 return;
616 }
617 if let Some(value) = (self.getter)(metric) {
618 map.insert(self.property.into(), value);
619 self.set = true;
620 }
621 }
622}
623
624fn get_single_value_tags(metric: &Metric) -> Option<Value> {
625 metric
626 .tags()
627 .cloned()
628 .map(|tags| metric_tags_to_vrl_value(tags, MetricTagMode::Single))
629}
630
631fn get_multi_value_tags(metric: &Metric) -> Option<Value> {
632 metric
633 .tags()
634 .cloned()
635 .map(|tags| metric_tags_to_vrl_value(tags, MetricTagMode::Full))
636}
637
638fn get_auto_value_tags(metric: &Metric) -> Option<Value> {
645 metric
646 .tags()
647 .cloned()
648 .map(|tags| metric_tags_to_vrl_value(tags, MetricTagMode::Auto))
649}
650
651fn tag_ref_to_vrl_value(value: Option<&str>) -> Value {
652 value.map_or(Value::Null, Value::from)
653}
654
655fn tag_value_set_to_vrl_value(tag_set: &TagValueSet, tag_mode: MetricTagMode) -> Value {
656 match tag_mode {
657 MetricTagMode::Single => tag_set.as_single().map_or(Value::Null, Value::from),
658 MetricTagMode::Full => Value::Array(tag_set.iter().map(tag_ref_to_vrl_value).collect()),
659 MetricTagMode::Auto => match tag_set.len() {
660 1 => match tag_set.iter().next() {
661 Some(Some(s)) => Value::from(s),
662 _ => Value::Null,
663 },
664 _ => Value::Array(tag_set.iter().map(tag_ref_to_vrl_value).collect()),
665 },
666 }
667}
668
669fn metric_tags_to_vrl_value(tags: MetricTags, tag_mode: MetricTagMode) -> Value {
670 match tag_mode {
671 MetricTagMode::Single => tags
672 .into_iter_single()
673 .map(|(tag, value)| (tag.into(), value.into()))
674 .collect::<ObjectMap>()
675 .into(),
676 MetricTagMode::Full | MetricTagMode::Auto => tags
677 .iter_sets()
678 .map(|(tag, tag_set)| (tag.into(), tag_value_set_to_vrl_value(tag_set, tag_mode)))
679 .collect::<ObjectMap>()
680 .into(),
681 }
682}
683
684fn precompute_metric_value(metric: &Metric, info: &ProgramInfo, tag_mode: MetricTagMode) -> Value {
689 let mut name = MetricProperty::new("name", |metric| Some(metric.name().to_owned().into()));
690 let mut kind = MetricProperty::new("kind", |metric| Some(metric.kind().into()));
691 let mut type_ = MetricProperty::new("type", |metric| Some(metric.value().clone().into()));
692 let mut namespace = MetricProperty::new("namespace", |metric| {
693 metric.namespace().map(String::from).map(Into::into)
694 });
695 let mut interval_ms =
696 MetricProperty::new("interval_ms", |metric| metric.interval_ms().map(Into::into));
697 let mut timestamp =
698 MetricProperty::new("timestamp", |metric| metric.timestamp().map(Into::into));
699 let mut tags = MetricProperty::new(
700 "tags",
701 match tag_mode {
702 MetricTagMode::Single => get_single_value_tags,
703 MetricTagMode::Full => get_multi_value_tags,
704 MetricTagMode::Auto => get_auto_value_tags,
705 },
706 );
707
708 let mut map = ObjectMap::default();
709
710 for target_path in &info.target_queries {
711 if target_path == &OwnedTargetPath::event_root() {
713 let mut properties = [
714 &mut name,
715 &mut kind,
716 &mut type_,
717 &mut namespace,
718 &mut interval_ms,
719 &mut timestamp,
720 &mut tags,
721 ];
722 for property in &mut properties {
723 property.insert(metric, &mut map);
724 }
725 break;
726 }
727
728 if let Some(OwnedSegment::Field(field)) = target_path.path.segments.first() {
731 let property = match field.as_ref() {
732 "name" => Some(&mut name),
733 "kind" => Some(&mut kind),
734 "type" => Some(&mut type_),
735 "namespace" => Some(&mut namespace),
736 "timestamp" => Some(&mut timestamp),
737 "interval_ms" => Some(&mut interval_ms),
738 "tags" => Some(&mut tags),
739 _ => None,
740 };
741 if let Some(property) = property {
742 property.insert(metric, &mut map);
743 }
744 }
745 }
746
747 map.into()
748}
749
750#[derive(Debug, Snafu)]
751enum MetricPathError<'a> {
752 #[snafu(display("cannot set root path"))]
753 SetPathError,
754
755 #[snafu(display("invalid path {}: expected one of {}", path, expected))]
756 InvalidPath { path: &'a str, expected: &'a str },
757}
758
759#[cfg(test)]
760mod test {
761 use chrono::{Utc, offset::TimeZone};
762 use lookup::owned_value_path;
763 use similar_asserts::assert_eq;
764 use vrl::{btreemap, value::kind::Index};
765
766 use super::{super::MetricValue, *};
767 use crate::metric_tags;
768
769 #[test]
770 fn test_field_definitions_in_message() {
771 let definition =
772 Definition::new_with_default_metadata(Kind::bytes(), [LogNamespace::Legacy]);
773 assert_eq!(
774 Definition::new_with_default_metadata(
775 Kind::object(BTreeMap::from([("message".into(), Kind::bytes())])),
776 [LogNamespace::Legacy]
777 ),
778 move_field_definitions_into_message(definition)
779 );
780
781 let definition = Definition::new_with_default_metadata(
783 Kind::object(BTreeMap::from([("message".into(), Kind::integer())])).or_bytes(),
784 [LogNamespace::Legacy],
785 );
786 assert_eq!(
787 Definition::new_with_default_metadata(
788 Kind::object(BTreeMap::from([(
789 "message".into(),
790 Kind::bytes().or_integer()
791 )])),
792 [LogNamespace::Legacy]
793 ),
794 move_field_definitions_into_message(definition)
795 );
796 }
797
798 #[test]
799 fn test_merged_array_definitions_simple() {
800 let object: BTreeMap<vrl::value::kind::Field, Kind> = [
803 ("carrot".into(), Kind::bytes()),
804 ("potato".into(), Kind::integer()),
805 ]
806 .into();
807
808 let kind = Kind::array(Collection::from_unknown(Kind::object(object)));
809
810 let definition = Definition::new_with_default_metadata(kind, [LogNamespace::Legacy]);
811
812 let kind = Kind::object(BTreeMap::from([
813 ("carrot".into(), Kind::bytes()),
814 ("potato".into(), Kind::integer()),
815 ]));
816
817 let wanted = Definition::new_with_default_metadata(kind, [LogNamespace::Legacy]);
818 let merged = merge_array_definitions(definition);
819
820 assert_eq!(wanted, merged);
821 }
822
823 #[test]
824 fn test_merged_array_definitions_complex() {
825 let object: BTreeMap<vrl::value::kind::Field, Kind> = [
828 ("carrot".into(), Kind::bytes()),
829 ("potato".into(), Kind::integer()),
830 ]
831 .into();
832
833 let array: BTreeMap<Index, Kind> = [
834 (Index::from(0), Kind::integer()),
835 (Index::from(1), Kind::boolean()),
836 (
837 Index::from(2),
838 Kind::object(BTreeMap::from([("peas".into(), Kind::bytes())])),
839 ),
840 ]
841 .into();
842
843 let mut kind = Kind::bytes();
844 kind.add_object(object);
845 kind.add_array(array);
846
847 let definition = Definition::new_with_default_metadata(kind, [LogNamespace::Legacy]);
848
849 let mut kind = Kind::bytes();
850 kind.add_integer();
851 kind.add_boolean();
852 kind.add_object(BTreeMap::from([
853 ("carrot".into(), Kind::bytes().or_undefined()),
854 ("potato".into(), Kind::integer().or_undefined()),
855 ("peas".into(), Kind::bytes().or_undefined()),
856 ]));
857
858 let wanted = Definition::new_with_default_metadata(kind, [LogNamespace::Legacy]);
859 let merged = merge_array_definitions(definition);
860
861 assert_eq!(wanted, merged);
862 }
863
864 #[test]
865 fn log_get() {
866 let cases = vec![
867 (
868 BTreeMap::new(),
869 owned_value_path!(),
870 Ok(Some(BTreeMap::new().into())),
871 ),
872 (
873 BTreeMap::from([("foo".into(), "bar".into())]),
874 owned_value_path!(),
875 Ok(Some(BTreeMap::from([("foo".into(), "bar".into())]).into())),
876 ),
877 (
878 BTreeMap::from([("foo".into(), "bar".into())]),
879 owned_value_path!("foo"),
880 Ok(Some("bar".into())),
881 ),
882 (
883 BTreeMap::from([("foo".into(), "bar".into())]),
884 owned_value_path!("bar"),
885 Ok(None),
886 ),
887 (
888 btreemap! { "foo" => vec![btreemap! { "bar" => true }] },
889 owned_value_path!("foo", 0, "bar"),
890 Ok(Some(true.into())),
891 ),
892 (
893 btreemap! { "foo" => btreemap! { "bar baz" => btreemap! { "baz" => 2 } } },
894 owned_value_path!("foo", r"bar baz", "baz"),
895 Ok(Some(2.into())),
896 ),
897 ];
898
899 for (value, path, expect) in cases {
900 let value: ObjectMap = value;
901 let info = ProgramInfo {
902 fallible: false,
903 abortable: false,
904 target_queries: vec![],
905 target_assignments: vec![],
906 };
907 let target = VrlTarget::new(
908 Event::Log(LogEvent::from(value)),
909 &info,
910 MetricTagMode::Single,
911 );
912 let path = OwnedTargetPath::event(path);
913
914 assert_eq!(
915 Target::target_get(&target, &path).map(Option::<&Value>::cloned),
916 expect
917 );
918 }
919 }
920
921 #[allow(clippy::too_many_lines)]
922 #[test]
923 fn log_insert() {
924 let cases = vec![
925 (
926 BTreeMap::from([("foo".into(), "bar".into())]),
927 owned_value_path!(0),
928 btreemap! { "baz" => "qux" }.into(),
929 btreemap! { "baz" => "qux" },
930 Ok(()),
931 ),
932 (
933 BTreeMap::from([("foo".into(), "bar".into())]),
934 owned_value_path!("foo"),
935 "baz".into(),
936 btreemap! { "foo" => "baz" },
937 Ok(()),
938 ),
939 (
940 BTreeMap::from([("foo".into(), "bar".into())]),
941 owned_value_path!("foo", 2, "bar baz", "a", "b"),
942 true.into(),
943 btreemap! {
944 "foo" => vec![
945 Value::Null,
946 Value::Null,
947 btreemap! {
948 "bar baz" => btreemap! { "a" => btreemap! { "b" => true } },
949 }.into()
950 ]
951 },
952 Ok(()),
953 ),
954 (
955 btreemap! { "foo" => vec![0, 1, 2] },
956 owned_value_path!("foo", 5),
957 "baz".into(),
958 btreemap! {
959 "foo" => vec![
960 0.into(),
961 1.into(),
962 2.into(),
963 Value::Null,
964 Value::Null,
965 Value::from("baz"),
966 ],
967 },
968 Ok(()),
969 ),
970 (
971 BTreeMap::from([("foo".into(), "bar".into())]),
972 owned_value_path!("foo", 0),
973 "baz".into(),
974 btreemap! { "foo" => vec!["baz"] },
975 Ok(()),
976 ),
977 (
978 btreemap! { "foo" => Value::Array(vec![]) },
979 owned_value_path!("foo", 0),
980 "baz".into(),
981 btreemap! { "foo" => vec!["baz"] },
982 Ok(()),
983 ),
984 (
985 btreemap! { "foo" => Value::Array(vec![0.into()]) },
986 owned_value_path!("foo", 0),
987 "baz".into(),
988 btreemap! { "foo" => vec!["baz"] },
989 Ok(()),
990 ),
991 (
992 btreemap! { "foo" => Value::Array(vec![0.into(), 1.into()]) },
993 owned_value_path!("foo", 0),
994 "baz".into(),
995 btreemap! { "foo" => Value::Array(vec!["baz".into(), 1.into()]) },
996 Ok(()),
997 ),
998 (
999 btreemap! { "foo" => Value::Array(vec![0.into(), 1.into()]) },
1000 owned_value_path!("foo", 1),
1001 "baz".into(),
1002 btreemap! { "foo" => Value::Array(vec![0.into(), "baz".into()]) },
1003 Ok(()),
1004 ),
1005 ];
1006
1007 for (object, path, value, expect, result) in cases {
1008 let object: ObjectMap = object;
1009 let info = ProgramInfo {
1010 fallible: false,
1011 abortable: false,
1012 target_queries: vec![],
1013 target_assignments: vec![],
1014 };
1015 let mut target = VrlTarget::new(
1016 Event::Log(LogEvent::from(object)),
1017 &info,
1018 MetricTagMode::Single,
1019 );
1020 let expect = LogEvent::from(expect);
1021 let value: Value = value;
1022 let path = OwnedTargetPath::event(path);
1023
1024 assert_eq!(
1025 Target::target_insert(&mut target, &path, value.clone()),
1026 result
1027 );
1028 assert_eq!(
1029 Target::target_get(&target, &path).map(Option::<&Value>::cloned),
1030 Ok(Some(value))
1031 );
1032 assert_eq!(
1033 match target.into_events(LogNamespace::Legacy) {
1034 TargetEvents::One(event) => vec![event],
1035 TargetEvents::Logs(events) => events.collect::<Vec<_>>(),
1036 TargetEvents::Traces(events) => events.collect::<Vec<_>>(),
1037 }
1038 .first()
1039 .cloned()
1040 .unwrap(),
1041 Event::Log(expect)
1042 );
1043 }
1044 }
1045
1046 #[test]
1047 fn log_remove() {
1048 let cases = vec![
1049 (
1050 BTreeMap::from([("foo".into(), "bar".into())]),
1051 owned_value_path!("foo"),
1052 false,
1053 Some(BTreeMap::new().into()),
1054 ),
1055 (
1056 BTreeMap::from([("foo".into(), "bar".into())]),
1057 owned_value_path!(r"foo bar", "foo"),
1058 false,
1059 Some(btreemap! { "foo" => "bar"}.into()),
1060 ),
1061 (
1062 btreemap! { "foo" => "bar", "baz" => "qux" },
1063 owned_value_path!(),
1064 false,
1065 Some(BTreeMap::new().into()),
1066 ),
1067 (
1068 btreemap! { "foo" => "bar", "baz" => "qux" },
1069 owned_value_path!(),
1070 true,
1071 Some(BTreeMap::new().into()),
1072 ),
1073 (
1074 btreemap! { "foo" => vec![0] },
1075 owned_value_path!("foo", 0),
1076 false,
1077 Some(btreemap! { "foo" => Value::Array(vec![]) }.into()),
1078 ),
1079 (
1080 btreemap! { "foo" => vec![0] },
1081 owned_value_path!("foo", 0),
1082 true,
1083 Some(BTreeMap::new().into()),
1084 ),
1085 (
1086 btreemap! {
1087 "foo" => btreemap! { "bar baz" => vec![0] },
1088 "bar" => "baz",
1089 },
1090 owned_value_path!("foo", r"bar baz", 0),
1091 false,
1092 Some(
1093 btreemap! {
1094 "foo" => btreemap! { "bar baz" => Value::Array(vec![]) },
1095 "bar" => "baz",
1096 }
1097 .into(),
1098 ),
1099 ),
1100 (
1101 btreemap! {
1102 "foo" => btreemap! { "bar baz" => vec![0] },
1103 "bar" => "baz",
1104 },
1105 owned_value_path!("foo", r"bar baz", 0),
1106 true,
1107 Some(btreemap! { "bar" => "baz" }.into()),
1108 ),
1109 ];
1110
1111 for (object, path, compact, expect) in cases {
1112 let info = ProgramInfo {
1113 fallible: false,
1114 abortable: false,
1115 target_queries: vec![],
1116 target_assignments: vec![],
1117 };
1118 let mut target = VrlTarget::new(
1119 Event::Log(LogEvent::from(object)),
1120 &info,
1121 MetricTagMode::Single,
1122 );
1123 let path = OwnedTargetPath::event(path);
1124 let removed = Target::target_get(&target, &path).unwrap().cloned();
1125
1126 assert_eq!(
1127 Target::target_remove(&mut target, &path, compact),
1128 Ok(removed)
1129 );
1130 assert_eq!(
1131 Target::target_get(&target, &OwnedTargetPath::event_root())
1132 .map(Option::<&Value>::cloned),
1133 Ok(expect)
1134 );
1135 }
1136 }
1137
1138 #[test]
1139 fn log_into_events() {
1140 use vrl::btreemap;
1141
1142 let cases = vec![
1143 (
1144 Value::from(btreemap! {"foo" => "bar"}),
1145 vec![btreemap! {"foo" => "bar"}],
1146 ),
1147 (Value::from(1), vec![btreemap! {"message" => 1}]),
1148 (Value::from("2"), vec![btreemap! {"message" => "2"}]),
1149 (Value::from(true), vec![btreemap! {"message" => true}]),
1150 (
1151 Value::from(vec![
1152 Value::from(1),
1153 Value::from("2"),
1154 Value::from(true),
1155 Value::from(btreemap! {"foo" => "bar"}),
1156 ]),
1157 vec![
1158 btreemap! {"message" => 1},
1159 btreemap! {"message" => "2"},
1160 btreemap! {"message" => true},
1161 btreemap! {"foo" => "bar"},
1162 ],
1163 ),
1164 ];
1165
1166 for (value, expect) in cases {
1167 let metadata = EventMetadata::default();
1168 let info = ProgramInfo {
1169 fallible: false,
1170 abortable: false,
1171 target_queries: vec![],
1172 target_assignments: vec![],
1173 };
1174 let mut target = VrlTarget::new(
1175 Event::Log(LogEvent::new_with_metadata(metadata.clone())),
1176 &info,
1177 MetricTagMode::Single,
1178 );
1179
1180 Target::target_insert(&mut target, &OwnedTargetPath::event_root(), value).unwrap();
1181
1182 assert_eq!(
1183 match target.into_events(LogNamespace::Legacy) {
1184 TargetEvents::One(event) => vec![event],
1185 TargetEvents::Logs(events) => events.collect::<Vec<_>>(),
1186 TargetEvents::Traces(events) => events.collect::<Vec<_>>(),
1187 },
1188 expect
1189 .into_iter()
1190 .map(|v| Event::Log(LogEvent::from_map(v, metadata.clone())))
1191 .collect::<Vec<_>>()
1192 );
1193 }
1194 }
1195
1196 #[test]
1197 fn metric_all_fields() {
1198 let metric = Metric::new(
1199 "zub",
1200 MetricKind::Absolute,
1201 MetricValue::Counter { value: 1.23 },
1202 )
1203 .with_namespace(Some("zoob"))
1204 .with_tags(Some(metric_tags!("tig" => "tog")))
1205 .with_timestamp(Some(
1206 Utc.with_ymd_and_hms(2020, 12, 10, 12, 0, 0)
1207 .single()
1208 .expect("invalid timestamp"),
1209 ))
1210 .with_interval_ms(Some(NonZero::<u32>::new(507).unwrap()));
1211
1212 let info = ProgramInfo {
1213 fallible: false,
1214 abortable: false,
1215 target_queries: vec![
1216 OwnedTargetPath::event(owned_value_path!("name")),
1217 OwnedTargetPath::event(owned_value_path!("namespace")),
1218 OwnedTargetPath::event(owned_value_path!("interval_ms")),
1219 OwnedTargetPath::event(owned_value_path!("timestamp")),
1220 OwnedTargetPath::event(owned_value_path!("kind")),
1221 OwnedTargetPath::event(owned_value_path!("type")),
1222 OwnedTargetPath::event(owned_value_path!("tags")),
1223 ],
1224 target_assignments: vec![],
1225 };
1226 let target = VrlTarget::new(Event::Metric(metric), &info, MetricTagMode::Single);
1227
1228 assert_eq!(
1229 Ok(Some(
1230 btreemap! {
1231 "name" => "zub",
1232 "namespace" => "zoob",
1233 "interval_ms" => 507,
1234 "timestamp" => Utc.with_ymd_and_hms(2020, 12, 10, 12, 0, 0).single().expect("invalid timestamp"),
1235 "tags" => btreemap! { "tig" => "tog" },
1236 "kind" => "absolute",
1237 "type" => "counter",
1238 }
1239 .into()
1240 )),
1241 target
1242 .target_get(&OwnedTargetPath::event_root())
1243 .map(Option::<&Value>::cloned)
1244 );
1245 }
1246
1247 #[test]
1248 fn metric_fields() {
1249 struct Case {
1250 path: OwnedValuePath,
1251 current: Option<Value>,
1252 new: Value,
1253 delete: bool,
1254 }
1255
1256 let metric = Metric::new(
1257 "name",
1258 MetricKind::Absolute,
1259 MetricValue::Counter { value: 1.23 },
1260 )
1261 .with_tags(Some(metric_tags!("tig" => "tog")));
1262
1263 let cases = vec![
1264 Case {
1265 path: owned_value_path!("name"),
1266 current: Some(Value::from("name")),
1267 new: Value::from("namefoo"),
1268 delete: false,
1269 },
1270 Case {
1271 path: owned_value_path!("namespace"),
1272 current: None,
1273 new: "namespacefoo".into(),
1274 delete: true,
1275 },
1276 Case {
1277 path: owned_value_path!("timestamp"),
1278 current: None,
1279 new: Utc
1280 .with_ymd_and_hms(2020, 12, 8, 12, 0, 0)
1281 .single()
1282 .expect("invalid timestamp")
1283 .into(),
1284 delete: true,
1285 },
1286 Case {
1287 path: owned_value_path!("interval_ms"),
1288 current: None,
1289 new: 123_456.into(),
1290 delete: true,
1291 },
1292 Case {
1293 path: owned_value_path!("kind"),
1294 current: Some(Value::from("absolute")),
1295 new: "incremental".into(),
1296 delete: false,
1297 },
1298 Case {
1299 path: owned_value_path!("tags", "thing"),
1300 current: None,
1301 new: "footag".into(),
1302 delete: true,
1303 },
1304 ];
1305
1306 let info = ProgramInfo {
1307 fallible: false,
1308 abortable: false,
1309 target_queries: vec![
1310 OwnedTargetPath::event(owned_value_path!("name")),
1311 OwnedTargetPath::event(owned_value_path!("namespace")),
1312 OwnedTargetPath::event(owned_value_path!("timestamp")),
1313 OwnedTargetPath::event(owned_value_path!("interval_ms")),
1314 OwnedTargetPath::event(owned_value_path!("kind")),
1315 ],
1316 target_assignments: vec![],
1317 };
1318 let mut target = VrlTarget::new(Event::Metric(metric), &info, MetricTagMode::Single);
1319
1320 for Case {
1321 path,
1322 current,
1323 new,
1324 delete,
1325 } in cases
1326 {
1327 let path = OwnedTargetPath::event(path);
1328
1329 assert_eq!(
1330 Ok(current),
1331 target.target_get(&path).map(Option::<&Value>::cloned)
1332 );
1333 assert_eq!(Ok(()), target.target_insert(&path, new.clone()));
1334 assert_eq!(
1335 Ok(Some(new.clone())),
1336 target.target_get(&path).map(Option::<&Value>::cloned)
1337 );
1338
1339 if delete {
1340 assert_eq!(Ok(Some(new)), target.target_remove(&path, true));
1341 assert_eq!(
1342 Ok(None),
1343 target.target_get(&path).map(Option::<&Value>::cloned)
1344 );
1345 }
1346 }
1347 }
1348
1349 #[test]
1350 fn metric_set_tags() {
1351 let metric = Metric::new(
1352 "name",
1353 MetricKind::Absolute,
1354 MetricValue::Counter { value: 1.23 },
1355 )
1356 .with_tags(Some(metric_tags!("tig" => "tog")));
1357
1358 let info = ProgramInfo {
1359 fallible: false,
1360 abortable: false,
1361 target_queries: vec![],
1362 target_assignments: vec![],
1363 };
1364 let mut target = VrlTarget::new(Event::Metric(metric), &info, MetricTagMode::Single);
1365 let _result = target.target_insert(
1366 &OwnedTargetPath::event(owned_value_path!("tags")),
1367 Value::Object(BTreeMap::from([("a".into(), "b".into())])),
1368 );
1369
1370 match target {
1371 VrlTarget::Metric {
1372 metric,
1373 value: _,
1374 tag_mode: _,
1375 } => {
1376 assert!(metric.tags().is_some());
1377 assert_eq!(metric.tags().unwrap(), &crate::metric_tags!("a" => "b"));
1378 }
1379 _ => panic!("must be a metric"),
1380 }
1381 }
1382
1383 #[test]
1384 fn metric_invalid_paths() {
1385 let metric = Metric::new(
1386 "name",
1387 MetricKind::Absolute,
1388 MetricValue::Counter { value: 1.23 },
1389 );
1390
1391 let validpaths_get = [
1392 ".name",
1393 ".namespace",
1394 ".interval_ms",
1395 ".timestamp",
1396 ".kind",
1397 ".tags",
1398 ".type",
1399 ];
1400
1401 let validpaths_set = [
1402 ".name",
1403 ".namespace",
1404 ".interval_ms",
1405 ".timestamp",
1406 ".kind",
1407 ".tags",
1408 ];
1409
1410 let info = ProgramInfo {
1411 fallible: false,
1412 abortable: false,
1413 target_queries: vec![],
1414 target_assignments: vec![],
1415 };
1416 let mut target = VrlTarget::new(Event::Metric(metric), &info, MetricTagMode::Single);
1417
1418 assert_eq!(
1419 Err(format!(
1420 "invalid path zork: expected one of {}",
1421 validpaths_get.join(", ")
1422 )),
1423 target.target_get(&OwnedTargetPath::event(owned_value_path!("zork")))
1424 );
1425
1426 assert_eq!(
1427 Err(format!(
1428 "invalid path zork: expected one of {}",
1429 validpaths_set.join(", ")
1430 )),
1431 target.target_insert(
1432 &OwnedTargetPath::event(owned_value_path!("zork")),
1433 "thing".into()
1434 )
1435 );
1436
1437 assert_eq!(
1438 Err(format!(
1439 "invalid path zork: expected one of {}",
1440 validpaths_set.join(", ")
1441 )),
1442 target.target_remove(&OwnedTargetPath::event(owned_value_path!("zork")), true)
1443 );
1444
1445 assert_eq!(
1446 Err(format!(
1447 "invalid path tags.foo.flork: expected one of {}",
1448 validpaths_get.join(", ")
1449 )),
1450 target.target_get(&OwnedTargetPath::event(owned_value_path!(
1451 "tags", "foo", "flork"
1452 )))
1453 );
1454 }
1455
1456 #[test]
1457 fn test_metric_insert_get_multi_value_tag() {
1458 let metric = Metric::new(
1459 "name",
1460 MetricKind::Absolute,
1461 MetricValue::Counter { value: 1.23 },
1462 );
1463 let info = ProgramInfo {
1464 fallible: false,
1465 abortable: false,
1466 target_queries: vec![],
1467 target_assignments: vec![],
1468 };
1469
1470 let mut target = VrlTarget::new(Event::Metric(metric), &info, MetricTagMode::Full);
1471
1472 let value = Value::Array(vec!["a".into(), "".into(), Value::Null, "b".into()]);
1473 target
1474 .target_insert(
1475 &OwnedTargetPath::event(owned_value_path!("tags", "foo")),
1476 value,
1477 )
1478 .unwrap();
1479
1480 let vrl_tags_value = target
1481 .target_get(&OwnedTargetPath::event(owned_value_path!("tags")))
1482 .unwrap()
1483 .unwrap();
1484
1485 assert_eq!(
1486 vrl_tags_value,
1487 &Value::Object(BTreeMap::from([(
1488 "foo".into(),
1489 Value::Array(vec!["a".into(), "".into(), Value::Null, "b".into()])
1490 )]))
1491 );
1492
1493 let VrlTarget::Metric { metric, .. } = target else {
1494 unreachable!()
1495 };
1496
1497 assert_eq!(metric.tag_value("foo"), Some("b".into()));
1499 }
1500
1501 fn auto_mode_program_info() -> ProgramInfo {
1502 ProgramInfo {
1503 fallible: false,
1504 abortable: false,
1505 target_queries: vec![OwnedTargetPath::event(owned_value_path!("tags"))],
1506 target_assignments: vec![],
1507 }
1508 }
1509
1510 #[test]
1514 fn metric_auto_read_single_value_tag_returns_string() {
1515 let metric = Metric::new(
1516 "m",
1517 MetricKind::Absolute,
1518 MetricValue::Counter { value: 1.0 },
1519 )
1520 .with_tags(Some(metric_tags!("env" => "prod")));
1521
1522 let info = auto_mode_program_info();
1523 let target = VrlTarget::new(Event::Metric(metric), &info, MetricTagMode::Auto);
1524
1525 let tags = target
1526 .target_get(&OwnedTargetPath::event(owned_value_path!("tags")))
1527 .unwrap()
1528 .unwrap()
1529 .clone();
1530
1531 assert_eq!(
1532 tags,
1533 Value::Object(BTreeMap::from([("env".into(), Value::from("prod"))])),
1534 );
1535 }
1536
1537 #[test]
1541 fn metric_auto_read_multi_value_tag_returns_array() {
1542 use super::super::metric::{MetricTags, TagValueSet};
1543 let metric = Metric::new(
1544 "m",
1545 MetricKind::Absolute,
1546 MetricValue::Counter { value: 1.0 },
1547 )
1548 .with_tags(Some(MetricTags(BTreeMap::from([(
1549 "shard".to_string(),
1550 TagValueSet::from(vec![
1551 TagValue::from("a".to_string()),
1552 TagValue::from("b".to_string()),
1553 ]),
1554 )]))));
1555
1556 let info = auto_mode_program_info();
1557 let target = VrlTarget::new(Event::Metric(metric), &info, MetricTagMode::Auto);
1558
1559 let tags = target
1560 .target_get(&OwnedTargetPath::event(owned_value_path!("tags")))
1561 .unwrap()
1562 .unwrap()
1563 .clone();
1564
1565 assert_eq!(
1566 tags,
1567 Value::Object(BTreeMap::from([(
1568 "shard".into(),
1569 Value::Array(vec!["a".into(), "b".into()])
1570 )])),
1571 );
1572 }
1573
1574 #[test]
1577 fn metric_auto_read_mixed_tags_preserves_both_shapes() {
1578 use super::super::metric::{MetricTags, TagValueSet};
1579 let mut tags = BTreeMap::new();
1580 tags.insert(
1581 "env".to_string(),
1582 TagValueSet::from(vec!["prod".to_string()]),
1583 );
1584 tags.insert(
1585 "shard".to_string(),
1586 TagValueSet::from(vec!["a".to_string(), "b".to_string()]),
1587 );
1588 let metric = Metric::new(
1589 "m",
1590 MetricKind::Absolute,
1591 MetricValue::Counter { value: 1.0 },
1592 )
1593 .with_tags(Some(MetricTags(tags)));
1594
1595 let info = auto_mode_program_info();
1596 let target = VrlTarget::new(Event::Metric(metric), &info, MetricTagMode::Auto);
1597
1598 let tags = target
1599 .target_get(&OwnedTargetPath::event(owned_value_path!("tags")))
1600 .unwrap()
1601 .unwrap()
1602 .clone();
1603
1604 assert_eq!(
1605 tags,
1606 Value::Object(BTreeMap::from([
1607 ("env".into(), Value::from("prod")),
1608 ("shard".into(), Value::Array(vec!["a".into(), "b".into()])),
1609 ])),
1610 );
1611 }
1612
1613 #[test]
1616 fn metric_auto_write_string_creates_single_tag() {
1617 let metric = Metric::new(
1618 "m",
1619 MetricKind::Absolute,
1620 MetricValue::Counter { value: 1.0 },
1621 );
1622 let info = auto_mode_program_info();
1623 let mut target = VrlTarget::new(Event::Metric(metric), &info, MetricTagMode::Auto);
1624
1625 target
1626 .target_insert(
1627 &OwnedTargetPath::event(owned_value_path!("tags", "env")),
1628 Value::from("staging"),
1629 )
1630 .unwrap();
1631
1632 let VrlTarget::Metric { metric, .. } = target else {
1633 unreachable!()
1634 };
1635 let metric_tags = metric.tags().expect("tag should be set");
1636 let tag_set = metric_tags
1637 .iter_sets()
1638 .find(|(k, _)| *k == "env")
1639 .expect("env tag missing")
1640 .1;
1641 assert_eq!(
1642 tag_set.len(),
1643 1,
1644 "scalar assignment must produce a 1-element set"
1645 );
1646 assert_eq!(tag_set.as_single(), Some("staging"));
1647 }
1648
1649 #[test]
1653 fn metric_auto_write_array_creates_multi_value_tag() {
1654 let metric = Metric::new(
1655 "m",
1656 MetricKind::Absolute,
1657 MetricValue::Counter { value: 1.0 },
1658 );
1659 let info = auto_mode_program_info();
1660 let mut target = VrlTarget::new(Event::Metric(metric), &info, MetricTagMode::Auto);
1661
1662 target
1663 .target_insert(
1664 &OwnedTargetPath::event(owned_value_path!("tags", "shard")),
1665 Value::Array(vec!["a".into(), "b".into(), "c".into()]),
1666 )
1667 .unwrap();
1668
1669 let VrlTarget::Metric { metric, .. } = target else {
1670 unreachable!()
1671 };
1672 let metric_tags = metric.tags().expect("tag should be set");
1673 let tag_set = metric_tags
1674 .iter_sets()
1675 .find(|(k, _)| *k == "shard")
1676 .expect("shard tag missing")
1677 .1;
1678 assert_eq!(tag_set.len(), 3);
1679 let collected: Vec<Option<&str>> = tag_set.iter().collect();
1680 assert_eq!(
1681 collected,
1682 vec![Some("a"), Some("b"), Some("c")],
1683 "array assignment must preserve every element"
1684 );
1685 }
1686
1687 #[test]
1699 fn metric_auto_one_element_array_normalises_to_scalar() {
1700 let metric = Metric::new(
1701 "m",
1702 MetricKind::Absolute,
1703 MetricValue::Counter { value: 1.0 },
1704 );
1705 let info = auto_mode_program_info();
1706 let mut first_pass = VrlTarget::new(Event::Metric(metric), &info, MetricTagMode::Auto);
1707
1708 first_pass
1709 .target_insert(
1710 &OwnedTargetPath::event(owned_value_path!("tags", "region")),
1711 Value::Array(vec!["us-east-1".into()]),
1712 )
1713 .unwrap();
1714
1715 let VrlTarget::Metric { metric, .. } = first_pass else {
1716 unreachable!()
1717 };
1718 let tag_set = metric
1719 .tags()
1720 .expect("tag should be set")
1721 .iter_sets()
1722 .find(|(k, _)| *k == "region")
1723 .expect("region tag missing")
1724 .1;
1725 assert_eq!(
1726 tag_set.len(),
1727 1,
1728 "storage layer must collapse a 1-element multi-value tag to a Single",
1729 );
1730
1731 let second_pass = VrlTarget::new(Event::Metric(metric), &info, MetricTagMode::Auto);
1732 let tags = second_pass
1733 .target_get(&OwnedTargetPath::event(owned_value_path!("tags")))
1734 .unwrap()
1735 .unwrap()
1736 .clone();
1737
1738 assert_eq!(
1739 tags,
1740 Value::Object(BTreeMap::from([(
1741 "region".into(),
1742 Value::from("us-east-1"),
1743 )])),
1744 "a length-1 array written in one Auto pass must read back as a \
1745 scalar on the next pass",
1746 );
1747 }
1748
1749 #[test]
1754 fn metric_auto_read_empty_tag_set_returns_array() {
1755 use super::super::metric::{MetricTags, TagValueSet};
1756 let mut tags = BTreeMap::new();
1757 tags.insert("empty".to_string(), TagValueSet::default());
1758 let metric = Metric::new(
1759 "m",
1760 MetricKind::Absolute,
1761 MetricValue::Counter { value: 1.0 },
1762 )
1763 .with_tags(Some(MetricTags(tags)));
1764
1765 let info = auto_mode_program_info();
1766 let target = VrlTarget::new(Event::Metric(metric), &info, MetricTagMode::Auto);
1767
1768 let tags = target
1769 .target_get(&OwnedTargetPath::event(owned_value_path!("tags")))
1770 .unwrap()
1771 .unwrap()
1772 .clone();
1773
1774 assert_eq!(
1775 tags,
1776 Value::Object(BTreeMap::from([("empty".into(), Value::Array(vec![]))])),
1777 "empty tag set must surface as an empty array, not as null",
1778 );
1779 }
1780
1781 #[test]
1785 fn metric_auto_write_null_creates_single_bare_tag() {
1786 let metric = Metric::new(
1787 "m",
1788 MetricKind::Absolute,
1789 MetricValue::Counter { value: 1.0 },
1790 );
1791 let info = auto_mode_program_info();
1792 let mut target = VrlTarget::new(Event::Metric(metric), &info, MetricTagMode::Auto);
1793
1794 target
1795 .target_insert(
1796 &OwnedTargetPath::event(owned_value_path!("tags", "bare")),
1797 Value::Null,
1798 )
1799 .unwrap();
1800
1801 let VrlTarget::Metric { metric, .. } = target else {
1802 unreachable!()
1803 };
1804 let metric_tags = metric.tags().expect("tag should be set");
1805 let tag_set = metric_tags
1806 .iter_sets()
1807 .find(|(k, _)| *k == "bare")
1808 .expect("bare tag missing")
1809 .1;
1810 assert_eq!(tag_set.len(), 1);
1811 let collected: Vec<Option<&str>> = tag_set.iter().collect();
1812 assert_eq!(
1813 collected,
1814 vec![None],
1815 "null must produce a single bare value"
1816 );
1817 }
1818
1819 #[test]
1823 fn metric_auto_remove_multi_value_tag_returns_array() {
1824 use super::super::metric::{MetricTags, TagValue, TagValueSet};
1825 let metric = Metric::new(
1826 "m",
1827 MetricKind::Absolute,
1828 MetricValue::Counter { value: 1.0 },
1829 )
1830 .with_tags(Some(MetricTags(BTreeMap::from([(
1831 "shard".to_string(),
1832 TagValueSet::from(vec![
1833 TagValue::from("a".to_string()),
1834 TagValue::from("b".to_string()),
1835 ]),
1836 )]))));
1837
1838 let info = auto_mode_program_info();
1839 let mut target = VrlTarget::new(Event::Metric(metric), &info, MetricTagMode::Auto);
1840
1841 let removed = target
1842 .target_remove(
1843 &OwnedTargetPath::event(owned_value_path!("tags", "shard")),
1844 true,
1845 )
1846 .unwrap()
1847 .expect("shard tag should be removed");
1848
1849 assert_eq!(removed, Value::Array(vec!["a".into(), "b".into()]));
1850
1851 let VrlTarget::Metric { metric, .. } = target else {
1852 unreachable!()
1853 };
1854 assert!(metric.tags().is_none());
1855 }
1856
1857 #[test]
1859 fn metric_auto_remove_all_tags_preserves_shapes() {
1860 use super::super::metric::{MetricTags, TagValueSet};
1861 let mut tags = BTreeMap::new();
1862 tags.insert(
1863 "env".to_string(),
1864 TagValueSet::from(vec!["prod".to_string()]),
1865 );
1866 tags.insert(
1867 "shard".to_string(),
1868 TagValueSet::from(vec!["a".to_string(), "b".to_string()]),
1869 );
1870 let metric = Metric::new(
1871 "m",
1872 MetricKind::Absolute,
1873 MetricValue::Counter { value: 1.0 },
1874 )
1875 .with_tags(Some(MetricTags(tags)));
1876
1877 let info = auto_mode_program_info();
1878 let mut target = VrlTarget::new(Event::Metric(metric), &info, MetricTagMode::Auto);
1879
1880 let removed = target
1881 .target_remove(&OwnedTargetPath::event(owned_value_path!("tags")), true)
1882 .unwrap()
1883 .expect("tags should be removed");
1884
1885 assert_eq!(
1886 removed,
1887 Value::Object(BTreeMap::from([
1888 ("env".into(), Value::from("prod")),
1889 ("shard".into(), Value::Array(vec!["a".into(), "b".into()]))
1890 ]))
1891 );
1892 }
1893
1894 #[test]
1896 fn metric_full_remove_multi_value_tag_returns_array() {
1897 use super::super::metric::{MetricTags, TagValue, TagValueSet};
1898 let metric = Metric::new(
1899 "m",
1900 MetricKind::Absolute,
1901 MetricValue::Counter { value: 1.0 },
1902 )
1903 .with_tags(Some(MetricTags(BTreeMap::from([(
1904 "shard".to_string(),
1905 TagValueSet::from(vec![
1906 TagValue::from("a".to_string()),
1907 TagValue::from("b".to_string()),
1908 ]),
1909 )]))));
1910
1911 let info = ProgramInfo {
1912 fallible: false,
1913 abortable: false,
1914 target_queries: vec![],
1915 target_assignments: vec![],
1916 };
1917 let mut target = VrlTarget::new(Event::Metric(metric), &info, MetricTagMode::Full);
1918
1919 let removed = target
1920 .target_remove(
1921 &OwnedTargetPath::event(owned_value_path!("tags", "shard")),
1922 true,
1923 )
1924 .unwrap()
1925 .expect("shard tag should be removed");
1926
1927 assert_eq!(removed, Value::Array(vec!["a".into(), "b".into()]));
1928 }
1929}