Skip to main content

vector/template/
unconfined.rs

1use super::parsing::{parse_template, render_metric_field, render_timestamp};
2use super::*;
3
4impl fmt::Debug for UnconfinedTemplate {
5    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6        f.debug_struct("UnconfinedTemplate")
7            .field("src", &self.src)
8            .field("is_static", &self.is_static)
9            .field("tz_offset", &self.tz_offset)
10            .finish()
11    }
12}
13
14impl TryFrom<&str> for UnconfinedTemplate {
15    type Error = TemplateParseError;
16
17    fn try_from(src: &str) -> Result<Self, Self::Error> {
18        UnconfinedTemplate::try_from(Cow::Borrowed(src))
19    }
20}
21
22impl TryFrom<String> for UnconfinedTemplate {
23    type Error = TemplateParseError;
24
25    fn try_from(src: String) -> Result<Self, Self::Error> {
26        UnconfinedTemplate::try_from(Cow::Owned(src))
27    }
28}
29
30impl TryFrom<PathBuf> for UnconfinedTemplate {
31    type Error = TemplateParseError;
32
33    fn try_from(p: PathBuf) -> Result<Self, Self::Error> {
34        UnconfinedTemplate::try_from(p.to_string_lossy().into_owned())
35    }
36}
37
38impl TryFrom<Cow<'_, str>> for UnconfinedTemplate {
39    type Error = TemplateParseError;
40
41    fn try_from(src: Cow<'_, str>) -> Result<Self, Self::Error> {
42        parse_template(&src).map(|parts| {
43            let is_static =
44                parts.is_empty() || (parts.len() == 1 && matches!(parts[0], Part::Literal(..)));
45
46            let reserve_size = parts
47                .iter()
48                .map(|part| match part {
49                    Part::Literal(lit) => lit.len(),
50                    Part::Reference(_path) => 1,
51                    Part::Strftime(parsed) => parsed.reserve_size(),
52                })
53                .sum();
54
55            UnconfinedTemplate {
56                parts,
57                src: src.into_owned(),
58                is_static,
59                reserve_size,
60                tz_offset: None,
61            }
62        })
63    }
64}
65
66impl From<UnconfinedTemplate> for String {
67    fn from(template: UnconfinedTemplate) -> String {
68        template.src
69    }
70}
71
72impl fmt::Display for UnconfinedTemplate {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        self.src.fmt(f)
75    }
76}
77
78// This is safe because we literally defer to `String` for the schema of `UnconfinedTemplate`.
79impl ConfigurableString for UnconfinedTemplate {}
80
81impl UnconfinedTemplate {
82    /// Set tz offset.
83    pub const fn with_tz_offset(mut self, tz_offset: Option<FixedOffset>) -> Self {
84        self.tz_offset = tz_offset;
85        self
86    }
87
88    /// Renders the given template with data from the event, returning raw bytes.
89    pub fn render<'a>(
90        &self,
91        event: impl Into<EventRef<'a>>,
92    ) -> Result<Bytes, TemplateRenderingError> {
93        self.render_string(event.into()).map(Into::into)
94    }
95
96    /// Renders the given template with data from the event.
97    ///
98    pub fn render_string<'a>(
99        &self,
100        event: impl Into<EventRef<'a>>,
101    ) -> Result<String, TemplateRenderingError> {
102        if self.is_static {
103            Ok(self.src.clone())
104        } else {
105            self.render_event(event.into())
106        }
107    }
108
109    fn render_event(&self, event: EventRef<'_>) -> Result<String, TemplateRenderingError> {
110        let mut missing_keys = Vec::new();
111        let mut out = String::with_capacity(self.reserve_size);
112        for part in &self.parts {
113            match part {
114                Part::Literal(lit) => out.push_str(lit),
115                Part::Strftime(items) => {
116                    out.push_str(&render_timestamp(items, event, self.tz_offset))
117                }
118                Part::Reference(key) => {
119                    out.push_str(
120                        &match event {
121                            EventRef::Log(log) => log
122                                .parse_path_and_get_value(key)
123                                .ok()
124                                .and_then(|v| v.map(Value::to_string_lossy)),
125                            EventRef::Metric(metric) => {
126                                render_metric_field(key, metric).map(Cow::Borrowed)
127                            }
128                            EventRef::Trace(trace) => trace
129                                .parse_path_and_get_value(key)
130                                .ok()
131                                .and_then(|v| v.map(Value::to_string_lossy)),
132                        }
133                        .unwrap_or_else(|| {
134                            missing_keys.push(key.to_owned());
135                            Cow::Borrowed("")
136                        }),
137                    );
138                }
139            }
140        }
141        if missing_keys.is_empty() {
142            Ok(out)
143        } else {
144            Err(TemplateRenderingError::MissingKeys { missing_keys })
145        }
146    }
147
148    /// Returns the names of the fields that are rendered in this template.
149    pub fn get_fields(&self) -> Option<Vec<String>> {
150        let parts: Vec<_> = self
151            .parts
152            .iter()
153            .filter_map(|part| {
154                if let Part::Reference(r) = part {
155                    Some(r.to_owned())
156                } else {
157                    None
158                }
159            })
160            .collect();
161        (!parts.is_empty()).then_some(parts)
162    }
163
164    /// Longest leading substring of the template source that is rendered
165    /// verbatim — no `{{ field }}` reference and no strftime specifier.
166    ///
167    /// Sinks use this to derive a confinement boundary from the
168    /// operator-authored portion of the template.
169    pub fn literal_prefix(&self) -> &str {
170        let bytes = self.src.as_bytes();
171        let mut i = 0;
172        while i < bytes.len() {
173            // `{{` starts a field reference.
174            if bytes[i] == b'{' && bytes.get(i + 1) == Some(&b'{') {
175                break;
176            }
177            // Any `%` may start a strftime sequence. `%%` is an escaped `%`,
178            // but in a mixed literal like `/tmp/100%%/%Y/` the whole segment
179            // is processed by chrono, which decodes `%%` to `%` and expands
180            // `%Y` to the year. We cannot know what chrono will emit without
181            // an actual timestamp, so stop at the first `%` unconditionally.
182            if bytes[i] == b'%' {
183                break;
184            }
185            i += 1;
186        }
187        self.src.split_at(i).0
188    }
189
190    /// Returns a reference to the template string.
191    pub const fn get_ref(&self) -> &str {
192        self.src.as_str()
193    }
194
195    /// Returns `true` if this template string has a length of zero, and `false` otherwise.
196    pub const fn is_empty(&self) -> bool {
197        self.src.is_empty()
198    }
199
200    /// A dynamic template string contains sections that depend on the input event or time.
201    pub const fn is_dynamic(&self) -> bool {
202        !self.is_static
203    }
204}