vector_core/schema/
definition.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use lookup::lookup_v2::TargetPath;
4use lookup::{owned_value_path, OwnedTargetPath, OwnedValuePath, PathPrefix};
5use vrl::value::{kind::Collection, Kind};
6
7use crate::config::{log_schema, LegacyKey, LogNamespace};
8
9/// The definition of a schema.
10///
11/// This struct contains all the information needed to inspect the schema of an event emitted by
12/// a source/transform.
13#[derive(Clone, Debug, PartialEq, PartialOrd)]
14pub struct Definition {
15    /// The type of the event
16    event_kind: Kind,
17
18    /// The type of the metadata.
19    metadata_kind: Kind,
20
21    /// Semantic meaning assigned to fields within the collection.
22    ///
23    /// The value within this map points to a path inside the `event_kind`.
24    /// Meanings currently can't point to metadata.
25    meaning: BTreeMap<String, MeaningPointer>,
26
27    /// Type definitions of components can change depending on the log namespace chosen.
28    /// This records which ones are possible.
29    /// An empty set means the definition can't be for a log
30    log_namespaces: BTreeSet<LogNamespace>,
31}
32
33/// In regular use, a semantic meaning points to exactly _one_ location in the collection. However,
34/// when merging two [`Definition`]s, we need to be able to allow for two definitions with the same
35/// semantic meaning identifier to be merged together.
36///
37/// We cannot error when this happens, because a follow-up component (such as the `remap`
38/// transform) might rectify the issue of having a semantic meaning with multiple pointers.
39///
40/// Because of this, we encapsulate this state in an enum. The schema validation step done by the
41/// sink builder, will return an error if the definition stores an "invalid" meaning pointer.
42#[derive(Clone, Debug, PartialEq, PartialOrd)]
43enum MeaningPointer {
44    Valid(OwnedTargetPath),
45    Invalid(BTreeSet<OwnedTargetPath>),
46}
47
48impl MeaningPointer {
49    fn merge(self, other: Self) -> Self {
50        let set = match (self, other) {
51            (Self::Valid(lhs), Self::Valid(rhs)) if lhs == rhs => return Self::Valid(lhs),
52            (Self::Valid(lhs), Self::Valid(rhs)) => BTreeSet::from([lhs, rhs]),
53            (Self::Valid(lhs), Self::Invalid(mut rhs)) => {
54                rhs.insert(lhs);
55                rhs
56            }
57            (Self::Invalid(mut lhs), Self::Valid(rhs)) => {
58                lhs.insert(rhs);
59                lhs
60            }
61            (Self::Invalid(mut lhs), Self::Invalid(rhs)) => {
62                lhs.extend(rhs);
63                lhs
64            }
65        };
66
67        Self::Invalid(set)
68    }
69}
70
71impl Definition {
72    /// The most general possible definition. The `Kind` is `any`, and all `log_namespaces` are enabled.
73    pub fn any() -> Self {
74        Self {
75            event_kind: Kind::any(),
76            metadata_kind: Kind::any(),
77            meaning: BTreeMap::default(),
78            log_namespaces: [LogNamespace::Legacy, LogNamespace::Vector].into(),
79        }
80    }
81
82    /// Creates a new definition that is of the event kind specified, and an empty object for metadata.
83    /// There are no meanings.
84    /// The `log_namespaces` are used to list the possible namespaces the schema is for.
85    pub fn new_with_default_metadata(
86        event_kind: Kind,
87        log_namespaces: impl Into<BTreeSet<LogNamespace>>,
88    ) -> Self {
89        Self {
90            event_kind,
91            metadata_kind: Kind::object(Collection::any()),
92            meaning: BTreeMap::default(),
93            log_namespaces: log_namespaces.into(),
94        }
95    }
96
97    /// Creates a new definition, specifying both the event and metadata kind.
98    /// There are no meanings.
99    /// The `log_namespaces` are used to list the possible namespaces the schema is for.
100    pub fn new(
101        event_kind: Kind,
102        metadata_kind: Kind,
103        log_namespaces: impl Into<BTreeSet<LogNamespace>>,
104    ) -> Self {
105        Self {
106            event_kind,
107            metadata_kind,
108            meaning: BTreeMap::default(),
109            log_namespaces: log_namespaces.into(),
110        }
111    }
112
113    /// An object with any fields, and the `Legacy` namespace.
114    /// This is the default schema for a source that does not explicitly provide one yet.
115    pub fn default_legacy_namespace() -> Self {
116        Self::new_with_default_metadata(Kind::any_object(), [LogNamespace::Legacy])
117    }
118
119    /// An object with no fields, and the `Legacy` namespace.
120    /// This is what most sources use for the legacy namespace.
121    pub fn empty_legacy_namespace() -> Self {
122        Self::new_with_default_metadata(Kind::object(Collection::empty()), [LogNamespace::Legacy])
123    }
124
125    /// Returns the source schema for a source that produce the listed log namespaces,
126    /// but an explicit schema was not provided.
127    pub fn default_for_namespace(log_namespaces: &BTreeSet<LogNamespace>) -> Self {
128        let is_legacy = log_namespaces.contains(&LogNamespace::Legacy);
129        let is_vector = log_namespaces.contains(&LogNamespace::Vector);
130        match (is_legacy, is_vector) {
131            (false, false) => Self::new_with_default_metadata(Kind::any(), []),
132            (true, false) => Self::default_legacy_namespace(),
133            (false, true) => Self::new_with_default_metadata(Kind::any(), [LogNamespace::Vector]),
134            (true, true) => Self::any(),
135        }
136    }
137
138    /// The set of possible log namespaces that events can use. When merged, this is the union of all inputs.
139    pub fn log_namespaces(&self) -> &BTreeSet<LogNamespace> {
140        &self.log_namespaces
141    }
142
143    /// Adds the `source_type` and `ingest_timestamp` metadata fields, which are added to every Vector source.
144    /// This function should be called in the same order as the values are actually inserted into the event.
145    #[must_use]
146    pub fn with_standard_vector_source_metadata(self) -> Self {
147        self.with_vector_metadata(
148            log_schema().source_type_key(),
149            &owned_value_path!("source_type"),
150            Kind::bytes(),
151            None,
152        )
153        .with_vector_metadata(
154            log_schema().timestamp_key(),
155            &owned_value_path!("ingest_timestamp"),
156            Kind::timestamp(),
157            None,
158        )
159    }
160
161    /// This should be used wherever `LogNamespace::insert_source_metadata` is used to insert metadata.
162    /// This automatically detects which log namespaces are used, and also automatically
163    /// determines if there are possible conflicts from existing field names (usually from the selected decoder).
164    /// This function should be called in the same order as the values are actually inserted into the event.
165    #[must_use]
166    pub fn with_source_metadata(
167        self,
168        source_name: &str,
169        legacy_path: Option<LegacyKey<OwnedValuePath>>,
170        vector_path: &OwnedValuePath,
171        kind: Kind,
172        meaning: Option<&str>,
173    ) -> Self {
174        self.with_namespaced_metadata(source_name, legacy_path, vector_path, kind, meaning)
175    }
176
177    /// This should be used wherever `LogNamespace::insert_vector_metadata` is used to insert metadata.
178    /// This automatically detects which log namespaces are used, and also automatically
179    /// determines if there are possible conflicts from existing field names (usually from the selected decoder).
180    /// This function should be called in the same order as the values are actually inserted into the event.
181    #[must_use]
182    pub fn with_vector_metadata(
183        self,
184        legacy_path: Option<&OwnedValuePath>,
185        vector_path: &OwnedValuePath,
186        kind: Kind,
187        meaning: Option<&str>,
188    ) -> Self {
189        self.with_namespaced_metadata(
190            "vector",
191            legacy_path.cloned().map(LegacyKey::InsertIfEmpty),
192            vector_path,
193            kind,
194            meaning,
195        )
196    }
197
198    /// This generalizes the `LogNamespace::insert_*` methods for type definitions.
199    /// This assumes the legacy key is either guaranteed to not collide or is inserted with `try_insert`.
200    fn with_namespaced_metadata(
201        self,
202        prefix: &str,
203        legacy_path: Option<LegacyKey<OwnedValuePath>>,
204        vector_path: &OwnedValuePath,
205        kind: Kind,
206        meaning: Option<&str>,
207    ) -> Self {
208        let legacy_definition = legacy_path.and_then(|legacy_path| {
209            if self.log_namespaces.contains(&LogNamespace::Legacy) {
210                match legacy_path {
211                    LegacyKey::InsertIfEmpty(legacy_path) => Some(self.clone().try_with_field(
212                        &legacy_path,
213                        kind.clone(),
214                        meaning,
215                    )),
216                    LegacyKey::Overwrite(legacy_path) => Some(self.clone().with_event_field(
217                        &legacy_path,
218                        kind.clone(),
219                        meaning,
220                    )),
221                }
222            } else {
223                None
224            }
225        });
226
227        let vector_definition = if self.log_namespaces.contains(&LogNamespace::Vector) {
228            Some(self.clone().with_metadata_field(
229                &vector_path.with_field_prefix(prefix),
230                kind,
231                meaning,
232            ))
233        } else {
234            None
235        };
236
237        match (legacy_definition, vector_definition) {
238            (Some(a), Some(b)) => a.merge(b),
239            (Some(x), _) | (_, Some(x)) => x,
240            (None, None) => self,
241        }
242    }
243
244    /// Add type information for an event or metadata field.
245    /// A non-root required field means the root type must be an object, so the type will be automatically
246    /// restricted to an object.
247    ///
248    /// # Panics
249    /// - If the path is not root, and the definition does not allow the type to be an object.
250    #[must_use]
251    pub fn with_field(
252        self,
253        target_path: &OwnedTargetPath,
254        kind: Kind,
255        meaning: Option<&str>,
256    ) -> Self {
257        match target_path.prefix {
258            PathPrefix::Event => self.with_event_field(&target_path.path, kind, meaning),
259            PathPrefix::Metadata => self.with_metadata_field(&target_path.path, kind, meaning),
260        }
261    }
262
263    /// Add type information for an event field.
264    /// A non-root required field means the root type must be an object, so the type will be automatically
265    /// restricted to an object.
266    ///
267    /// # Panics
268    /// - If the path is not root, and the definition does not allow the type to be an object.
269    /// - Provided path has one or more coalesced segments (e.g. `.(foo | bar)`).
270    #[must_use]
271    pub fn with_event_field(
272        mut self,
273        path: &OwnedValuePath,
274        kind: Kind,
275        meaning: Option<&str>,
276    ) -> Self {
277        if !path.is_root() {
278            assert!(
279                self.event_kind.as_object().is_some(),
280                "Setting a field on a value that cannot be an object"
281            );
282        }
283
284        self.event_kind.set_at_path(path, kind);
285
286        if let Some(meaning) = meaning {
287            self.meaning.insert(
288                meaning.to_owned(),
289                MeaningPointer::Valid(OwnedTargetPath::event(path.clone())),
290            );
291        }
292
293        self
294    }
295
296    /// Add type information for an event field.
297    /// This inserts type information similar to `LogEvent::try_insert`.
298    #[must_use]
299    pub fn try_with_field(
300        mut self,
301        path: &OwnedValuePath,
302        kind: Kind,
303        meaning: Option<&str>,
304    ) -> Self {
305        let existing_type = self.event_kind.at_path(path);
306
307        if existing_type.is_undefined() {
308            // Guaranteed to never be set, so the insertion will always succeed.
309            self.with_event_field(path, kind, meaning)
310        } else if !existing_type.contains_undefined() {
311            // Guaranteed to always be set (or is never), so the insertion will always fail.
312            self
313        } else {
314            // Not sure if the insertion will be successful. The type definition should contain both
315            // possibilities. The meaning is not set, since it can't be relied on.
316
317            let success_definition = self.clone().with_event_field(path, kind, None);
318            // If the existing type contains `undefined`, the new type will always be used, so remove it.
319            self.event_kind
320                .set_at_path(path, existing_type.without_undefined());
321            self.merge(success_definition)
322        }
323    }
324
325    /// Add type information for an event field.
326    /// A non-root required field means the root type must be an object, so the type will be automatically
327    /// restricted to an object.
328    ///
329    /// # Panics
330    /// - If the path is not root, and the definition does not allow the type to be an object
331    /// - Provided path has one or more coalesced segments (e.g. `.(foo | bar)`).
332    #[must_use]
333    pub fn with_metadata_field(
334        mut self,
335        path: &OwnedValuePath,
336        kind: Kind,
337        meaning: Option<&str>,
338    ) -> Self {
339        if !path.is_root() {
340            assert!(
341                self.metadata_kind.as_object().is_some(),
342                "Setting a field on a value that cannot be an object"
343            );
344        }
345
346        self.metadata_kind.set_at_path(path, kind);
347
348        if let Some(meaning) = meaning {
349            self.meaning.insert(
350                meaning.to_owned(),
351                MeaningPointer::Valid(OwnedTargetPath::metadata(path.clone())),
352            );
353        }
354
355        self
356    }
357
358    /// Add type information for an optional event field.
359    ///
360    /// # Panics
361    ///
362    /// See `Definition::require_field`.
363    #[must_use]
364    pub fn optional_field(self, path: &OwnedValuePath, kind: Kind, meaning: Option<&str>) -> Self {
365        self.with_event_field(path, kind.or_undefined(), meaning)
366    }
367
368    /// Register a semantic meaning for the definition.
369    ///
370    /// # Panics
371    ///
372    /// This method panics if the provided path points to an unknown location in the collection.
373    #[must_use]
374    pub fn with_meaning(mut self, target_path: OwnedTargetPath, meaning: &str) -> Self {
375        self.add_meaning(target_path, meaning);
376        self
377    }
378
379    /// Adds the meaning pointing to the given path to our list of meanings.
380    ///
381    /// # Panics
382    ///
383    /// This method panics if the provided path points to an unknown location in the collection.
384    pub fn add_meaning(&mut self, target_path: OwnedTargetPath, meaning: &str) {
385        self.try_with_meaning(target_path, meaning)
386            .unwrap_or_else(|err| panic!("{}", err));
387    }
388
389    /// Register a semantic meaning for the definition.
390    ///
391    /// # Errors
392    ///
393    /// Returns an error if the provided path points to an unknown location in the collection.
394    pub fn try_with_meaning(
395        &mut self,
396        target_path: OwnedTargetPath,
397        meaning: &str,
398    ) -> Result<(), &'static str> {
399        match target_path.prefix {
400            PathPrefix::Event
401                if !self
402                    .event_kind
403                    .at_path(&target_path.path)
404                    .contains_any_defined() =>
405            {
406                Err("meaning must point to a valid path")
407            }
408
409            PathPrefix::Metadata
410                if !self
411                    .metadata_kind
412                    .at_path(&target_path.path)
413                    .contains_any_defined() =>
414            {
415                Err("meaning must point to a valid path")
416            }
417
418            _ => {
419                self.meaning
420                    .insert(meaning.to_owned(), MeaningPointer::Valid(target_path));
421                Ok(())
422            }
423        }
424    }
425
426    /// Set the kind for all unknown fields.
427    #[must_use]
428    pub fn unknown_fields(mut self, unknown: impl Into<Kind>) -> Self {
429        let unknown = unknown.into();
430        if let Some(object) = self.event_kind.as_object_mut() {
431            object.set_unknown(unknown.clone());
432        }
433        if let Some(array) = self.event_kind.as_array_mut() {
434            array.set_unknown(unknown);
435        }
436        self
437    }
438
439    /// Merge `other` definition into `self`.
440    ///
441    /// This just takes the union of both definitions.
442    #[must_use]
443    pub fn merge(mut self, mut other: Self) -> Self {
444        for (other_id, other_meaning) in other.meaning {
445            let meaning = match self.meaning.remove(&other_id) {
446                Some(this_meaning) => this_meaning.merge(other_meaning),
447                None => other_meaning,
448            };
449
450            self.meaning.insert(other_id, meaning);
451        }
452
453        self.event_kind = self.event_kind.union(other.event_kind);
454        self.metadata_kind = self.metadata_kind.union(other.metadata_kind);
455        self.log_namespaces.append(&mut other.log_namespaces);
456        self
457    }
458
459    /// If the schema definition depends on the `LogNamespace`, this combines the individual
460    /// definitions for each `LogNamespace`.
461    pub fn combine_log_namespaces(
462        log_namespaces: &BTreeSet<LogNamespace>,
463        legacy: Self,
464        vector: Self,
465    ) -> Self {
466        let mut combined =
467            Definition::new_with_default_metadata(Kind::never(), log_namespaces.clone());
468
469        if log_namespaces.contains(&LogNamespace::Legacy) {
470            combined = combined.merge(legacy);
471        }
472        if log_namespaces.contains(&LogNamespace::Vector) {
473            combined = combined.merge(vector);
474        }
475        combined
476    }
477
478    /// Returns an `OwnedTargetPath` into an event, based on the provided `meaning`, if the meaning exists.
479    pub fn meaning_path(&self, meaning: &str) -> Option<&OwnedTargetPath> {
480        match self.meaning.get(meaning) {
481            Some(MeaningPointer::Valid(path)) => Some(path),
482            None | Some(MeaningPointer::Invalid(_)) => None,
483        }
484    }
485
486    pub fn invalid_meaning(&self, meaning: &str) -> Option<&BTreeSet<OwnedTargetPath>> {
487        match &self.meaning.get(meaning) {
488            Some(MeaningPointer::Invalid(paths)) => Some(paths),
489            None | Some(MeaningPointer::Valid(_)) => None,
490        }
491    }
492
493    pub fn meanings(&self) -> impl Iterator<Item = (&String, &OwnedTargetPath)> {
494        self.meaning
495            .iter()
496            .filter_map(|(id, pointer)| match pointer {
497                MeaningPointer::Valid(path) => Some((id, path)),
498                MeaningPointer::Invalid(_) => None,
499            })
500    }
501
502    /// Adds the meanings provided by an iterator over the given meanings.
503    ///
504    /// # Panics
505    ///
506    /// This method panics if the provided path from any of the incoming meanings point to
507    /// an unknown location in the collection.
508    pub fn add_meanings<'a>(
509        &'a mut self,
510        meanings: impl Iterator<Item = (&'a String, &'a OwnedTargetPath)>,
511    ) {
512        for (meaning, path) in meanings {
513            self.add_meaning(path.clone(), meaning);
514        }
515    }
516
517    pub fn event_kind(&self) -> &Kind {
518        &self.event_kind
519    }
520
521    pub fn event_kind_mut(&mut self) -> &mut Kind {
522        &mut self.event_kind
523    }
524
525    pub fn metadata_kind(&self) -> &Kind {
526        &self.metadata_kind
527    }
528
529    #[allow(clippy::needless_pass_by_value)]
530    pub fn kind_at<'a>(&self, target_path: impl TargetPath<'a>) -> Kind {
531        match target_path.prefix() {
532            PathPrefix::Event => self.event_kind.at_path(target_path.value_path()),
533            PathPrefix::Metadata => self.metadata_kind.at_path(target_path.value_path()),
534        }
535    }
536}
537
538#[cfg(any(test, feature = "test"))]
539mod test_utils {
540    use super::{Definition, Kind};
541    use crate::event::{Event, LogEvent};
542
543    impl Definition {
544        /// Checks that the schema definition is _valid_ for the given event.
545        ///
546        /// # Errors
547        ///
548        /// If the definition is not valid, debug info will be returned.
549        pub fn is_valid_for_event(&self, event: &Event) -> Result<(), String> {
550            if let Some(log) = event.maybe_as_log() {
551                let log: &LogEvent = log;
552
553                let actual_kind = Kind::from(log.value());
554                if let Err(path) = self.event_kind.is_superset(&actual_kind) {
555                    return Result::Err(format!("Event value doesn't match at path: {}\n\nEvent type at path = {:?}\n\nDefinition at path = {:?}",
556                        path,
557                        actual_kind.at_path(&path).debug_info(),
558                        self.event_kind.at_path(&path).debug_info()
559                    ));
560                }
561
562                let actual_metadata_kind = Kind::from(log.metadata().value());
563                if let Err(path) = self.metadata_kind.is_superset(&actual_metadata_kind) {
564                    // return Result::Err(format!("Event metadata doesn't match definition.\n\nDefinition type=\n{:?}\n\nActual event metadata type=\n{:?}\n",
565                    //                            self.metadata_kind.debug_info(), actual_metadata_kind.debug_info()));
566                    return Result::Err(format!(
567                        "Event METADATA value doesn't match at path: {}\n\nMetadata type at path = {:?}\n\nDefinition at path = {:?}",
568                        path,
569                        actual_metadata_kind.at_path(&path).debug_info(),
570                        self.metadata_kind.at_path(&path).debug_info()
571                    ));
572                }
573                if !self.log_namespaces.contains(&log.namespace()) {
574                    return Result::Err(format!(
575                        "Event uses the {:?} LogNamespace, but the definition only contains: {:?}",
576                        log.namespace(),
577                        self.log_namespaces
578                    ));
579                }
580
581                Ok(())
582            } else {
583                // schema definitions currently only apply to logs
584                Ok(())
585            }
586        }
587
588        /// Asserts that the schema definition is _valid_ for the given event.
589        ///
590        /// # Panics
591        ///
592        /// If the definition is not valid for the event.
593        pub fn assert_valid_for_event(&self, event: &Event) {
594            if let Err(err) = self.is_valid_for_event(event) {
595                panic!("Schema definition assertion failed: {err}");
596            }
597        }
598
599        /// Asserts that the schema definition is _invalid_ for the given event.
600        ///
601        /// # Panics
602        ///
603        /// If the definition is valid for the event.
604        pub fn assert_invalid_for_event(&self, event: &Event) {
605            assert!(
606                self.is_valid_for_event(event).is_err(),
607                "Schema definition assertion should not be valid"
608            );
609        }
610    }
611}
612
613#[cfg(test)]
614mod tests {
615    use crate::event::{Event, EventMetadata, LogEvent};
616    use lookup::lookup_v2::parse_target_path;
617    use lookup::owned_value_path;
618    use std::collections::{BTreeMap, HashMap};
619    use vrl::value::Value;
620
621    use super::*;
622
623    #[test]
624    fn test_definition_validity() {
625        struct TestCase {
626            title: &'static str,
627            definition: Definition,
628            event: Event,
629            valid: bool,
630        }
631
632        for TestCase {
633            title,
634            definition,
635            event,
636            valid,
637        } in [
638            TestCase {
639                title: "match",
640                definition: Definition::new(Kind::any(), Kind::any(), [LogNamespace::Legacy]),
641                event: Event::Log(LogEvent::from(BTreeMap::new())),
642                valid: true,
643            },
644            TestCase {
645                title: "event mismatch",
646                definition: Definition::new(
647                    Kind::object(Collection::empty()),
648                    Kind::any(),
649                    [LogNamespace::Legacy],
650                ),
651                event: Event::Log(LogEvent::from(BTreeMap::from([("foo".into(), 4.into())]))),
652                valid: false,
653            },
654            TestCase {
655                title: "metadata mismatch",
656                definition: Definition::new(
657                    Kind::any(),
658                    Kind::object(Collection::empty()),
659                    [LogNamespace::Legacy],
660                ),
661                event: Event::Log(LogEvent::from_parts(
662                    Value::Object(BTreeMap::new()),
663                    EventMetadata::default_with_value(
664                        BTreeMap::from([("foo".into(), 4.into())]).into(),
665                    ),
666                )),
667                valid: false,
668            },
669            TestCase {
670                title: "wrong log namespace",
671                definition: Definition::new(Kind::any(), Kind::any(), []),
672                event: Event::Log(LogEvent::from(BTreeMap::new())),
673                valid: false,
674            },
675            TestCase {
676                title: "event mismatch - null vs undefined",
677                definition: Definition::new(
678                    Kind::object(Collection::empty()),
679                    Kind::any(),
680                    [LogNamespace::Legacy],
681                ),
682                event: Event::Log(LogEvent::from(BTreeMap::from([(
683                    "foo".into(),
684                    Value::Null,
685                )]))),
686                valid: false,
687            },
688        ] {
689            let result = definition.is_valid_for_event(&event);
690            assert_eq!(result.is_ok(), valid, "{title}");
691        }
692    }
693
694    #[test]
695    fn test_empty_legacy_field() {
696        let definition = Definition::default_legacy_namespace().with_vector_metadata(
697            Some(&owned_value_path!()),
698            &owned_value_path!(),
699            Kind::integer(),
700            None,
701        );
702
703        // adding empty string legacy key doesn't change the definition (insertion will never succeed)
704        assert_eq!(definition, Definition::default_legacy_namespace());
705    }
706
707    #[test]
708    fn test_required_field() {
709        struct TestCase {
710            path: OwnedValuePath,
711            kind: Kind,
712            meaning: Option<&'static str>,
713            want: Definition,
714        }
715
716        for (
717            title,
718            TestCase {
719                path,
720                kind,
721                meaning,
722                want,
723            },
724        ) in HashMap::from([
725            (
726                "simple",
727                TestCase {
728                    path: owned_value_path!("foo"),
729                    kind: Kind::boolean(),
730                    meaning: Some("foo_meaning"),
731                    want: Definition {
732                        event_kind: Kind::object(BTreeMap::from([("foo".into(), Kind::boolean())])),
733                        metadata_kind: Kind::object(Collection::empty()),
734                        meaning: [(
735                            "foo_meaning".to_owned(),
736                            MeaningPointer::Valid(parse_target_path("foo").unwrap()),
737                        )]
738                        .into(),
739                        log_namespaces: BTreeSet::new(),
740                    },
741                },
742            ),
743            (
744                "nested fields",
745                TestCase {
746                    path: owned_value_path!("foo", "bar"),
747                    kind: Kind::regex().or_null(),
748                    meaning: Some("foobar"),
749                    want: Definition {
750                        event_kind: Kind::object(BTreeMap::from([(
751                            "foo".into(),
752                            Kind::object(BTreeMap::from([("bar".into(), Kind::regex().or_null())])),
753                        )])),
754                        metadata_kind: Kind::object(Collection::empty()),
755                        meaning: [(
756                            "foobar".to_owned(),
757                            MeaningPointer::Valid(parse_target_path(".foo.bar").unwrap()),
758                        )]
759                        .into(),
760                        log_namespaces: BTreeSet::new(),
761                    },
762                },
763            ),
764            (
765                "no meaning",
766                TestCase {
767                    path: owned_value_path!("foo"),
768                    kind: Kind::boolean(),
769                    meaning: None,
770                    want: Definition {
771                        event_kind: Kind::object(BTreeMap::from([("foo".into(), Kind::boolean())])),
772                        metadata_kind: Kind::object(Collection::empty()),
773                        meaning: BTreeMap::default(),
774                        log_namespaces: BTreeSet::new(),
775                    },
776                },
777            ),
778        ]) {
779            let got = Definition::empty_legacy_namespace().with_event_field(&path, kind, meaning);
780            assert_eq!(got.event_kind(), want.event_kind(), "{title}");
781        }
782    }
783
784    #[test]
785    fn test_optional_field() {
786        struct TestCase {
787            path: OwnedValuePath,
788            kind: Kind,
789            meaning: Option<&'static str>,
790            want: Definition,
791        }
792
793        for (
794            title,
795            TestCase {
796                path,
797                kind,
798                meaning,
799                want,
800            },
801        ) in [
802            (
803                "simple",
804                TestCase {
805                    path: owned_value_path!("foo"),
806                    kind: Kind::boolean(),
807                    meaning: Some("foo_meaning"),
808                    want: Definition {
809                        event_kind: Kind::object(BTreeMap::from([(
810                            "foo".into(),
811                            Kind::boolean().or_undefined(),
812                        )])),
813                        metadata_kind: Kind::object(Collection::any()),
814                        meaning: [(
815                            "foo_meaning".to_owned(),
816                            MeaningPointer::Valid(parse_target_path("foo").unwrap()),
817                        )]
818                        .into(),
819                        log_namespaces: BTreeSet::new(),
820                    },
821                },
822            ),
823            (
824                "nested fields",
825                TestCase {
826                    path: owned_value_path!("foo", "bar"),
827                    kind: Kind::regex().or_null(),
828                    meaning: Some("foobar"),
829                    want: Definition {
830                        event_kind: Kind::object(BTreeMap::from([(
831                            "foo".into(),
832                            Kind::object(BTreeMap::from([(
833                                "bar".into(),
834                                Kind::regex().or_null().or_undefined(),
835                            )])),
836                        )])),
837                        metadata_kind: Kind::object(Collection::any()),
838                        meaning: [(
839                            "foobar".to_owned(),
840                            MeaningPointer::Valid(parse_target_path(".foo.bar").unwrap()),
841                        )]
842                        .into(),
843                        log_namespaces: BTreeSet::new(),
844                    },
845                },
846            ),
847            (
848                "no meaning",
849                TestCase {
850                    path: owned_value_path!("foo"),
851                    kind: Kind::boolean(),
852                    meaning: None,
853                    want: Definition {
854                        event_kind: Kind::object(BTreeMap::from([(
855                            "foo".into(),
856                            Kind::boolean().or_undefined(),
857                        )])),
858                        metadata_kind: Kind::object(Collection::any()),
859                        meaning: BTreeMap::default(),
860                        log_namespaces: BTreeSet::new(),
861                    },
862                },
863            ),
864        ] {
865            let mut got = Definition::new_with_default_metadata(Kind::object(BTreeMap::new()), []);
866            got = got.optional_field(&path, kind, meaning);
867
868            assert_eq!(got, want, "{title}");
869        }
870    }
871
872    #[test]
873    fn test_unknown_fields() {
874        let want = Definition {
875            event_kind: Kind::object(Collection::from_unknown(Kind::bytes().or_integer())),
876            metadata_kind: Kind::object(Collection::any()),
877            meaning: BTreeMap::default(),
878            log_namespaces: BTreeSet::new(),
879        };
880
881        let mut got = Definition::new_with_default_metadata(Kind::object(Collection::empty()), []);
882        got = got.unknown_fields(Kind::boolean());
883        got = got.unknown_fields(Kind::bytes().or_integer());
884
885        assert_eq!(got, want);
886    }
887
888    #[test]
889    fn test_meaning_path() {
890        let def = Definition::new(
891            Kind::object(Collection::empty()),
892            Kind::object(Collection::empty()),
893            [LogNamespace::Legacy],
894        )
895        .with_event_field(
896            &owned_value_path!("foo"),
897            Kind::boolean(),
898            Some("foo_meaning"),
899        )
900        .with_metadata_field(
901            &owned_value_path!("bar"),
902            Kind::boolean(),
903            Some("bar_meaning"),
904        );
905
906        assert_eq!(
907            def.meaning_path("foo_meaning").unwrap(),
908            &OwnedTargetPath::event(owned_value_path!("foo"))
909        );
910        assert_eq!(
911            def.meaning_path("bar_meaning").unwrap(),
912            &OwnedTargetPath::metadata(owned_value_path!("bar"))
913        );
914    }
915
916    #[test]
917    #[allow(clippy::too_many_lines)]
918    fn test_merge() {
919        struct TestCase {
920            this: Definition,
921            other: Definition,
922            want: Definition,
923        }
924
925        for (title, TestCase { this, other, want }) in HashMap::from([
926            (
927                "equal definitions",
928                TestCase {
929                    this: Definition {
930                        event_kind: Kind::object(Collection::from(BTreeMap::from([(
931                            "foo".into(),
932                            Kind::boolean().or_null(),
933                        )]))),
934                        metadata_kind: Kind::object(Collection::empty()),
935                        meaning: BTreeMap::from([(
936                            "foo_meaning".to_owned(),
937                            MeaningPointer::Valid(parse_target_path("foo").unwrap()),
938                        )]),
939                        log_namespaces: BTreeSet::new(),
940                    },
941                    other: Definition {
942                        event_kind: Kind::object(Collection::from(BTreeMap::from([(
943                            "foo".into(),
944                            Kind::boolean().or_null(),
945                        )]))),
946                        metadata_kind: Kind::object(Collection::empty()),
947                        meaning: BTreeMap::from([(
948                            "foo_meaning".to_owned(),
949                            MeaningPointer::Valid(parse_target_path("foo").unwrap()),
950                        )]),
951                        log_namespaces: BTreeSet::new(),
952                    },
953                    want: Definition {
954                        event_kind: Kind::object(Collection::from(BTreeMap::from([(
955                            "foo".into(),
956                            Kind::boolean().or_null(),
957                        )]))),
958                        metadata_kind: Kind::object(Collection::empty()),
959                        meaning: BTreeMap::from([(
960                            "foo_meaning".to_owned(),
961                            MeaningPointer::Valid(parse_target_path("foo").unwrap()),
962                        )]),
963                        log_namespaces: BTreeSet::new(),
964                    },
965                },
966            ),
967            (
968                "this optional, other required",
969                TestCase {
970                    this: Definition {
971                        event_kind: Kind::object(Collection::from(BTreeMap::from([(
972                            "foo".into(),
973                            Kind::boolean().or_null(),
974                        )]))),
975                        metadata_kind: Kind::object(Collection::empty()),
976                        meaning: BTreeMap::default(),
977                        log_namespaces: BTreeSet::new(),
978                    },
979                    other: Definition {
980                        event_kind: Kind::object(Collection::from(BTreeMap::from([(
981                            "foo".into(),
982                            Kind::boolean(),
983                        )]))),
984                        metadata_kind: Kind::object(Collection::empty()),
985                        meaning: BTreeMap::default(),
986                        log_namespaces: BTreeSet::new(),
987                    },
988                    want: Definition {
989                        event_kind: Kind::object(Collection::from(BTreeMap::from([(
990                            "foo".into(),
991                            Kind::boolean().or_null(),
992                        )]))),
993                        metadata_kind: Kind::object(Collection::empty()),
994                        meaning: BTreeMap::default(),
995                        log_namespaces: BTreeSet::new(),
996                    },
997                },
998            ),
999            (
1000                "this required, other optional",
1001                TestCase {
1002                    this: Definition {
1003                        event_kind: Kind::object(Collection::from(BTreeMap::from([(
1004                            "foo".into(),
1005                            Kind::boolean(),
1006                        )]))),
1007                        metadata_kind: Kind::object(Collection::empty()),
1008                        meaning: BTreeMap::default(),
1009                        log_namespaces: BTreeSet::new(),
1010                    },
1011                    other: Definition {
1012                        event_kind: Kind::object(Collection::from(BTreeMap::from([(
1013                            "foo".into(),
1014                            Kind::boolean().or_null(),
1015                        )]))),
1016                        metadata_kind: Kind::object(Collection::empty()),
1017                        meaning: BTreeMap::default(),
1018                        log_namespaces: BTreeSet::new(),
1019                    },
1020                    want: Definition {
1021                        event_kind: Kind::object(Collection::from(BTreeMap::from([(
1022                            "foo".into(),
1023                            Kind::boolean().or_null(),
1024                        )]))),
1025                        metadata_kind: Kind::object(Collection::empty()),
1026                        meaning: BTreeMap::default(),
1027                        log_namespaces: BTreeSet::new(),
1028                    },
1029                },
1030            ),
1031            (
1032                "this required, other required",
1033                TestCase {
1034                    this: Definition {
1035                        event_kind: Kind::object(Collection::from(BTreeMap::from([(
1036                            "foo".into(),
1037                            Kind::boolean(),
1038                        )]))),
1039                        metadata_kind: Kind::object(Collection::empty()),
1040                        meaning: BTreeMap::default(),
1041                        log_namespaces: BTreeSet::new(),
1042                    },
1043                    other: Definition {
1044                        event_kind: Kind::object(Collection::from(BTreeMap::from([(
1045                            "foo".into(),
1046                            Kind::boolean(),
1047                        )]))),
1048                        metadata_kind: Kind::object(Collection::empty()),
1049                        meaning: BTreeMap::default(),
1050                        log_namespaces: BTreeSet::new(),
1051                    },
1052                    want: Definition {
1053                        event_kind: Kind::object(Collection::from(BTreeMap::from([(
1054                            "foo".into(),
1055                            Kind::boolean(),
1056                        )]))),
1057                        metadata_kind: Kind::object(Collection::empty()),
1058                        meaning: BTreeMap::default(),
1059                        log_namespaces: BTreeSet::new(),
1060                    },
1061                },
1062            ),
1063            (
1064                "same meaning, pointing to different paths",
1065                TestCase {
1066                    this: Definition {
1067                        event_kind: Kind::object(Collection::from(BTreeMap::from([(
1068                            "foo".into(),
1069                            Kind::boolean(),
1070                        )]))),
1071                        metadata_kind: Kind::object(Collection::empty()),
1072                        meaning: BTreeMap::from([(
1073                            "foo".into(),
1074                            MeaningPointer::Valid(parse_target_path("foo").unwrap()),
1075                        )]),
1076                        log_namespaces: BTreeSet::new(),
1077                    },
1078                    other: Definition {
1079                        event_kind: Kind::object(Collection::from(BTreeMap::from([(
1080                            "foo".into(),
1081                            Kind::boolean(),
1082                        )]))),
1083                        metadata_kind: Kind::object(Collection::empty()),
1084                        meaning: BTreeMap::from([(
1085                            "foo".into(),
1086                            MeaningPointer::Valid(parse_target_path("bar").unwrap()),
1087                        )]),
1088                        log_namespaces: BTreeSet::new(),
1089                    },
1090                    want: Definition {
1091                        event_kind: Kind::object(Collection::from(BTreeMap::from([(
1092                            "foo".into(),
1093                            Kind::boolean(),
1094                        )]))),
1095                        metadata_kind: Kind::object(Collection::empty()),
1096                        meaning: BTreeMap::from([(
1097                            "foo".into(),
1098                            MeaningPointer::Invalid(BTreeSet::from([
1099                                parse_target_path("foo").unwrap(),
1100                                parse_target_path("bar").unwrap(),
1101                            ])),
1102                        )]),
1103                        log_namespaces: BTreeSet::new(),
1104                    },
1105                },
1106            ),
1107            (
1108                "same meaning, pointing to same path",
1109                TestCase {
1110                    this: Definition {
1111                        event_kind: Kind::object(Collection::from(BTreeMap::from([(
1112                            "foo".into(),
1113                            Kind::boolean(),
1114                        )]))),
1115                        metadata_kind: Kind::object(Collection::empty()),
1116                        meaning: BTreeMap::from([(
1117                            "foo".into(),
1118                            MeaningPointer::Valid(parse_target_path("foo").unwrap()),
1119                        )]),
1120                        log_namespaces: BTreeSet::new(),
1121                    },
1122                    other: Definition {
1123                        event_kind: Kind::object(Collection::from(BTreeMap::from([(
1124                            "foo".into(),
1125                            Kind::boolean(),
1126                        )]))),
1127                        metadata_kind: Kind::object(Collection::empty()),
1128                        meaning: BTreeMap::from([(
1129                            "foo".into(),
1130                            MeaningPointer::Valid(parse_target_path("foo").unwrap()),
1131                        )]),
1132                        log_namespaces: BTreeSet::new(),
1133                    },
1134                    want: Definition {
1135                        event_kind: Kind::object(Collection::from(BTreeMap::from([(
1136                            "foo".into(),
1137                            Kind::boolean(),
1138                        )]))),
1139                        metadata_kind: Kind::object(Collection::empty()),
1140                        meaning: BTreeMap::from([(
1141                            "foo".into(),
1142                            MeaningPointer::Valid(parse_target_path("foo").unwrap()),
1143                        )]),
1144                        log_namespaces: BTreeSet::new(),
1145                    },
1146                },
1147            ),
1148        ]) {
1149            let got = this.merge(other);
1150
1151            assert_eq!(got, want, "{title}");
1152        }
1153    }
1154}