1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
use std::collections::HashMap;

use bytes::BytesMut;
use serde::Deserialize;
use serde_json::Value;
use snafu::Snafu;
use tokio_util::codec::Encoder as _;
use vector_lib::codecs::encoding::format::JsonSerializerOptions;

use crate::codecs::Encoder;
use vector_lib::codecs::{
    encoding, JsonSerializer, LengthDelimitedEncoder, LogfmtSerializer, MetricTagValues,
    NewlineDelimitedEncoder,
};
use vector_lib::event::{Event, LogEvent};

/// A test case event for deserialization from yaml file.
/// This is an intermediary step to TestEvent.
#[derive(Clone, Debug, Deserialize)]
#[serde(untagged)]
pub enum RawTestEvent {
    /// The event is used, as-is, without modification.
    Passthrough(EventData),

    /// The event is potentially modified by the external resource.
    ///
    /// The modification made is dependent on the external resource, but this mode is made available
    /// for when a test case wants to exercise the failure path, but cannot cause a failure simply
    /// by constructing the event in a certain way i.e. adding an invalid field, or removing a
    /// required field, or using an invalid field value, and so on.
    ///
    /// For transforms and sinks, generally, the only way to cause an error is if the event itself
    /// is malformed in some way, which can be achieved without this test event variant.
    AlternateEncoder { fail_encoding_of: EventData },

    /// The event will be rejected by the external resource.
    ResourceReject {
        external_resource_rejects: EventData,
    },
}

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EventData {
    /// A simple log event.
    Log(String),
    /// A log event built from key-value pairs
    LogBuilder(HashMap<String, Value>),
}

impl EventData {
    /// Converts this event data into an `Event`.
    pub fn into_event(self) -> Event {
        match self {
            Self::Log(message) => Event::Log(LogEvent::from_bytes_legacy(&message.into())),
            Self::LogBuilder(data) => {
                let mut log_event = LogEvent::default();
                for (k, v) in data {
                    log_event
                        .parse_path_and_insert(&k, v)
                        .unwrap_or_else(|_| panic!("Unable to build log event for {}", &k));
                }
                Event::Log(log_event)
            }
        }
    }
}

/// An event used in a test case.
/// It is important to have created the event with all fields, immediately after deserializing from the
/// test case definition yaml file. This ensures that the event data we are using in the expected/actual
/// metrics collection is based on the same event. Namely, one issue that can arise from creating the event
/// from the event data twice (once for the expected and once for actual), it can result in a timestamp in
/// the event which may or may not have the same millisecond precision as it's counterpart.
///
/// For transforms and sinks, generally, the only way to cause an error is if the event itself
/// is malformed in some way, which can be achieved without this test event variant.
#[derive(Clone, Debug, Deserialize)]
#[serde(from = "RawTestEvent")]
#[serde(untagged)]
pub enum TestEvent {
    /// The event is used, as-is, without modification.
    Passthrough(Event),

    /// The event is encoded using an encoding that differs from the component's
    /// configured encoding, which should cause an error when the event is decoded.
    FailWithAlternateEncoder(Event),

    /// The event encodes successfully but when the external resource receives that event, it should
    /// throw a failure.
    FailWithExternalResource(Event),
}

impl TestEvent {
    #[allow(clippy::missing_const_for_fn)] // const cannot run destructor
    pub fn into_event(self) -> Event {
        match self {
            Self::Passthrough(event) => event,
            Self::FailWithAlternateEncoder(event) => event,
            Self::FailWithExternalResource(event) => event,
        }
    }

    pub fn get_event(&mut self) -> &mut Event {
        match self {
            Self::Passthrough(event) => event,
            Self::FailWithAlternateEncoder(event) => event,
            Self::FailWithExternalResource(event) => event,
        }
    }

    /// (should_fail, event)
    pub fn get(self) -> (bool, Event) {
        match self {
            Self::Passthrough(event) => (false, event),
            Self::FailWithAlternateEncoder(event) => (true, event),
            Self::FailWithExternalResource(event) => (true, event),
        }
    }

    /// True if the event should fail, false otherwise.
    pub const fn should_fail(&self) -> bool {
        match self {
            Self::Passthrough(_) => false,
            Self::FailWithAlternateEncoder(_) | Self::FailWithExternalResource(_) => true,
        }
    }

    /// True if the event should be rejected by the external resource in order to
    /// trigger a failure path.
    pub const fn should_reject(&self) -> bool {
        match self {
            Self::Passthrough(_) | Self::FailWithAlternateEncoder(_) => false,
            Self::FailWithExternalResource(_) => true,
        }
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Snafu)]
pub enum RawTestEventParseError {}

impl From<RawTestEvent> for TestEvent {
    fn from(other: RawTestEvent) -> Self {
        match other {
            RawTestEvent::Passthrough(event_data) => {
                TestEvent::Passthrough(event_data.into_event())
            }
            RawTestEvent::AlternateEncoder {
                fail_encoding_of: event_data,
            } => TestEvent::FailWithAlternateEncoder(event_data.into_event()),
            RawTestEvent::ResourceReject {
                external_resource_rejects: event_data,
            } => TestEvent::FailWithExternalResource(event_data.into_event()),
        }
    }
}

pub fn encode_test_event(
    encoder: &mut Encoder<encoding::Framer>,
    buf: &mut BytesMut,
    event: TestEvent,
) {
    match event {
        TestEvent::Passthrough(event) | TestEvent::FailWithExternalResource(event) => {
            // Encode the event normally.
            encoder
                .encode(event, buf)
                .expect("should not fail to encode input event");
        }
        TestEvent::FailWithAlternateEncoder(event) => {
            // This is a little fragile, but we check what serializer this encoder uses, and based
            // on `Serializer::supports_json`, we choose an opposing codec. For example, if the
            // encoder supports JSON, we'll use a serializer that doesn't support JSON, and vise
            // versa.
            let mut alt_encoder = if encoder.serializer().supports_json() {
                Encoder::<encoding::Framer>::new(
                    LengthDelimitedEncoder::default().into(),
                    LogfmtSerializer.into(),
                )
            } else {
                Encoder::<encoding::Framer>::new(
                    NewlineDelimitedEncoder::default().into(),
                    JsonSerializer::new(
                        MetricTagValues::default(),
                        JsonSerializerOptions::default(),
                    )
                    .into(),
                )
            };

            alt_encoder
                .encode(event, buf)
                .expect("should not fail to encode input event");
        }
    }
}